tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

document-walker.js (5574B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 loader.lazyRequireGetter(
      8  this,
      9  "nodeFilterConstants",
     10  "resource://devtools/shared/dom-node-filter-constants.js"
     11 );
     12 loader.lazyRequireGetter(
     13  this,
     14  "standardTreeWalkerFilter",
     15  "resource://devtools/server/actors/inspector/utils.js",
     16  true
     17 );
     18 
     19 // SKIP_TO_* arguments are used with the DocumentWalker, driving the strategy to use if
     20 // the starting node is incompatible with the filter function of the walker.
     21 const SKIP_TO_PARENT = "SKIP_TO_PARENT";
     22 const SKIP_TO_SIBLING = "SKIP_TO_SIBLING";
     23 
     24 class DocumentWalker {
     25  /**
     26   * Wrapper for inDeepTreeWalker.  Adds filtering to the traversal methods.
     27   * See inDeepTreeWalker for more information about the methods.
     28   *
     29   * @param {DOMNode} node
     30   * @param {Window} rootWin
     31   * @param {object}
     32   *        - {Function} filter
     33   *          A custom filter function Taking in a DOMNode and returning an Int. See
     34   *          WalkerActor.nodeFilter for an example.
     35   *        - {String} skipTo
     36   *          Either SKIP_TO_PARENT or SKIP_TO_SIBLING. If the provided node is not
     37   *          compatible with the filter function for this walker, try to find a compatible
     38   *          one either in the parents or in the siblings of the node.
     39   *        - {Boolean} showAnonymousContent
     40   *          Pass true to let the walker return and traverse anonymous content.
     41   *          When navigating host elements to which shadow DOM is attached, the light tree
     42   *          will be visible only to a walker with showAnonymousContent=false. The shadow
     43   *          tree will only be visible to a walker with showAnonymousContent=true.
     44   */
     45  constructor(
     46    node,
     47    rootWin,
     48    {
     49      filter = standardTreeWalkerFilter,
     50      skipTo = SKIP_TO_PARENT,
     51      showAnonymousContent = true,
     52    } = {}
     53  ) {
     54    if (Cu.isDeadWrapper(rootWin) || !rootWin.location) {
     55      throw new Error("Got an invalid root window in DocumentWalker");
     56    }
     57 
     58    this.walker = Cc[
     59      "@mozilla.org/inspector/deep-tree-walker;1"
     60    ].createInstance(Ci.inIDeepTreeWalker);
     61    this.walker.showAnonymousContent = showAnonymousContent;
     62    this.walker.showSubDocuments = true;
     63    this.walker.showDocumentsAsNodes = true;
     64    this.walker.init(rootWin.document);
     65    this.filter = filter;
     66 
     67    // Make sure that the walker knows about the initial node (which could
     68    // be skipped due to a filter).
     69    this.walker.currentNode = this.getStartingNode(node, skipTo);
     70  }
     71 
     72  get currentNode() {
     73    return this.walker.currentNode;
     74  }
     75  set currentNode(val) {
     76    this.walker.currentNode = val;
     77  }
     78 
     79  parentNode() {
     80    return this.walker.parentNode();
     81  }
     82 
     83  nextNode() {
     84    const node = this.walker.currentNode;
     85    if (!node) {
     86      return null;
     87    }
     88 
     89    let nextNode = this.walker.nextNode();
     90    while (nextNode && this.isSkippedNode(nextNode)) {
     91      nextNode = this.walker.nextNode();
     92    }
     93 
     94    return nextNode;
     95  }
     96 
     97  firstChild() {
     98    if (!this.walker.currentNode) {
     99      return null;
    100    }
    101 
    102    let firstChild = this.walker.firstChild();
    103    while (firstChild && this.isSkippedNode(firstChild)) {
    104      firstChild = this.walker.nextSibling();
    105    }
    106 
    107    return firstChild;
    108  }
    109 
    110  lastChild() {
    111    if (!this.walker.currentNode) {
    112      return null;
    113    }
    114 
    115    let lastChild = this.walker.lastChild();
    116    while (lastChild && this.isSkippedNode(lastChild)) {
    117      lastChild = this.walker.previousSibling();
    118    }
    119 
    120    return lastChild;
    121  }
    122 
    123  previousSibling() {
    124    let node = this.walker.previousSibling();
    125    while (node && this.isSkippedNode(node)) {
    126      node = this.walker.previousSibling();
    127    }
    128    return node;
    129  }
    130 
    131  nextSibling() {
    132    let node = this.walker.nextSibling();
    133    while (node && this.isSkippedNode(node)) {
    134      node = this.walker.nextSibling();
    135    }
    136    return node;
    137  }
    138 
    139  getStartingNode(node, skipTo) {
    140    // Keep a reference on the starting node in case we can't find a node compatible with
    141    // the filter.
    142    const startingNode = node;
    143 
    144    if (skipTo === SKIP_TO_PARENT) {
    145      while (node && this.isSkippedNode(node)) {
    146        node = node.parentNode;
    147      }
    148    } else if (skipTo === SKIP_TO_SIBLING) {
    149      node = this.getClosestAcceptedSibling(node);
    150    }
    151 
    152    return node || startingNode;
    153  }
    154 
    155  /**
    156   * Loop on all of the provided node siblings until finding one that is compliant with
    157   * the filter function.
    158   */
    159  getClosestAcceptedSibling(node) {
    160    if (this.filter(node) === nodeFilterConstants.FILTER_ACCEPT) {
    161      // node is already valid, return immediately.
    162      return node;
    163    }
    164 
    165    // Loop on starting node siblings.
    166    let previous = node;
    167    let next = node;
    168    while (previous || next) {
    169      previous = previous?.previousSibling;
    170      next = next?.nextSibling;
    171 
    172      if (
    173        previous &&
    174        this.filter(previous) === nodeFilterConstants.FILTER_ACCEPT
    175      ) {
    176        // A valid node was found in the previous siblings of the node.
    177        return previous;
    178      }
    179 
    180      if (next && this.filter(next) === nodeFilterConstants.FILTER_ACCEPT) {
    181        // A valid node was found in the next siblings of the node.
    182        return next;
    183      }
    184    }
    185 
    186    return null;
    187  }
    188 
    189  isSkippedNode(node) {
    190    return this.filter(node) === nodeFilterConstants.FILTER_SKIP;
    191  }
    192 }
    193 
    194 exports.DocumentWalker = DocumentWalker;
    195 exports.SKIP_TO_PARENT = SKIP_TO_PARENT;
    196 exports.SKIP_TO_SIBLING = SKIP_TO_SIBLING;