Bug 1735551 - [devtools] Turn WalkerFront#findNodeFront into a command. r=ochameau.

The callsites are migrated to the new command, except from the webconsole test
in which we replace usage with existing inspector test helper.

Differential Revision: https://phabricator.services.mozilla.com/D128712
This commit is contained in:
Nicolas Chevobbe 2021-11-04 08:58:03 +00:00
Родитель 90fcf31b4f
Коммит c64f0fb67c
7 изменённых файлов: 220 добавлений и 95 удалений

Просмотреть файл

@ -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;

Просмотреть файл

@ -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.

Просмотреть файл

@ -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);

Просмотреть файл

@ -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",
`<h2 id="iframe-1">`
);
@ -91,8 +94,7 @@ add_task(async function() {
await testUseInConsole(
hud,
inspector,
".iframe-2",
"h2",
[".iframe-2", "h2"],
"temp0",
`<h2 id="iframe-2">`
);
@ -107,8 +109,7 @@ add_task(async function() {
await testUseInConsole(
hud,
inspector,
":root",
"h1",
["h1"],
"temp0",
`<h1 id="top-level">`
);
@ -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

Просмотреть файл

@ -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
* <html>
* <iframe id="level-1" src="…">
* <iframe id="level-2" src="…">
* <h1>Waldo</h1>
* </iframe>
* </iframe>
*
* If you want to retrieve the `<h1>` 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<NodeFront|null>} 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:

Просмотреть файл

@ -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]

Просмотреть файл

@ -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(`<h2 id="in-iframe"></h2>`);
const html = encodeURIComponent(
`<main class="foo bar">
<button id="child">Click</button>
</main>
<iframe src="https://example.org/document-builder.sjs?html=${iframeHtml}"></iframe>`
);
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 <h1> 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();
});