aboutsummaryrefslogtreecommitdiff
path: root/node_modules/jsdom/lib/jsdom/living/helpers/mutation-observers.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/jsdom/lib/jsdom/living/helpers/mutation-observers.js')
-rw-r--r--node_modules/jsdom/lib/jsdom/living/helpers/mutation-observers.js198
1 files changed, 198 insertions, 0 deletions
diff --git a/node_modules/jsdom/lib/jsdom/living/helpers/mutation-observers.js b/node_modules/jsdom/lib/jsdom/living/helpers/mutation-observers.js
new file mode 100644
index 0000000..c1c9209
--- /dev/null
+++ b/node_modules/jsdom/lib/jsdom/living/helpers/mutation-observers.js
@@ -0,0 +1,198 @@
+"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(target._globalObject, [], {
+ 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 > 0) {
+ try {
+ const moWrapper = idlUtils.wrapperForImpl(mo);
+ mo._callback.call(
+ moWrapper,
+ records.map(idlUtils.wrapperForImpl),
+ moWrapper
+ );
+ } catch (e) {
+ const { target } = records[0];
+ const window = target._ownerDocument._defaultView;
+
+ reportException(window, e);
+ }
+ }
+ }
+
+ for (const slot of signalList) {
+ const slotChangeEvent = Event.createImpl(
+ slot._globalObject,
+ [
+ "slotchange",
+ { bubbles: true }
+ ],
+ { isTrusted: true }
+ );
+
+ slot._dispatch(slotChangeEvent);
+ }
+}
+
+module.exports = {
+ MUTATION_TYPE,
+
+ queueMutationRecord,
+ queueTreeMutationRecord,
+ queueAttributeMutationRecord,
+
+ queueMutationObserverMicrotask,
+
+ signalSlotList
+};