Bilibili-Evolved/src/core/shadow-root/dom-entry.ts
2024-09-04 22:29:10 +08:00

151 lines
4.4 KiB
TypeScript

import { childListSubtree } from '../observer'
import { deleteValue } from '../utils'
import type { ShadowDomObserver } from './dom-observer'
import { ShadowRootObserver } from './root-observer'
import { ShadowRootEvents } from './types'
export const ShadowDomEntrySymbol = Symbol.for('ShadowDomEntry')
export type ShadowDomParent = ShadowDomEntry | ShadowDomObserver
export type ShadowDomCallback = (shadowDom: ShadowDomEntry) => void
export class ShadowDomEntry extends ShadowRootObserver {
readonly shadowRoot: ShadowRoot
readonly parent: ShadowDomParent | null = null
readonly children: ShadowDomEntry[] = []
protected observer: MutationObserver
protected callbacksMap = new Map()
constructor(shadowRoot: ShadowRoot, parent: ShadowDomParent) {
super()
this.shadowRoot = shadowRoot
this.parent = parent
this.element[ShadowDomEntrySymbol] = this
ShadowRootObserver.queryAllShadowRoots(shadowRoot)
.filter(it => it !== shadowRoot)
.map(it => this.addChild(it))
this.observe()
}
get element() {
return this.shadowRoot.host
}
get elementName() {
return this.element.tagName.toLowerCase()
}
override dispatchEvent(event: Event): boolean {
return super.dispatchEvent(event) && this.parent.dispatchEvent(event)
}
protected mutationHandler(records: MutationRecord[]) {
if (records.length > 0) {
this.dispatchEvent(new CustomEvent(ShadowRootEvents.Updated, { detail: records }))
}
records.forEach(record => {
record.removedNodes.forEach(node => {
if (node instanceof Element) {
ShadowRootObserver.queryAllShadowRoots(node).forEach(shadowRoot => {
const child = this.children.find(it => it.shadowRoot === shadowRoot)
if (child === undefined) {
return
}
this.removeChild(child)
})
}
})
record.addedNodes.forEach(node => {
if (node instanceof Element) {
ShadowRootObserver.queryAllShadowRoots(node).forEach(shadowRoot => {
this.addChild(shadowRoot)
})
}
})
})
}
addChild(childShadowRoot: ShadowRoot) {
const match = this.children.find(child => child.shadowRoot === childShadowRoot)
if (match) {
return match
}
const child = new ShadowDomEntry(childShadowRoot, this)
this.children.push(child)
this.dispatchEvent(new CustomEvent(ShadowRootEvents.Added, { detail: child }))
return child
}
removeChild(child: ShadowDomEntry) {
child.disconnect()
deleteValue(this.children, it => it === child)
this.dispatchEvent(new CustomEvent(ShadowRootEvents.Removed, { detail: child }))
}
protected queryThroughChildren<T>(predicate: (current: ShadowDomEntry) => T | null): {
entry: ShadowDomEntry | null
result: T | null
} {
const selfResult = predicate(this)
if (selfResult !== null) {
return {
entry: this,
result: selfResult,
}
}
for (const child of this.children) {
const childResult = child.queryThroughChildren(predicate)
if (childResult.result !== null) {
return childResult
}
}
return {
entry: null,
result: null,
}
}
querySelectorAsEntry(selectors: string): ShadowDomEntry | null {
const { entry } = this.queryThroughChildren(current => {
if (current === this) {
return null
}
if (current.element.matches(selectors)) {
return current
}
return null
})
return entry
}
querySelectorAllAsEntry(selectors: string): ShadowDomEntry[] {
const currentMatch = this.children.filter(child => child.element.matches(selectors))
const childrenMatch = this.children.flatMap(child => child.querySelectorAllAsEntry(selectors))
return [...currentMatch, ...childrenMatch]
}
querySelector(selectors: string): Element | null {
const { result } = this.queryThroughChildren(current =>
current.shadowRoot.querySelector(selectors),
)
return result
}
querySelectorAll(selectors: string): Element[] {
return [
...this.shadowRoot.querySelectorAll(selectors),
...this.children.flatMap(child => child.querySelectorAll(selectors)),
]
}
observe() {
const [observer] = childListSubtree(this.shadowRoot, records => {
this.mutationHandler(records)
})
this.observer = observer
}
disconnect() {
this.children.forEach(child => this.removeChild(child))
this.observer?.disconnect()
}
}