mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-19 19:51:20 -08:00
refactor(core): CORE-04 scope initialization and make destruction reentrant-safe
This commit is contained in:
1 parent
3e38dd0d19
commit
4eec8112fa
32 files changed
+2779
-189
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import ResourceScope, { ResourceCleanupError } from './scope'
|
||||
|
||||
interface LifecycleOwner {
|
||||
isDestroy: boolean
|
||||
reset: () => void
|
||||
emit: (name: 'destroy') => unknown
|
||||
template?: { $video?: unknown, destroy: (removeHtml: boolean) => void }
|
||||
}
|
||||
|
||||
interface State {
|
||||
scope: ResourceScope
|
||||
destroying: boolean
|
||||
rollback?: () => void
|
||||
releaseContainer?: () => void
|
||||
}
|
||||
|
||||
const states = new WeakMap<object, State>()
|
||||
const containers = new WeakMap<Element, object>()
|
||||
|
||||
export function beginLifecycle(owner: object): void {
|
||||
states.set(owner, { scope: new ResourceScope(), destroying: false })
|
||||
}
|
||||
|
||||
function stateOf(owner: object): State {
|
||||
const state = states.get(owner)
|
||||
if (!state)
|
||||
throw new Error('ArtPlayer lifecycle has not been initialized')
|
||||
return state
|
||||
}
|
||||
|
||||
export function getScope(owner: object): ResourceScope {
|
||||
return stateOf(owner).scope
|
||||
}
|
||||
|
||||
export function ownContainer(owner: object, container: Element, rollback: () => void): void {
|
||||
const current = containers.get(container)
|
||||
if (current && current !== owner)
|
||||
throw new Error('Cannot mount multiple instances on the same dom element')
|
||||
const state = stateOf(owner)
|
||||
state.rollback = rollback
|
||||
containers.set(container, owner)
|
||||
state.releaseContainer = () => {
|
||||
if (containers.get(container) === owner)
|
||||
containers.delete(container)
|
||||
}
|
||||
}
|
||||
|
||||
export function finishLifecycle(owner: object): boolean {
|
||||
const state = stateOf(owner)
|
||||
state.rollback = undefined
|
||||
return !state.scope.closed
|
||||
}
|
||||
|
||||
export function destroyInstance(owner: LifecycleOwner, instances: LifecycleOwner[], removeHtml: boolean, removeSource: boolean, failed = false): void {
|
||||
const state = stateOf(owner)
|
||||
if (state.destroying || state.scope.closed)
|
||||
return
|
||||
state.destroying = true
|
||||
const errors: unknown[] = []
|
||||
const attempt = (cleanup: () => void) => {
|
||||
try {
|
||||
cleanup()
|
||||
}
|
||||
catch (error) {
|
||||
errors.push(...(error instanceof ResourceCleanupError ? error.errors : [error]))
|
||||
}
|
||||
}
|
||||
if (removeSource && owner.template?.$video)
|
||||
attempt(() => owner.reset())
|
||||
attempt(() => state.scope.dispose())
|
||||
attempt(() => owner.template?.destroy(removeHtml))
|
||||
const index = instances.indexOf(owner)
|
||||
if (index !== -1)
|
||||
instances.splice(index, 1)
|
||||
owner.isDestroy = true
|
||||
if (!failed) {
|
||||
state.releaseContainer?.()
|
||||
state.releaseContainer = undefined
|
||||
}
|
||||
attempt(() => owner.emit('destroy'))
|
||||
if (failed && state.rollback)
|
||||
attempt(state.rollback)
|
||||
state.rollback = undefined
|
||||
state.releaseContainer?.()
|
||||
state.releaseContainer = undefined
|
||||
// Preserve the first thrown value while making additional failures observable.
|
||||
for (const error of errors.slice(1))
|
||||
console.warn('Additional ArtPlayer cleanup failure:', error)
|
||||
if (errors.length)
|
||||
throw errors[0]
|
||||
}
|
||||
@@ -83,3 +83,22 @@ export function objectURL(scope: ResourceScope, blob: Blob): string {
|
||||
})
|
||||
return url
|
||||
}
|
||||
|
||||
export function wait(scope: ResourceScope, delay = 0): Promise<boolean> {
|
||||
if (scope.closed)
|
||||
return Promise.resolve(false)
|
||||
return new Promise((resolve) => {
|
||||
let completed = false
|
||||
let release = () => {}
|
||||
const timer = setTimeout(() => {
|
||||
completed = true
|
||||
release()
|
||||
resolve(true)
|
||||
}, delay)
|
||||
release = scope.add(() => {
|
||||
clearTimeout(timer)
|
||||
if (!completed)
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
interface Snapshot {
|
||||
node: Node
|
||||
children: Node[]
|
||||
attributes?: [string, string][]
|
||||
value: string | null
|
||||
}
|
||||
|
||||
export default function captureTemplate(container: Element): () => void {
|
||||
const snapshots: Snapshot[] = []
|
||||
function capture(node: Node) {
|
||||
const children = Array.from(node.childNodes)
|
||||
snapshots.push({
|
||||
node,
|
||||
children,
|
||||
attributes: node.nodeType === 1 ? Array.from((node as Element).attributes, attr => [attr.name, attr.value]) : undefined,
|
||||
value: node.nodeValue,
|
||||
})
|
||||
children.forEach(capture)
|
||||
}
|
||||
capture(container)
|
||||
return () => {
|
||||
for (const { node, children, attributes, value } of snapshots) {
|
||||
while (node.firstChild)
|
||||
node.removeChild(node.firstChild)
|
||||
for (const child of children)
|
||||
node.appendChild(child)
|
||||
if (attributes) {
|
||||
const element = node as Element
|
||||
for (const attr of Array.from(element.attributes))
|
||||
element.removeAttribute(attr.name)
|
||||
for (const [name, content] of attributes)
|
||||
element.setAttribute(name, content)
|
||||
}
|
||||
else {
|
||||
node.nodeValue = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user