--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1148,40 +1148,40 @@ Inspector.prototype = {
*/
onNewSelection: function (event, value, reason) {
if (reason === "selection-destroy") {
return;
}
// Wait for all the known tools to finish updating and then let the
// client know.
- let selection = this.selection.nodeFront;
+ let selectionNodeFront = this.selection.nodeFront;
// Update the state of the add button in the toolbar depending on the
// current selection.
let btn = this.panelDoc.querySelector("#inspector-element-add-button");
if (this.canAddHTMLChild()) {
btn.removeAttribute("disabled");
} else {
btn.setAttribute("disabled", "true");
}
// On any new selection made by the user, store the unique css selector
// of the selected node so it can be restored after reload of the same page
if (this.canGetUniqueSelector &&
this.selection.isElementNode()) {
- selection.getUniqueSelector().then(selector => {
+ selectionNodeFront.getUniqueSelector().then(selector => {
this.selectionCssSelector = selector;
}, this._handleRejectionIfNotDestroyed);
}
let selfUpdate = this.updating("inspector-panel");
executeSoon(() => {
try {
- selfUpdate(selection);
+ selfUpdate(selectionNodeFront);
} catch (ex) {
console.error(ex);
}
});
},
/**
* Delay the "inspector-updated" notification while a tool
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -13,17 +13,17 @@ const EventEmitter = require("devtools/s
const {LocalizationHelper} = require("devtools/shared/l10n");
const {PluralForm} = require("devtools/shared/plural-form");
const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
const {scrollIntoViewIfNeeded} = require("devtools/client/shared/scroll");
const {UndoStack} = require("devtools/client/shared/undo");
const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
const {PrefObserver} = require("devtools/client/shared/prefs");
-const ShadowNodeContainer = require("devtools/client/inspector/markup/views/shadow-node-container");
+const SlottedNodeContainer = require("devtools/client/inspector/markup/views/slotted-node-container");
const MarkupElementContainer = require("devtools/client/inspector/markup/views/element-container");
const MarkupReadOnlyContainer = require("devtools/client/inspector/markup/views/read-only-container");
const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container");
const RootContainer = require("devtools/client/inspector/markup/views/root-container");
const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties");
@@ -957,23 +957,23 @@ MarkupView.prototype = {
return null;
}
if (this._containers.has(node)) {
return this.getContainer(node);
}
let container;
- let {nodeType, isPseudoElement, isShadowNode} = node;
+ let {nodeType, isPseudoElement, isSlottedNode} = node;
if (node === this.walker.rootNode) {
container = new RootContainer(this, node);
this._elt.appendChild(container.elt);
this._rootNode = node;
- } else if (isShadowNode) {
- container = new ShadowNodeContainer(this, node, this.inspector);
+ } else if (isSlottedNode) {
+ container = new SlottedNodeContainer(this, node, this.inspector);
} else if (nodeType == nodeConstants.ELEMENT_NODE && !isPseudoElement) {
container = new MarkupElementContainer(this, node, this.inspector);
} else if (nodeType == nodeConstants.COMMENT_NODE ||
nodeType == nodeConstants.TEXT_NODE) {
container = new MarkupTextContainer(this, node, this.inspector);
} else {
container = new MarkupReadOnlyContainer(this, node, this.inspector);
}
--- a/devtools/client/inspector/markup/views/moz.build
+++ b/devtools/client/inspector/markup/views/moz.build
@@ -7,13 +7,13 @@
DevToolsModules(
'element-container.js',
'element-editor.js',
'html-editor.js',
'markup-container.js',
'read-only-container.js',
'read-only-editor.js',
'root-container.js',
- 'shadow-node-container.js',
- 'shadow-node-editor.js',
+ 'slotted-node-container.js',
+ 'slotted-node-editor.js',
'text-container.js',
'text-editor.js',
)
rename from devtools/client/inspector/markup/views/shadow-node-container.js
rename to devtools/client/inspector/markup/views/slotted-node-container.js
--- a/devtools/client/inspector/markup/views/shadow-node-container.js
+++ b/devtools/client/inspector/markup/views/slotted-node-container.js
@@ -1,38 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const ShadowNodeEditor = require("devtools/client/inspector/markup/views/shadow-node-editor");
+const SlottedNodeEditor = require("devtools/client/inspector/markup/views/slotted-node-editor");
const MarkupContainer = require("devtools/client/inspector/markup/views/markup-container");
const {extend} = require("devtools/shared/extend");
/**
* An implementation of MarkupContainer for Pseudo Elements,
* Doctype nodes, or any other type generic node that doesn't
* fit for other editors.
* Does not allow any editing, just viewing / selecting.
*
* @param {MarkupView} markupView
* The markup view that owns this container.
* @param {NodeFront} node
* The node to display.
*/
-function ShadowNodeContainer(markupView, node) {
+function SlottedNodeContainer(markupView, node) {
MarkupContainer.prototype.initialize.call(this, markupView, node,
"readonlycontainer");
- this.editor = new ShadowNodeEditor(this, node);
+ this.editor = new SlottedNodeEditor(this, node);
this.tagLine.appendChild(this.editor.elt);
}
-ShadowNodeContainer.prototype = extend(MarkupContainer.prototype, {
+SlottedNodeContainer.prototype = extend(MarkupContainer.prototype, {
_onMouseDown: function (event) {
if (event.target.classList.contains("reveal-link")) {
event.stopPropagation();
event.preventDefault();
return;
}
MarkupContainer.prototype._onMouseDown.call(this, event);
},
@@ -42,9 +42,9 @@ ShadowNodeContainer.prototype = extend(M
let actorID = this.node._form.nodeActor;
let walkerFront = this.markup.inspector.walker;
let nodeFront = await walkerFront.getNodeFromActor(actorID, []);
this.markup.inspector.selection.setNodeFront(nodeFront);
}
}
});
-module.exports = ShadowNodeContainer;
+module.exports = SlottedNodeContainer;
rename from devtools/client/inspector/markup/views/shadow-node-editor.js
rename to devtools/client/inspector/markup/views/slotted-node-editor.js
--- a/devtools/server/actors/highlighters/selector.js
+++ b/devtools/server/actors/highlighters/selector.js
@@ -16,16 +16,26 @@ const MAX_HIGHLIGHTED_ELEMENTS = 100;
* document of the provided context node and then uses the BoxModelHighlighter
* to highlight the matching nodes
*/
function SelectorHighlighter(highlighterEnv) {
this.highlighterEnv = highlighterEnv;
this._highlighters = [];
}
+function getShadowRoot(node) {
+ let parent = node;
+ while ((parent = parent.parentNode)) {
+ if (parent.nodeType === 11 && parent.mode && parent.host) {
+ return parent;
+ }
+ }
+ return null;
+}
+
SelectorHighlighter.prototype = {
typeName: "SelectorHighlighter",
/**
* Show BoxModelHighlighter on each node that matches that provided selector.
* @param {DOMNode} node A context node that is used to get the document on
* which querySelectorAll should be executed. This node will NOT be
* highlighted.
@@ -35,19 +45,23 @@ SelectorHighlighter.prototype = {
*/
show: function (node, options = {}) {
this.hide();
if (!isNodeValid(node) || !options.selector) {
return false;
}
+ // If the node is in a shadow dom tree, return the shadow root for this element.
+ let shadowRoot = getShadowRoot(node);
+ let document = shadowRoot || node.ownerDocument;
+
let nodes = [];
try {
- nodes = [...node.ownerDocument.querySelectorAll(options.selector)];
+ nodes = [...document.querySelectorAll(options.selector)];
} catch (e) {
// It's fine if the provided selector is invalid, nodes will be an empty
// array.
}
delete options.selector;
let i = 0;
--- a/devtools/server/actors/inspector/document-walker.js
+++ b/devtools/server/actors/inspector/document-walker.js
@@ -49,16 +49,17 @@ function DocumentWalker(node, rootWin,
this.walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
.createInstance(Ci.inIDeepTreeWalker);
this.walker.showAnonymousContent = showAnonymousContent;
this.walker.showSubDocuments = true;
this.walker.showDocumentsAsNodes = true;
this.walker.init(rootWin.document, whatToShow);
this.filter = filter;
+ this.skipTo = skipTo;
// Make sure that the walker knows about the initial node (which could
// be skipped due to a filter).
this.walker.currentNode = this.getStartingNode(node, skipTo);
}
DocumentWalker.prototype = {
@@ -67,16 +68,20 @@ DocumentWalker.prototype = {
},
get currentNode() {
return this.walker.currentNode;
},
set currentNode(val) {
this.walker.currentNode = val;
},
+ setStartingNode: function (node) {
+ this.walker.currentNode = this.getStartingNode(node, this.skipTo);
+ },
+
parentNode: function () {
return this.walker.parentNode();
},
nextNode: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
--- a/devtools/server/actors/inspector/moz.build
+++ b/devtools/server/actors/inspector/moz.build
@@ -3,16 +3,16 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'document-walker.js',
'inspector-actor.js',
'node-actor.js',
- 'shadow-node-actor.js',
+ 'slotted-node-actor.js',
'utils.js',
'walker-actor.js',
'walker-search.js',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Inspector')
--- a/devtools/server/actors/inspector/node-actor.js
+++ b/devtools/server/actors/inspector/node-actor.js
@@ -108,17 +108,17 @@ const NodeActor = protocol.ActorClassWit
publicId: this.rawNode.publicId,
systemId: this.rawNode.systemId,
attrs: this.writeAttrs(),
isBeforePseudoElement: this.isBeforePseudoElement,
isAfterPseudoElement: this.isAfterPseudoElement,
isAnonymous: isAnonymous(this.rawNode),
isShadowRoot: !!this.rawNode.host,
- isShadowNode: false,
+ isSlottedNode: false,
isNativeAnonymous: isNativeAnonymous(this.rawNode),
isXBLAnonymous: isXBLAnonymous(this.rawNode),
isShadowAnonymous: isShadowAnonymous(this.rawNode),
pseudoClassLocks: this.writePseudoClassLocks(),
isDisplayed: this.isDisplayed,
isInHTMLDocument: this.rawNode.ownerDocument &&
this.rawNode.ownerDocument.contentType === "text/html",
rename from devtools/server/actors/inspector/shadow-node-actor.js
rename to devtools/server/actors/inspector/slotted-node-actor.js
--- a/devtools/server/actors/inspector/shadow-node-actor.js
+++ b/devtools/server/actors/inspector/slotted-node-actor.js
@@ -1,28 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const protocol = require("devtools/shared/protocol");
-const {shadowNodeSpec} = require("devtools/shared/specs/node");
+const {slottedNodeSpec} = require("devtools/shared/specs/node");
/**
* Server side of the node actor.
*/
-const ShadowNodeActor = protocol.ActorClassWithSpec(shadowNodeSpec, {
+const SlottedNodeActor = protocol.ActorClassWithSpec(slottedNodeSpec, {
initialize: function (nodeActor) {
protocol.Actor.prototype.initialize.call(this, null);
this.nodeActor = nodeActor;
},
toString: function () {
- return "[ShadowNodeActor " + this.actorID + " for NodeActor" +
+ return "[SlottedNodeActor " + this.actorID + " for NodeActor" +
this.nodeActor.actorID + "]";
},
get conn() {
return this.nodeActor.conn;
},
isDocumentElement: function () {
@@ -39,17 +39,17 @@ const ShadowNodeActor = protocol.ActorCl
return this.actorID;
}
let form = this.nodeActor.form();
form.actor = this.actorID;
form.nodeActor = this.nodeActor.actorID;
form.numChildren = 0;
form.inlineTextChild = undefined;
- form.isShadowNode = true;
+ form.isSlottedNode = true;
return form;
},
watchDocument: function () {
// no-op
},
@@ -160,9 +160,9 @@ const ShadowNodeActor = protocol.ActorCl
* rgba(r, g, b, a). Defaults to rgba(255, 255, 255, 1) if no
* background color is found.
*/
getClosestBackgroundColor: function () {
return this.nodeActor.getClosestBackgroundColor();
}
});
-exports.ShadowNodeActor = ShadowNodeActor;
+exports.SlottedNodeActor = SlottedNodeActor;
--- a/devtools/server/actors/inspector/walker-actor.js
+++ b/devtools/server/actors/inspector/walker-actor.js
@@ -20,17 +20,17 @@ loader.lazyRequireGetter(this, "throttle
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);
loader.lazyRequireGetter(this, "SKIP_TO_SIBLING", "devtools/server/actors/inspector/document-walker", true);
loader.lazyRequireGetter(this, "NodeActor", "devtools/server/actors/inspector/node-actor", true);
-loader.lazyRequireGetter(this, "ShadowNodeActor", "devtools/server/actors/inspector/shadow-node-actor", true);
+loader.lazyRequireGetter(this, "SlottedNodeActor", "devtools/server/actors/inspector/slotted-node-actor", true);
loader.lazyRequireGetter(this, "NodeListActor", "devtools/server/actors/inspector/node-actor", true);
loader.lazyRequireGetter(this, "WalkerSearch", "devtools/server/actors/inspector/walker-search", true);
loader.lazyRequireGetter(this, "LayoutActor", "devtools/server/actors/layout", true);
loader.lazyRequireGetter(this, "getLayoutChangesObserver", "devtools/server/actors/reflow", true);
loader.lazyRequireGetter(this, "releaseLayoutChangesObserver", "devtools/server/actors/reflow", true);
loader.lazyServiceGetter(this, "eventListenerService",
"@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
@@ -118,17 +118,17 @@ var WalkerActor = protocol.ActorClassWit
* The server connection.
*/
initialize: function (conn, tabActor, options) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this.rootWin = tabActor.window;
this.rootDoc = this.rootWin.document;
this._refMap = new Map();
- this._shadowRefMap = new Map();
+ this._slottedRefMap = new Map();
this._pendingMutations = [];
this._activePseudoClassLocks = new Set();
this.showAllAnonymousContent = options.showAllAnonymousContent;
this.walkerSearch = new WalkerSearch(this);
// Nodes which have been removed from the client's known
// ownership tree are considered "orphaned", and stored in
@@ -221,17 +221,17 @@ var WalkerActor = protocol.ActorClassWit
this._hoveredNode = null;
this.rootWin = null;
this.rootDoc = null;
this.rootNode = null;
this.layoutHelpers = null;
this._orphaned = null;
this._retainedOrphans = null;
this._refMap = null;
- this._shadowRefMap = null;
+ this._slottedRefMap = null;
this.tabActor.off("will-navigate", this.onFrameUnload);
this.tabActor.off("window-ready", this.onFrameLoad);
this.onFrameLoad = null;
this.onFrameUnload = null;
this.walkerSearch.destroy();
@@ -259,74 +259,83 @@ var WalkerActor = protocol.ActorClassWit
unmanage: function (actor) {
if (actor instanceof NodeActor) {
if (this._activePseudoClassLocks &&
this._activePseudoClassLocks.has(actor)) {
this.clearPseudoClassLocks(actor);
}
this._refMap.delete(actor.rawNode);
- this._shadowRefMap.delete(actor.rawNode);
+ this._slottedRefMap.delete(actor.rawNode);
}
protocol.Actor.prototype.unmanage.call(this, actor);
},
/**
* Determine if the walker has come across this DOM node before.
* @param {DOMNode} rawNode
* @return {Boolean}
*/
- hasNode: function (rawNode, shadow) {
- if (shadow) {
- return this._shadowRefMap.has(rawNode);
- }
+ hasNode: function (rawNode) {
return this._refMap.has(rawNode);
},
+ hasSlottedNode: function (rawNode) {
+ return this._slottedRefMap.has(rawNode);
+ },
+
/**
* If the walker has come across this DOM node before, then get the
* corresponding node actor.
* @param {DOMNode} rawNode
* @return {NodeActor}
*/
- getNode: function (rawNode, shadow = false) {
- if (shadow) {
- return this._shadowRefMap.get(rawNode);
- }
+ getNode: function (rawNode) {
return this._refMap.get(rawNode);
},
- _ref: function (node, shadow) {
- let actor = this.getNode(node, shadow);
+ getSlottedNode: function (rawNode) {
+ return this._slottedRefMap.get(rawNode);
+ },
+
+ _ref: function (node) {
+ let actor = this.getNode(node);
if (actor) {
return actor;
}
- if (shadow) {
- actor = new ShadowNodeActor(node);
- } else {
- actor = new NodeActor(this, node);
- }
+ actor = new NodeActor(this, node);
// Add the node actor as a child of this walker actor, assigning
// it an actorID.
this.manage(actor);
-
- if (shadow) {
- this._shadowRefMap.set(node, actor);
- } else {
- this._refMap.set(node, actor);
- }
+ this._refMap.set(node, actor);
if (node.nodeType === Ci.nsIDOMNode.DOCUMENT_NODE) {
actor.watchDocument(this.onMutations);
}
return actor;
},
+ _slottedRef: function (node) {
+ let actor = this.getSlottedNode(node);
+ if (actor) {
+ return actor;
+ }
+
+ let nodeActor = this._ref(node);
+ actor = new SlottedNodeActor(nodeActor);
+
+ // Add the node actor as a child of this walker actor, assigning
+ // it an actorID.
+ this.manage(actor);
+ this._slottedRefMap.set(node, actor);
+ return actor;
+ },
+
_onReflows: function (reflows) {
// Going through the nodes the walker knows about, see which ones have
// had their display changed and send a display-change event if any
let changes = [];
for (let [node, actor] of this._refMap) {
if (Cu.isDeadWrapper(node)) {
continue;
}
@@ -463,23 +472,25 @@ var WalkerActor = protocol.ActorClassWit
parents.push(this._ref(cur));
}
return parents;
},
parentNode: function (node) {
try {
- let walker;
- if (node.rawNode && node.rawNode.parentNode &&
- node.rawNode.parentNode.shadowRoot && !node.nodeActor) {
- walker = this.getDocumentWalker(node.rawNode, {showAnonymousContent: false});
- } else {
- walker = this.getDocumentWalker(node.rawNode);
- }
+ let parentNode = node.rawNode && node.rawNode.parentNode && node.rawNode.parentNode;
+ let isInLightDOM =
+ !node.nodeActor // not a shadow node actor
+ && !node.isBeforePseudoElement && !node.isAfterPseudoElement // not after/before
+ && parentNode && parentNode.shadowRoot; // parentNode is a shadow host;
+
+ let walker = this.getDocumentWalker(node.rawNode, {
+ showAnonymousContent: !isInLightDOM
+ });
let parent = walker.parentNode();
if (parent) {
return this._ref(parent);
}
} catch (e) {
dump("CAUGHT ERROR\n");
}
return null;
@@ -639,34 +650,30 @@ var WalkerActor = protocol.ActorClassWit
throw Error("Can't specify both 'center' and 'start' options.");
}
let maxNodes = options.maxNodes || -1;
if (maxNodes == -1) {
maxNodes = Number.MAX_VALUE;
}
let isShadowHost = !!node.rawNode.shadowRoot;
+ let isShadowRoot = !!node.rawNode.host;
- // We're going to create a few document walkers with the same filter,
- // make it easier.
- let getFilteredWalker = (documentWalkerNode, showAnonymousContent) => {
- let { 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.
- return this.getDocumentWalker(documentWalkerNode, {
- whatToShow,
- skipTo: SKIP_TO_SIBLING,
- showAnonymousContent: showAnonymousContent || !isShadowHost
- });
- };
+ let rawNode = node.rawNode;
+ let walker = this.getDocumentWalker(rawNode, {
+ whatToShow: options.whatToShow,
+ skipTo: SKIP_TO_SIBLING,
+ showAnonymousContent: !isShadowHost && !isShadowRoot
+ });
// Need to know the first and last child.
- let rawNode = node.rawNode;
- let firstChild = getFilteredWalker(rawNode).firstChild();
- let lastChild = getFilteredWalker(rawNode).lastChild();
+ let firstChild = walker.firstChild();
+
+ walker.setStartingNode(rawNode);
+ let lastChild = walker.lastChild();
if (!firstChild) {
// No children, we're done.
return { hasFirst: true, hasLast: true, nodes: [] };
}
let start;
if (options.center) {
@@ -675,68 +682,108 @@ var WalkerActor = protocol.ActorClassWit
start = options.start.rawNode;
} else {
start = firstChild;
}
let nodes = [];
// Start by reading backward from the starting point if we're centering...
- let backwardWalker = getFilteredWalker(start);
- if (backwardWalker.currentNode != firstChild && options.center) {
- backwardWalker.previousSibling();
+ walker.setStartingNode(start);
+
+ let lastBackwardNode;
+ if (walker.currentNode != firstChild && options.center) {
+ walker.previousSibling();
let backwardCount = Math.floor(maxNodes / 2);
- let backwardNodes = this._readBackward(backwardWalker, backwardCount);
+ let backwardNodes = this._readBackward(walker, backwardCount);
nodes = backwardNodes;
+ lastBackwardNode = walker.currentNode;
}
// Then read forward by any slack left in the max children...
- let forwardWalker = getFilteredWalker(start);
+ walker.setStartingNode(start);
let forwardCount = maxNodes - nodes.length;
- nodes = nodes.concat(this._readForward(forwardWalker, forwardCount));
+ nodes = nodes.concat(this._readForward(walker, forwardCount));
// If there's any room left, it means we've run all the way to the end.
// If we're centering, check if there are more items to read at the front.
let remaining = maxNodes - nodes.length;
if (options.center && remaining > 0 && nodes[0].rawNode != firstChild) {
- let firstNodes = this._readBackward(backwardWalker, remaining);
+ walker.setStartingNode = lastBackwardNode;
+ let firstNodes = this._readBackward(walker, remaining);
// Then put it all back together.
nodes = firstNodes.concat(nodes);
}
- nodes = nodes.map(n => {
- if (n.rawNode.parentNode === node.rawNode) {
- return n;
- }
- return this._ref(n, true);
- });
+ // Identify slotted elements if they are listed as children of a slot (not when they
+ // are listed as part of the light DOM). Convert their nodeActors to
+ // slottedNodeActors.
+ if (node.rawNode.nodeName === "SLOT") {
+ nodes = this._convertNodesToSlottedNodes(node, nodes);
+ }
if (nodes.length === 0) {
return { hasFirst: true, hasLast: true, nodes: [] };
}
+ // Compare first/last with expected nodes before modifying the nodes array in case
+ // this is a shadow host.
let hasFirst = nodes[0].rawNode == firstChild;
let hasLast = nodes[nodes.length - 1].rawNode == lastChild;
if (isShadowHost) {
- let before = this._ref(getFilteredWalker(rawNode, true).firstChild());
- let after = this._ref(getFilteredWalker(rawNode, true).lastChild());
- if (before.isBeforePseudoElement) {
- nodes = [before, ...nodes];
- }
- if (after.isAfterPseudoElement) {
- nodes.push(after);
- }
- nodes = [this._ref(node.rawNode.shadowRoot), ...nodes];
+ // We need a dedicated anonymous walker to fetch before / after pseudos.
+ let {before, after} = this._getBeforeAfterElements(rawNode);
+ nodes = [
+ // #shadow-root
+ this._ref(node.rawNode.shadowRoot),
+ // ::before
+ ...(before ? [before] : []),
+ // light dom nodes
+ ...nodes,
+ // ::after
+ ...(after ? [after] : []),
+ ];
}
return {hasFirst, hasLast, nodes};
},
+ _getBeforeAfterElements: function (node) {
+ let anonymousWalker = this.getDocumentWalker(node, {
+ showAnonymousContent: true
+ });
+ let before = this._ref(anonymousWalker.firstChild());
+
+ anonymousWalker.setStartingNode(node);
+ let after = this._ref(anonymousWalker.lastChild());
+
+ return {
+ before: before.isBeforePseudoElement ? before : undefined,
+ after: after.isAfterPseudoElement ? after : undefined,
+ };
+ },
+
+ _convertNodesToSlottedNodes: function (parentActor, nodes) {
+ return nodes.map(nodeActor => {
+ if (nodeActor.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
+ return nodeActor;
+ }
+
+ // An unused slot will list its default children (if any) and those should not be
+ // converted to slotted node actors.
+ if (nodeActor.rawNode.parentNode === parentActor.rawNode) {
+ return nodeActor;
+ }
+
+ return this._slottedRef(nodeActor.rawNode);
+ });
+ },
+
/**
* Return siblings of the given node. By default this method will return
* all siblings of the node, but there are options that can restrict this
* to a more manageable subset.
*
* If `start` or `center` are not specified, this method will center on the
* node whose siblings are requested.
*
--- a/devtools/shared/fronts/node.js
+++ b/devtools/shared/fronts/node.js
@@ -270,18 +270,18 @@ const NodeFront = FrontClassWithSpec(nod
},
get hasEventListeners() {
return this._form.hasEventListeners;
},
get isShadowRoot() {
return this._form.isShadowRoot;
},
- get isShadowNode() {
- return this._form.isShadowNode;
+ get isSlottedNode() {
+ return this._form.isSlottedNode;
},
get isBeforePseudoElement() {
return this._form.isBeforePseudoElement;
},
get isAfterPseudoElement() {
return this._form.isAfterPseudoElement;
},
get isPseudoElement() {
--- a/devtools/shared/specs/node.js
+++ b/devtools/shared/specs/node.js
@@ -123,18 +123,18 @@ const nodeSpec = generateActorSpec({
value: RetVal("string")
}
},
}
});
exports.nodeSpec = nodeSpec;
-const shadowNodeSpec = generateActorSpec({
- typeName: "shadownode",
+const slottedNodeSpec = generateActorSpec({
+ typeName: "slottednode",
methods: {
getNodeValue: {
request: {},
response: {
value: RetVal("longstring")
}
},
@@ -188,9 +188,9 @@ const shadowNodeSpec = generateActorSpec
request: {},
response: {
value: RetVal("string")
}
},
}
});
-exports.shadowNodeSpec = shadowNodeSpec;
+exports.slottedNodeSpec = slottedNodeSpec;
--- a/toolkit/modules/css-selector.js
+++ b/toolkit/modules/css-selector.js
@@ -3,16 +3,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["findCssSelector"];
+function getShadowRoot(node) {
+ let parent = node;
+ while ((parent = parent.parentNode)) {
+ if (parent.nodeType === 11 && parent.mode && parent.host) {
+ return parent;
+ }
+ }
+ return null;
+}
+
/**
* 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,
@@ -20,16 +30,17 @@ this.EXPORTED_SYMBOLS = ["findCssSelecto
*
*/
function getRootBindingParent(node) {
let parent;
let doc = node.ownerDocument;
if (!doc) {
return node;
}
+
while ((parent = doc.getBindingParent(node))) {
node = parent;
}
return node;
}
/**
* Find the position of [element] in [nodeList].
@@ -45,18 +56,26 @@ function positionInNodeList(element, nod
}
/**
* Find a unique CSS selector for a given element
* @returns a string such that ele.ownerDocument.querySelector(reply) === ele
* and ele.ownerDocument.querySelectorAll(reply).length === 1
*/
const findCssSelector = function(ele) {
- ele = getRootBindingParent(ele);
- let document = ele.ownerDocument;
+ let shadowRoot = getShadowRoot(ele);
+
+ let document;
+ if (shadowRoot) {
+ document = shadowRoot;
+ } else {
+ ele = getRootBindingParent(ele);
+ document = ele.ownerDocument;
+ }
+
if (!document || !document.contains(ele)) {
throw new Error("findCssSelector received element not inside document");
}
let cssEscape = ele.ownerGlobal.CSS.escape;
// document.querySelectorAll("#id") returns multiple if elements share an ID
if (ele.id &&
@@ -103,12 +122,14 @@ const findCssSelector = function(ele) {
}
// Not unique enough yet. As long as it's not a child of the document,
// continue recursing up until it is unique enough.
if (ele.parentNode !== document) {
index = positionInNodeList(ele, ele.parentNode.children) + 1;
selector = findCssSelector(ele.parentNode) + " > " +
cssEscape(tagName) + ":nth-child(" + index + ")";
+ } else if (!!shadowRoot) {
+ selector = cssEscape(tagName) + ":nth-child(" + index + ")";
}
return selector;
};