Bug 1465873 - part3: Move generic node utils from Node actor to layout/utils helper;r=bgrins draft
authorJulian Descottes <jdescottes@mozilla.com>
Thu, 28 Jun 2018 11:22:12 +0200
changeset 814184 892bbcc01923bdbf4819a0155d7f7fb7653557d1
parent 814183 6d591fb5c90e81a1aad2c5bef278765e2ca8cae8
child 814185 9dbae35e59b81283337281d7eae47e2d0b8f8800
push id115123
push userjdescottes@mozilla.com
push dateWed, 04 Jul 2018 17:42:29 +0000
reviewersbgrins
bugs1465873
milestone63.0a1
Bug 1465873 - part3: Move generic node utils from Node actor to layout/utils helper;r=bgrins MozReview-Commit-ID: ASB5m4Eyrck
devtools/server/actors/inspector/node.js
devtools/server/actors/inspector/walker.js
devtools/shared/layout/utils.js
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -11,20 +11,25 @@ const protocol = require("devtools/share
 const { nodeSpec, nodeListSpec } = require("devtools/shared/specs/node");
 
 loader.lazyRequireGetter(this, "colorUtils", "devtools/shared/css/color", true);
 
 loader.lazyRequireGetter(this, "getCssPath", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "getXPath", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
 
+loader.lazyRequireGetter(this, "isAfterPseudoElement", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isBeforePseudoElement", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isDirectShadowHostChild", "devtools/shared/layout/utils", true);
 loader.lazyRequireGetter(this, "isNativeAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isShadowAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isShadowHost", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isShadowRoot", "devtools/shared/layout/utils", true);
 loader.lazyRequireGetter(this, "isXBLAnonymous", "devtools/shared/layout/utils", true);
-loader.lazyRequireGetter(this, "isShadowAnonymous", "devtools/shared/layout/utils", true);
-loader.lazyRequireGetter(this, "isAnonymous", "devtools/shared/layout/utils", true);
 
 loader.lazyRequireGetter(this, "InspectorActorUtils", "devtools/server/actors/inspector/utils");
 loader.lazyRequireGetter(this, "LongStringActor", "devtools/server/actors/string", true);
 loader.lazyRequireGetter(this, "getFontPreviewData", "devtools/server/actors/styles", true);
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/actors/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "EventParsers", "devtools/server/actors/inspector/event-parsers", true);
 
 const SUBGRID_ENABLED =
@@ -112,25 +117,25 @@ const NodeActor = protocol.ActorClassWit
       displayType: this.displayType,
 
       // doctype attributes
       name: this.rawNode.name,
       publicId: this.rawNode.publicId,
       systemId: this.rawNode.systemId,
 
       attrs: this.writeAttrs(),
-      isBeforePseudoElement: this.isBeforePseudoElement,
-      isAfterPseudoElement: this.isAfterPseudoElement,
+      isBeforePseudoElement: isBeforePseudoElement(this.rawNode),
+      isAfterPseudoElement: isAfterPseudoElement(this.rawNode),
       isAnonymous: isAnonymous(this.rawNode),
       isNativeAnonymous: isNativeAnonymous(this.rawNode),
       isXBLAnonymous: isXBLAnonymous(this.rawNode),
       isShadowAnonymous: isShadowAnonymous(this.rawNode),
-      isShadowRoot: this.isShadowRoot,
-      isShadowHost: this.isShadowHost,
-      isDirectShadowHostChild: this.isDirectShadowHostChild,
+      isShadowRoot: isShadowRoot(this.rawNode),
+      isShadowHost: isShadowHost(this.rawNode),
+      isDirectShadowHostChild: isDirectShadowHostChild(this.rawNode),
       pseudoClassLocks: this.writePseudoClassLocks(),
 
       isDisplayed: this.isDisplayed,
       isInHTMLDocument: this.rawNode.ownerDocument &&
         this.rawNode.ownerDocument.contentType === "text/html",
       hasEventListeners: this._hasEventListeners,
     };
 
@@ -165,54 +170,22 @@ const NodeActor = protocol.ActorClassWit
   /**
    * Watch for all "slotchange" events on the node.
    */
   watchSlotchange: function(callback) {
     this.slotchangeListener = callback;
     this.rawNode.addEventListener("slotchange", this.slotchangeListener);
   },
 
-  get isBeforePseudoElement() {
-    return this.rawNode.nodeName === "_moz_generated_content_before";
-  },
-
-  get isAfterPseudoElement() {
-    return this.rawNode.nodeName === "_moz_generated_content_after";
-  },
-
-  get isShadowRoot() {
-    const isFragment = this.rawNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
-    return isFragment && !!this.rawNode.host;
-  },
-
-  get isShadowHost() {
-    const shadowRoot = this.rawNode.openOrClosedShadowRoot;
-    return shadowRoot && shadowRoot.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
-  },
-
-  get isDirectShadowHostChild() {
-    // Pseudo elements are always part of the anonymous tree.
-    if (this.isBeforePseudoElement || this.isAfterPseudoElement) {
-      return false;
-    }
-
-    const parentNode = this.rawNode.parentNode;
-    return parentNode && !!parentNode.openOrClosedShadowRoot;
-  },
-
-  get isTemplateElement() {
-    return this.rawNode instanceof this.rawNode.ownerGlobal.HTMLTemplateElement;
-  },
-
   // Estimate the number of children that the walker will return without making
   // a call to children() if possible.
   get numChildren() {
     // For pseudo elements, childNodes.length returns 1, but the walker
     // will return 0.
-    if (this.isBeforePseudoElement || this.isAfterPseudoElement) {
+    if (isBeforePseudoElement(this.rawNode) || isAfterPseudoElement(this.rawNode)) {
       return 0;
     }
 
     const rawNode = this.rawNode;
     let numChildren = rawNode.childNodes.length;
     const hasAnonChildren = rawNode.nodeType === Node.ELEMENT_NODE &&
                           rawNode.ownerDocument.getAnonymousNodes(rawNode);
 
@@ -220,17 +193,17 @@ const NodeActor = protocol.ActorClassWit
     const hasSVGDocument = rawNode.getSVGDocument && rawNode.getSVGDocument();
     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) {
+    if (numChildren === 0 || hasAnonChildren || isShadowHost(this.rawNode)) {
       numChildren = this.walker.countChildren(this);
     }
 
     return numChildren;
   },
 
   get computedStyle() {
     if (!this._computedStyle) {
@@ -241,18 +214,18 @@ const NodeActor = protocol.ActorClassWit
 
   /**
    * Returns the computed display style property value of the node.
    */
   get displayType() {
     // Consider all non-element nodes as displayed.
     if (InspectorActorUtils.isNodeDead(this) ||
         this.rawNode.nodeType !== Node.ELEMENT_NODE ||
-        this.isAfterPseudoElement ||
-        this.isBeforePseudoElement) {
+        isAfterPseudoElement(this.rawNode) ||
+        isBeforePseudoElement(this.rawNode)) {
       return null;
     }
 
     const style = this.computedStyle;
     if (!style) {
       return null;
     }
 
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -8,18 +8,25 @@ const {Cc, Ci, Cu} = require("chrome");
 
 const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
 const {walkerSpec} = require("devtools/shared/specs/inspector");
 const {LongStringActor} = require("devtools/server/actors/string");
 const InspectorUtils = require("InspectorUtils");
 
 loader.lazyRequireGetter(this, "getFrameElement", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isAfterPseudoElement", "devtools/shared/layout/utils", true);
 loader.lazyRequireGetter(this, "isAnonymous", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isBeforePseudoElement", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isDirectShadowHostChild", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isShadowHost", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isShadowRoot", "devtools/shared/layout/utils", true);
+loader.lazyRequireGetter(this, "isTemplateElement", "devtools/shared/layout/utils", true);
 loader.lazyRequireGetter(this, "loadSheet", "devtools/shared/layout/utils", true);
+
 loader.lazyRequireGetter(this, "throttle", "devtools/shared/throttle", true);
 
 loader.lazyRequireGetter(this, "allAnonymousContentTreeWalkerFilter", "devtools/server/actors/inspector/utils", true);
 loader.lazyRequireGetter(this, "isNodeDead", "devtools/server/actors/inspector/utils", true);
 loader.lazyRequireGetter(this, "nodeDocument", "devtools/server/actors/inspector/utils", true);
 loader.lazyRequireGetter(this, "standardTreeWalkerFilter", "devtools/server/actors/inspector/utils", true);
 
 loader.lazyRequireGetter(this, "DocumentWalker", "devtools/server/actors/inspector/document-walker", true);
@@ -313,17 +320,17 @@ var WalkerActor = protocol.ActorClassWit
     // it an actorID.
     this.manage(actor);
     this._refMap.set(node, actor);
 
     if (node.nodeType === Node.DOCUMENT_NODE) {
       actor.watchDocument(node, this.onMutations);
     }
 
-    if (actor.isShadowRoot) {
+    if (isShadowRoot(actor.rawNode)) {
       actor.watchDocument(node.ownerDocument, this.onMutations);
       actor.watchSlotchange(this.onSlotchange);
     }
 
     return actor;
   },
 
   _onReflows: function(reflows) {
@@ -433,17 +440,17 @@ var WalkerActor = protocol.ActorClassWit
     return this._ref(elt);
   },
 
   parentNode: function(node) {
     let parent;
     try {
       // If the node is the child of a shadow host, we can not use an anonymous walker to
       // get the shadow host parent.
-      const walker = node.isDirectShadowHostChild
+      const walker = isDirectShadowHostChild(node.rawNode)
         ? this.getNonAnonymousWalker(node.rawNode)
         : this.getDocumentWalker(node.rawNode);
       parent = walker.parentNode();
     } catch (e) {
       // When getting the parent node for a child of a non-slotted shadow host child,
       // walker.parentNode() will throw if the walker is anonymous, because non-slotted
       // shadow host children are not accessible anywhere in the anonymous tree.
       const walker = this.getNonAnonymousWalker(node.rawNode);
@@ -460,25 +467,26 @@ var WalkerActor = protocol.ActorClassWit
   /**
    * If the given NodeActor only has a single text node as a child with a text
    * content small enough to be inlined, return that child's NodeActor.
    *
    * @param NodeActor node
    */
   inlineTextChild: function(node) {
     // Quick checks to prevent creating a new walker if possible.
-    if (node.isBeforePseudoElement ||
-        node.isAfterPseudoElement ||
+    if (isBeforePseudoElement(node.rawNode) ||
+        isAfterPseudoElement(node.rawNode) ||
         node.rawNode.nodeType != Node.ELEMENT_NODE ||
         node.rawNode.children.length > 0) {
       return undefined;
     }
 
-    const walker = node.isDirectShadowHostChild ? this.getNonAnonymousWalker(node.rawNode)
-                                              : this.getDocumentWalker(node.rawNode);
+    const walker = isDirectShadowHostChild(node.rawNode)
+      ? this.getNonAnonymousWalker(node.rawNode)
+      : this.getDocumentWalker(node.rawNode);
     const firstChild = walker.firstChild();
 
     // Bail out if:
     // - more than one child
     // - unique child is not a text node
     // - unique child is a text node, but is too long to be inlined
     if (!firstChild ||
         walker.nextSibling() ||
@@ -649,52 +657,50 @@ var WalkerActor = protocol.ActorClassWit
     if (options.center && options.start) {
       throw Error("Can't specify both 'center' and 'start' options.");
     }
     let maxNodes = options.maxNodes || -1;
     if (maxNodes == -1) {
       maxNodes = Number.MAX_VALUE;
     }
 
-    const {
-      isDirectShadowHostChild,
-      isShadowHost,
-      isShadowRoot,
-      isTemplateElement,
-    } = node;
+    const directShadowHostChild = isDirectShadowHostChild(node.rawNode);
+    const shadowHost = isShadowHost(node.rawNode);
+    const shadowRoot = isShadowRoot(node.rawNode);
+    const templateElement = isTemplateElement(node.rawNode);
 
-    if (isTemplateElement) {
+    if (templateElement) {
       // <template> tags should have a single child pointing to the element's template
       // content.
       const documentFragment = node.rawNode.content;
       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) {
+    if (directShadowHostChild) {
       try {
         this.getDocumentWalker(node.rawNode, options.whatToShow, SKIP_TO_SIBLING);
       } catch (e) {
         isUnslottedHostChild = true;
       }
     }
 
     // We're going to create a few document walkers with the same filter,
     // make it easier.
     const getFilteredWalker = documentWalkerNode => {
       const { whatToShow } = options;
 
       // Use SKIP_TO_SIBLING to force the walker to use a sibling of the provided node
       // in case this one is incompatible with the walker's filter function.
       const skipTo = SKIP_TO_SIBLING;
 
-      const useNonAnonymousWalker = isShadowRoot || isShadowHost || isUnslottedHostChild;
+      const useNonAnonymousWalker = shadowRoot || shadowHost || isUnslottedHostChild;
       if (useNonAnonymousWalker) {
         // Do not use an anonymous walker for :
         // - shadow roots: if the host element has an ::after pseudo element, a walker on
         //   the last child of the shadow root will jump to the ::after element, which is
         //   not a child of the shadow root.
         //   TODO: For this case, should rather use an anonymous walker with a new
         //         dedicated filter.
         // - shadow hosts: anonymous children of host elements make up the shadow dom,
@@ -706,17 +712,17 @@ var WalkerActor = protocol.ActorClassWit
       return this.getDocumentWalker(documentWalkerNode, whatToShow, skipTo);
     };
 
     // Need to know the first and last child.
     const rawNode = node.rawNode;
     const firstChild = getFilteredWalker(rawNode).firstChild();
     const lastChild = getFilteredWalker(rawNode).lastChild();
 
-    if (!firstChild && !isShadowHost) {
+    if (!firstChild && !shadowHost) {
       // No children, we're done.
       return { hasFirst: true, hasLast: true, nodes: [] };
     }
 
     let nodes = [];
 
     if (firstChild) {
       let start;
@@ -760,17 +766,17 @@ var WalkerActor = protocol.ActorClassWit
       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) {
+    if (shadowHost) {
       // Use anonymous walkers to fetch ::before / ::after pseudo elements
       const firstChildWalker = this.getDocumentWalker(node.rawNode);
       const first = firstChildWalker.firstChild();
       const hasBefore = first && first.nodeName === "_moz_generated_content_before";
 
       const lastChildWalker = this.getDocumentWalker(node.rawNode);
       const last = lastChildWalker.lastChild();
       const hasAfter = last && last.nodeName === "_moz_generated_content_after";
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -582,16 +582,93 @@ function isShadowAnonymous(node) {
 
   // If there is a shadowRoot and this is part of it then this
   // is not native anonymous
   return parent.openOrClosedShadowRoot && parent.openOrClosedShadowRoot.contains(node);
 }
 exports.isShadowAnonymous = isShadowAnonymous;
 
 /**
+ * Determine whether a node is a template element.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isTemplateElement(node) {
+  return node.ownerGlobal && node instanceof node.ownerGlobal.HTMLTemplateElement;
+}
+exports.isTemplateElement = isTemplateElement;
+
+/**
+ * Determine whether a node is a shadow root.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isShadowRoot(node) {
+  const isFragment = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
+  return isFragment && !!node.host;
+}
+exports.isShadowRoot = isShadowRoot;
+
+/**
+ * Determine whether a node is a shadow host, ie. an element that has a shadowRoot
+ * attached to itself.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isShadowHost(node) {
+  const shadowRoot = node.openOrClosedShadowRoot;
+  return shadowRoot && shadowRoot.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
+}
+exports.isShadowHost = isShadowHost;
+
+/**
+ * Determine whether a node is a child of a shadow host. Even if the element has been
+ * assigned to a slot in the attached shadow DOM, the parent node for this element is
+ * still considered to be the "host" element, and we need to walk them differently.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isDirectShadowHostChild(node) {
+  // Pseudo elements are always part of the anonymous tree.
+  if (isBeforePseudoElement(node) || isAfterPseudoElement(node)) {
+    return false;
+  }
+
+  const parentNode = node.parentNode;
+  return parentNode && !!parentNode.openOrClosedShadowRoot;
+}
+exports.isDirectShadowHostChild = isDirectShadowHostChild;
+
+/**
+ * Determine whether a node is a ::before pseudo.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isBeforePseudoElement(node) {
+  return node.nodeName === "_moz_generated_content_before";
+}
+exports.isBeforePseudoElement = isBeforePseudoElement;
+
+/**
+ * Determine whether a node is a ::after pseudo.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isAfterPseudoElement(node) {
+  return node.nodeName === "_moz_generated_content_after";
+}
+exports.isAfterPseudoElement = isAfterPseudoElement;
+
+/**
  * Get the current zoom factor applied to the container window of a given node.
  * Container windows are used as a weakmap key to store the corresponding
  * nsIDOMWindowUtils instance to avoid querying it every time.
  *
  * @param {DOMNode|DOMWindow}
  *        The node for which the zoom factor should be calculated, or its
  *        owner window.
  * @return {Number}