Bug 1465873 - part6: Allow selection and breadcrumbs to walk from shadowRoot to host element;r=bgrins
By returning the shadow root as the parentNode of some elements, the breadcrumbs could no longer
display the chain of elements correctly, because shadowRoot.parentNode is null.
This changeset:
- returns the host actor ID as part of the shadowRoot form
- adds a parentOrHost convenience method on the node form
- uses said method in selection and breadcrumbs when walking up the ancestor chain
I don't think we should unconditionally return the host element as the parentNode of the
shadow root, because that is too disconnected from the reality.
MozReview-Commit-ID: JLeDb4VuT1q
--- a/devtools/client/framework/selection.js
+++ b/devtools/client/framework/selection.js
@@ -168,17 +168,17 @@ Selection.prototype = {
if (!node || !node.actorID) {
return false;
}
while (node) {
if (node === this._walker.rootNode) {
return true;
}
- node = node.parentNode();
+ node = node.parentOrHost();
}
return false;
},
isHTMLNode: function() {
const xhtmlNs = "http://www.w3.org/1999/xhtml";
return this.isNode() && this.nodeFront.namespaceURI == xhtmlNs;
},
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -734,17 +734,17 @@ HTMLBreadcrumbs.prototype = {
fragment.insertBefore(button, lastButtonInserted);
lastButtonInserted = button;
this.nodeHierarchy.splice(originalLength, 0, {
node,
button,
currentPrettyPrintText: this.prettyPrintNodeAsText(node)
});
}
- node = node.parentNode();
+ node = node.parentOrHost();
}
this.container.appendChild(fragment, this.container.firstChild);
},
/**
* Find the "youngest" ancestor of a node which is already in the breadcrumbs.
* @param {NodeFront} node.
* @return {Number} Index of the ancestor in the cache, or -1 if not found.
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -97,19 +97,22 @@ const NodeActor = protocol.ActorClassWit
// Returns the JSON representation of this object over the wire.
form: function(detail) {
if (detail === "actorid") {
return this.actorID;
}
const parentNode = this.walker.parentNode(this);
const inlineTextChild = this.walker.inlineTextChild(this);
+ const shadowRoot = isShadowRoot(this.rawNode);
+ const hostActor = shadowRoot ? this.walker.getNode(this.rawNode.host) : null;
const form = {
actor: this.actorID,
+ host: hostActor ? hostActor.actorID : undefined,
baseURI: this.rawNode.baseURI,
parent: parentNode ? parentNode.actorID : undefined,
nodeType: this.rawNode.nodeType,
namespaceURI: this.rawNode.namespaceURI,
nodeName: this.rawNode.nodeName,
nodeValue: this.rawNode.nodeValue,
displayName: InspectorActorUtils.getNodeDisplayName(this.rawNode),
numChildren: this.numChildren,
@@ -123,17 +126,17 @@ const NodeActor = protocol.ActorClassWit
attrs: this.writeAttrs(),
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: isShadowRoot(this.rawNode),
+ isShadowRoot: shadowRoot,
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,
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -75,23 +75,23 @@ const WalkerFront = FrontClassWithSpec(w
this._rootNodeDeferred = defer();
this._rootNodeDeferred.promise.then(() => {
this.emit("new-root");
});
},
/**
* When reading an actor form off the wire, we want to hook it up to its
- * parent front. The protocol guarantees that the parent will be seen
- * by the client in either a previous or the current request.
+ * parent or host front. The protocol guarantees that the parent will
+ * be seen by the client in either a previous or the current request.
* So if we've already seen this parent return it, otherwise create
* a bare-bones stand-in node. The stand-in node will be updated
* with a real form by the end of the deserialization.
*/
- ensureParentFront: function(id) {
+ ensureDOMNodeFront: function(id) {
const front = this.get(id);
if (front) {
return front;
}
return types.getType("domnode").read({ actor: id }, this, "standin");
},
--- a/devtools/shared/fronts/node.js
+++ b/devtools/shared/fronts/node.js
@@ -158,36 +158,48 @@ const NodeFront = FrontClassWithSpec(nod
// eventually we'll want to update some of the data.
this._form = Object.assign({}, form);
this._form.attrs = this._form.attrs ? this._form.attrs.slice() : [];
if (form.parent) {
// Get the owner actor for this actor (the walker), and find the
// parent node of this actor from it, creating a standin node if
// necessary.
- const parentNodeFront = ctx.marshallPool().ensureParentFront(form.parent);
+ const parentNodeFront = ctx.marshallPool().ensureDOMNodeFront(form.parent);
this.reparent(parentNodeFront);
}
+ if (form.host) {
+ this.host = ctx.marshallPool().ensureDOMNodeFront(form.host);
+ }
+
if (form.inlineTextChild) {
this.inlineTextChild =
types.getType("domnode").read(form.inlineTextChild, ctx);
} else {
this.inlineTextChild = undefined;
}
},
/**
* Returns the parent NodeFront for this NodeFront.
*/
parentNode: function() {
return this._parent;
},
/**
+ * Returns the NodeFront corresponding to the parentNode of this NodeFront, or the
+ * NodeFront corresponding to the host element for shadowRoot elements.
+ */
+ parentOrHost: function() {
+ return this.isShadowRoot ? this.host : this._parent;
+ },
+
+ /**
* Process a mutation entry as returned from the walker's `getMutations`
* request. Only tries to handle changes of the node's contents
* themselves (character data and attribute changes), the walker itself
* will keep the ownership tree up to date.
*/
updateMutation: function(change) {
if (change.type === "attributes") {
// We'll need to lazily reparse the attributes after this change.