diff options
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.js | 198 |
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 +}; |