diff --git a/devtools/client/fronts/walker.js b/devtools/client/fronts/walker.js
index 60a148b225b9..98baeed9d05f 100644
--- a/devtools/client/fronts/walker.js
+++ b/devtools/client/fronts/walker.js
@@ -391,64 +391,6 @@ class WalkerFront extends FrontClassWithSpec(walkerSpec) {
documentNode.reparent(parentNode);
}
- /**
- * Evaluate the cross iframes query selectors for the current walker front.
- *
- * @param {Array} selectors
- * An array of CSS selectors to find the target accessible object.
- * Several selectors can be needed if the element is nested in frames
- * and not directly in the root document.
- * @return {Promise} a promise that resolves when the node front is found for
- * selection using inspector tools.
- */
- async findNodeFront(nodeSelectors) {
- const querySelectors = async nodeFront => {
- const selector = nodeSelectors.shift();
- if (!selector) {
- return nodeFront;
- }
- nodeFront = await nodeFront.walkerFront.querySelector(
- nodeFront,
- selector
- );
- // It's possible the containing iframe isn't available by the time
- // walkerFront.querySelector is called, which causes the re-selected node to be
- // unavailable. There also isn't a way for us to know when all iframes on the page
- // have been created after a reload. Because of this, we should should bail here.
- if (!nodeFront) {
- return null;
- }
-
- if (nodeSelectors.length > 0) {
- await nodeFront.waitForFrameLoad();
-
- const { nodes } = await this.children(nodeFront);
-
- // If there are remaining selectors to process, they will target a document or a
- // document-fragment under the current node. Whether the element is a frame or
- // a web component, it can only contain one document/document-fragment, so just
- // select the first one available.
- nodeFront = nodes.find(node => {
- const { nodeType } = node;
- return (
- nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
- nodeType === Node.DOCUMENT_NODE
- );
- });
-
- // The iframe selector might have matched an element which is not an
- // iframe in the new page (or an iframe with no document?). In this
- // case, bail out and fallback to the root body element.
- if (!nodeFront) {
- return null;
- }
- }
- return querySelectors(nodeFront) || nodeFront;
- };
- const nodeFront = await this.getRootNode();
- return querySelectors(nodeFront);
- }
-
_onRootNodeAvailable(rootNode) {
if (rootNode.isTopLevelDocument) {
this.rootNode = rootNode;
diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js
index 70cdfd4604cd..93ab0b698cc6 100644
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -571,7 +571,12 @@ Inspector.prototype = {
// Try to find a default node using three strategies:
const defaultNodeSelectors = [
// - first try to match css selectors for the selection
- () => (cssSelectors.length ? walker.findNodeFront(cssSelectors) : null),
+ () =>
+ cssSelectors.length
+ ? this.commands.inspectorCommand.findNodeFrontFromSelectors(
+ cssSelectors
+ )
+ : null,
// - otherwise try to get the "body" element
() => walker.querySelector(rootNodeFront, "body"),
// - finally get the documentElement element if nothing else worked.
diff --git a/devtools/client/inspector/shared/highlighters-overlay.js b/devtools/client/inspector/shared/highlighters-overlay.js
index c42858169172..1de2b29a1854 100644
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -1381,7 +1381,9 @@ class HighlightersOverlay {
return;
}
- const nodeFront = await this.inspectorFront.walker.findNodeFront(selectors);
+ const nodeFront = await this.inspector.commands.inspectorCommand.findNodeFrontFromSelectors(
+ selectors
+ );
if (nodeFront) {
await showFunction(nodeFront, options);
diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_inspector.js b/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_inspector.js
index 732c6ca7b287..c6917bc5ad4c 100644
--- a/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_inspector.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_inspector.js
@@ -11,6 +11,12 @@ const FILE_FOLDER = `browser/devtools/client/webconsole/test/browser`;
const TEST_URI = `https://example.com/${FILE_FOLDER}/test-console-evaluation-context-selector.html`;
const IFRAME_PATH = `${FILE_FOLDER}/test-console-evaluation-context-selector-child.html`;
+// Import helpers for the inspector
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
+ this
+);
+
requestLongerTimeout(2);
add_task(async function() {
@@ -29,7 +35,7 @@ add_task(async function() {
await waitForEagerEvaluationResult(hud, `"example.com"`);
info("Go to the inspector panel");
- const inspector = await openInspector();
+ const inspector = await hud.toolbox.selectTool("inspector");
info("Expand all the nodes");
await inspector.markup.expandAll();
@@ -38,7 +44,7 @@ add_task(async function() {
await hud.toolbox.openSplitConsole();
info("Select the first iframe h2 element");
- await selectIframeContentElement(inspector, ".iframe-1", "h2");
+ await selectNodeInFrames([".iframe-1", "h2"], inspector);
await waitFor(() =>
evaluationContextSelectorButton.innerText.includes("example.org")
@@ -49,7 +55,7 @@ add_task(async function() {
ok(true, "The instant evaluation result is updated in the iframe context");
info("Select the second iframe h2 element");
- await selectIframeContentElement(inspector, ".iframe-2", "h2");
+ await selectNodeInFrames([".iframe-2", "h2"], inspector);
await waitFor(() =>
evaluationContextSelectorButton.innerText.includes("example.net")
@@ -60,9 +66,7 @@ add_task(async function() {
ok(true, "The instant evaluation result is updated in the iframe context");
info("Select an element in the top document");
- const h1NodeFront = await inspector.walker.findNodeFront(["h1"]);
- inspector.selection.setNodeFront(null);
- inspector.selection.setNodeFront(h1NodeFront);
+ await selectNodeInFrames(["h1"], inspector);
await waitForEagerEvaluationResult(hud, `"example.com"`);
await waitFor(() =>
@@ -75,8 +79,7 @@ add_task(async function() {
await testUseInConsole(
hud,
inspector,
- ".iframe-1",
- "h2",
+ [".iframe-1", "h2"],
"temp0",
`
`
);
@@ -118,35 +119,14 @@ add_task(async function() {
ok(true, "The context selector was updated");
});
-async function selectIframeContentElement(
- inspector,
- iframeSelector,
- iframeContentSelector
-) {
- inspector.selection.setNodeFront(null);
- const iframeNodeFront = await inspector.walker.findNodeFront([
- iframeSelector,
- ]);
- const childrenNodeFront = await iframeNodeFront
- .treeChildren()[0]
- .walkerFront.findNodeFront([iframeContentSelector]);
- inspector.selection.setNodeFront(childrenNodeFront);
- return childrenNodeFront;
-}
-
async function testUseInConsole(
hud,
inspector,
- iframeSelector,
- iframeContentSelector,
+ selectors,
variableName,
expectedTextResult
) {
- const nodeFront = await selectIframeContentElement(
- inspector,
- iframeSelector,
- iframeContentSelector
- );
+ const nodeFront = await selectNodeInFrames(selectors, inspector);
const container = inspector.markup.getContainer(nodeFront);
// Clear the input before clicking on "Use in Console" to workaround an bug
diff --git a/devtools/shared/commands/inspector/inspector-command.js b/devtools/shared/commands/inspector/inspector-command.js
index ce9f02c0812c..5f9b0f0c6853 100644
--- a/devtools/shared/commands/inspector/inspector-command.js
+++ b/devtools/shared/commands/inspector/inspector-command.js
@@ -134,6 +134,98 @@ class InspectorCommand {
// Descending sort the list by count, i.e. second element of the arrays
return sortSuggestions(mergedSuggestions);
}
+
+ /**
+ * Find a nodeFront from an array of selectors. The last item of the array is the selector
+ * for the element in its owner document, and the previous items are selectors to iframes
+ * that lead to the frame where the searched node lives in.
+ *
+ * For example, with the following markup
+ *
+ *
+ *
+ * If you want to retrieve the `
` nodeFront, `selectors` would be:
+ * [
+ * "#level-1",
+ * "#level-2",
+ * "h1",
+ * ]
+ *
+ * @param {Array} selectors
+ * An array of CSS selectors to find the target accessible object.
+ * Several selectors can be needed if the element is nested in frames
+ * and not directly in the root document.
+ * @return {Promise} a promise that resolves when the node front is found
+ * for selection using inspector tools. It resolves with the deepest frame document
+ * that could be retrieved when the "final" nodeFront couldn't be found in the page.
+ */
+ async findNodeFrontFromSelectors(nodeSelectors) {
+ if (
+ !nodeSelectors ||
+ !Array.isArray(nodeSelectors) ||
+ nodeSelectors.length === 0
+ ) {
+ console.warn(
+ "findNodeFrontFromSelectors expect a non-empty array but got",
+ nodeSelectors
+ );
+ return null;
+ }
+
+ const { walker } = await this.commands.targetCommand.targetFront.getFront(
+ "inspector"
+ );
+ const querySelectors = async nodeFront => {
+ const selector = nodeSelectors.shift();
+ if (!selector) {
+ return nodeFront;
+ }
+ nodeFront = await nodeFront.walkerFront.querySelector(
+ nodeFront,
+ selector
+ );
+ // It's possible the containing iframe isn't available by the time
+ // walkerFront.querySelector is called, which causes the re-selected node to be
+ // unavailable. There also isn't a way for us to know when all iframes on the page
+ // have been created after a reload. Because of this, we should should bail here.
+ if (!nodeFront) {
+ return null;
+ }
+
+ if (nodeSelectors.length > 0) {
+ await nodeFront.waitForFrameLoad();
+
+ const { nodes } = await walker.children(nodeFront);
+
+ // If there are remaining selectors to process, they will target a document or a
+ // document-fragment under the current node. Whether the element is a frame or
+ // a web component, it can only contain one document/document-fragment, so just
+ // select the first one available.
+ nodeFront = nodes.find(node => {
+ const { nodeType } = node;
+ return (
+ nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
+ nodeType === Node.DOCUMENT_NODE
+ );
+ });
+
+ // The iframe selector might have matched an element which is not an
+ // iframe in the new page (or an iframe with no document?). In this
+ // case, bail out and fallback to the root body element.
+ if (!nodeFront) {
+ return null;
+ }
+ }
+ const childrenNodeFront = await querySelectors(nodeFront);
+ return childrenNodeFront || nodeFront;
+ };
+ const rootNodeFront = await walker.getRootNode();
+ return querySelectors(rootNodeFront);
+ }
}
// This is a fork of the server sort:
diff --git a/devtools/shared/commands/inspector/tests/browser.ini b/devtools/shared/commands/inspector/tests/browser.ini
index f28bd5d4b6d4..a3290ed82d49 100644
--- a/devtools/shared/commands/inspector/tests/browser.ini
+++ b/devtools/shared/commands/inspector/tests/browser.ini
@@ -7,5 +7,6 @@ support-files =
!/devtools/client/shared/test/highlighter-test-actor.js
head.js
+[browser_inspector_command_findNodeFrontFromSelectors.js]
[browser_inspector_command_getSuggestionsForQuery.js]
[browser_inspector_command_search.js]
diff --git a/devtools/shared/commands/inspector/tests/browser_inspector_command_findNodeFrontFromSelectors.js b/devtools/shared/commands/inspector/tests/browser_inspector_command_findNodeFrontFromSelectors.js
new file mode 100644
index 000000000000..ecd9ab6a2f4d
--- /dev/null
+++ b/devtools/shared/commands/inspector/tests/browser_inspector_command_findNodeFrontFromSelectors.js
@@ -0,0 +1,103 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ // Build a simple test page with a remote iframe, using two distinct origins .com and .org
+ const iframeHtml = encodeURIComponent(``);
+ const html = encodeURIComponent(
+ `
+
+
+ `
+ );
+ const tab = await addTab(
+ "https://example.com/document-builder.sjs?html=" + html
+ );
+
+ const commands = await CommandsFactory.forTab(tab);
+ await commands.targetCommand.startListening();
+
+ info("Check that it returns null when no params are passed");
+ let nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors();
+ is(
+ nodeFront,
+ null,
+ `findNodeFrontFromSelectors returns null when no param is passed`
+ );
+
+ info("Check that it returns null when a string is passed");
+ nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors(
+ "body main"
+ );
+ is(
+ nodeFront,
+ null,
+ `findNodeFrontFromSelectors returns null when passed a string`
+ );
+
+ info("Check it returns null when an empty array is passed");
+ nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors([]);
+ is(
+ nodeFront,
+ null,
+ `findNodeFrontFromSelectors returns null when passed an empty array`
+ );
+
+ info("Check that passing a selector for a non-matching element returns null");
+ nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors([
+ "h1",
+ ]);
+ is(
+ nodeFront,
+ null,
+ "findNodeFrontFromSelectors returns null as there's no
element in the page"
+ );
+
+ info("Check passing a selector for an element in the top document");
+ nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors([
+ "button",
+ ]);
+ is(
+ nodeFront.typeName,
+ "domnode",
+ "findNodeFrontFromSelectors returns a nodeFront"
+ );
+ is(
+ nodeFront.displayName,
+ "button",
+ "findNodeFrontFromSelectors returned the appropriate nodeFront"
+ );
+
+ info("Check passing a selector for an element in an iframe");
+ nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors([
+ "iframe",
+ "#in-iframe",
+ ]);
+ is(
+ nodeFront.displayName,
+ "h2",
+ "findNodeFrontFromSelectors returned the appropriate nodeFront"
+ );
+
+ info(
+ "Check passing a selector for an non-existing element in an existing iframe"
+ );
+ nodeFront = await commands.inspectorCommand.findNodeFrontFromSelectors([
+ "iframe",
+ "#non-existant-id",
+ ]);
+ is(
+ nodeFront.displayName,
+ "#document",
+ "findNodeFrontFromSelectors returned the last matching iframe document if the children selector isn't found"
+ );
+ is(
+ nodeFront.parentNode().displayName,
+ "iframe",
+ "findNodeFrontFromSelectors returned the last matching iframe document if the children selector isn't found"
+ );
+
+ await commands.destroy();
+});