Bug 1596800 - Remove document.getBindingParent usage from devtools. r=jdescottes
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 18 Nov 2019 20:54:10 +0000
changeset 502473 c2817cb864536cf73287225833f89c0619209ac6
parent 502472 cceae677e859c9915d895bee2783ca1c0c34a296
child 502474 d5db2146b9c5519a2d42233c4699818ba347ec2a
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1596800
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1596800 - Remove document.getBindingParent usage from devtools. r=jdescottes This removes the concept of shadowAnonymous, which doesn't make a lot of sense, and re-enables the shadow dom tests which were disabled when we removed the old style system (as stylo didn't supported shadow DOM yet by then). This is a change in behavior as you can now remove nodes from shadow DOM (no reason you weren't able to, before). Differential Revision: https://phabricator.services.mozilla.com/D53340
devtools/client/inspector/markup/test/browser_markup_anonymous_03.js
devtools/client/inspector/markup/test/browser_markup_anonymous_04.js
devtools/server/actors/inspector/node.js
devtools/server/tests/browser/browser_inspector-anonymous.js
devtools/server/tests/browser/inspector-traversal-data.html
devtools/shared/inspector/css-logic.js
devtools/shared/layout/utils.js
--- a/devtools/client/inspector/markup/test/browser_markup_anonymous_03.js
+++ b/devtools/client/inspector/markup/test/browser_markup_anonymous_03.js
@@ -28,14 +28,14 @@ add_task(async function() {
   const shadowRootFront = containers[0].node;
   const children = await inspector.walker.children(shadowRootFront);
 
   is(shadowRootFront.numChildren, 2, "Children of the shadow root are counted");
   is(children.nodes.length, 2, "Children returned from walker");
 
   info("Checking the <h3> shadow element");
   const shadowChild1 = children.nodes[0];
-  await isEditingMenuDisabled(shadowChild1, inspector);
+  await isEditingMenuEnabled(shadowChild1, inspector);
 
   info("Checking the <select> shadow element");
   const shadowChild2 = children.nodes[1];
-  await isEditingMenuDisabled(shadowChild2, inspector);
+  await isEditingMenuEnabled(shadowChild2, inspector);
 });
--- a/devtools/client/inspector/markup/test/browser_markup_anonymous_04.js
+++ b/devtools/client/inspector/markup/test/browser_markup_anonymous_04.js
@@ -27,13 +27,12 @@ add_task(async function() {
   is(
     grandchildren.nodes.length,
     2,
     "<input type=file> has native anonymous children"
   );
 
   for (const node of grandchildren.nodes) {
     ok(node.isAnonymous, "Child is anonymous");
-    ok(!node._form.isShadowAnonymous, "Child is not shadow anonymous");
     ok(node._form.isNativeAnonymous, "Child is native anonymous");
     await isEditingMenuDisabled(node, inspector);
   }
 });
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -68,22 +68,16 @@ loader.lazyRequireGetter(
 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",
@@ -249,17 +243,16 @@ const NodeActor = protocol.ActorClassWit
 
       attrs: this.writeAttrs(),
       customElementLocation: this.getCustomElementLocation(),
       isMarkerPseudoElement: isMarkerPseudoElement(this.rawNode),
       isBeforePseudoElement: isBeforePseudoElement(this.rawNode),
       isAfterPseudoElement: isAfterPseudoElement(this.rawNode),
       isAnonymous: isAnonymous(this.rawNode),
       isNativeAnonymous: isNativeAnonymous(this.rawNode),
-      isShadowAnonymous: isShadowAnonymous(this.rawNode),
       isShadowRoot: shadowRoot,
       shadowRootMode: getShadowRootMode(this.rawNode),
       isShadowHost: isShadowHost(this.rawNode),
       isDirectShadowHostChild: isDirectShadowHostChild(this.rawNode),
       pseudoClassLocks: this.writePseudoClassLocks(),
       mutationBreakpoints: this.walker.getMutationBreakpoints(this),
 
       isDisplayed: this.isDisplayed,
@@ -353,21 +346,24 @@ 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.
+    //
+    // FIXME: We should be able to just check <slot> rather than
+    // containingShadowRoot.
     if (
       numChildren === 0 ||
       hasAnonChildren ||
       isShadowHost(this.rawNode) ||
-      isShadowAnonymous(this.rawNode)
+      this.rawNode.containingShadowRoot
     ) {
       numChildren = this.walker.countChildren(this);
     }
 
     return numChildren;
   },
 
   get computedStyle() {
--- a/devtools/server/tests/browser/browser_inspector-anonymous.js
+++ b/devtools/server/tests/browser/browser_inspector-anonymous.js
@@ -109,25 +109,23 @@ async function testPseudoElements(walker
     pseudo.numChildren,
     1,
     "::before/::after are not counted if there is a child"
   );
   is(children.nodes.length, 3, "Correct number of children");
 
   const before = children.nodes[0];
   ok(before.isAnonymous, "Child is anonymous");
-  ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
   ok(before._form.isNativeAnonymous, "Child is native anonymous");
 
   const span = children.nodes[1];
   ok(!span.isAnonymous, "Child is not anonymous");
 
   const after = children.nodes[2];
   ok(after.isAnonymous, "Child is anonymous");
-  ok(!after._form.isShadowAnonymous, "Child is not shadow anonymous");
   ok(after._form.isNativeAnonymous, "Child is native anonymous");
 }
 
 async function testEmptyWithPseudo(walker) {
   info("Testing elements with no childrent, except for pseudos.");
 
   info("Checking an element whose only child is a pseudo element");
   const pseudo = await walker.querySelector(walker.rootNode, "#pseudo-empty");
@@ -137,53 +135,65 @@ async function testEmptyWithPseudo(walke
     pseudo.numChildren,
     1,
     "::before/::after are is counted if there are no other children"
   );
   is(children.nodes.length, 1, "Correct number of children");
 
   const before = children.nodes[0];
   ok(before.isAnonymous, "Child is anonymous");
-  ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
   ok(before._form.isNativeAnonymous, "Child is native anonymous");
 }
 
 async function testShadowAnonymous(walker) {
-  if (true) {
-    // FIXME(bug 1465114)
-    return;
-  }
-
   info("Testing shadow DOM content.");
 
-  const shadow = await walker.querySelector(walker.rootNode, "#shadow");
-  const children = await walker.children(shadow);
+  const host = await walker.querySelector(walker.rootNode, "#shadow");
+  const children = await walker.children(host);
 
-  is(shadow.numChildren, 3, "Children of the shadow root are counted");
+  // #shadow-root, ::before, light dom
+  is(host.numChildren, 3, "Children of the shadow root are counted");
   is(children.nodes.length, 3, "Children returned from walker");
 
-  const before = children.nodes[0];
-  ok(before.isAnonymous, "Child is anonymous");
-  ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
-  ok(before._form.isNativeAnonymous, "Child is native anonymous");
+  const before = children.nodes[1];
+  is(
+    before._form.nodeName,
+    "_moz_generated_content_before",
+    "Should be the ::before pseudo-element"
+  );
+  ok(before.isAnonymous, "::before is anonymous");
+  ok(before._form.isNativeAnonymous, "::before is native anonymous");
+  info(JSON.stringify(before._form));
+
+  const shadow = children.nodes[0];
+  const shadowChildren = await walker.children(shadow);
+  // <h3>...</h3>, <select multiple></select>
+  is(shadow.numChildren, 2, "Children of the shadow root are counted");
+  is(shadowChildren.nodes.length, 2, "Children returned from walker");
 
   // <h3>Shadow <em>DOM</em></h3>
-  const shadowChild1 = children.nodes[1];
-  ok(shadowChild1.isAnonymous, "Child is anonymous");
-  ok(shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
-  ok(!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
+  const shadowChild1 = shadowChildren.nodes[0];
+  ok(!shadowChild1.isAnonymous, "Shadow child is not anonymous");
+  ok(
+    !shadowChild1._form.isNativeAnonymous,
+    "Shadow child is not native anonymous"
+  );
 
-  const shadowSubChildren = await walker.children(children.nodes[1]);
+  const shadowSubChildren = await walker.children(shadowChild1);
   is(shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
   is(shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
 
   // <em>DOM</em>
-  const shadowSubChild = children.nodes[1];
-  ok(shadowSubChild.isAnonymous, "Child is anonymous");
-  ok(shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
-  ok(!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
+  const shadowSubChild = shadowSubChildren.nodes[1];
+  ok(
+    !shadowSubChild.isAnonymous,
+    "Subchildren of shadow root are not anonymous"
+  );
+  ok(
+    !shadowSubChild._form.isNativeAnonymous,
+    "Subchildren of shadow root is not native anonymous"
+  );
 
   // <select multiple></select>
-  const shadowChild2 = children.nodes[2];
-  ok(shadowChild2.isAnonymous, "Child is anonymous");
-  ok(shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
+  const shadowChild2 = shadowChildren.nodes[1];
+  ok(!shadowChild2.isAnonymous, "Child is anonymous");
   ok(!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
 }
--- a/devtools/server/tests/browser/inspector-traversal-data.html
+++ b/devtools/server/tests/browser/inspector-traversal-data.html
@@ -18,31 +18,29 @@
     }
   </style>
   <script type="text/javascript">
     "use strict";
 
     window.onload = function() {
       // Set up a basic shadow DOM
       const host = document.querySelector("#shadow");
-      if (host.attachShadow) {
-        const root = host.attachShadow({ mode: "open" });
+      const root = host.attachShadow({ mode: "open" });
 
-        const h3 = document.createElement("h3");
-        h3.append("Shadow ");
+      const h3 = document.createElement("h3");
+      h3.append("Shadow ");
 
-        const em = document.createElement("em");
-        em.append("DOM");
+      const em = document.createElement("em");
+      em.append("DOM");
 
-        const select = document.createElement("select");
-        select.setAttribute("multiple", "");
-        h3.appendChild(em);
-        root.appendChild(h3);
-        root.appendChild(select);
-      }
+      const select = document.createElement("select");
+      select.setAttribute("multiple", "");
+      h3.appendChild(em);
+      root.appendChild(h3);
+      root.appendChild(select);
 
       // Put a copy of the body in an iframe to test frame traversal.
       const body = document.querySelector("body");
       const data = "data:text/html,<html>" + body.outerHTML + "<html>";
       const iframe = document.createElement("iframe");
       iframe.setAttribute("id", "childFrame");
       iframe.src = data;
       body.appendChild(iframe);
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -26,17 +26,16 @@ loader.lazyRequireGetter(
   true
 );
 loader.lazyRequireGetter(
   this,
   "getTabPrefs",
   "devtools/shared/indentation",
   true
 );
-const { getRootBindingParent } = require("devtools/shared/layout/utils");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const styleInspectorL10N = new LocalizationHelper(
   "devtools/shared/locales/styleinspector.properties"
 );
 
 /**
  * Special values for filter, in addition to an href these values can be used
  */
@@ -533,35 +532,16 @@ function hasVisitedState(node) {
   return (
     !!(InspectorUtils.getContentState(node) & NS_EVENT_STATE_VISITED) ||
     InspectorUtils.hasPseudoClassLock(node, ":visited")
   );
 }
 exports.hasVisitedState = hasVisitedState;
 
 /**
- * Return the node's parent shadow root if the node in shadow DOM, null
- * otherwise.
- */
-function getShadowRoot(node) {
-  const doc = node.ownerDocument;
-  if (!doc) {
-    return null;
-  }
-
-  const parent = doc.getBindingParent(node);
-  const shadowRoot = parent && parent.openOrClosedShadowRoot;
-  if (shadowRoot) {
-    return shadowRoot;
-  }
-
-  return null;
-}
-
-/**
  * Find the position of [element] in [nodeList].
  * @returns an index of the match, or -1 if there is no match
  */
 function positionInNodeList(element, nodeList) {
   for (let i = 0; i < nodeList.length; i++) {
     if (element === nodeList[i]) {
       return i;
     }
@@ -570,32 +550,35 @@ function positionInNodeList(element, nod
 }
 
 /**
  * For a provided node, find the appropriate container/node couple so that
  * container.contains(node) and a CSS selector can be created from the
  * container to the node.
  */
 function findNodeAndContainer(node) {
-  const shadowRoot = getShadowRoot(node);
+  const shadowRoot = node.containingShadowRoot;
+  while (node && node.isNativeAnonymous) {
+    node = node.parentNode;
+  }
+
   if (shadowRoot) {
     // If the node is under a shadow root, the shadowRoot contains the node and
     // we can find the node via shadowRoot.querySelector(path).
     return {
       containingDocOrShadow: shadowRoot,
       node,
     };
   }
 
   // Otherwise, get the root binding parent to get a non anonymous element that
   // will be accessible from the ownerDocument.
-  const bindingParent = getRootBindingParent(node);
   return {
-    containingDocOrShadow: bindingParent.ownerDocument,
-    node: bindingParent,
+    containingDocOrShadow: node.ownerDocument,
+    node,
   };
 }
 
 /**
  * Find a unique CSS selector for a given element
  * @returns a string such that:
  *   - ele.containingDocOrShadow.querySelector(reply) === ele
  *   - ele.containingDocOrShadow.querySelectorAll(reply).length === 1
@@ -665,17 +648,17 @@ const findCssSelector = function(ele) {
 };
 exports.findCssSelector = findCssSelector;
 
 /**
  * If the element is in a frame or under a shadowRoot, return the corresponding
  * element.
  */
 function getSelectorParent(node) {
-  const shadowRoot = getShadowRoot(node);
+  const shadowRoot = node.containingShadowRoot;
   if (shadowRoot) {
     // The element is in a shadowRoot, return the host component.
     return shadowRoot.host;
   }
 
   // Otherwise return the parent frameElement.
   return node.ownerGlobal.frameElement;
 }
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -428,111 +428,27 @@ function isNodeConnected(node) {
   } catch (e) {
     // "can't access dead object" error
     return false;
   }
 }
 exports.isNodeConnected = isNodeConnected;
 
 /**
- * Traverse getBindingParent until arriving upon the bound element
- * responsible for the generation of the specified node.
- * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/DOM_Interfaces#getBindingParent.
- *
- * @param {DOMNode} node
- * @return {DOMNode}
- *         If node is not anonymous, this will return node. Otherwise,
- *         it will return the bound element
- *
- */
-function getRootBindingParent(node) {
-  let parent;
-  const doc = node.ownerDocument;
-  if (!doc) {
-    return node;
-  }
-  while ((parent = doc.getBindingParent(node))) {
-    node = parent;
-  }
-  return node;
-}
-exports.getRootBindingParent = getRootBindingParent;
-
-function getBindingParent(node) {
-  const doc = node.ownerDocument;
-  if (!doc) {
-    return null;
-  }
-
-  // If there is no binding parent then it is not anonymous.
-  const parent = doc.getBindingParent(node);
-  if (!parent) {
-    return null;
-  }
-
-  return parent;
-}
-exports.getBindingParent = getBindingParent;
-
-/**
- * Determine whether a node is anonymous by determining if there
- * is a bindingParent.
+ * Determine whether a node is anonymous.
  *
  * @param {DOMNode} node
  * @return {Boolean}
  *
- */
-const isAnonymous = node => getRootBindingParent(node) !== node;
-exports.isAnonymous = isAnonymous;
-
-/**
- * Determine whether a node has a bindingParent.
- *
- * @param {DOMNode} node
- * @return {Boolean}
- *
- */
-const hasBindingParent = node => !!getBindingParent(node);
-
-/**
- * Determine whether a node is native anonymous content (as opposed
- * to shadow DOM).
- * Native anonymous content includes elements like internals to form
- * controls and ::before/::after.
- *
- * @param {DOMNode} node
- * @return {Boolean}
- *
+ * FIXME(bug 1597411): Remove one of these (or both, as
+ * `node.isNativeAnonymous` is quite clear).
  */
-const isNativeAnonymous = node =>
-  hasBindingParent(node) && !isShadowAnonymous(node);
-
-exports.isNativeAnonymous = isNativeAnonymous;
-
-/**
- * Determine whether a node is a child of a shadow root.
- * See https://w3c.github.io/webcomponents/spec/shadow/
- *
- * @param {DOMNode} node
- * @return {Boolean}
- */
-function isShadowAnonymous(node) {
-  const parent = getBindingParent(node);
-  if (!parent) {
-    return false;
-  }
-
-  // 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;
+const isAnonymous = node => node.isNativeAnonymous;
+exports.isAnonymous = isAnonymous;
+exports.isNativeAnonymous = isAnonymous;
 
 /**
  * Determine whether a node is a template element.
  *
  * @param {DOMNode} node
  * @return {Boolean}
  */
 function isTemplateElement(node) {
@@ -543,20 +459,17 @@ function isTemplateElement(node) {
 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;
-}
+const isShadowRoot = node => node.containingShadowRoot == node;
 exports.isShadowRoot = isShadowRoot;
 
 /*
  * Gets the shadow root mode (open or closed).
  *
  * @param {DOMNode} node
  * @return {String|null}
  */
@@ -587,17 +500,17 @@ exports.isShadowHost = isShadowHost;
  * @return {Boolean}
  */
 function isDirectShadowHostChild(node) {
   // Pseudo elements and native anonymous elements are always part of the anonymous tree.
   if (
     isMarkerPseudoElement(node) ||
     isBeforePseudoElement(node) ||
     isAfterPseudoElement(node) ||
-    isNativeAnonymous(node)
+    node.isNativeAnonymous
   ) {
     return false;
   }
 
   const parentNode = node.parentNode;
   return parentNode && !!parentNode.openOrClosedShadowRoot;
 }
 exports.isDirectShadowHostChild = isDirectShadowHostChild;