sitoctt/node_modules/@mixmark-io/domino/NodeIterator.js

218 lines
5.8 KiB
JavaScript

"use strict";
module.exports = NodeIterator;
var NodeFilter = require('./NodeFilter');
var NodeTraversal = require('./NodeTraversal');
var utils = require('./utils');
/* Private methods and helpers */
/**
* @based on WebKit's NodeIterator::moveToNext and NodeIterator::moveToPrevious
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeIterator.cpp?rev=186279#L51
*/
function move(node, stayWithin, directionIsNext) {
if (directionIsNext) {
return NodeTraversal.next(node, stayWithin);
} else {
if (node === stayWithin) {
return null;
}
return NodeTraversal.previous(node, null);
}
}
function isInclusiveAncestor(node, possibleChild) {
for ( ; possibleChild; possibleChild = possibleChild.parentNode) {
if (node === possibleChild) { return true; }
}
return false;
}
/**
* @spec http://www.w3.org/TR/dom/#concept-nodeiterator-traverse
* @method
* @access private
* @param {NodeIterator} ni
* @param {string} direction One of 'next' or 'previous'.
* @return {Node|null}
*/
function traverse(ni, directionIsNext) {
var node, beforeNode;
node = ni._referenceNode;
beforeNode = ni._pointerBeforeReferenceNode;
while (true) {
if (beforeNode === directionIsNext) {
beforeNode = !beforeNode;
} else {
node = move(node, ni._root, directionIsNext);
if (node === null) {
return null;
}
}
var result = ni._internalFilter(node);
if (result === NodeFilter.FILTER_ACCEPT) {
break;
}
}
ni._referenceNode = node;
ni._pointerBeforeReferenceNode = beforeNode;
return node;
}
/* Public API */
/**
* Implemented version: http://www.w3.org/TR/2015/WD-dom-20150618/#nodeiterator
* Latest version: http://www.w3.org/TR/dom/#nodeiterator
*
* @constructor
* @param {Node} root
* @param {number} whatToShow [optional]
* @param {Function|NodeFilter} filter [optional]
* @throws Error
*/
function NodeIterator(root, whatToShow, filter) {
if (!root || !root.nodeType) {
utils.NotSupportedError();
}
// Read-only properties
this._root = root;
this._referenceNode = root;
this._pointerBeforeReferenceNode = true;
this._whatToShow = Number(whatToShow) || 0;
this._filter = filter || null;
this._active = false;
// Record active node iterators in the document, in order to perform
// "node iterator pre-removal steps".
root.doc._attachNodeIterator(this);
}
Object.defineProperties(NodeIterator.prototype, {
root: { get: function root() {
return this._root;
} },
referenceNode: { get: function referenceNode() {
return this._referenceNode;
} },
pointerBeforeReferenceNode: { get: function pointerBeforeReferenceNode() {
return this._pointerBeforeReferenceNode;
} },
whatToShow: { get: function whatToShow() {
return this._whatToShow;
} },
filter: { get: function filter() {
return this._filter;
} },
/**
* @method
* @param {Node} node
* @return {Number} Constant NodeFilter.FILTER_ACCEPT,
* NodeFilter.FILTER_REJECT or NodeFilter.FILTER_SKIP.
*/
_internalFilter: { value: function _internalFilter(node) {
/* jshint bitwise: false */
var result, filter;
if (this._active) {
utils.InvalidStateError();
}
// Maps nodeType to whatToShow
if (!(((1 << (node.nodeType - 1)) & this._whatToShow))) {
return NodeFilter.FILTER_SKIP;
}
filter = this._filter;
if (filter === null) {
result = NodeFilter.FILTER_ACCEPT;
} else {
this._active = true;
try {
if (typeof filter === 'function') {
result = filter(node);
} else {
result = filter.acceptNode(node);
}
} finally {
this._active = false;
}
}
// Note that coercing to a number means that
// `true` becomes `1` (which is NodeFilter.FILTER_ACCEPT)
// `false` becomes `0` (neither accept, reject, or skip)
return (+result);
} },
/**
* @spec https://dom.spec.whatwg.org/#nodeiterator-pre-removing-steps
* @method
* @return void
*/
_preremove: { value: function _preremove(toBeRemovedNode) {
if (isInclusiveAncestor(toBeRemovedNode, this._root)) { return; }
if (!isInclusiveAncestor(toBeRemovedNode, this._referenceNode)) { return; }
if (this._pointerBeforeReferenceNode) {
var next = toBeRemovedNode;
while (next.lastChild) {
next = next.lastChild;
}
next = NodeTraversal.next(next, this.root);
if (next) {
this._referenceNode = next;
return;
}
this._pointerBeforeReferenceNode = false;
// fall through
}
if (toBeRemovedNode.previousSibling === null) {
this._referenceNode = toBeRemovedNode.parentNode;
} else {
this._referenceNode = toBeRemovedNode.previousSibling;
var lastChild;
for (lastChild = this._referenceNode.lastChild;
lastChild;
lastChild = this._referenceNode.lastChild) {
this._referenceNode = lastChild;
}
}
} },
/**
* @spec http://www.w3.org/TR/dom/#dom-nodeiterator-nextnode
* @method
* @return {Node|null}
*/
nextNode: { value: function nextNode() {
return traverse(this, true);
} },
/**
* @spec http://www.w3.org/TR/dom/#dom-nodeiterator-previousnode
* @method
* @return {Node|null}
*/
previousNode: { value: function previousNode() {
return traverse(this, false);
} },
/**
* @spec http://www.w3.org/TR/dom/#dom-nodeiterator-detach
* @method
* @return void
*/
detach: { value: function detach() {
/* "The detach() method must do nothing.
* Its functionality (disabling a NodeIterator object) was removed,
* but the method itself is preserved for compatibility.
*/
} },
/** For compatibility with web-platform-tests. */
toString: { value: function toString() {
return "[object NodeIterator]";
} },
});