You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
5.0 KiB
JavaScript
186 lines
5.0 KiB
JavaScript
'use strict';
|
|
const {MUTATION_OBSERVER} = require('../shared/symbols.js');
|
|
|
|
const createRecord =
|
|
(type, target, element, addedNodes, removedNodes, attributeName, oldValue) =>
|
|
({
|
|
type,
|
|
target,
|
|
addedNodes,
|
|
removedNodes,
|
|
attributeName,
|
|
oldValue,
|
|
previousSibling: element?.previousSibling || null,
|
|
nextSibling: element?.nextSibling || null,
|
|
});
|
|
|
|
const queueAttribute = (
|
|
observer, target, attributeName, attributeFilter, attributeOldValue, oldValue
|
|
) => {
|
|
if ((!attributeFilter || attributeFilter.includes(attributeName))) {
|
|
const {callback, records, scheduled} = observer;
|
|
records.push(createRecord(
|
|
'attributes', target, null,
|
|
[], [],
|
|
attributeName, attributeOldValue ? oldValue : void 0
|
|
));
|
|
if (!scheduled) {
|
|
observer.scheduled = true;
|
|
Promise.resolve().then(() => {
|
|
observer.scheduled = false;
|
|
callback(records.splice(0), observer);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const attributeChangedCallback = (element, attributeName, oldValue) => {
|
|
const {ownerDocument} = element;
|
|
const {active, observers} = ownerDocument[MUTATION_OBSERVER];
|
|
if (active) {
|
|
for (const observer of observers) {
|
|
for (const [
|
|
target,
|
|
{
|
|
childList,
|
|
subtree,
|
|
attributes,
|
|
attributeFilter,
|
|
attributeOldValue
|
|
}
|
|
] of observer.nodes) {
|
|
if (childList) {
|
|
if (
|
|
(subtree && (target === ownerDocument || target.contains(element))) ||
|
|
(!subtree && target.children.includes(element))
|
|
) {
|
|
queueAttribute(
|
|
observer, element,
|
|
attributeName, attributeFilter, attributeOldValue, oldValue
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
else if (
|
|
attributes &&
|
|
target === element
|
|
) {
|
|
queueAttribute(
|
|
observer, element,
|
|
attributeName, attributeFilter, attributeOldValue, oldValue
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
exports.attributeChangedCallback = attributeChangedCallback;
|
|
|
|
const moCallback = (element, parentNode) => {
|
|
const {ownerDocument} = element;
|
|
const {active, observers} = ownerDocument[MUTATION_OBSERVER];
|
|
if (active) {
|
|
for (const observer of observers) {
|
|
for (const [target, {subtree, childList, characterData}] of observer.nodes) {
|
|
if (childList) {
|
|
if (
|
|
(parentNode && (target === parentNode || /* c8 ignore next */(subtree && target.contains(parentNode)))) ||
|
|
(!parentNode && ((subtree && (target === ownerDocument || /* c8 ignore next */target.contains(element))) ||
|
|
(!subtree && target[characterData ? 'childNodes' : 'children'].includes(element))))
|
|
) {
|
|
const {callback, records, scheduled} = observer;
|
|
records.push(createRecord(
|
|
'childList', target, element,
|
|
parentNode ? [] : [element], parentNode ? [element] : []
|
|
));
|
|
if (!scheduled) {
|
|
observer.scheduled = true;
|
|
Promise.resolve().then(() => {
|
|
observer.scheduled = false;
|
|
callback(records.splice(0), observer);
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
exports.moCallback = moCallback;
|
|
|
|
class MutationObserverClass {
|
|
constructor(ownerDocument) {
|
|
const observers = new Set;
|
|
this.observers = observers;
|
|
this.active = false;
|
|
|
|
/**
|
|
* @implements globalThis.MutationObserver
|
|
*/
|
|
this.class = class MutationObserver {
|
|
|
|
constructor(callback) {
|
|
/**
|
|
* @private
|
|
*/
|
|
this.callback = callback;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.nodes = new Map;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.records = [];
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.scheduled = false;
|
|
}
|
|
|
|
disconnect() {
|
|
this.records.splice(0);
|
|
this.nodes.clear();
|
|
observers.delete(this);
|
|
ownerDocument[MUTATION_OBSERVER].active = !!observers.size;
|
|
}
|
|
|
|
/**
|
|
* @param {Element} target
|
|
* @param {MutationObserverInit} options
|
|
*/
|
|
observe(target, options = {
|
|
subtree: false,
|
|
childList: false,
|
|
attributes: false,
|
|
attributeFilter: null,
|
|
attributeOldValue: false,
|
|
characterData: false,
|
|
// TODO: not implemented yet
|
|
// characterDataOldValue: false
|
|
}) {
|
|
if (('attributeOldValue' in options) || ('attributeFilter' in options))
|
|
options.attributes = true;
|
|
// if ('characterDataOldValue' in options)
|
|
// options.characterData = true;
|
|
options.childList = !!options.childList;
|
|
options.subtree = !!options.subtree;
|
|
this.nodes.set(target, options);
|
|
observers.add(this);
|
|
ownerDocument[MUTATION_OBSERVER].active = true;
|
|
}
|
|
|
|
/**
|
|
* @returns {MutationRecord[]}
|
|
*/
|
|
takeRecords() { return this.records.splice(0); }
|
|
}
|
|
}
|
|
}
|
|
exports.MutationObserverClass = MutationObserverClass
|