diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/server/actors/inspector/document-walker.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/actors/inspector/document-walker.js')
-rw-r--r-- | devtools/server/actors/inspector/document-walker.js | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/devtools/server/actors/inspector/document-walker.js b/devtools/server/actors/inspector/document-walker.js new file mode 100644 index 0000000000..7ced18ecd8 --- /dev/null +++ b/devtools/server/actors/inspector/document-walker.js @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +loader.lazyRequireGetter( + this, + "nodeFilterConstants", + "resource://devtools/shared/dom-node-filter-constants.js" +); +loader.lazyRequireGetter( + this, + "standardTreeWalkerFilter", + "resource://devtools/server/actors/inspector/utils.js", + true +); + +// SKIP_TO_* arguments are used with the DocumentWalker, driving the strategy to use if +// the starting node is incompatible with the filter function of the walker. +const SKIP_TO_PARENT = "SKIP_TO_PARENT"; +const SKIP_TO_SIBLING = "SKIP_TO_SIBLING"; + +class DocumentWalker { + /** + * Wrapper for inDeepTreeWalker. Adds filtering to the traversal methods. + * See inDeepTreeWalker for more information about the methods. + * + * @param {DOMNode} node + * @param {Window} rootWin + * @param {Object} + * - {Function} filter + * A custom filter function Taking in a DOMNode and returning an Int. See + * WalkerActor.nodeFilter for an example. + * - {String} skipTo + * Either SKIP_TO_PARENT or SKIP_TO_SIBLING. If the provided node is not + * compatible with the filter function for this walker, try to find a compatible + * one either in the parents or in the siblings of the node. + * - {Boolean} showAnonymousContent + * Pass true to let the walker return and traverse anonymous content. + * When navigating host elements to which shadow DOM is attached, the light tree + * will be visible only to a walker with showAnonymousContent=false. The shadow + * tree will only be visible to a walker with showAnonymousContent=true. + */ + constructor( + node, + rootWin, + { + filter = standardTreeWalkerFilter, + skipTo = SKIP_TO_PARENT, + showAnonymousContent = true, + } = {} + ) { + if (Cu.isDeadWrapper(rootWin) || !rootWin.location) { + throw new Error("Got an invalid root window in DocumentWalker"); + } + + this.walker = Cc[ + "@mozilla.org/inspector/deep-tree-walker;1" + ].createInstance(Ci.inIDeepTreeWalker); + this.walker.showAnonymousContent = showAnonymousContent; + this.walker.showSubDocuments = true; + this.walker.showDocumentsAsNodes = true; + this.walker.init(rootWin.document); + this.filter = filter; + + // Make sure that the walker knows about the initial node (which could + // be skipped due to a filter). + this.walker.currentNode = this.getStartingNode(node, skipTo); + } + + get currentNode() { + return this.walker.currentNode; + } + set currentNode(val) { + this.walker.currentNode = val; + } + + parentNode() { + return this.walker.parentNode(); + } + + nextNode() { + const node = this.walker.currentNode; + if (!node) { + return null; + } + + let nextNode = this.walker.nextNode(); + while (nextNode && this.isSkippedNode(nextNode)) { + nextNode = this.walker.nextNode(); + } + + return nextNode; + } + + firstChild() { + if (!this.walker.currentNode) { + return null; + } + + let firstChild = this.walker.firstChild(); + while (firstChild && this.isSkippedNode(firstChild)) { + firstChild = this.walker.nextSibling(); + } + + return firstChild; + } + + lastChild() { + if (!this.walker.currentNode) { + return null; + } + + let lastChild = this.walker.lastChild(); + while (lastChild && this.isSkippedNode(lastChild)) { + lastChild = this.walker.previousSibling(); + } + + return lastChild; + } + + previousSibling() { + let node = this.walker.previousSibling(); + while (node && this.isSkippedNode(node)) { + node = this.walker.previousSibling(); + } + return node; + } + + nextSibling() { + let node = this.walker.nextSibling(); + while (node && this.isSkippedNode(node)) { + node = this.walker.nextSibling(); + } + return node; + } + + getStartingNode(node, skipTo) { + // Keep a reference on the starting node in case we can't find a node compatible with + // the filter. + const startingNode = node; + + if (skipTo === SKIP_TO_PARENT) { + while (node && this.isSkippedNode(node)) { + node = node.parentNode; + } + } else if (skipTo === SKIP_TO_SIBLING) { + node = this.getClosestAcceptedSibling(node); + } + + return node || startingNode; + } + + /** + * Loop on all of the provided node siblings until finding one that is compliant with + * the filter function. + */ + getClosestAcceptedSibling(node) { + if (this.filter(node) === nodeFilterConstants.FILTER_ACCEPT) { + // node is already valid, return immediately. + return node; + } + + // Loop on starting node siblings. + let previous = node; + let next = node; + while (previous || next) { + previous = previous?.previousSibling; + next = next?.nextSibling; + + if ( + previous && + this.filter(previous) === nodeFilterConstants.FILTER_ACCEPT + ) { + // A valid node was found in the previous siblings of the node. + return previous; + } + + if (next && this.filter(next) === nodeFilterConstants.FILTER_ACCEPT) { + // A valid node was found in the next siblings of the node. + return next; + } + } + + return null; + } + + isSkippedNode(node) { + return this.filter(node) === nodeFilterConstants.FILTER_SKIP; + } +} + +exports.DocumentWalker = DocumentWalker; +exports.SKIP_TO_PARENT = SKIP_TO_PARENT; +exports.SKIP_TO_SIBLING = SKIP_TO_SIBLING; |