UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

119 lines (114 loc) 6.09 kB
import { scheduleDestroyed, scheduleDestroy } from '../global-context/index.js'; import { debugToString as debugToString$1 } from '../util/index.js'; import { isDevelopingApp } from '@embroider/macros'; var DestroyingState = function (DestroyingState) { return DestroyingState[DestroyingState.Live = 0] = "Live", DestroyingState[DestroyingState.Destroying = 1] = "Destroying", DestroyingState[DestroyingState.Destroyed = 2] = "Destroyed", DestroyingState; }(DestroyingState || {}); let enableDestroyableTracking, assertDestroyablesDestroyed, DESTROYABLE_META = new WeakMap(); function push(collection, newItem) { return null === collection ? newItem : Array.isArray(collection) ? (collection.push(newItem), collection) : [collection, newItem]; } function iterate(collection, fn) { Array.isArray(collection) ? collection.forEach(fn) : null !== collection && fn(collection); } function remove(collection, item, message) { if (isDevelopingApp()) { let collectionIsItem = collection === item, collectionContainsItem = Array.isArray(collection) && -1 !== collection.indexOf(item); if (!collectionIsItem && !collectionContainsItem) throw new Error(String(message)); } if (Array.isArray(collection) && collection.length > 1) { let index = collection.indexOf(item); return collection.splice(index, 1), collection; } return null; } function getDestroyableMeta(destroyable) { let meta = DESTROYABLE_META.get(destroyable); return void 0 === meta && (meta = { parents: null, children: null, eagerDestructors: null, destructors: null, state: DestroyingState.Live }, isDevelopingApp() && (meta.source = destroyable), DESTROYABLE_META.set(destroyable, meta)), meta; } function associateDestroyableChild(parent, child) { if (isDevelopingApp() && isDestroying(parent)) throw new Error("Attempted to associate a destroyable child with an object that is already destroying or destroyed"); let parentMeta = getDestroyableMeta(parent), childMeta = getDestroyableMeta(child); return parentMeta.children = push(parentMeta.children, child), childMeta.parents = push(childMeta.parents, parent), child; } function registerDestructor(destroyable, destructor, eager = !1) { if (isDevelopingApp() && isDestroying(destroyable)) throw new Error("Attempted to register a destructor with an object that is already destroying or destroyed"); let meta = getDestroyableMeta(destroyable), destructorsKey = !0 === eager ? "eagerDestructors" : "destructors"; return meta[destructorsKey] = push(meta[destructorsKey], destructor), destructor; } function unregisterDestructor(destroyable, destructor, eager = !1) { if (isDevelopingApp() && isDestroying(destroyable)) throw new Error("Attempted to unregister a destructor with an object that is already destroying or destroyed"); let meta = getDestroyableMeta(destroyable), destructorsKey = !0 === eager ? "eagerDestructors" : "destructors"; meta[destructorsKey] = remove(meta[destructorsKey], destructor, isDevelopingApp() && "attempted to remove a destructor that was not registered with the destroyable"); } //////////// function destroy(destroyable) { let meta = getDestroyableMeta(destroyable); if (meta.state >= DestroyingState.Destroying) return; let { parents: parents, children: children, eagerDestructors: eagerDestructors, destructors: destructors } = meta; meta.state = DestroyingState.Destroying, iterate(children, destroy), iterate(eagerDestructors, destructor => destructor(destroyable)), iterate(destructors, destructor => scheduleDestroy(destroyable, destructor)), scheduleDestroyed(() => { iterate(parents, parent => function (child, parent) { let parentMeta = getDestroyableMeta(parent); parentMeta.state === DestroyingState.Live && (parentMeta.children = remove(parentMeta.children, child, isDevelopingApp() && "attempted to remove child from parent, but the parent's children did not contain the child. This is likely a bug with destructors.")); }(destroyable, parent)), meta.state = DestroyingState.Destroyed; }); } function destroyChildren(destroyable) { let { children: children } = getDestroyableMeta(destroyable); iterate(children, destroy); } function _hasDestroyableChildren(destroyable) { let meta = DESTROYABLE_META.get(destroyable); return void 0 !== meta && null !== meta.children; } function isDestroying(destroyable) { let meta = DESTROYABLE_META.get(destroyable); return void 0 !== meta && meta.state >= DestroyingState.Destroying; } function isDestroyed(destroyable) { let meta = DESTROYABLE_META.get(destroyable); return void 0 !== meta && meta.state >= DestroyingState.Destroyed; } //////////// if (isDevelopingApp()) { let isTesting = !1; enableDestroyableTracking = () => { if (isTesting) // Reset destroyable meta just in case, before throwing the error throw DESTROYABLE_META = new WeakMap(), new Error("Attempted to start destroyable testing, but you did not end the previous destroyable test. Did you forget to call `assertDestroyablesDestroyed()`"); isTesting = !0, DESTROYABLE_META = new Map(); }, assertDestroyablesDestroyed = () => { if (!isTesting) throw new Error("Attempted to assert destroyables destroyed, but you did not start a destroyable test. Did you forget to call `enableDestroyableTracking()`"); isTesting = !1; let map = DESTROYABLE_META; DESTROYABLE_META = new WeakMap(); let undestroyed = []; if (map.forEach(meta => { meta.state !== DestroyingState.Destroyed && undestroyed.push(meta.source); }), undestroyed.length > 0) { let objectsToString = undestroyed.map(debugToString$1).join("\n "), error = new Error(`Some destroyables were not destroyed during this test:\n ${objectsToString}`); throw error.destroyables = undestroyed, error; } }; } export { _hasDestroyableChildren, assertDestroyablesDestroyed, associateDestroyableChild, destroy, destroyChildren, enableDestroyableTracking, isDestroyed, isDestroying, registerDestructor, unregisterDestructor };