You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.2 KiB
195 lines
5.2 KiB
"use strict";
|
|
|
|
const { domSymbolTree } = require("./internal-constants");
|
|
const reportException = require("./runtime-script-errors");
|
|
|
|
const Event = require("../generated/Event");
|
|
const idlUtils = require("../generated/utils");
|
|
const MutationRecord = require("../generated/MutationRecord");
|
|
|
|
const MUTATION_TYPE = {
|
|
ATTRIBUTES: "attributes",
|
|
CHARACTER_DATA: "characterData",
|
|
CHILD_LIST: "childList"
|
|
};
|
|
|
|
// Note:
|
|
// Since jsdom doesn't currently implement the concept of "unit of related similar-origin browsing contexts"
|
|
// (https://html.spec.whatwg.org/multipage/browsers.html#unit-of-related-similar-origin-browsing-contexts)
|
|
// we will approximate that the following properties are global for now.
|
|
|
|
// https://dom.spec.whatwg.org/#mutation-observer-compound-microtask-queued-flag
|
|
let mutationObserverMicrotaskQueueFlag = false;
|
|
|
|
// Non-spec compliant: List of all the mutation observers with mutation records enqueued. It's a replacement for
|
|
// mutation observer list (https://dom.spec.whatwg.org/#mutation-observer-list) but without leaking since it's empty
|
|
// before notifying the mutation observers.
|
|
const activeMutationObservers = new Set();
|
|
|
|
// https://dom.spec.whatwg.org/#signal-slot-list
|
|
const signalSlotList = [];
|
|
|
|
// https://dom.spec.whatwg.org/#queue-a-mutation-record
|
|
function queueMutationRecord(
|
|
type,
|
|
target,
|
|
name,
|
|
namespace,
|
|
oldValue,
|
|
addedNodes,
|
|
removedNodes,
|
|
previousSibling,
|
|
nextSibling
|
|
) {
|
|
const interestedObservers = new Map();
|
|
|
|
const nodes = domSymbolTree.ancestorsToArray(target);
|
|
|
|
for (const node of nodes) {
|
|
for (const registered of node._registeredObserverList) {
|
|
const { options, observer: mo } = registered;
|
|
|
|
if (
|
|
!(node !== target && options.subtree === false) &&
|
|
!(type === MUTATION_TYPE.ATTRIBUTES && options.attributes !== true) &&
|
|
!(type === MUTATION_TYPE.ATTRIBUTES && options.attributeFilter &&
|
|
!options.attributeFilter.some(value => value === name || value === namespace)) &&
|
|
!(type === MUTATION_TYPE.CHARACTER_DATA && options.characterData !== true) &&
|
|
!(type === MUTATION_TYPE.CHILD_LIST && options.childList === false)
|
|
) {
|
|
if (!interestedObservers.has(mo)) {
|
|
interestedObservers.set(mo, null);
|
|
}
|
|
|
|
if (
|
|
(type === MUTATION_TYPE.ATTRIBUTES && options.attributeOldValue === true) ||
|
|
(type === MUTATION_TYPE.CHARACTER_DATA && options.characterDataOldValue === true)
|
|
) {
|
|
interestedObservers.set(mo, oldValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const [observer, mappedOldValue] of interestedObservers.entries()) {
|
|
const record = MutationRecord.createImpl([], {
|
|
type,
|
|
target,
|
|
attributeName: name,
|
|
attributeNamespace: namespace,
|
|
oldValue: mappedOldValue,
|
|
addedNodes,
|
|
removedNodes,
|
|
previousSibling,
|
|
nextSibling
|
|
});
|
|
|
|
observer._recordQueue.push(record);
|
|
activeMutationObservers.add(observer);
|
|
}
|
|
|
|
queueMutationObserverMicrotask();
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#queue-a-tree-mutation-record
|
|
function queueTreeMutationRecord(target, addedNodes, removedNodes, previousSibling, nextSibling) {
|
|
queueMutationRecord(
|
|
MUTATION_TYPE.CHILD_LIST,
|
|
target,
|
|
null,
|
|
null,
|
|
null,
|
|
addedNodes,
|
|
removedNodes,
|
|
previousSibling,
|
|
nextSibling
|
|
);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#queue-an-attribute-mutation-record
|
|
function queueAttributeMutationRecord(target, name, namespace, oldValue) {
|
|
queueMutationRecord(
|
|
MUTATION_TYPE.ATTRIBUTES,
|
|
target,
|
|
name,
|
|
namespace,
|
|
oldValue,
|
|
[],
|
|
[],
|
|
null,
|
|
null
|
|
);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask
|
|
function queueMutationObserverMicrotask() {
|
|
if (mutationObserverMicrotaskQueueFlag) {
|
|
return;
|
|
}
|
|
|
|
mutationObserverMicrotaskQueueFlag = true;
|
|
|
|
Promise.resolve().then(() => {
|
|
notifyMutationObservers();
|
|
});
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#notify-mutation-observers
|
|
function notifyMutationObservers() {
|
|
mutationObserverMicrotaskQueueFlag = false;
|
|
|
|
const notifyList = [...activeMutationObservers].sort((a, b) => a._id - b._id);
|
|
activeMutationObservers.clear();
|
|
|
|
const signalList = [...signalSlotList];
|
|
signalSlotList.splice(0, signalSlotList.length);
|
|
|
|
for (const mo of notifyList) {
|
|
const records = [...mo._recordQueue];
|
|
mo._recordQueue = [];
|
|
|
|
for (const node of mo._nodeList) {
|
|
node._registeredObserverList = node._registeredObserverList.filter(registeredObserver => {
|
|
return registeredObserver.source !== mo;
|
|
});
|
|
|
|
if (records.length) {
|
|
try {
|
|
mo._callback(
|
|
records.map(idlUtils.wrapperForImpl),
|
|
idlUtils.wrapperForImpl(mo)
|
|
);
|
|
} catch (e) {
|
|
const { target } = records[0];
|
|
const window = target._ownerDocument._defaultView;
|
|
|
|
reportException(window, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const slot of signalList) {
|
|
const slotChangeEvent = Event.createImpl(
|
|
[
|
|
"slotchange",
|
|
{ bubbles: true }
|
|
],
|
|
{ isTrusted: true }
|
|
);
|
|
|
|
slot._dispatch(slotChangeEvent);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
MUTATION_TYPE,
|
|
|
|
queueMutationRecord,
|
|
queueTreeMutationRecord,
|
|
queueAttributeMutationRecord,
|
|
|
|
queueMutationObserverMicrotask,
|
|
|
|
signalSlotList
|
|
};
|
|
|