diff --git a/devtools/client/webconsole/components/Input/EvaluationContextSelector.js b/devtools/client/webconsole/components/Input/EvaluationContextSelector.js index 7c5162879183..15a7e3fb0d25 100644 --- a/devtools/client/webconsole/components/Input/EvaluationContextSelector.js +++ b/devtools/client/webconsole/components/Input/EvaluationContextSelector.js @@ -144,7 +144,10 @@ class EvaluationContextSelector extends Component { ); } - return MenuList({ id: "webconsole-console-settings-menu-list" }, items); + return MenuList( + { id: "webconsole-console-evaluation-context-selector-menu-list" }, + items + ); } getLabel() { diff --git a/devtools/client/webconsole/test/browser/_jsterm.ini b/devtools/client/webconsole/test/browser/_jsterm.ini index 16ae15fba0ed..5c790bc34cb3 100644 --- a/devtools/client/webconsole/test/browser/_jsterm.ini +++ b/devtools/client/webconsole/test/browser/_jsterm.ini @@ -105,6 +105,8 @@ skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only [browser_jsterm_editor_toolbar.js] [browser_jsterm_error_docs.js] [browser_jsterm_error_outside_valid_range.js] +[browser_jsterm_evaluation_context_selector_inspector.js] +skip-if = !fission # context selector is only visible when fission is enabled. [browser_jsterm_evaluation_context_selector.js] skip-if = !fission # context selector is only visible when fission is enabled. [browser_jsterm_file_load_save_keyboard_shortcut.js] diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector.js b/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector.js index 85f11314d9f6..112b6c5f6492 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector.js @@ -14,32 +14,10 @@ add_task(async function() { await pushPref("devtools.contenttoolbox.fission", true); await pushPref("devtools.contenttoolbox.webconsole.input.context", true); - await addTab(TEST_URI); - - info("Create new iframes and add them to the page."); - await ContentTask.spawn(gBrowser.selectedBrowser, IFRAME_PATH, async function( - iframPath - ) { - const iframe1 = content.document.createElement("iframe"); - const iframe2 = content.document.createElement("iframe"); - iframe1.classList.add("iframe-1"); - iframe2.classList.add("iframe-2"); - content.document.body.append(iframe1, iframe2); - - const onLoadIframe1 = new Promise(resolve => { - iframe1.addEventListener("load", resolve, { once: true }); - }); - const onLoadIframe2 = new Promise(resolve => { - iframe2.addEventListener("load", resolve, { once: true }); - }); - - iframe1.src = `http://example.org/${iframPath}?id=iframe-1`; - iframe2.src = `http://mochi.test:8888/${iframPath}?id=iframe-2`; - - await Promise.all([onLoadIframe1, onLoadIframe2]); - }); - - const hud = await openConsole(); + const hud = await openNewTabWithIframesAndConsole(TEST_URI, [ + `http://example.org/${IFRAME_PATH}?id=iframe-1`, + `http://mochi.test:8888/${IFRAME_PATH}?id=iframe-2`, + ]); const evaluationContextSelectorButton = hud.ui.outputNode.querySelector( ".webconsole-evaluation-selector-button" @@ -72,17 +50,39 @@ add_task(async function() { setInputValue(hud, "document.location.host"); await waitForEagerEvaluationResult(hud, `"example.com"`); - info("Go to the inspector panel"); - const inspector = await openInspector(); + info("Check the context selector menu"); + const expectedTopItem = { + label: "Top", + tooltip: TEST_URI, + }; + const expectedSeparatorItem = { separator: true }; + const expectedFirstIframeItem = { + label: "iframe-1|example.org", + tooltip: `http://example.org/${IFRAME_PATH}?id=iframe-1`, + }; + const expectedSecondIframeItem = { + label: "iframe-2|mochi.test:8888", + tooltip: `http://mochi.test:8888/${IFRAME_PATH}?id=iframe-2`, + }; - info("Expand all the nodes"); - await inspector.markup.expandAll(); + await checkContextSelectorMenu(hud, [ + { + ...expectedTopItem, + checked: true, + }, + expectedSeparatorItem, + { + ...expectedFirstIframeItem, + checked: false, + }, + { + ...expectedSecondIframeItem, + checked: false, + }, + ]); - info("Open the split console"); - await hud.toolbox.openSplitConsole(); - - info("Select the first iframe h2 element"); - await selectIframeContentElement(inspector, ".iframe-1", "h2"); + info("Select the first iframe"); + selectTargetInContextSelector(hud, expectedFirstIframeItem.label); await waitFor(() => evaluationContextSelectorButton.innerText.includes("example.org") @@ -107,8 +107,23 @@ add_task(async function() { ); setInputValue(hud, "document.location.host"); - info("Select the second iframe h2 element"); - await selectIframeContentElement(inspector, ".iframe-2", "h2"); + info("Select the second iframe in the context selector menu"); + await checkContextSelectorMenu(hud, [ + { + ...expectedTopItem, + checked: false, + }, + expectedSeparatorItem, + { + ...expectedFirstIframeItem, + checked: true, + }, + { + ...expectedSecondIframeItem, + checked: false, + }, + ]); + selectTargetInContextSelector(hud, expectedSecondIframeItem.label); await waitFor(() => evaluationContextSelectorButton.innerText.includes("mochi.test") @@ -133,10 +148,23 @@ add_task(async function() { ); setInputValue(hud, "document.location.host"); - info("Select an element in the top document"); - const h1NodeFront = await inspector.walker.findNodeFront(["h1"]); - inspector.selection.setNodeFront(null); - inspector.selection.setNodeFront(h1NodeFront); + info("Select the top frame in the context selector menu"); + await checkContextSelectorMenu(hud, [ + { + ...expectedTopItem, + checked: false, + }, + expectedSeparatorItem, + { + ...expectedFirstIframeItem, + checked: false, + }, + { + ...expectedSecondIframeItem, + checked: true, + }, + ]); + selectTargetInContextSelector(hud, expectedTopItem.label); await waitForEagerEvaluationResult(hud, `"example.com"`); await waitFor(() => @@ -150,7 +178,6 @@ add_task(async function() { "The non-top class isn't applied" ); - await hud.toolbox.selectTool("webconsole"); info("Check that 'Store as global variable' selects the right context"); await testStoreAsGlobalVariable( hud, @@ -193,75 +220,8 @@ add_task(async function() { evaluationContextSelectorButton.innerText.includes("Top") ); ok(true, "The context was set to the top document"); - - info("Open the inspector again"); - await hud.toolbox.selectTool("inspector"); - - info( - "Check that 'Use in console' works as expected for element in the first iframe" - ); - await testUseInConsole( - hud, - inspector, - ".iframe-1", - "h2", - "temp1", - `

` - ); - await waitFor(() => - evaluationContextSelectorButton.innerText.includes("example.org") - ); - ok(true, "The context selector was updated"); - - info( - "Check that 'Use in console' works as expected for element in the second iframe" - ); - await testUseInConsole( - hud, - inspector, - ".iframe-2", - "h2", - "temp1", - `

` - ); - await waitFor(() => - evaluationContextSelectorButton.innerText.includes("mochi.test:8888") - ); - ok(true, "The context selector was updated"); - - info( - "Check that 'Use in console' works as expected for element in the top frame" - ); - await testUseInConsole( - hud, - inspector, - ":root", - "h1", - "temp1", - `

` - ); - await waitFor(() => - evaluationContextSelectorButton.innerText.includes("Top") - ); - 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 testStoreAsGlobalVariable( hud, msg, @@ -292,47 +252,3 @@ async function testStoreAsGlobalVariable( ); ok(true, "Correct variable assigned into console."); } - -async function testUseInConsole( - hud, - inspector, - iframeSelector, - iframeContentSelector, - variableName, - expectedTextResult -) { - const nodeFront = await selectIframeContentElement( - inspector, - iframeSelector, - iframeContentSelector - ); - const container = inspector.markup.getContainer(nodeFront); - - const onConsoleReady = inspector.once("console-var-ready"); - const menu = inspector.markup.contextMenu._openMenu({ - target: container.tagLine, - }); - const useInConsoleItem = menu.items.find( - ({ id }) => id === "node-menu-useinconsole" - ); - useInConsoleItem.click(); - await onConsoleReady; - - menu.clear(); - - is( - getInputValue(hud), - variableName, - "A variable with the expected name was created" - ); - await waitForEagerEvaluationResult(hud, expectedTextResult); - ok(true, "The eager evaluation display the expected result"); - - await executeAndWaitForMessage( - hud, - variableName, - expectedTextResult, - ".result" - ); - ok(true, "the expected variable was created with the expected value."); -} 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 new file mode 100644 index 000000000000..b82a528bf9f2 --- /dev/null +++ b/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_inspector.js @@ -0,0 +1,180 @@ +/* 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"; + +// Test that the evaluation context selector reacts as expected when performing some +// inspector actions (selecting a node, "use in console" context menu entry, …). + +const FILE_FOLDER = `browser/devtools/client/webconsole/test/browser`; +const TEST_URI = `http://example.com/${FILE_FOLDER}/test-console-evaluation-context-selector.html`; +const IFRAME_PATH = `${FILE_FOLDER}/test-console-evaluation-context-selector-child.html`; + +requestLongerTimeout(2); + +add_task(async function() { + await pushPref("devtools.contenttoolbox.fission", true); + await pushPref("devtools.contenttoolbox.webconsole.input.context", true); + + const hud = await openNewTabWithIframesAndConsole(TEST_URI, [ + `http://example.org/${IFRAME_PATH}?id=iframe-1`, + `http://mochi.test:8888/${IFRAME_PATH}?id=iframe-2`, + ]); + + const evaluationContextSelectorButton = hud.ui.outputNode.querySelector( + ".webconsole-evaluation-selector-button" + ); + + setInputValue(hud, "document.location.host"); + await waitForEagerEvaluationResult(hud, `"example.com"`); + + info("Go to the inspector panel"); + const inspector = await openInspector(); + + info("Expand all the nodes"); + await inspector.markup.expandAll(); + + info("Open the split console"); + await hud.toolbox.openSplitConsole(); + + info("Select the first iframe h2 element"); + await selectIframeContentElement(inspector, ".iframe-1", "h2"); + + await waitFor(() => + evaluationContextSelectorButton.innerText.includes("example.org") + ); + ok(true, "The context was set to the selected iframe document"); + + await waitForEagerEvaluationResult(hud, `"example.org"`); + 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 waitFor(() => + evaluationContextSelectorButton.innerText.includes("mochi.test") + ); + ok(true, "The context was set to the selected iframe document"); + + await waitForEagerEvaluationResult(hud, `"mochi.test:8888"`); + 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 waitForEagerEvaluationResult(hud, `"example.com"`); + await waitFor(() => + evaluationContextSelectorButton.innerText.includes("Top") + ); + + info( + "Check that 'Use in console' works as expected for element in the first iframe" + ); + await testUseInConsole( + hud, + inspector, + ".iframe-1", + "h2", + "temp0", + `

` + ); + await waitFor(() => + evaluationContextSelectorButton.innerText.includes("example.org") + ); + ok(true, "The context selector was updated"); + + info( + "Check that 'Use in console' works as expected for element in the second iframe" + ); + await testUseInConsole( + hud, + inspector, + ".iframe-2", + "h2", + "temp0", + `

` + ); + await waitFor(() => + evaluationContextSelectorButton.innerText.includes("mochi.test:8888") + ); + ok(true, "The context selector was updated"); + + info( + "Check that 'Use in console' works as expected for element in the top frame" + ); + await testUseInConsole( + hud, + inspector, + ":root", + "h1", + "temp0", + `

` + ); + await waitFor(() => + evaluationContextSelectorButton.innerText.includes("Top") + ); + 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, + variableName, + expectedTextResult +) { + const nodeFront = await selectIframeContentElement( + inspector, + iframeSelector, + iframeContentSelector + ); + const container = inspector.markup.getContainer(nodeFront); + + const onConsoleReady = inspector.once("console-var-ready"); + const menu = inspector.markup.contextMenu._openMenu({ + target: container.tagLine, + }); + const useInConsoleItem = menu.items.find( + ({ id }) => id === "node-menu-useinconsole" + ); + useInConsoleItem.click(); + await onConsoleReady; + + menu.clear(); + + is( + getInputValue(hud), + variableName, + "A variable with the expected name was created" + ); + await waitForEagerEvaluationResult(hud, expectedTextResult); + ok(true, "The eager evaluation display the expected result"); + + await executeAndWaitForMessage( + hud, + variableName, + expectedTextResult, + ".result" + ); + ok(true, "the expected variable was created with the expected value."); +} diff --git a/devtools/client/webconsole/test/browser/head.js b/devtools/client/webconsole/test/browser/head.js index 38e867fdcac1..bae2465ee1d5 100644 --- a/devtools/client/webconsole/test/browser/head.js +++ b/devtools/client/webconsole/test/browser/head.js @@ -73,7 +73,7 @@ registerCleanupFunction(async function() { * The type of toolbox host to be used. * @return Promise * Resolves when the tab has been added, loaded and the toolbox has been opened. - * Resolves to the toolbox. + * Resolves to the hud. */ async function openNewTabAndConsole(url, clearJstermHistory = true, hostId) { const toolbox = await openNewTabAndToolbox(url, "webconsole", hostId); @@ -87,6 +87,42 @@ async function openNewTabAndConsole(url, clearJstermHistory = true, hostId) { return hud; } +/** + * Add a new tab with iframes, open the toolbox in it, and select the webconsole. + * + * @param string url + * The URL for the tab to be opened. + * @param Arra iframes + * An array of URLs that will be added to the top document. + * @return Promise + * Resolves when the tab has been added, loaded, iframes loaded, and the toolbox + * has been opened. Resolves to the hud. + */ +async function openNewTabWithIframesAndConsole(tabUrl, iframes) { + // We need to add the tab and the iframes before opening the console in case we want + // to handle remote frames (we don't support creating frames target when the toolbox + // is already open). + await addTab(tabUrl); + await ContentTask.spawn(gBrowser.selectedBrowser, iframes, async function( + urls + ) { + const iframesLoadPromises = urls.map((url, i) => { + const iframe = content.document.createElement("iframe"); + iframe.classList.add(`iframe-${i + 1}`); + const onLoadIframe = new Promise(resolve => { + iframe.addEventListener("load", resolve, { once: true }); + }); + content.document.body.append(iframe); + iframe.src = url; + return onLoadIframe; + }); + + await Promise.all(iframesLoadPromises); + }); + + return openConsole(); +} + /** * Open a new window with a tab,open the toolbox, and select the webconsole. * @@ -1686,3 +1722,83 @@ async function clearOutput(hud, { keepStorage = false } = {}) { ui.clearOutput(!keepStorage); await Promise.all(promises); } + +/** + * Retrieve all the items of the context selector menu. + * + * @param {WebConsole} hud + * @return Array + */ +function getContextSelectorItems(hud) { + const toolbox = hud.toolbox; + const doc = toolbox ? toolbox.doc : hud.chromeWindow.document; + const list = doc.getElementById( + "webconsole-console-evaluation-context-selector-menu-list" + ); + return Array.from(list.querySelectorAll("li.menuitem button")); +} + +/** + * Check that the evaluation context selector menu has the expected item, in the expected + * state. + * + * @param {WebConsole} hud + * @param {Array} expected: An array of object which can have the following shape: + * - {String} label: The label of the target + * - {String} tooltip: The tooltip of the target element in the menu + * - {Boolean} checked: if the target should be selected or not + * - {Boolean} separator: if the element is a simple separator + */ +function checkContextSelectorMenu(hud, expected) { + const items = getContextSelectorItems(hud); + + is( + items.length, + expected.length, + "The context selector menu has the expected number of items" + ); + + expected.forEach(({ label, tooltip, checked, separator }, i) => { + const el = items[i]; + + if (separator === true) { + is( + el.getAttribute("role"), + "menuseparator", + "The element is a separator" + ); + return; + } + + const elChecked = el.getAttribute("aria-checked") === "true"; + const elTooltip = el.getAttribute("title"); + const elLabel = el.querySelector(".label").innerText; + + is(elLabel, label, `The item has the expected label`); + is(elTooltip, tooltip, `Item "${label}" has the expected tooltip`); + is( + elChecked, + checked, + `Item "${label}" is ${checked ? "checked" : "unchecked"}` + ); + }); +} + +/** + * Select a target in the context selector. + * + * @param {WebConsole} hud + * @param {String} targetLabel: The label of the target to select. + */ +function selectTargetInContextSelector(hud, targetLabel) { + const items = getContextSelectorItems(hud); + const itemToSelect = items.find( + item => item.querySelector(".label").innerText === targetLabel + ); + if (!itemToSelect) { + ok(false, `Couldn't find target with "${targetLabel}" label`); + return; + } + + itemToSelect.click(); +} diff --git a/devtools/client/webconsole/test/browser/test-console-evaluation-context-selector-child.html b/devtools/client/webconsole/test/browser/test-console-evaluation-context-selector-child.html index 8847f14f7b21..689bb72806d2 100644 --- a/devtools/client/webconsole/test/browser/test-console-evaluation-context-selector-child.html +++ b/devtools/client/webconsole/test/browser/test-console-evaluation-context-selector-child.html @@ -10,6 +10,7 @@ console.log("iframe", document); var id = new URLSearchParams(document.location.search).get("id"); document.querySelector("h2").id = id; + document.title = `${id}|${document.location.host}`;