ember-source
Version:
A JavaScript framework for creating ambitious web applications
183 lines (176 loc) • 6.3 kB
JavaScript
import { d as debugToString } from '../../shared-chunks/debug-to-string-CFb7h0lY.js';
import { scheduleDestroyed, scheduleDestroy } from '../global-context/index.js';
const LIVE_STATE = 0;
const DESTROYING_STATE = 1;
const DESTROYED_STATE = 2;
let DESTROYABLE_META = new WeakMap();
const branded = Symbol('BrandedArray');
function isBrandedArray(collection) {
return Array.isArray(collection) && branded in collection;
}
function push(collection, newItem) {
if (collection === null) {
return newItem;
} else if (isBrandedArray(collection)) {
collection.push(newItem);
return collection;
} else {
const b = [collection, newItem];
b[branded] = true;
return b;
}
}
function iterate(collection, fn) {
if (isBrandedArray(collection)) {
collection.forEach(fn);
} else if (collection !== null) {
fn(collection);
}
}
function remove(collection, item, message) {
{
let collectionIsItem = collection === item;
let collectionContainsItem = isBrandedArray(collection) && collection.indexOf(item) !== -1;
if (!collectionIsItem && !collectionContainsItem) {
throw new Error(String(message));
}
}
if (isBrandedArray(collection) && collection.length > 1) {
let index = collection.indexOf(item);
collection.splice(index, 1);
return collection;
} else {
return null;
}
}
function getDestroyableMeta(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
if (meta === undefined) {
meta = {
parents: null,
children: null,
eagerDestructors: null,
destructors: null,
state: LIVE_STATE
};
{
meta.source = destroyable;
}
DESTROYABLE_META.set(destroyable, meta);
}
return meta;
}
function associateDestroyableChild(parent, child) {
if (isDestroying(parent)) {
throw new Error('Attempted to associate a destroyable child with an object that is already destroying or destroyed');
}
let parentMeta = getDestroyableMeta(parent);
let childMeta = getDestroyableMeta(child);
parentMeta.children = push(parentMeta.children, child);
childMeta.parents = push(childMeta.parents, parent);
return child;
}
function registerDestructor(destroyable, destructor, eager = false) {
if (isDestroying(destroyable)) {
throw new Error('Attempted to register a destructor with an object that is already destroying or destroyed');
}
let meta = getDestroyableMeta(destroyable);
let destructorsKey = eager ? 'eagerDestructors' : 'destructors';
meta[destructorsKey] = push(meta[destructorsKey], destructor);
return destructor;
}
function unregisterDestructor(destroyable, destructor, eager = false) {
if (isDestroying(destroyable)) {
throw new Error('Attempted to unregister a destructor with an object that is already destroying or destroyed');
}
let meta = getDestroyableMeta(destroyable);
let destructorsKey = eager ? 'eagerDestructors' : 'destructors';
meta[destructorsKey] = remove(meta[destructorsKey], destructor, 'attempted to remove a destructor that was not registered with the destroyable');
}
////////////
function destroy(destroyable) {
let meta = getDestroyableMeta(destroyable);
if (meta.state >= DESTROYING_STATE) return;
let {
parents,
children,
eagerDestructors,
destructors
} = meta;
meta.state = DESTROYING_STATE;
iterate(children, destroy);
iterate(eagerDestructors, destructor => {
destructor(destroyable);
});
iterate(destructors, destructor => {
scheduleDestroy(destroyable, destructor);
});
scheduleDestroyed(() => {
iterate(parents, parent => {
removeChildFromParent(destroyable, parent);
});
meta.state = DESTROYED_STATE;
});
}
function removeChildFromParent(child, parent) {
let parentMeta = getDestroyableMeta(parent);
if (parentMeta.state !== DESTROYED_STATE) {
parentMeta.children = remove(parentMeta.children, child, "attempted to remove child from parent, but the parent's children did not contain the child. This is likely a bug with destructors.");
}
}
function destroyChildren(destroyable) {
let {
children
} = getDestroyableMeta(destroyable);
iterate(children, destroy);
}
function _hasDestroyableChildren(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return meta === undefined ? false : meta.children !== null;
}
function isDestroying(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return meta === undefined ? false : meta.state >= DESTROYING_STATE;
}
function isDestroyed(destroyable) {
let meta = DESTROYABLE_META.get(destroyable);
return meta === undefined ? false : meta.state >= DESTROYED_STATE;
}
////////////
let enableDestroyableTracking;
let assertDestroyablesDestroyed;
{
let isTesting = false;
enableDestroyableTracking = () => {
if (isTesting) {
// Reset destroyable meta just in case, before throwing the error
DESTROYABLE_META = new WeakMap();
throw new Error('Attempted to start destroyable testing, but you did not end the previous destroyable test. Did you forget to call `assertDestroyablesDestroyed()`');
}
isTesting = true;
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 = false;
let map = DESTROYABLE_META;
DESTROYABLE_META = new WeakMap();
let undestroyed = [];
map.forEach(meta => {
if (meta.state !== DESTROYED_STATE) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
undestroyed.push(meta.source);
}
});
if (undestroyed.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
let objectsToString = undestroyed.map(debugToString).join('\n ');
let error = new Error(`Some destroyables were not destroyed during this test:\n ${objectsToString}`);
error.destroyables = undestroyed;
throw error;
}
};
}
export { _hasDestroyableChildren, assertDestroyablesDestroyed, associateDestroyableChild, destroy, destroyChildren, enableDestroyableTracking, isDestroyed, isDestroying, registerDestructor, unregisterDestructor };