From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../framework/browser-toolbox/test/browser.ini | 46 ++++ .../test/browser_browser_toolbox.js | 66 ++++++ .../test/browser_browser_toolbox_debugger.js | 222 ++++++++++++++++++++ .../browser_browser_toolbox_evaluation_context.js | 199 ++++++++++++++++++ ...owser_toolbox_fission_contentframe_inspector.js | 66 ++++++ .../browser_browser_toolbox_fission_inspector.js | 220 +++++++++++++++++++ ...owser_toolbox_fission_inspector_webextension.js | 94 +++++++++ .../test/browser_browser_toolbox_l10n_buttons.js | 88 ++++++++ .../test/browser_browser_toolbox_navigate_tab.js | 85 ++++++++ .../test/browser_browser_toolbox_netmonitor.js | 152 ++++++++++++++ .../test/browser_browser_toolbox_print_preview.js | 58 +++++ .../test/browser_browser_toolbox_rtl.js | 29 +++ .../browser_browser_toolbox_ruleview_stylesheet.js | 80 +++++++ ...browser_browser_toolbox_shouldprocessupdates.js | 42 ++++ ...browser_browser_toolbox_unavailable_children.js | 144 +++++++++++++ .../browser_browser_toolbox_watchedByDevTools.js | 122 +++++++++++ ...olbox_fission_contentframe_inspector_frame.html | 14 ++ ...oolbox_fission_contentframe_inspector_page.html | 16 ++ .../doc_browser_toolbox_ruleview_stylesheet.html | 12 ++ .../client/framework/browser-toolbox/test/head.js | 13 ++ .../test/helpers-browser-toolbox.js | 233 +++++++++++++++++++++ .../style_browser_toolbox_ruleview_stylesheet.css | 3 + 22 files changed, 2004 insertions(+) create mode 100644 devtools/client/framework/browser-toolbox/test/browser.ini create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_evaluation_context.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_contentframe_inspector.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector_webextension.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_l10n_buttons.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_navigate_tab.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_netmonitor.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_print_preview.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_rtl.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_shouldprocessupdates.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_unavailable_children.js create mode 100644 devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_watchedByDevTools.js create mode 100644 devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_frame.html create mode 100644 devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_page.html create mode 100644 devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_ruleview_stylesheet.html create mode 100644 devtools/client/framework/browser-toolbox/test/head.js create mode 100644 devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js create mode 100644 devtools/client/framework/browser-toolbox/test/style_browser_toolbox_ruleview_stylesheet.css (limited to 'devtools/client/framework/browser-toolbox/test') diff --git a/devtools/client/framework/browser-toolbox/test/browser.ini b/devtools/client/framework/browser-toolbox/test/browser.ini new file mode 100644 index 0000000000..723d049592 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser.ini @@ -0,0 +1,46 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +# UNTIL Bug 1591064 IS FIXED ALL NEW TESTS SHOULD BE SKIPPED ON ASAN +skip-if = asan # Bug 1591064 +support-files = + doc_browser_toolbox_fission_contentframe_inspector_frame.html + doc_browser_toolbox_fission_contentframe_inspector_page.html + doc_browser_toolbox_ruleview_stylesheet.html + style_browser_toolbox_ruleview_stylesheet.css + head.js + helpers-browser-toolbox.js + !/devtools/client/debugger/test/mochitest/shared-head.js + !/devtools/client/inspector/test/shared-head.js + !/devtools/client/shared/test/shared-head.js + !/devtools/client/shared/test/telemetry-test-helpers.js + !/devtools/client/shared/test/highlighter-test-actor.js +# This is far from ideal. https://bugzilla.mozilla.org/show_bug.cgi?id=1565279 +# covers removing this pref flip. +prefs = + security.allow_unsafe_parent_loads=true + + +[browser_browser_toolbox.js] +[browser_browser_toolbox_debugger.js] +skip-if = + os == "linux" && bits == 64 && debug # Bug 1756616 +[browser_browser_toolbox_evaluation_context.js] +[browser_browser_toolbox_fission_contentframe_inspector.js] +skip-if = + os == 'linux' && bits == 64 && debug # Bug 1604751 +[browser_browser_toolbox_fission_inspector_webextension.js] +[browser_browser_toolbox_fission_inspector.js] +[browser_browser_toolbox_l10n_buttons.js] +[browser_browser_toolbox_navigate_tab.js] +[browser_browser_toolbox_netmonitor.js] +skip-if = + os == 'linux' && bits == 64 && !debug # Bug 1777831 +[browser_browser_toolbox_print_preview.js] +[browser_browser_toolbox_rtl.js] +[browser_browser_toolbox_ruleview_stylesheet.js] +skip-if = + os == "mac" && fission # high frequency intermittent +[browser_browser_toolbox_shouldprocessupdates.js] +[browser_browser_toolbox_unavailable_children.js] +[browser_browser_toolbox_watchedByDevTools.js] diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox.js new file mode 100644 index 0000000000..29d8856b05 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +add_task(async function () { + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({}); + + const hasCloseButton = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + return !!gToolbox.doc.getElementById("toolbox-close"); + }); + ok(!hasCloseButton, "Browser toolbox doesn't have a close button"); + + info("Trigger F5 key shortcut and ensure nothing happens"); + info( + "If F5 triggers a full reload, the mochitest will stop here as firefox instance will be restarted" + ); + const previousInnerWindowId = + window.browsingContext.currentWindowGlobal.innerWindowId; + function onUnload() { + ok(false, "The top level window shouldn't be reloaded/closed"); + } + window.addEventListener("unload", onUnload); + await ToolboxTask.spawn(null, async () => { + const isMacOS = Services.appinfo.OS === "Darwin"; + const { win } = gToolbox; + // Simulate CmdOrCtrl+R + win.dispatchEvent( + new win.KeyboardEvent("keydown", { + bubbles: true, + ctrlKey: !isMacOS, + metaKey: isMacOS, + keyCode: "r".charCodeAt(0), + }) + ); + // Simulate F5 + win.dispatchEvent( + new win.KeyboardEvent("keydown", { + bubbles: true, + keyCode: win.KeyEvent.DOM_VK_F5, + }) + ); + }); + + // Let a chance to trigger the regression where the top level document closes or reloads + await wait(1000); + + is( + window.browsingContext.currentWindowGlobal.innerWindowId, + previousInnerWindowId, + "Check the browser.xhtml wasn't reloaded when pressing F5" + ); + window.removeEventListener("unload", onUnload); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js new file mode 100644 index 0000000000..edcba359e2 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js @@ -0,0 +1,222 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test asserts that the new debugger works from the browser toolbox process + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +// On debug test runner, it takes about 50s to run the test. +requestLongerTimeout(4); + +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +const { fetch } = require("resource://devtools/shared/DevToolsUtils.js"); + +const debuggerHeadURL = + CHROME_URL_ROOT + "../../../debugger/test/mochitest/shared-head.js"; + +add_task(async function runTest() { + let { content: debuggerHead } = await fetch(debuggerHeadURL); + + // We remove its import of shared-head, which isn't available in browser toolbox process + // And isn't needed thanks to testHead's symbols + debuggerHead = debuggerHead.replace( + /Services.scriptloader.loadSubScript[^\)]*\);/g, + "" + ); + + await pushPref("devtools.browsertoolbox.scope", "everything"); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + // head.js uses this method + registerCleanupFunction: () => {}, + waitForDispatch, + waitUntil, + }); + await ToolboxTask.importScript(debuggerHead); + + info("### First test breakpoint in the parent process script"); + const s = Cu.Sandbox("http://mozilla.org"); + + // Use a unique id for the fake script name in order to be able to run + // this test more than once. That's because the Sandbox is not immediately + // destroyed and so the debugger would display only one file but not necessarily + // connected to the latest sandbox. + const id = new Date().getTime(); + + // Pass a fake URL to evalInSandbox. If we just pass a filename, + // Debugger is going to fail and only display root folder (`/`) listing. + // But it won't try to fetch this url and use sandbox content as expected. + const testUrl = `http://mozilla.org/browser-toolbox-test-${id}.js`; + Cu.evalInSandbox( + `this.plop = function plop() { + const foo = 1; + return foo; +};`, + s, + "1.8", + testUrl, + 0 + ); + + // Execute the function every second in order to trigger the breakpoint + const interval = setInterval(s.plop, 1000); + + await ToolboxTask.spawn(testUrl, async _testUrl => { + /* global gToolbox, createDebuggerContext, waitForSources, waitForPaused, + addBreakpoint, assertPausedAtSourceAndLine, stepIn, findSource, + removeBreakpoint, resume, selectSource, assertNotPaused, assertBreakpoint, + assertTextContentOnLine, waitForResumed */ + Services.prefs.clearUserPref("devtools.debugger.tabs"); + Services.prefs.clearUserPref("devtools.debugger.pending-selected-location"); + + info("Waiting for debugger load"); + await gToolbox.selectTool("jsdebugger"); + const dbg = createDebuggerContext(gToolbox); + + await waitForSources(dbg, _testUrl); + + info("Loaded, selecting the test script to debug"); + const fileName = _testUrl.match(/browser-toolbox-test.*\.js/)[0]; + await selectSource(dbg, fileName); + + info("Add a breakpoint and wait to be paused"); + const onPaused = waitForPaused(dbg); + await addBreakpoint(dbg, fileName, 2); + await onPaused; + + const source = findSource(dbg, fileName); + assertPausedAtSourceAndLine(dbg, source.id, 2); + assertTextContentOnLine(dbg, 2, "const foo = 1;"); + is( + dbg.selectors.getBreakpointCount(), + 1, + "There is exactly one breakpoint" + ); + + await stepIn(dbg); + + assertPausedAtSourceAndLine(dbg, source.id, 3); + assertTextContentOnLine(dbg, 3, "return foo;"); + is( + dbg.selectors.getBreakpointCount(), + 1, + "We still have only one breakpoint after step-in" + ); + + // Remove the breakpoint before resuming in order to prevent hitting the breakpoint + // again during test closing. + await removeBreakpoint(dbg, source.id, 2); + + await resume(dbg); + + // Let a change for the interval to re-execute + await new Promise(r => setTimeout(r, 1000)); + + is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints"); + + assertNotPaused(dbg); + }); + + clearInterval(interval); + + info("### Now test breakpoint in a privileged content process script"); + const testUrl2 = `http://mozilla.org/content-process-test-${id}.js`; + await SpecialPowers.spawn(gBrowser.selectedBrowser, [testUrl2], testUrl => { + // Use a sandbox in order to have a URL to set a breakpoint + const s = Cu.Sandbox("http://mozilla.org"); + Cu.evalInSandbox( + `this.foo = function foo() { + const plop = 1; + return plop; +};`, + s, + "1.8", + testUrl, + 0 + ); + content.interval = content.setInterval(s.foo, 1000); + }); + await ToolboxTask.spawn(testUrl2, async _testUrl => { + const dbg = createDebuggerContext(gToolbox); + + const fileName = _testUrl.match(/content-process-test.*\.js/)[0]; + await waitForSources(dbg, _testUrl); + + await selectSource(dbg, fileName); + + const onPaused = waitForPaused(dbg); + await addBreakpoint(dbg, fileName, 2); + await onPaused; + + const source = findSource(dbg, fileName); + assertPausedAtSourceAndLine(dbg, source.id, 2); + assertTextContentOnLine(dbg, 2, "const plop = 1;"); + await assertBreakpoint(dbg, 2); + is(dbg.selectors.getBreakpointCount(), 1, "We have exactly one breakpoint"); + + await stepIn(dbg); + + assertPausedAtSourceAndLine(dbg, source.id, 3); + assertTextContentOnLine(dbg, 3, "return plop;"); + is( + dbg.selectors.getBreakpointCount(), + 1, + "We still have only one breakpoint after step-in" + ); + + // Remove the breakpoint before resuming in order to prevent hitting the breakpoint + // again during test closing. + await removeBreakpoint(dbg, source.id, 2); + + await resume(dbg); + + // Let a change for the interval to re-execute + await new Promise(r => setTimeout(r, 1000)); + + is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints"); + + assertNotPaused(dbg); + }); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.clearInterval(content.interval); + }); + + info("Trying pausing in a content process that crashes"); + + const crashingUrl = + "data:text/html,"; + const crashingTab = await addTab(crashingUrl); + await ToolboxTask.spawn(crashingUrl, async url => { + const dbg = createDebuggerContext(gToolbox); + await waitForPaused(dbg); + const source = findSource(dbg, url); + assertPausedAtSourceAndLine(dbg, source.id, 1); + const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread()); + is(thread.isTopLevel, false, "The current thread is not the top level one"); + is(thread.targetType, "process", "The current thread is the tab one"); + }); + + info( + "Crash the tab and ensure the debugger resumes and switch to the main thread" + ); + await BrowserTestUtils.crashFrame(crashingTab.linkedBrowser); + + await ToolboxTask.spawn(null, async () => { + const dbg = createDebuggerContext(gToolbox); + await waitForResumed(dbg); + const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread()); + is(thread.isTopLevel, true, "The current thread is the top level one"); + }); + + await removeTab(crashingTab); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_evaluation_context.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_evaluation_context.js new file mode 100644 index 0000000000..34e18d15c5 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_evaluation_context.js @@ -0,0 +1,199 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// This test is used to test fission-like features via the Browser Toolbox: +// - the evaluation context selector in the console show the right targets +// - the iframe dropdown also show the right targets +// - both are updated accordingly when toggle to parent-process only scope + +add_task(async function () { + // Forces the Browser Toolbox to open on the console by default + await pushPref("devtools.browsertoolbox.panel", "webconsole"); + await pushPref("devtools.webconsole.input.context", true); + // Force EFT to have targets for all WindowGlobals + await pushPref("devtools.every-frame-target.enabled", true); + // Enable Multiprocess Browser Toolbox + await pushPref("devtools.browsertoolbox.scope", "everything"); + + // Open the test *before* opening the Browser toolbox in order to have the right target title. + // Once created, the target won't update its title, and so would be "New Tab", instead of "Test tab" + const tab = await addTab( + "https://example.com/document-builder.sjs?html=Test tab" + ); + + const ToolboxTask = await initBrowserToolboxTask(); + + await ToolboxTask.importFunctions({ + waitUntil, + getContextLabels, + getFramesLabels, + }); + + const tabProcessID = + tab.linkedBrowser.browsingContext.currentWindowGlobal.osPid; + + const decodedTabURI = decodeURI(tab.linkedBrowser.currentURI.spec); + + await ToolboxTask.spawn( + [tabProcessID, isFissionEnabled(), decodedTabURI], + async (processID, _isFissionEnabled, tabURI) => { + /* global gToolbox */ + const { hud } = await gToolbox.getPanel("webconsole"); + + const evaluationContextSelectorButton = hud.ui.outputNode.querySelector( + ".webconsole-evaluation-selector-button" + ); + + is( + !!evaluationContextSelectorButton, + true, + "The evaluation context selector is visible" + ); + is( + evaluationContextSelectorButton.innerText, + "Top", + "The button has the expected 'Top' text" + ); + + const labelTexts = getContextLabels(gToolbox); + + const expectedTitle = _isFissionEnabled + ? `(pid ${processID}) https://example.com` + : `(pid ${processID}) web`; + ok( + labelTexts.includes(expectedTitle), + `${processID} content process visible in the execution context (${labelTexts})` + ); + + ok( + labelTexts.includes(`Test tab`), + `Test tab is visible in the execution context (${labelTexts})` + ); + + // Also assert the behavior of the iframe dropdown and the mode selector + info("Check the iframe dropdown, start by opening it"); + const btn = gToolbox.doc.getElementById("command-button-frames"); + btn.click(); + + const panel = gToolbox.doc.getElementById("command-button-frames-panel"); + ok(panel, "popup panel has created."); + await waitUntil( + () => panel.classList.contains("tooltip-visible"), + "Wait for the menu to be displayed" + ); + + is( + getFramesLabels(gToolbox)[0], + "chrome://browser/content/browser.xhtml", + "The iframe dropdown lists first browser.xhtml, running in the parent process" + ); + ok( + getFramesLabels(gToolbox).includes(tabURI), + "The iframe dropdown lists the tab document, running in the content process" + ); + + // Click on top frame to hide the iframe picker, so clicks on other elements can be registered. + gToolbox.doc.querySelector("#toolbox-frame-menu .command").click(); + + await waitUntil( + () => !panel.classList.contains("tooltip-visible"), + "Wait for the menu to be hidden" + ); + + info("Check that the ChromeDebugToolbar is displayed"); + const chromeDebugToolbar = gToolbox.doc.querySelector( + ".chrome-debug-toolbar" + ); + ok(!!chromeDebugToolbar, "ChromeDebugToolbar is displayed"); + const chromeDebugToolbarScopeInputs = Array.from( + chromeDebugToolbar.querySelectorAll(`[name="chrome-debug-mode"]`) + ); + is( + chromeDebugToolbarScopeInputs.length, + 2, + "There are 2 mode inputs in the chromeDebugToolbar" + ); + const [ + chromeDebugToolbarParentProcessModeInput, + chromeDebugToolbarMultiprocessModeInput, + ] = chromeDebugToolbarScopeInputs; + is( + chromeDebugToolbarParentProcessModeInput.value, + "parent-process", + "Got expected value for the first input" + ); + is( + chromeDebugToolbarMultiprocessModeInput.value, + "everything", + "Got expected value for the second input" + ); + ok( + chromeDebugToolbarMultiprocessModeInput.checked, + "The multiprocess mode is selected" + ); + + info( + "Click on the parent-process input and check that it restricts the targets" + ); + chromeDebugToolbarParentProcessModeInput.click(); + info("Wait for the iframe dropdown to hide the tab target"); + await waitUntil(() => { + return !getFramesLabels(gToolbox).includes(tabURI); + }); + + info("Wait for the context selector to hide the tab context"); + await waitUntil(() => { + return !getContextLabels(gToolbox).includes(`Test tab`); + }); + + ok( + !chromeDebugToolbarMultiprocessModeInput.checked, + "Now, the multiprocess mode is disabled…" + ); + ok( + chromeDebugToolbarParentProcessModeInput.checked, + "…and the parent process mode is enabled" + ); + + info("Switch back to multiprocess mode"); + chromeDebugToolbarMultiprocessModeInput.click(); + + info("Wait for the iframe dropdown to show again the tab target"); + await waitUntil(() => { + return getFramesLabels(gToolbox).includes(tabURI); + }); + + info("Wait for the context selector to show again the tab context"); + await waitUntil(() => { + return getContextLabels(gToolbox).includes(`Test tab`); + }); + } + ); + + await ToolboxTask.destroy(); +}); + +function getContextLabels(toolbox) { + // Note that the context menu is in the top level chrome document (toolbox.xhtml) + // instead of webconsole.xhtml. + const labels = toolbox.doc.querySelectorAll( + "#webconsole-console-evaluation-context-selector-menu-list li .label" + ); + return Array.from(labels).map(item => item.textContent); +} + +function getFramesLabels(toolbox) { + return Array.from( + toolbox.doc.querySelectorAll("#toolbox-frame-menu .command .label") + ).map(el => el.textContent); +} diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_contentframe_inspector.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_contentframe_inspector.js new file mode 100644 index 0000000000..ba2ab2779c --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_contentframe_inspector.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +/** + * Check that different-site iframes can be expanded in the Omniscient Browser + * Toolbox. The test is supposed to run successfully with or without fission. + * Pass --enable-fission to ./mach test to enable fission when running this + * test locally. + */ +add_task(async function () { + await pushPref("devtools.browsertoolbox.scope", "everything"); + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + getNodeFront, + getNodeFrontInFrames, + selectNode, + // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames. + selectNodeInFrames, + }); + + const tab = await addTab( + `https://example.com/browser/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_page.html` + ); + + // Set a custom attribute on the tab's browser, in order to easily select it in the markup view + tab.linkedBrowser.setAttribute("test-tab", "true"); + + const testAttribute = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + const inspector = await gToolbox.selectTool("inspector"); + const onSidebarSelect = inspector.sidebar.once("select"); + inspector.sidebar.select("computedview"); + await onSidebarSelect; + + info("Select the test element nested in the remote iframe"); + const nodeFront = await selectNodeInFrames( + ['browser[remote="true"][test-tab]', "iframe", "#inside-iframe"], + inspector + ); + + return nodeFront.getAttribute("test-attribute"); + }); + + is( + testAttribute, + "fission", + "Could successfully read attribute on a node inside a remote iframe" + ); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector.js new file mode 100644 index 0000000000..24c7fa7918 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector.js @@ -0,0 +1,220 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// This test is used to test fission-like features via the Browser Toolbox: +// - computed view is correct when selecting an element in a remote frame + +add_task(async function () { + // Forces the Browser Toolbox to open on the inspector by default + await pushPref("devtools.browsertoolbox.panel", "inspector"); + // Enable Multiprocess Browser Toolbox + await pushPref("devtools.browsertoolbox.scope", "everything"); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + getNodeFront, + getNodeFrontInFrames, + selectNode, + // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames. + selectNodeInFrames, + }); + + // Open the tab *after* opening the Browser Toolbox in order to force creating the remote frames + // late and exercise frame target watching code. + const tab = await addTab( + `data:text/html,
Foo
Foo
` + ); + // Set a custom attribute on the tab's browser, in order to easily select it in the markup view + tab.linkedBrowser.setAttribute("test-tab", "true"); + + const color = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + const inspector = gToolbox.getPanel("inspector"); + const onSidebarSelect = inspector.sidebar.once("select"); + inspector.sidebar.select("computedview"); + await onSidebarSelect; + + await selectNodeInFrames( + ['browser[remote="true"][test-tab]', "#my-div"], + inspector + ); + + const view = inspector.getPanel("computedview").computedView; + function getProperty(name) { + const propertyViews = view.propertyViews; + for (const propView of propertyViews) { + if (propView.name == name) { + return propView; + } + } + return null; + } + const prop = getProperty("color"); + return prop.valueNode.textContent; + }); + + is( + color, + "rgb(255, 0, 0)", + "The color property of the
within a tab isn't red" + ); + + info("Check that the node picker can be used on element in the content page"); + await pickNodeInContentPage( + ToolboxTask, + tab, + "browser[test-tab]", + "#second-div" + ); + const secondColor = await ToolboxTask.spawn(null, async () => { + const inspector = gToolbox.getPanel("inspector"); + + is( + inspector.selection.nodeFront.id, + "second-div", + "The expected element is selected in the inspector" + ); + + const view = inspector.getPanel("computedview").computedView; + function getProperty(name) { + const propertyViews = view.propertyViews; + for (const propView of propertyViews) { + if (propView.name == name) { + return propView; + } + } + return null; + } + const prop = getProperty("color"); + return prop.valueNode.textContent; + }); + + is( + secondColor, + "rgb(0, 0, 255)", + "The color property of the
within a tab isn't blue" + ); + + info( + "Check that the node picker can be used for element in non-remote " + ); + const nonRemoteUrl = "about:robots"; + const nonRemoteTab = await addTab(nonRemoteUrl); + // Set a custom attribute on the tab's browser, in order to target it + nonRemoteTab.linkedBrowser.setAttribute("test-tab-non-remote", ""); + + // check that the browser element is indeed not remote. If that changes for about:robots, + // this should be replaced with another page + is( + nonRemoteTab.linkedBrowser.hasAttribute("remote"), + false, + "The element for about:robots is not remote" + ); + + await pickNodeInContentPage( + ToolboxTask, + nonRemoteTab, + "browser[test-tab-non-remote]", + "#errorTryAgain" + ); + + await ToolboxTask.spawn(null, async () => { + const inspector = gToolbox.getPanel("inspector"); + is( + inspector.selection.nodeFront.id, + "errorTryAgain", + "The element inside a non-remote element is selected in the inspector" + ); + }); + + await ToolboxTask.destroy(); +}); + +async function pickNodeInContentPage( + ToolboxTask, + tab, + browserElementSelector, + contentElementSelector +) { + await ToolboxTask.spawn(contentElementSelector, async _selector => { + const onPickerStarted = gToolbox.nodePicker.once("picker-started"); + + // Wait until the inspector front was initialized in the target that + // contains the element we want to pick. + // Otherwise, even if the picker is "started", the corresponding WalkerActor + // might not be listening to the correct pick events (WalkerActor::pick) + const onPickerReady = new Promise(resolve => { + gToolbox.nodePicker.on( + "inspector-front-ready-for-picker", + async function onFrontReady(walker) { + if (await walker.querySelector(walker.rootNode, _selector)) { + gToolbox.nodePicker.off( + "inspector-front-ready-for-picker", + onFrontReady + ); + resolve(); + } + } + ); + }); + + gToolbox.nodePicker.start(); + await onPickerStarted; + await onPickerReady; + + const inspector = gToolbox.getPanel("inspector"); + + // Save the promises for later tasks, in order to start listening + // *before* hovering the element and wait for resolution *after* hovering. + this.onPickerStopped = gToolbox.nodePicker.once("picker-stopped"); + this.onInspectorUpdated = inspector.once("inspector-updated"); + }); + + // Retrieve the position of the element we want to pick in the content page + const { x, y } = await SpecialPowers.spawn( + tab.linkedBrowser, + [contentElementSelector], + _selector => { + const rect = content.document + .querySelector(_selector) + .getBoundingClientRect(); + return { x: rect.x, y: rect.y }; + } + ); + + // Synthesize the mouse event in the top level browsing context, but on the + // element containing the tab we're looking at, at the position where should be the + // content element. + // We need to do this to mimick what's actually done in node-picker.js + await EventUtils.synthesizeMouse( + document.querySelector(browserElementSelector), + x + 5, + y + 5, + {} + ); + + await ToolboxTask.spawn(null, async () => { + info(" # Waiting for picker stop"); + await this.onPickerStopped; + info(" # Waiting for inspector-updated"); + await this.onInspectorUpdated; + + delete this.onPickerStopped; + delete this.onInspectorUpdated; + }); +} diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector_webextension.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector_webextension.js new file mode 100644 index 0000000000..4f8a2f7535 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector_webextension.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// Test that expanding a browser element of a webextension in the browser toolbox works +// as expected (See Bug 1696862). + +add_task(async function () { + const extension = ExtensionTestUtils.loadExtension({ + // manifest_version: 2, + manifest: { + sidebar_action: { + default_title: "SideBarExtensionTest", + default_panel: "sidebar.html", + }, + }, + useAddonManager: "temporary", + + files: { + "sidebar.html": ` + + + + + + + +

