"use strict"; const idlUtils = require("./generated/utils"); const domSymbolTree = require("./helpers/internal-constants").domSymbolTree; const defineGetter = require("../utils").defineGetter; const INTERNAL = Symbol("NodeIterator internal"); const DocumentImpl = require("./nodes/Document-impl").implementation; module.exports = function (core) { // https://dom.spec.whatwg.org/#interface-nodeiterator function NodeIteratorInternal(document, root, whatToShow, filter) { this.active = true; this.document = document; this.root = root; this.referenceNode = root; this.pointerBeforeReferenceNode = true; this.whatToShow = whatToShow; this.filter = filter; } NodeIteratorInternal.prototype.throwIfNotActive = function () { // (only thrown for getters/methods that are affected by removing steps) if (!this.active) { throw Error("This NodeIterator is no longer active. " + "More than " + this.document._activeNodeIteratorsMax + " iterators are being used concurrently. " + "You can increase the 'concurrentNodeIterators' option to " + "make this error go away." ); // Alternatively, you can pester Ecma to add support for weak references, // the DOM standard assumes the implementor has control over object life cycles. } }; NodeIteratorInternal.prototype.traverse = function (next) { let node = this.referenceNode; let beforeNode = this.pointerBeforeReferenceNode; do { if (next) { if (!beforeNode) { node = domSymbolTree.following(node, { root: this.root }); if (!node) { return null; } } beforeNode = false; } else { // previous if (beforeNode) { node = domSymbolTree.preceding(node, { root: this.root }); if (!node) { return null; } } beforeNode = true; } } while (this.filterNode(node) !== core.NodeFilter.FILTER_ACCEPT); this.pointerBeforeReferenceNode = beforeNode; this.referenceNode = node; return node; }; NodeIteratorInternal.prototype.filterNode = function (node) { const n = node.nodeType - 1; if (!(this.whatToShow & (1 << n))) { return core.NodeFilter.FILTER_SKIP; } let ret = core.NodeFilter.FILTER_ACCEPT; const filter = this.filter; if (typeof filter === "function") { ret = filter(node); } else if (filter && typeof filter.acceptNode === "function") { ret = filter.acceptNode(node); } if (ret === true) { return core.NodeFilter.FILTER_ACCEPT; } else if (ret === false) { return core.NodeFilter.FILTER_REJECT; } return ret; }; NodeIteratorInternal.prototype.runRemovingSteps = function (oldNode, oldParent, oldPreviousSibling) { if (oldNode.contains(this.root)) { return; } // If oldNode is not an inclusive ancestor of the referenceNode // attribute value, terminate these steps. if (!oldNode.contains(this.referenceNode)) { return; } if (this.pointerBeforeReferenceNode) { // Let nextSibling be oldPreviousSibling’s next sibling, if oldPreviousSibling is non-null, // and oldParent’s first child otherwise. const nextSibling = oldPreviousSibling ? oldPreviousSibling.nextSibling : oldParent.firstChild; // If nextSibling is non-null, set the referenceNode attribute to nextSibling // and terminate these steps. if (nextSibling) { this.referenceNode = nextSibling; return; } // Let next be the first node following oldParent (excluding any children of oldParent). const next = domSymbolTree.following(oldParent, { skipChildren: true }); // If root is an inclusive ancestor of next, set the referenceNode // attribute to next and terminate these steps. if (this.root.contains(next)) { this.referenceNode = next; return; } // Otherwise, set the pointerBeforeReferenceNode attribute to false. this.pointerBeforeReferenceNode = false; // Note: Steps are not terminated here. } // Set the referenceNode attribute to the last inclusive descendant in tree order of oldPreviousSibling, // if oldPreviousSibling is non-null, and to oldParent otherwise. this.referenceNode = oldPreviousSibling ? domSymbolTree.lastInclusiveDescendant(oldPreviousSibling) : oldParent; }; DocumentImpl._removingSteps.push((document, oldNode, oldParent, oldPreviousSibling) => { for (let i = 0; i < document._activeNodeIterators.length; ++i) { const internal = document._activeNodeIterators[i]; internal.runRemovingSteps(oldNode, oldParent, oldPreviousSibling); } }); core.Document.prototype.createNodeIterator = function (root, whatToShow, filter) { if (!root) { throw new TypeError("Not enough arguments to Document.createNodeIterator."); } root = idlUtils.implForWrapper(root); if (filter === undefined) { filter = null; } if (filter !== null && typeof filter !== "function" && typeof filter.acceptNode !== "function") { throw new TypeError("Argument 3 of Document.createNodeIterator should be a function or implement NodeFilter."); } const document = root._ownerDocument; whatToShow = whatToShow === undefined ? core.NodeFilter.SHOW_ALL : (whatToShow & core.NodeFilter.SHOW_ALL) >>> 0; // >>> makes sure the result is unsigned filter = filter || null; const it = Object.create(core.NodeIterator.prototype); const internal = new NodeIteratorInternal(document, root, whatToShow, filter); it[INTERNAL] = internal; document._activeNodeIterators.push(internal); while (document._activeNodeIterators.length > document._activeNodeIteratorsMax) { const internalOther = document._activeNodeIterators.shift(); internalOther.active = false; } return it; }; core.NodeIterator = function NodeIterator() { throw new TypeError("Illegal constructor"); }; defineGetter(core.NodeIterator.prototype, "root", function () { return idlUtils.wrapperForImpl(this[INTERNAL].root); }); defineGetter(core.NodeIterator.prototype, "referenceNode", function () { const internal = this[INTERNAL]; internal.throwIfNotActive(); return idlUtils.wrapperForImpl(internal.referenceNode); }); defineGetter(core.NodeIterator.prototype, "pointerBeforeReferenceNode", function () { const internal = this[INTERNAL]; internal.throwIfNotActive(); return internal.pointerBeforeReferenceNode; }); defineGetter(core.NodeIterator.prototype, "whatToShow", function () { return this[INTERNAL].whatToShow; }); defineGetter(core.NodeIterator.prototype, "filter", function () { return this[INTERNAL].filter; }); core.NodeIterator.prototype.previousNode = function () { const internal = this[INTERNAL]; internal.throwIfNotActive(); return idlUtils.wrapperForImpl(internal.traverse(false)); }; core.NodeIterator.prototype.nextNode = function () { const internal = this[INTERNAL]; internal.throwIfNotActive(); return idlUtils.wrapperForImpl(internal.traverse(true)); }; core.NodeIterator.prototype.detach = function () { // noop }; core.NodeIterator.prototype.toString = function () { return "[object NodeIterator]"; }; };