196 lines
5.4 KiB
JavaScript
196 lines
5.4 KiB
JavaScript
/* 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;
|