diff --git a/src/worker-thread/dom/Document.ts b/src/worker-thread/dom/Document.ts index 5a2dcc25b..635120e1b 100644 --- a/src/worker-thread/dom/Document.ts +++ b/src/worker-thread/dom/Document.ts @@ -45,6 +45,8 @@ import { propagate as propagateResize } from '../ResizePropagation'; import { TransferrableKeys } from '../../transfer/TransferrableKeys'; import { WorkerDOMGlobalScope, GlobalScope } from '../WorkerDOMGlobalScope'; import { set as setPhase } from '../phase'; +import { NodeIterator, NodeFilter } from './NodeIterator'; + const DOCUMENT_NAME = '#document'; @@ -151,4 +153,12 @@ export class Document extends Element { public getElementById(id: string): Element | null { return matchChildElement(this.body, (element) => element.id === id); } + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/NodeIterator + * @return NodeIterator object. + */ + public createNodeIterator(root: Node, whatToShow: number = NodeFilter.SHOW_ALL, filter: ((node: Node) => number) | null = null): NodeIterator { + return new NodeIterator(root, whatToShow, filter); + } } diff --git a/src/worker-thread/dom/NodeIterator.ts b/src/worker-thread/dom/NodeIterator.ts new file mode 100644 index 000000000..962cff3da --- /dev/null +++ b/src/worker-thread/dom/NodeIterator.ts @@ -0,0 +1,91 @@ +import { Node } from "./Node"; + +export const NodeFilter = { + FILTER_ACCEPT: 1, + FILTER_REJECT: 2, + FILTER_SKIP: 3, + SHOW_ALL: 0xFFFFFFFF, + SHOW_ELEMENT: 0x1, + SHOW_ATTRIBUTE: 0x2, + SHOW_TEXT: 0x4, +} + +export class NodeIterator { + private currentNode: Node; + private root: Node; + private whatToShow: number; + private filter: ((node: Node) => number) | null; + + constructor(root: Node, whatToShow: number = NodeFilter.SHOW_ALL, filter: ((node: Node) => number) | null = null) { + this.currentNode = root; + this.root = root; + this.whatToShow = whatToShow; + this.filter = filter; + } + + nextNode(): Node | null { + let next = this._nextNode(this.currentNode); + while (next) { + if (this._acceptNode(next) === NodeFilter.FILTER_ACCEPT) { + this.currentNode = next; + return next; + } + next = this._nextNode(next); + } + return null; + } + + private _nextNode(node: Node): Node | null { + if (node.firstChild) { + return node.firstChild; + } + while (node) { + if (node === this.root) { + return null; + } + if (node.nextSibling) { + return node.nextSibling; + } + (node as Node | null) = node.parentNode; + } + return null; + } + + private _acceptNode(node: Node): number { + if ((this.whatToShow & (1 << node.nodeType - 1)) === 0) { + return NodeFilter.FILTER_REJECT; + } + if (this.filter) { + return this.filter(node); + } + return NodeFilter.FILTER_ACCEPT; + } + + previousNode(): Node | null { + let prev = this._previousNode(this.currentNode); + while (prev) { + if (this._acceptNode(prev) === NodeFilter.FILTER_ACCEPT) { + this.currentNode = prev; + return prev; + } + prev = this._previousNode(prev); + } + return null; + } + + private _previousNode(node: Node): Node | null { + if (node === this.root) { + return null; + } + if (node.previousSibling) { + node = node.previousSibling; + while (node.lastChild) { + node = node.lastChild; + } + return node; + } + return node.parentNode; + } +} + +