Sidebar Extension Test

+ + `, + "sidebar.js": function () { + window.onload = () => { + // eslint-disable-next-line no-undef + browser.test.sendMessage("sidebar-ready"); + }; + }, + }, + }); + await extension.startup(); + await extension.awaitMessage("sidebar-ready"); + + ok(true, "Extension sidebar is displayed"); + + // Forces the Browser Toolbox to open on the inspector by default + await pushPref("devtools.browsertoolbox.panel", "inspector"); + // Enable Multiprocess Browser Toolbox + await pushPref("devtools.browsertoolbox.scope", "everything"); + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + getNodeFront, + getNodeFrontInFrames, + selectNode, + // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames. + selectNodeInFrames, + }); + + const nodeId = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + const inspector = gToolbox.getPanel("inspector"); + + const nodeFront = await selectNodeInFrames( + [ + "browser#sidebar", + "browser#webext-panels-browser", + "html.sidebar-extension-test h1", + ], + inspector + ); + return nodeFront.id; + }); + + is( + nodeId, + "sidebar-extension-h1", + "The Browser Toolbox can inspect a node in the webextension sidebar document" + ); + + await ToolboxTask.destroy(); + await extension.unload(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_l10n_buttons.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_l10n_buttons.js new file mode 100644 index 0000000000..abd2f3806a --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_l10n_buttons.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +/** + * In the browser toolbox there are options to switch the language to the "bidi" and + * "accented" languages. These are useful for making sure the browser is correctly + * localized. This test opens the browser toolbox, and checks that these buttons + * work. + */ +add_task(async function () { + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ clickMeatballItem }); + + is(getPseudoLocale(), "", "Starts out as empty"); + + await ToolboxTask.spawn(null, () => clickMeatballItem("accented")); + is(getPseudoLocale(), "accented", "Enabled the accented pseudo-locale"); + + await ToolboxTask.spawn(null, () => clickMeatballItem("accented")); + is(getPseudoLocale(), "", "Disabled the accented pseudo-locale."); + + await ToolboxTask.spawn(null, () => clickMeatballItem("bidi")); + is(getPseudoLocale(), "bidi", "Enabled the bidi pseudo-locale."); + + await ToolboxTask.spawn(null, () => clickMeatballItem("bidi")); + is(getPseudoLocale(), "", "Disabled the bidi pseudo-locale."); + + await ToolboxTask.spawn(null, () => clickMeatballItem("bidi")); + is(getPseudoLocale(), "bidi", "Enabled the bidi before closing."); + + await ToolboxTask.destroy(); + + is(getPseudoLocale(), "", "After closing the pseudo-locale is disabled."); +}); + +/** + * Return the pseudo-locale preference of the debuggee browser (not the browser toolbox). + * + * Another option for this test would be to test the text and layout of the + * browser directly, but this could be brittle. Checking the preference will + * hopefully provide adequate coverage. + */ +function getPseudoLocale() { + return Services.prefs.getCharPref("intl.l10n.pseudo"); +} + +/** + * This function is a ToolboxTask and is cloned into the toolbox context. It opens the + * "meatball menu" in the browser toolbox, clicks one of the pseudo-locale + * options, and finally returns the pseudo-locale preference from the target browser. + * + * @param {"accented" | "bidi"} type + */ +function clickMeatballItem(type) { + return new Promise(resolve => { + /* global gToolbox */ + + dump(`Opening the meatball menu in the browser toolbox.\n`); + gToolbox.doc.getElementById("toolbox-meatball-menu-button").click(); + + gToolbox.doc.addEventListener( + "popupshown", + async () => { + const menuItem = gToolbox.doc.getElementById( + "toolbox-meatball-menu-pseudo-locale-" + type + ); + dump(`Clicking the meatball menu item: "${type}".\n`); + menuItem.click(); + + // Request the pseudo-locale so that we know the preference actor is fully + // done setting the debuggee browser. + await gToolbox.getPseudoLocale(); + resolve(); + }, + { once: true } + ); + }); +} diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_navigate_tab.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_navigate_tab.js new file mode 100644 index 0000000000..46a6564a39 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_navigate_tab.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// Test that the Browser Toolbox still works after navigating a content tab +add_task(async function () { + // Forces the Browser Toolbox to open on the inspector by default + await pushPref("devtools.browsertoolbox.panel", "inspector"); + + await testNavigate("everything"); + await testNavigate("parent-process"); +}); + +async function testNavigate(browserToolboxScope) { + await pushPref("devtools.browsertoolbox.scope", browserToolboxScope); + + const tab = await addTab( + `data:text/html,
NAVIGATE TEST - BEFORE: ${browserToolboxScope}
` + ); + // Set the scope on the browser element to assert it easily in the Toolbox + // task. + tab.linkedBrowser.setAttribute("data-test-scope", browserToolboxScope); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + getNodeFront, + selectNode, + }); + + const hasBrowserContainerTask = async ({ scope, hasNavigated }) => { + /* global gToolbox */ + const inspector = await gToolbox.selectTool("inspector"); + info("Select the test browser element in the inspector"); + let selector = `browser[data-test-scope="${scope}"]`; + if (hasNavigated) { + selector += `[navigated="true"]`; + } + const nodeFront = await getNodeFront(selector, inspector); + await selectNode(nodeFront, inspector); + const browserContainer = inspector.markup.getContainer(nodeFront); + return !!browserContainer; + }; + + info("Select the test browser in the Browser Toolbox (before navigation)"); + const hasContainerBeforeNavigation = await ToolboxTask.spawn( + { scope: browserToolboxScope, hasNavigated: false }, + hasBrowserContainerTask + ); + ok( + hasContainerBeforeNavigation, + "Found a valid container for the browser element before navigation" + ); + + info("Navigate the test tab to another data-uri"); + await navigateTo( + `data:text/html,
NAVIGATE TEST - AFTER: ${browserToolboxScope}
` + ); + tab.linkedBrowser.setAttribute("navigated", "true"); + + info("Select the test browser in the Browser Toolbox (after navigation)"); + const hasContainerAfterNavigation = await ToolboxTask.spawn( + { scope: browserToolboxScope, hasNavigated: true }, + hasBrowserContainerTask + ); + ok( + hasContainerAfterNavigation, + "Found a valid container for the browser element after navigation" + ); + + await ToolboxTask.destroy(); +} diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_netmonitor.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_netmonitor.js new file mode 100644 index 0000000000..68a210aff1 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_netmonitor.js @@ -0,0 +1,152 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* global gToolbox */ + +add_task(async function () { + // Disable several prefs to avoid network requests. + await pushPref("browser.safebrowsing.blockedURIs.enabled", false); + await pushPref("browser.safebrowsing.downloads.enabled", false); + await pushPref("browser.safebrowsing.malware.enabled", false); + await pushPref("browser.safebrowsing.passwords.enabled", false); + await pushPref("browser.safebrowsing.phishing.enabled", false); + await pushPref("privacy.query_stripping.enabled", false); + await pushPref("extensions.systemAddon.update.enabled", false); + + await pushPref("services.settings.server", "invalid://err"); + + // Define a set list of visible columns + await pushPref( + "devtools.netmonitor.visibleColumns", + JSON.stringify(["file", "url", "status"]) + ); + + // Force observice all processes to see the content process requests + await pushPref("devtools.browsertoolbox.scope", "everything"); + + const ToolboxTask = await initBrowserToolboxTask(); + + await ToolboxTask.importFunctions({ + waitUntil, + }); + + await ToolboxTask.spawn(null, async () => { + const { resourceCommand } = gToolbox.commands; + + // Assert that the toolbox is not listening to network events + // before the netmonitor panel is opened. + is( + resourceCommand.isResourceWatched(resourceCommand.TYPES.NETWORK_EVENT), + false, + "The toolox is not watching for network event resources" + ); + + await gToolbox.selectTool("netmonitor"); + const monitor = gToolbox.getCurrentPanel(); + const { document, store, windowRequire } = monitor.panelWin; + + const Actions = windowRequire( + "devtools/client/netmonitor/src/actions/index" + ); + + store.dispatch(Actions.batchEnable(false)); + + await waitUntil( + () => !!document.querySelector(".request-list-empty-notice") + ); + + is( + resourceCommand.isResourceWatched(resourceCommand.TYPES.NETWORK_EVENT), + true, + "The network panel is now watching for network event resources" + ); + + const emptyListNotice = document.querySelector( + ".request-list-empty-notice" + ); + + ok( + !!emptyListNotice, + "An empty notice should be displayed when the frontend is opened." + ); + + is( + emptyListNotice.innerText, + "Perform a request to see detailed information about network activity.", + "The reload and perfomance analysis details should not be visible in the browser toolbox" + ); + + is( + store.getState().requests.requests.length, + 0, + "The requests should be empty when the frontend is opened." + ); + + ok( + !document.querySelector(".requests-list-network-summary-button"), + "The perfomance analysis button should not be visible in the browser toolbox" + ); + }); + + info("Trigger request in parent process and check that it shows up"); + await fetch("https://example.org/document-builder.sjs?html=fromParent"); + + await ToolboxTask.spawn(null, async () => { + const monitor = gToolbox.getCurrentPanel(); + const { document, store } = monitor.panelWin; + + await waitUntil( + () => !document.querySelector(".request-list-empty-notice") + ); + ok(true, "The empty notice is no longer displayed"); + is( + store.getState().requests.requests.length, + 1, + "There's 1 request in the store" + ); + + const requests = Array.from( + document.querySelectorAll("tbody .requests-list-column.requests-list-url") + ); + is(requests.length, 1, "One request displayed"); + is( + requests[0].textContent, + "https://example.org/document-builder.sjs?html=fromParent", + "Expected request is displayed" + ); + }); + + info("Trigger content process requests"); + const urlImg = `${URL_ROOT_SSL}test-image.png?fromContent&${Date.now()}-${Math.random()}`; + await addTab( + `https://example.com/document-builder.sjs?html=${encodeURIComponent( + `` + )}` + ); + + await ToolboxTask.spawn(urlImg, async innerUrlImg => { + const monitor = gToolbox.getCurrentPanel(); + const { document, store } = monitor.panelWin; + + await waitUntil(() => store.getState().requests.requests.length >= 3); + ok(true, "Expected content requests are displayed"); + + const requests = Array.from( + document.querySelectorAll("tbody .requests-list-column.requests-list-url") + ); + is(requests.length, 3, "Three requests displayed"); + ok( + requests[1].textContent.includes( + `https://example.com/document-builder.sjs` + ), + "Request for the tab is displayed" + ); + is( + requests[2].textContent, + innerUrlImg, + "Request for image image in tab is displayed" + ); + }); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_print_preview.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_print_preview.js new file mode 100644 index 0000000000..81ff0808fb --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_print_preview.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// Test that the MultiProcessBrowserToolbox can be opened when print preview is +// started, and can select elements in the print preview document. +add_task(async function () { + // Forces the Browser Toolbox to open on the inspector by default + await pushPref("devtools.browsertoolbox.panel", "inspector"); + + // Enable Multiprocess Browser Toolbox + await pushPref("devtools.browsertoolbox.scope", "everything"); + + // Open the tab *after* opening the Browser Toolbox in order to force creating the remote frames + // late and exercise frame target watching code. + await addTab(`data:text/html,
PRINT PREVIEW TEST
`); + + info("Start the print preview for the current tab"); + document.getElementById("cmd_print").doCommand(); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + getNodeFront, + getNodeFrontInFrames, + selectNode, + // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames. + selectNodeInFrames, + }); + + const hasCloseButton = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + const inspector = gToolbox.getPanel("inspector"); + + info("Select the #test-div element in the printpreview document"); + await selectNodeInFrames( + ['browser[printpreview="true"]', "#test-div"], + inspector + ); + return !!gToolbox.doc.getElementById("toolbox-close"); + }); + ok(!hasCloseButton, "Browser toolbox doesn't have a close button"); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_rtl.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_rtl.js new file mode 100644 index 0000000000..558be1a16c --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_rtl.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// Test that DevTools panels are rendered in "rtl" (right-to-left) in the Browser Toolbox. +add_task(async function () { + await pushPref("intl.l10n.pseudo", "bidi"); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({}); + + const dir = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + const inspector = await gToolbox.selectTool("inspector"); + return inspector.panelDoc.dir; + }); + is(dir, "rtl", "Inspector panel has the expected direction"); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js new file mode 100644 index 0000000000..88583b76b4 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// Check that CSS rules are displayed with the proper source label in the +// browser toolbox. +add_task(async function () { + // Forces the Browser Toolbox to open on the inspector by default + await pushPref("devtools.browsertoolbox.panel", "inspector"); + // Enable Multiprocess Browser Toolbox + await pushPref("devtools.browsertoolbox.scope", "everything"); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({ + getNodeFront, + getNodeFrontInFrames, + getRuleViewLinkByIndex, + selectNode, + // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames. + selectNodeInFrames, + }); + + // This is a simple test page, which contains a
with a CSS rule `color: red` + // coming from a dedicated stylesheet. + const tab = await addTab( + `https://example.com/browser/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_ruleview_stylesheet.html` + ); + + // Set a custom attribute on the tab's browser, in order to easily select it in the markup view + tab.linkedBrowser.setAttribute("test-tab", "true"); + + info( + "Get the source label for a rule displayed in the Browser Toolbox ruleview" + ); + const sourceLabel = await ToolboxTask.spawn(null, async () => { + /* global gToolbox */ + const inspector = gToolbox.getPanel("inspector"); + + info("Select the rule view"); + const onSidebarSelect = inspector.sidebar.once("select"); + inspector.sidebar.select("ruleview"); + await onSidebarSelect; + + info("Select a DIV element in the test page"); + await selectNodeInFrames( + ['browser[remote="true"][test-tab]', "div"], + inspector + ); + + info("Retrieve the sourceLabel for the rule at index 1"); + const ruleView = inspector.getPanel("ruleview").view; + const sourceLabelEl = getRuleViewLinkByIndex(ruleView, 1).querySelector( + ".ruleview-rule-source-label" + ); + + return sourceLabelEl.textContent; + }); + + is( + sourceLabel, + "style_browser_toolbox_ruleview_stylesheet.css:1", + "source label has the expected value in the ruleview" + ); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_shouldprocessupdates.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_shouldprocessupdates.js new file mode 100644 index 0000000000..6f5e18791b --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_shouldprocessupdates.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +add_task(async function () { + // Running devtools should prevent processing updates. By setting this + // environment variable and then inspecting it from the launched devtools + // process, we can witness update processing being skipped. + Services.env.set("MOZ_TEST_PROCESS_UPDATES", "1"); + + const ToolboxTask = await initBrowserToolboxTask(); + await ToolboxTask.importFunctions({}); + + let result = await ToolboxTask.spawn(null, async () => { + const result = { + exists: Services.env.exists("MOZ_TEST_PROCESS_UPDATES"), + get: Services.env.get("MOZ_TEST_PROCESS_UPDATES"), + }; + // Log so that we have a hope of debugging. + console.log("result", result); + return JSON.stringify(result); + }); + + result = JSON.parse(result); + ok(result.exists, "MOZ_TEST_PROCESS_UPDATES exists in subprocess"); + is( + result.get, + "ShouldNotProcessUpdates(): DevToolsLaunching", + "MOZ_TEST_PROCESS_UPDATES is correct in subprocess" + ); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_unavailable_children.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_unavailable_children.js new file mode 100644 index 0000000000..5029c62306 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_unavailable_children.js @@ -0,0 +1,144 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// There are shutdown issues for which multiple rejections are left uncaught. +// See bug 1018184 for resolving these issues. +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +// On debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +// This test is used to test a badge displayed in the markup view under content +// browser elements when switching from Multi Process mode to Parent Process +// mode. + +add_task(async function () { + // Forces the Browser Toolbox to open on the inspector by default + await pushPref("devtools.browsertoolbox.panel", "inspector"); + await pushPref("devtools.browsertoolbox.scope", "everything"); + + const tab = await addTab( + "https://example.com/document-builder.sjs?html=
Pickme" + ); + tab.linkedBrowser.setAttribute("test-tab", "true"); + + const ToolboxTask = await initBrowserToolboxTask(); + + await ToolboxTask.importFunctions({ + waitUntil, + getNodeFront, + selectNode, + }); + + const tabProcessID = + tab.linkedBrowser.browsingContext.currentWindowGlobal.osPid; + + const decodedTabURI = decodeURI(tab.linkedBrowser.currentURI.spec); + + await ToolboxTask.spawn( + [tabProcessID, isFissionEnabled(), decodedTabURI], + async (processID, _isFissionEnabled, tabURI) => { + /* global gToolbox */ + const inspector = gToolbox.getPanel("inspector"); + + info("Select the test browser element."); + await selectNode('browser[remote="true"][test-tab]', inspector); + + info("Retrieve the node front for selected node."); + const browserNodeFront = inspector.selection.nodeFront; + ok(!!browserNodeFront, "Retrieved a node front for the browser"); + is(browserNodeFront.displayName, "browser"); + + // Small helper to expand containers and return the child container + // matching the provided display name. + async function expandContainer(container, expectedChildName) { + info(`Expand the node expected to contain a ${expectedChildName}`); + await inspector.markup.expandNode(container.node); + await waitUntil(() => !!container.getChildContainers().length); + + const children = container + .getChildContainers() + .filter(child => child.node.displayName === expectedChildName); + is(children.length, 1); + return children[0]; + } + + info("Check that the corresponding markup view container has children"); + const browserContainer = inspector.markup.getContainer(browserNodeFront); + ok(browserContainer.hasChildren); + ok( + !browserContainer.node.childrenUnavailable, + "childrenUnavailable un-set" + ); + ok( + !browserContainer.elt.querySelector(".unavailable-children"), + "The unavailable badge is not displayed" + ); + + // Store the asserts as a helper to reuse it later in the test. + async function assertMarkupView() { + info("Check that the children are #document > html > body > div"); + let container = await expandContainer(browserContainer, "#document"); + container = await expandContainer(container, "html"); + container = await expandContainer(container, "body"); + container = await expandContainer(container, "div"); + + info("Select the #pick-me div"); + await selectNode(container.node, inspector); + is(inspector.selection.nodeFront.id, "pick-me"); + } + await assertMarkupView(); + + const parentProcessScope = gToolbox.doc.querySelector( + 'input[name="chrome-debug-mode"][value="parent-process"]' + ); + + info("Switch to parent process only scope"); + const onInspectorUpdated = inspector.once("inspector-updated"); + parentProcessScope.click(); + await onInspectorUpdated; + + // Note: `getChildContainers` returns null when the container has no + // children, instead of an empty array. + await waitUntil(() => browserContainer.getChildContainers() === null); + + ok(!browserContainer.hasChildren, "browser container has no children"); + ok(browserContainer.node.childrenUnavailable, "childrenUnavailable set"); + ok( + !!browserContainer.elt.querySelector(".unavailable-children"), + "The unavailable badge is displayed" + ); + + const everythingScope = gToolbox.doc.querySelector( + 'input[name="chrome-debug-mode"][value="everything"]' + ); + + info("Switch to multi process scope"); + everythingScope.click(); + + info("Wait until browserContainer has children"); + await waitUntil(() => browserContainer.hasChildren); + ok(browserContainer.hasChildren, "browser container has children"); + ok( + !browserContainer.node.childrenUnavailable, + "childrenUnavailable un-set" + ); + ok( + !browserContainer.elt.querySelector(".unavailable-children"), + "The unavailable badge is no longer displayed" + ); + + await assertMarkupView(); + } + ); + + await ToolboxTask.destroy(); +}); diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_watchedByDevTools.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_watchedByDevTools.js new file mode 100644 index 0000000000..6201ae6922 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_watchedByDevTools.js @@ -0,0 +1,122 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Check that the "watchedByDevTools" flag is properly handled. + */ + +const EXAMPLE_NET_URI = + "https://example.net/document-builder.sjs?html=
net"; +const EXAMPLE_COM_URI = + "https://example.com/document-builder.sjs?html=
com"; +const EXAMPLE_ORG_URI = + "https://example.org/document-builder.sjs?headers=Cross-Origin-Opener-Policy:same-origin&html=
org
"; + +add_task(async function () { + await pushPref("devtools.browsertoolbox.scope", "everything"); + + const topWindow = Services.wm.getMostRecentWindow("navigator:browser"); + is( + topWindow.browsingContext.watchedByDevTools, + false, + "watchedByDevTools isn't set on the parent process browsing context when DevTools aren't opened" + ); + + // Open 2 tabs that we can check the flag on + const tabNet = await addTab(EXAMPLE_NET_URI); + const tabCom = await addTab(EXAMPLE_COM_URI); + + is( + tabNet.linkedBrowser.browsingContext.watchedByDevTools, + false, + "watchedByDevTools is not set on the .net tab" + ); + is( + tabCom.linkedBrowser.browsingContext.watchedByDevTools, + false, + "watchedByDevTools is not set on the .com tab" + ); + + info("Open the BrowserToolbox so the parent process will be watched"); + const ToolboxTask = await initBrowserToolboxTask(); + + is( + topWindow.browsingContext.watchedByDevTools, + true, + "watchedByDevTools is set when the browser toolbox is opened" + ); + + // Open a new tab when the browser toolbox is opened + const newTab = await addTab(EXAMPLE_COM_URI); + + is( + tabNet.linkedBrowser.browsingContext.watchedByDevTools, + true, + "watchedByDevTools is set on the .net tab browsing context after opening the browser toolbox" + ); + is( + tabCom.linkedBrowser.browsingContext.watchedByDevTools, + true, + "watchedByDevTools is set on the .com tab browsing context after opening the browser toolbox" + ); + + info( + "Check that adding watchedByDevTools is set on a tab that was added when the browser toolbox was opened" + ); + is( + newTab.linkedBrowser.browsingContext.watchedByDevTools, + true, + "watchedByDevTools is set on the newly opened tab" + ); + + info( + "Check that watchedByDevTools persist when navigating to a page that creates a new browsing context" + ); + const previousBrowsingContextId = newTab.linkedBrowser.browsingContext.id; + const onBrowserLoaded = BrowserTestUtils.browserLoaded( + newTab.linkedBrowser, + false, + encodeURI(EXAMPLE_ORG_URI) + ); + BrowserTestUtils.loadURIString(newTab.linkedBrowser, EXAMPLE_ORG_URI); + await onBrowserLoaded; + + isnot( + newTab.linkedBrowser.browsingContext.id, + previousBrowsingContextId, + "A new browsing context was created" + ); + + is( + newTab.linkedBrowser.browsingContext.watchedByDevTools, + true, + "watchedByDevTools is still set after navigating the tab to a page which forces a new browsing context" + ); + + info("Destroying browser toolbox"); + await ToolboxTask.destroy(); + + is( + topWindow.browsingContext.watchedByDevTools, + false, + "watchedByDevTools was reset when the browser toolbox was closed" + ); + + is( + tabNet.linkedBrowser.browsingContext.watchedByDevTools, + false, + "watchedByDevTools was reset on the .net tab after closing the browser toolbox" + ); + is( + tabCom.linkedBrowser.browsingContext.watchedByDevTools, + false, + "watchedByDevTools was reset on the .com tab after closing the browser toolbox" + ); + is( + newTab.linkedBrowser.browsingContext.watchedByDevTools, + false, + "watchedByDevTools was reset on the tab opened while the browser toolbox was opened" + ); +}); diff --git a/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_frame.html b/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_frame.html new file mode 100644 index 0000000000..1f365cc17f --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_frame.html @@ -0,0 +1,14 @@ + + + + + + + Frame for browser_browser_toolbox_fission_contentframe_inspector.js + + + +
Inside iframe
+ + diff --git a/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_page.html b/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_page.html new file mode 100644 index 0000000000..853c4ec91c --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_fission_contentframe_inspector_page.html @@ -0,0 +1,16 @@ + + + + + + + Frame for browser_browser_toolbox_fission_contentframe_inspector.js + + + + + + + diff --git a/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_ruleview_stylesheet.html b/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_ruleview_stylesheet.html new file mode 100644 index 0000000000..3fab2ff8a8 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/doc_browser_toolbox_ruleview_stylesheet.html @@ -0,0 +1,12 @@ + + + + + + + + +
test div with "color: red" applied from a stylesheet
+ + diff --git a/devtools/client/framework/browser-toolbox/test/head.js b/devtools/client/framework/browser-toolbox/test/head.js new file mode 100644 index 0000000000..4ea18d547e --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/head.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js", + this +); diff --git a/devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js b/devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js new file mode 100644 index 0000000000..bc97c20c01 --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/helpers-browser-toolbox.js @@ -0,0 +1,233 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable no-unused-vars, no-undef */ + +"use strict"; + +const { BrowserToolboxLauncher } = ChromeUtils.importESModule( + "resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs" +); +const { + DevToolsClient, +} = require("resource://devtools/client/devtools-client.js"); + +/** + * Open up a browser toolbox and return a ToolboxTask object for interacting + * with it. ToolboxTask has the following methods: + * + * importFunctions(object) + * + * The object contains functions from this process which should be defined in + * the global evaluation scope of the toolbox. The toolbox cannot load testing + * files directly. + * + * destroy() + * + * Destroy the browser toolbox and make sure it exits cleanly. + * + * @param {Object}: + * - {Function} existingProcessClose: if truth-y, connect to an existing + * browser toolbox process rather than launching a new one and + * connecting to it. The given function is expected to return an + * object containing an `exitCode`, like `{exitCode}`, and will be + * awaited in the returned `destroy()` function. `exitCode` is + * asserted to be 0 (success). + */ +async function initBrowserToolboxTask({ existingProcessClose } = {}) { + if (AppConstants.ASAN) { + ok( + false, + "ToolboxTask cannot be used on ASAN builds. This test should be skipped (Bug 1591064)." + ); + } + + await pushPref("devtools.chrome.enabled", true); + await pushPref("devtools.debugger.remote-enabled", true); + await pushPref("devtools.browsertoolbox.enable-test-server", true); + await pushPref("devtools.debugger.prompt-connection", false); + + // This rejection seems to affect all tests using the browser toolbox. + ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" + ).PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); + + let process; + let dbgProcess; + if (!existingProcessClose) { + [process, dbgProcess] = await new Promise(resolve => { + BrowserToolboxLauncher.init({ + onRun: (_process, _dbgProcess) => resolve([_process, _dbgProcess]), + overwritePreferences: true, + }); + }); + ok(true, "Browser toolbox started"); + is( + BrowserToolboxLauncher.getBrowserToolboxSessionState(), + true, + "Has session state" + ); + } else { + ok(true, "Connecting to existing browser toolbox"); + } + + // The port of the DevToolsServer installed in the toolbox process is fixed. + // See browser-toolbox/window.js + let transport; + while (true) { + try { + transport = await DevToolsClient.socketConnect({ + host: "localhost", + port: 6001, + webSocket: false, + }); + break; + } catch (e) { + await waitForTime(100); + } + } + ok(true, "Got transport"); + + const client = new DevToolsClient(transport); + await client.connect(); + + const commands = await CommandsFactory.forMainProcess({ client }); + const target = await commands.descriptorFront.getTarget(); + const consoleFront = await target.getFront("console"); + + ok(true, "Connected"); + + await importFunctions({ + info: msg => dump(msg + "\n"), + is: (a, b, description) => { + let msg = + "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'"; + if (description) { + msg += " - " + description; + } + if (a !== b) { + msg = "FAILURE: " + msg; + dump(msg + "\n"); + throw new Error(msg); + } else { + msg = "SUCCESS: " + msg; + dump(msg + "\n"); + } + }, + ok: (a, description) => { + let msg = "'" + JSON.stringify(a) + "' is true"; + if (description) { + msg += " - " + description; + } + if (!a) { + msg = "FAILURE: " + msg; + dump(msg + "\n"); + throw new Error(msg); + } else { + msg = "SUCCESS: " + msg; + dump(msg + "\n"); + } + }, + }); + + async function evaluateExpression(expression, options = {}) { + const onEvaluationResult = consoleFront.once("evaluationResult"); + await consoleFront.evaluateJSAsync({ text: expression, ...options }); + return onEvaluationResult; + } + + /** + * Invoke the given function and argument(s) within the global evaluation scope + * of the toolbox. The evaluation scope predefines the name "gToolbox" for the + * toolbox itself. + * + * @param {value|Array} arg + * If an Array is passed, we will consider it as the list of arguments + * to pass to `fn`. Otherwise we will consider it as the unique argument + * to pass to it. + * @param {Function} fn + * Function to call in the global scope within the browser toolbox process. + * This function will be stringified and passed to the process via RDP. + * @return {Promise} + * Return the primitive value returned by `fn`. + */ + async function spawn(arg, fn) { + // Use JSON.stringify to ensure that we can pass strings + // as well as any JSON-able object. + const argString = JSON.stringify(Array.isArray(arg) ? arg : [arg]); + const rv = await evaluateExpression(`(${fn}).apply(null,${argString})`, { + // Use the following argument in order to ensure waiting for the completion + // of the promise returned by `fn` (in case this is an async method). + mapped: { await: true }, + }); + if (rv.exceptionMessage) { + throw new Error(`ToolboxTask.spawn failure: ${rv.exceptionMessage}`); + } else if (rv.topLevelAwaitRejected) { + throw new Error(`ToolboxTask.spawn await rejected`); + } + return rv.result; + } + + async function importFunctions(functions) { + for (const [key, fn] of Object.entries(functions)) { + await evaluateExpression(`this.${key} = ${fn}`); + } + } + + async function importScript(script) { + const response = await evaluateExpression(script); + if (response.hasException) { + ok( + false, + "ToolboxTask.spawn exception while importing script: " + + response.exceptionMessage + ); + } + } + + let destroyed = false; + async function destroy() { + // No need to do anything if `destroy` was already called. + if (destroyed) { + return; + } + + const closePromise = existingProcessClose + ? existingProcessClose() + : dbgProcess.wait(); + evaluateExpression("gToolbox.destroy()").catch(e => { + // Ignore connection close as the toolbox destroy may destroy + // everything quickly enough so that evaluate request is still pending + if (!e.message.includes("Connection closed")) { + throw e; + } + }); + + const { exitCode } = await closePromise; + ok(true, "Browser toolbox process closed"); + + is(exitCode, 0, "The remote debugger process died cleanly"); + + if (!existingProcessClose) { + is( + BrowserToolboxLauncher.getBrowserToolboxSessionState(), + false, + "No session state after closing" + ); + } + + await commands.destroy(); + destroyed = true; + } + + // When tests involving using this task fail, the spawned Browser Toolbox is not + // destroyed and might impact the next tests (e.g. pausing the content process before + // the debugger from the content toolbox does). So make sure to cleanup everything. + registerCleanupFunction(destroy); + + return { + importFunctions, + importScript, + spawn, + destroy, + }; +} diff --git a/devtools/client/framework/browser-toolbox/test/style_browser_toolbox_ruleview_stylesheet.css b/devtools/client/framework/browser-toolbox/test/style_browser_toolbox_ruleview_stylesheet.css new file mode 100644 index 0000000000..538fa56f4a --- /dev/null +++ b/devtools/client/framework/browser-toolbox/test/style_browser_toolbox_ruleview_stylesheet.css @@ -0,0 +1,3 @@ +div { + color: red; +} -- cgit v1.2.3