Created
May 16, 2017 10:30
-
-
Save radum/d67ffdd595a6c6daf7d125619fa4b9c4 to your computer and use it in GitHub Desktop.
polyfill.document.createTreeWalker.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint no-param-reassign: 0 */ | |
/** | |
* JavaScript implementation of W3 DOM4 TreeWalker interface. | |
* | |
* See also: | |
* - https://dom.spec.whatwg.org/#interface-treewalker | |
* | |
* Attributes like "read-only" and "private" are ignored in this implementation | |
* due to ECMAScript 3 (as opposed to ES5) not supporting creation of such properties. | |
* There are workarounds, but under "keep it simple" and "don't do stupid things" they | |
* are ignored in this implementation. | |
*/ | |
(function(window, factory) { | |
var lazySizes = factory(window, window.document); | |
window.lazySizes = lazySizes; | |
if(typeof module == 'object' && module.exports){ | |
module.exports = lazySizes; | |
} else if (typeof define == 'function' && define.amd) { | |
define(lazySizes); | |
} | |
}(window, function l(win, doc) { | |
var TreeWalker, NodeFilter, create, toString, is, mapChild, mapSibling, | |
nodeFilter, traverseChildren, traverseSiblings, nextSkippingChildren; | |
// Cross-browser polyfill for these constants | |
NodeFilter = { | |
// Constants for acceptNode() | |
FILTER_ACCEPT: 1, | |
FILTER_REJECT: 2, | |
FILTER_SKIP: 3, | |
// Constants for whatToShow | |
SHOW_ALL: 0xFFFFFFFF, | |
SHOW_ELEMENT: 0x1, | |
SHOW_ATTRIBUTE: 0x2, // historical | |
SHOW_TEXT: 0x4, | |
SHOW_CDATA_SECTION: 0x8, // historical | |
SHOW_ENTITY_REFERENCE: 0x10, // historical | |
SHOW_ENTITY: 0x20, // historical | |
SHOW_PROCESSING_INSTRUCTION: 0x40, | |
SHOW_COMMENT: 0x80, | |
SHOW_DOCUMENT: 0x100, | |
SHOW_DOCUMENT_TYPE: 0x200, | |
SHOW_DOCUMENT_FRAGMENT: 0x400, | |
SHOW_NOTATION: 0x800 // historical | |
}; | |
/* Local utilities */ | |
create = Object.create || function (proto) { | |
function Empty() {} | |
Empty.prototype = proto; | |
return new Empty(); | |
}; | |
mapChild = { | |
first: 'firstChild', | |
last: 'lastChild', | |
next: 'firstChild', | |
previous: 'lastChild' | |
}; | |
mapSibling = { | |
next: 'nextSibling', | |
previous: 'previousSibling' | |
}; | |
toString = mapChild.toString; | |
is = function (x, type) { | |
return toString.call(x).toLowerCase() === '[object ' + type.toLowerCase() + ']'; | |
}; | |
/* Private methods and helpers */ | |
/** | |
* See https://dom.spec.whatwg.org/#concept-node-filter | |
* | |
* @private | |
* @method | |
* @param {TreeWalker} tw | |
* @param {Node} node | |
*/ | |
nodeFilter = function (tw, node) { | |
// Maps nodeType to whatToShow | |
if (!(((1 << (node.nodeType - 1)) & tw.whatToShow))) { | |
return NodeFilter.FILTER_SKIP; | |
} | |
if (tw.filter === null) { | |
return NodeFilter.FILTER_ACCEPT; | |
} | |
return tw.filter.acceptNode(node); | |
}; | |
/** | |
* See https://dom.spec.whatwg.org/#concept-traverse-children | |
* | |
* @private | |
* @method | |
* @param {TreeWalker} tw | |
* @param {string} type One of 'first' or 'last'. | |
* @return {Node|null} | |
*/ | |
traverseChildren = function (tw, type) { | |
var child, node, parent, result, sibling; | |
node = tw.currentNode[ mapChild[ type ] ]; | |
while (node !== null) { | |
result = nodeFilter(tw, node); | |
if (result === NodeFilter.FILTER_ACCEPT) { | |
tw.currentNode = node; | |
return node; | |
} | |
if (result === NodeFilter.FILTER_SKIP) { | |
child = node[ mapChild[ type ] ]; | |
if (child !== null) { | |
node = child; | |
continue; | |
} | |
} | |
while (node !== null) { | |
sibling = node[ mapChild[ type ] ]; | |
if (sibling !== null) { | |
node = sibling; | |
break; | |
} | |
parent = node.parentNode; | |
if (parent === null || parent === tw.root || parent === tw.currentNode) { | |
return null; | |
} else { | |
node = parent; | |
} | |
} | |
} | |
return null; | |
}; | |
/** | |
* See https://dom.spec.whatwg.org/#concept-traverse-siblings | |
* | |
* @private | |
* @method | |
* @param {TreeWalker} tw | |
* @param {TreeWalker} type One of 'next' or 'previous'. | |
* @return {Node|null} | |
*/ | |
traverseSiblings = function (tw, type) { | |
var node, result, sibling; | |
node = tw.currentNode; | |
if (node === tw.root) { | |
return null; | |
} | |
while (true) { | |
sibling = node[ mapSibling[ type ] ]; | |
while (sibling !== null) { | |
node = sibling; | |
result = nodeFilter(tw, node); | |
if (result === NodeFilter.FILTER_ACCEPT) { | |
tw.currentNode = node; | |
return node; | |
} | |
sibling = node[ mapChild[ type ] ]; | |
if (result === NodeFilter.FILTER_REJECT) { | |
sibling = node[ mapSibling[ type ] ]; | |
} | |
} | |
node = node.parentNode; | |
if (node === null || node === tw.root) { | |
return null; | |
} | |
if (nodeFilter(tw, node) === NodeFilter.FILTER_ACCEPT) { | |
return null; | |
} | |
} | |
}; | |
/** | |
* Based on WebKit's NodeTraversal::nextSkippingChildren | |
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.h?rev=137221#L103 | |
*/ | |
nextSkippingChildren = function (node, stayWithin) { | |
if (node === stayWithin) { | |
return null; | |
} | |
if (node.nextSibling !== null) { | |
return node.nextSibling; | |
} | |
/** | |
* Based on WebKit's NodeTraversal::nextAncestorSibling | |
* https://trac.webkit.org/browser/trunk/Source/WebCore/dom/NodeTraversal.cpp?rev=137221#L43 | |
*/ | |
while (node.parentNode !== null) { | |
node = node.parentNode; | |
if (node === stayWithin) { | |
return null; | |
} | |
if (node.nextSibling !== null) { | |
return node.nextSibling; | |
} | |
} | |
return null; | |
}; | |
/** | |
* See https://dom.spec.whatwg.org/#interface-treewalker | |
* | |
* @constructor | |
* @param {Node} root | |
* @param {number} [whatToShow] | |
* @param {Function} [filter] | |
* @throws Error | |
*/ | |
TreeWalker = function (root, whatToShow, filter) { | |
var tw = this, active = false; | |
if (!root || !root.nodeType) { | |
throw new Error('DOMException: NOT_SUPPORTED_ERR'); | |
} | |
tw.root = root; | |
tw.whatToShow = Number(whatToShow) || 0; | |
tw.currentNode = root; | |
if (!is(filter, 'function')) { | |
tw.filter = null; | |
} else { | |
tw.filter = create(win.NodeFilter.prototype); | |
/** | |
* See https://dom.spec.whatwg.org/#dom-nodefilter-acceptnode | |
* | |
* @method | |
* @member NodeFilter | |
* @param {Node} node | |
* @return {number} Constant NodeFilter.FILTER_ACCEPT, | |
* NodeFilter.FILTER_REJECT or NodeFilter.FILTER_SKIP. | |
*/ | |
tw.filter.acceptNode = function (node) { | |
var result; | |
if (active) { | |
throw new Error('DOMException: INVALID_STATE_ERR'); | |
} | |
active = true; | |
result = filter(node); | |
active = false; | |
return result; | |
}; | |
} | |
}; | |
TreeWalker.prototype = { | |
constructor: TreeWalker, | |
/** | |
* See https://dom.spec.whatwg.org/#ddom-treewalker-parentnode | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
parentNode: function () { | |
var node = this.currentNode; | |
while (node !== null && node !== this.root) { | |
node = node.parentNode; | |
if (node !== null && nodeFilter(this, node) === NodeFilter.FILTER_ACCEPT) { | |
this.currentNode = node; | |
return node; | |
} | |
} | |
return null; | |
}, | |
/** | |
* See https://dom.spec.whatwg.org/#dom-treewalker-firstchild | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
firstChild: function () { | |
return traverseChildren(this, 'first'); | |
}, | |
/** | |
* See https://dom.spec.whatwg.org/#dom-treewalker-lastchild | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
lastChild: function () { | |
return traverseChildren(this, 'last'); | |
}, | |
/** | |
* See https://dom.spec.whatwg.org/#dom-treewalker-previoussibling | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
previousSibling: function () { | |
return traverseSiblings(this, 'previous'); | |
}, | |
/** | |
* See https://dom.spec.whatwg.org/#dom-treewalker-nextsibling | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
nextSibling: function () { | |
return traverseSiblings(this, 'next'); | |
}, | |
/** | |
* See https://dom.spec.whatwg.org/#dom-treewalker-previousnode | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
previousNode: function () { | |
var node, result, sibling; | |
node = this.currentNode; | |
while (node !== this.root) { | |
sibling = node.previousSibling; | |
while (sibling !== null) { | |
node = sibling; | |
result = nodeFilter(this, node); | |
while (result !== NodeFilter.FILTER_REJECT && node.lastChild !== null) { | |
node = node.lastChild; | |
result = nodeFilter(this, node); | |
} | |
if (result === NodeFilter.FILTER_ACCEPT) { | |
this.currentNode = node; | |
return node; | |
} | |
} | |
if (node === this.root || node.parentNode === null) { | |
return null; | |
} | |
node = node.parentNode; | |
if (nodeFilter(this, node) === NodeFilter.FILTER_ACCEPT) { | |
this.currentNode = node; | |
return node; | |
} | |
} | |
return null; | |
}, | |
/** | |
* See https://dom.spec.whatwg.org/#dom-treewalker-nextnode | |
* | |
* @method | |
* @return {Node|null} | |
*/ | |
nextNode: function () { | |
var node, result, following; | |
node = this.currentNode; | |
result = NodeFilter.FILTER_ACCEPT; | |
while (true) { | |
while (result !== NodeFilter.FILTER_REJECT && node.firstChild !== null) { | |
node = node.firstChild; | |
result = nodeFilter(this, node); | |
if (result === NodeFilter.FILTER_ACCEPT) { | |
this.currentNode = node; | |
return node; | |
} | |
} | |
following = nextSkippingChildren(node, this.root); | |
if (following !== null) { | |
node = following; | |
} else { | |
return null; | |
} | |
result = nodeFilter(this, node); | |
if (result === NodeFilter.FILTER_ACCEPT) { | |
this.currentNode = node; | |
return node; | |
} | |
} | |
} | |
}; | |
/** | |
* See http://www.w3.org/TR/dom/#dom-document-createtreewalker | |
* | |
* @param {Node} root | |
* @param {number} [whatToShow=NodeFilter.SHOW_ALL] | |
* @param {Function|Object} [filter=null] | |
* @return {TreeWalker} | |
*/ | |
doc.createTreeWalker = function (root, whatToShow, filter) { | |
whatToShow = whatToShow === undefined ? NodeFilter.SHOW_ALL : whatToShow; | |
if (filter && is(filter.acceptNode, 'function')) { | |
filter = filter.acceptNode; | |
// Support Gecko-ism of filter being a function. | |
// https://developer.mozilla.org/en-US/docs/DOM/document.createTreeWalker | |
} else if (!is(filter, 'function')) { | |
filter = null; | |
} | |
return new TreeWalker(root, whatToShow, filter); | |
}; | |
if (!win.NodeFilter) { | |
win.NodeFilter = NodeFilter.constructor = NodeFilter.prototype = NodeFilter; | |
} | |
if (!win.TreeWalker) { | |
win.TreeWalker = TreeWalker; | |
} | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment