Bug 1465873 - part1: Implement walker::countChildren() to count children without updating refMap;r=bgrins draft
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 27 Jun 2018 13:51:26 +0200
changeset 814182 2f5ad39436b98a833997851d98c950a817197f97
parent 814181 ea4f65b8dc6737bc1640b329fe1c4f94cb0961b7
child 814183 6d591fb5c90e81a1aad2c5bef278765e2ca8cae8
push id115123
push userjdescottes@mozilla.com
push dateWed, 04 Jul 2018 17:42:29 +0000
reviewersbgrins
bugs1465873
milestone63.0a1
Bug 1465873 - part1: Implement walker::countChildren() to count children without updating refMap;r=bgrins MozReview-Commit-ID: CvJFIvn7Kdr
devtools/server/actors/inspector/node.js
devtools/server/actors/inspector/walker.js
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -221,17 +221,17 @@ const NodeActor = protocol.ActorClassWit
     if (numChildren === 0 && (hasContentDocument || hasSVGDocument)) {
       // This might be an iframe with virtual children.
       numChildren = 1;
     }
 
     // Normal counting misses ::before/::after.  Also, some anonymous children
     // may ultimately be skipped, so we have to consult with the walker.
     if (numChildren === 0 || hasAnonChildren || this.isShadowHost) {
-      numChildren = this.walker.children(this).nodes.length;
+      numChildren = this.walker.countChildren(this);
     }
 
     return numChildren;
   },
 
   get computedStyle() {
     if (!this._computedStyle) {
       this._computedStyle = CssLogic.getComputedStyle(this.rawNode);
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -575,16 +575,29 @@ var WalkerActor = protocol.ActorClassWit
         // This parent did exist, so the client knows about it.
         return newParents;
       }
     }
     return newParents;
   },
 
   /**
+   * Return the number of children under the provided NodeActor.
+   *
+   * @param NodeActor node
+   *    See JSDoc for children()
+   * @param object options
+   *    See JSDoc for children()
+   * @return Number the number of children
+   */
+  countChildren: function(node, options = {}) {
+    return this._getChildren(node, options).nodes.length;
+  },
+
+  /**
    * Return children of the given node.  By default this method will return
    * all children of the node, but there are options that can restrict this
    * to a more manageable subset.
    *
    * @param NodeActor node
    *    The node whose children you're curious about.
    * @param object options
    *    Named options:
@@ -596,19 +609,44 @@ var WalkerActor = protocol.ActorClassWit
    *       as possible in the list, given how close to the ends of the child
    *       list it is.  Mutually exclusive with `start`.
    *    `whatToShow`: A bitmask of node types that should be included.  See
    *       https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter.
    *
    * @returns an object with three items:
    *    hasFirst: true if the first child of the node is included in the list.
    *    hasLast: true if the last child of the node is included in the list.
-   *    nodes: Child nodes returned by the request.
+   *    nodes: Array of NodeActor representing the nodes returned by the request.
    */
   children: function(node, options = {}) {
+    const { hasFirst, hasLast, nodes } = this._getChildren(node, options);
+    return {
+      hasFirst,
+      hasLast,
+      nodes: nodes.map(n => this._ref(n))
+    };
+  },
+
+  /**
+   * Return chidlren of the given node. Contrary to children children(), this method only
+   * returns DOMNodes. Therefore it will not create NodeActor wrappers and will not
+   * update the refMap for the discovered nodes either. This makes this method safe to
+   * call when you are not sure if the discovered nodes will be communicated to the
+   * client.
+   *
+   * @param NodeActor node
+   *    See JSDoc for children()
+   * @param object options
+   *    See JSDoc for children()
+   * @return  an object with three items:
+   *    hasFirst: true if the first child of the node is included in the list.
+   *    hasLast: true if the last child of the node is included in the list.
+   *    nodes: Array of DOMNodes.
+   */
+  _getChildren: function(node, options = {}) {
     if (isNodeDead(node)) {
       return { hasFirst: true, hasLast: true, nodes: [] };
     }
 
     if (options.center && options.start) {
       throw Error("Can't specify both 'center' and 'start' options.");
     }
     let maxNodes = options.maxNodes || -1;
@@ -622,17 +660,17 @@ var WalkerActor = protocol.ActorClassWit
       isShadowRoot,
       isTemplateElement,
     } = node;
 
     if (isTemplateElement) {
       // <template> tags should have a single child pointing to the element's template
       // content.
       const documentFragment = node.rawNode.content;
-      const nodes = [this._ref(documentFragment)];
+      const nodes = [documentFragment];
       return { hasFirst: true, hasLast: true, nodes };
     }
 
     // Detect special case of unslotted shadow host children that cannot rely on a
     // regular anonymous walker.
     let isUnslottedHostChild = false;
     if (isDirectShadowHostChild) {
       try {
@@ -714,18 +752,18 @@ var WalkerActor = protocol.ActorClassWit
         nodes = firstNodes.concat(nodes);
       }
     }
 
     let hasFirst, hasLast;
     if (nodes.length > 0) {
       // Compare first/last with expected nodes before modifying the nodes array in case
       // this is a shadow host.
-      hasFirst = nodes[0].rawNode == firstChild;
-      hasLast = nodes[nodes.length - 1].rawNode == lastChild;
+      hasFirst = nodes[0] == firstChild;
+      hasLast = nodes[nodes.length - 1] == lastChild;
     } else {
       // If nodes is still an empty array, we are on a host element with a shadow root but
       // no direct children.
       hasFirst = hasLast = true;
     }
 
     if (isShadowHost) {
       // Use anonymous walkers to fetch ::before / ::after pseudo elements
@@ -734,23 +772,23 @@ var WalkerActor = protocol.ActorClassWit
       const hasBefore = first && this._ref(first).isBeforePseudoElement;
 
       const lastChildWalker = this.getDocumentWalker(node.rawNode);
       const last = lastChildWalker.lastChild();
       const hasAfter = last && this._ref(last).isAfterPseudoElement;
 
       nodes = [
         // #shadow-root
-        this._ref(node.rawNode.openOrClosedShadowRoot),
+        node.rawNode.openOrClosedShadowRoot,
         // ::before
-        ...(hasBefore ? [this._ref(first)] : []),
+        ...(hasBefore ? [first] : []),
         // shadow host direct children
         ...nodes,
         // ::after
-        ...(hasAfter ? [this._ref(last)] : []),
+        ...(hasAfter ? [last] : []),
       ];
     }
 
     return { hasFirst, hasLast, nodes };
   },
 
   /**
    * Get the next sibling of a given node.  Getting nodes one at a time
@@ -797,17 +835,17 @@ var WalkerActor = protocol.ActorClassWit
   _readForward: function(walker, count) {
     const ret = [];
 
     let node = walker.currentNode;
     do {
       if (!walker.isSkippedNode(node)) {
         // The walker can be on a node that would be filtered out if it didn't find any
         // other node to fallback to.
-        ret.push(this._ref(node));
+        ret.push(node);
       }
       node = walker.nextSibling();
     } while (node && --count);
     return ret;
   },
 
   /**
    * Helper function for the `children` method: Read backward in the sibling
@@ -816,17 +854,17 @@ var WalkerActor = protocol.ActorClassWit
   _readBackward: function(walker, count) {
     const ret = [];
 
     let node = walker.currentNode;
     do {
       if (!walker.isSkippedNode(node)) {
         // The walker can be on a node that would be filtered out if it didn't find any
         // other node to fallback to.
-        ret.push(this._ref(node));
+        ret.push(node);
       }
       node = walker.previousSibling();
     } while (node && --count);
     ret.reverse();
     return ret;
   },
 
   /**