Bug 1465873 - part3: Move generic node utils from Node actor to layout/utils helper;r=bgrins
MozReview-Commit-ID: ASB5m4Eyrck
--- 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}