From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../test/browser/browser_ext_devtools_panel.js | 812 +++++++++++++++++++++ 1 file changed, 812 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_ext_devtools_panel.js (limited to 'browser/components/extensions/test/browser/browser_ext_devtools_panel.js') diff --git a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js new file mode 100644 index 0000000000..c9bf12b9fd --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js @@ -0,0 +1,812 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +// Like most of the mochitest-browser devtools test, +// on debug test machine, it takes about 50s to run the test. +requestLongerTimeout(4); + +loadTestSubscript("head_devtools.js"); + +ChromeUtils.defineESModuleGetters(this, { + Preferences: "resource://gre/modules/Preferences.sys.mjs", +}); + +const DEVTOOLS_THEME_PREF = "devtools.theme"; + +/** + * This test file ensures that: + * + * - devtools.panels.themeName returns the correct value, + * both from a page and a panel. + * - devtools.panels.onThemeChanged fires for theme changes, + * both from a page and a panel. + * - devtools.panels.create is able to create a devtools panel. + */ + +function createPage(jsScript, bodyText = "") { + return ` + + + + + + ${bodyText} + + + `; +} + +async function test_theme_name(testWithPanel = false) { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888/" + ); + + function switchTheme(theme) { + const waitforThemeChanged = gDevTools.once("theme-changed"); + Preferences.set(DEVTOOLS_THEME_PREF, theme); + return waitforThemeChanged; + } + + async function testThemeSwitching(extension, locations = ["page"]) { + for (let newTheme of ["dark", "light"]) { + await switchTheme(newTheme); + for (let location of locations) { + is( + await extension.awaitMessage(`devtools_theme_changed_${location}`), + newTheme, + `The onThemeChanged event listener fired for the ${location}.` + ); + is( + await extension.awaitMessage(`current_theme_${location}`), + newTheme, + `The current theme is reported as expected for the ${location}.` + ); + } + } + } + + async function devtools_page(createPanel) { + if (createPanel) { + await browser.devtools.panels.create( + "Test Panel Theme", + "fake-icon.png", + "devtools_panel.html" + ); + } + + browser.devtools.panels.onThemeChanged.addListener(themeName => { + browser.test.sendMessage("devtools_theme_changed_page", themeName); + browser.test.sendMessage( + "current_theme_page", + browser.devtools.panels.themeName + ); + }); + + browser.test.sendMessage( + "initial_theme_page", + browser.devtools.panels.themeName + ); + } + + async function devtools_panel() { + browser.devtools.panels.onThemeChanged.addListener(themeName => { + browser.test.sendMessage("devtools_theme_changed_panel", themeName); + browser.test.sendMessage( + "current_theme_panel", + browser.devtools.panels.themeName + ); + }); + + browser.test.sendMessage( + "initial_theme_panel", + browser.devtools.panels.themeName + ); + } + + let files = { + "devtools_page.html": createPage("devtools_page.js"), + "devtools_page.js": `(${devtools_page})(${testWithPanel})`, + }; + + if (testWithPanel) { + files["devtools_panel.js"] = devtools_panel; + files["devtools_panel.html"] = createPage( + "devtools_panel.js", + "Test Panel Theme" + ); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + devtools_page: "devtools_page.html", + }, + files, + }); + + // Ensure that the initial value of the devtools theme is "light". + await SpecialPowers.pushPrefEnv({ set: [[DEVTOOLS_THEME_PREF, "light"]] }); + registerCleanupFunction(async function () { + await SpecialPowers.popPrefEnv(); + }); + + await extension.startup(); + + const toolbox = await openToolboxForTab(tab); + + info("Waiting initial theme from devtools_page"); + is( + await extension.awaitMessage("initial_theme_page"), + "light", + "The initial theme is reported as expected." + ); + + if (testWithPanel) { + let toolboxAdditionalTools = toolbox.getAdditionalTools(); + is( + toolboxAdditionalTools.length, + 1, + "Got the expected number of toolbox specific panel registered." + ); + + let panelId = toolboxAdditionalTools[0].id; + + await gDevTools.showToolboxForTab(tab, { toolId: panelId }); + is( + await extension.awaitMessage("initial_theme_panel"), + "light", + "The initial theme is reported as expected from a devtools panel." + ); + + await testThemeSwitching(extension, ["page", "panel"]); + } else { + await testThemeSwitching(extension); + } + + await closeToolboxForTab(tab); + + await extension.unload(); + + BrowserTestUtils.removeTab(tab); +} + +add_task(async function test_devtools_page_theme() { + await test_theme_name(false); +}); + +add_task(async function test_devtools_panel_theme() { + await test_theme_name(true); +}); + +add_task(async function test_devtools_page_panels_create() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888/" + ); + + async function devtools_page() { + const result = { + devtoolsPageTabId: browser.devtools.inspectedWindow.tabId, + panelCreated: 0, + panelShown: 0, + panelHidden: 0, + }; + + try { + const panel = await browser.devtools.panels.create( + "Test Panel Create", + "fake-icon.png", + "devtools_panel.html" + ); + + result.panelCreated++; + + panel.onShown.addListener(contentWindow => { + result.panelShown++; + browser.test.assertEq( + "complete", + contentWindow.document.readyState, + "Got the expected 'complete' panel document readyState" + ); + browser.test.assertEq( + "test_panel_global", + contentWindow.TEST_PANEL_GLOBAL, + "Got the expected global in the panel contentWindow" + ); + browser.test.sendMessage("devtools_panel_shown", result); + }); + + panel.onHidden.addListener(() => { + result.panelHidden++; + + browser.test.sendMessage("devtools_panel_hidden", result); + }); + + browser.test.sendMessage("devtools_panel_created"); + } catch (err) { + // Make the test able to fail fast when it is going to be a failure. + browser.test.sendMessage("devtools_panel_created"); + throw err; + } + } + + function devtools_panel() { + // Set a property in the global and check that it is defined + // and accessible from the devtools_page when the panel.onShown + // event has been received. + window.TEST_PANEL_GLOBAL = "test_panel_global"; + + browser.test.sendMessage( + "devtools_panel_inspectedWindow_tabId", + browser.devtools.inspectedWindow.tabId + ); + } + + const longPrefix = new Array(80).fill("x").join(""); + // Extension ID includes "inspector" to verify Bug 1474379 doesn't regress. + const EXTENSION_ID = `${longPrefix}-inspector@create-devtools-panel.test`; + + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", + manifest: { + devtools_page: "devtools_page.html", + browser_specific_settings: { + gecko: { id: EXTENSION_ID }, + }, + }, + files: { + "devtools_page.html": createPage("devtools_page.js"), + "devtools_page.js": devtools_page, + "devtools_panel.html": createPage( + "devtools_panel.js", + "Test Panel Create" + ), + "devtools_panel.js": devtools_panel, + }, + }); + + await extension.startup(); + + const extensionPrefBranch = `devtools.webextensions.${EXTENSION_ID}.`; + const extensionPrefName = `${extensionPrefBranch}enabled`; + + let prefBranch = Services.prefs.getBranch(extensionPrefBranch); + ok( + prefBranch, + "The preference branch for the extension should have been created" + ); + is( + prefBranch.getBoolPref("enabled", false), + true, + "The 'enabled' bool preference for the extension should be initially true" + ); + + // Get the devtools panel info for the first item in the toolbox additional tools array. + const getPanelInfo = toolbox => { + let toolboxAdditionalTools = toolbox.getAdditionalTools(); + is( + toolboxAdditionalTools.length, + 1, + "Got the expected number of toolbox specific panel registered." + ); + return toolboxAdditionalTools[0]; + }; + + // Test the devtools panel shown and hide events. + const testPanelShowAndHide = async ({ + tab, + panelId, + isFirstPanelLoad, + expectedResults, + }) => { + info("Wait Addon Devtools Panel to be shown"); + + await gDevTools.showToolboxForTab(tab, { toolId: panelId }); + const { devtoolsPageTabId } = await extension.awaitMessage( + "devtools_panel_shown" + ); + + // If the panel is loaded for the first time, we expect to also + // receive the test messages and assert that both the page and the panel + // have the same devtools.inspectedWindow.tabId value. + if (isFirstPanelLoad) { + const devtoolsPanelTabId = await extension.awaitMessage( + "devtools_panel_inspectedWindow_tabId" + ); + is( + devtoolsPanelTabId, + devtoolsPageTabId, + "Got the same devtools.inspectedWindow.tabId from devtools page and panel" + ); + } + + info("Wait Addon Devtools Panel to be shown"); + + await gDevTools.showToolboxForTab(tab, { toolId: "testBlankPanel" }); + const results = await extension.awaitMessage("devtools_panel_hidden"); + + // We already checked the tabId, remove it from the results, so that we can check + // the remaining properties using a single Assert.deepEqual. + delete results.devtoolsPageTabId; + + Assert.deepEqual( + results, + expectedResults, + "Got the expected number of created panels and shown/hidden events" + ); + }; + + // Test the extension devtools_page enabling/disabling through the related + // about:config preference. + const testExtensionDevToolsPref = async ({ + prefValue, + toolbox, + oldPanelId, + }) => { + if (!prefValue) { + // Test that the extension devtools_page is shutting down when the related + // about:config preference has been set to false, and the panel on its left + // is being selected. + info( + "Turning off the extension devtools page from its about:config preference" + ); + let waitToolSelected = toolbox.once("select"); + Services.prefs.setBoolPref(extensionPrefName, false); + const selectedTool = await waitToolSelected; + isnot( + selectedTool, + oldPanelId, + "Expect a different panel to be selected" + ); + + let toolboxAdditionalTools = toolbox.getAdditionalTools(); + is( + toolboxAdditionalTools.length, + 0, + "Extension devtools panel unregistered" + ); + is( + toolbox.visibleAdditionalTools.filter(toolId => toolId == oldPanelId) + .length, + 0, + "Removed panel should not be listed in the visible additional tools" + ); + } else { + // Test that the extension devtools_page and panel are being created again when + // the related about:config preference has been set to true. + info( + "Turning on the extension devtools page from its about:config preference" + ); + Services.prefs.setBoolPref(extensionPrefName, true); + await extension.awaitMessage("devtools_panel_created"); + + let toolboxAdditionalTools = toolbox.getAdditionalTools(); + is( + toolboxAdditionalTools.length, + 1, + "Got one extension devtools panel registered" + ); + + let newPanelId = getPanelInfo(toolbox).id; + is( + toolbox.visibleAdditionalTools.filter(toolId => toolId == newPanelId) + .length, + 1, + "Extension panel is listed in the visible additional tools" + ); + } + }; + + // Wait that the devtools_page has created its devtools panel and retrieve its + // panel id. + let toolbox = await openToolboxForTab(tab); + await extension.awaitMessage("devtools_panel_created"); + let panelId = getPanelInfo(toolbox).id; + + info("Test panel show and hide - first cycle"); + await testPanelShowAndHide({ + tab, + panelId, + isFirstPanelLoad: true, + expectedResults: { + panelCreated: 1, + panelShown: 1, + panelHidden: 1, + }, + }); + + info("Test panel show and hide - second cycle"); + await testPanelShowAndHide({ + tab, + panelId, + isFirstPanelLoad: false, + expectedResults: { + panelCreated: 1, + panelShown: 2, + panelHidden: 2, + }, + }); + + // Go back to the extension devtools panel. + await gDevTools.showToolboxForTab(tab, { toolId: panelId }); + await extension.awaitMessage("devtools_panel_shown"); + + // Check that the aria-label has been set on the devtools panel. + const panelFrame = toolbox.doc.getElementById( + `toolbox-panel-iframe-${panelId}` + ); + const panelInfo = getPanelInfo(toolbox); + ok( + panelInfo.panelLabel && !!panelInfo.panelLabel.length, + "Expect the registered panel to include a non empty panelLabel property" + ); + is( + panelFrame && panelFrame.getAttribute("aria-label"), + panelInfo.panelLabel, + "Got the expected aria-label on the extension panel frame" + ); + + // Turn off the extension devtools page using the preference that enable/disable the + // devtools page for a given installed WebExtension. + await testExtensionDevToolsPref({ + toolbox, + prefValue: false, + oldPanelId: panelId, + }); + + // Close and Re-open the toolbox to verify that the toolbox doesn't load the + // devtools_page and the devtools panel. + info("Re-open the toolbox and expect no extension devtools panel"); + await closeToolboxForTab(tab); + toolbox = await openToolboxForTab(tab); + + let toolboxAdditionalTools = toolbox.getAdditionalTools(); + is( + toolboxAdditionalTools.length, + 0, + "Got no extension devtools panel on the opened toolbox as expected." + ); + + // Close and Re-open the toolbox to verify that the toolbox does load the + // devtools_page and the devtools panel again. + info("Restart the toolbox and enable the extension devtools panel"); + await closeToolboxForTab(tab); + toolbox = await openToolboxForTab(tab); + + // Turn the addon devtools panel back on using the preference that enable/disable the + // devtools page for a given installed WebExtension. + await testExtensionDevToolsPref({ + toolbox, + prefValue: true, + }); + + // Test devtools panel is loaded correctly after being toggled and + // devtools panel events has been fired as expected. + panelId = getPanelInfo(toolbox).id; + + info("Test panel show and hide - after disabling/enabling devtools_page"); + await testPanelShowAndHide({ + tab, + panelId, + isFirstPanelLoad: true, + expectedResults: { + panelCreated: 1, + panelShown: 1, + panelHidden: 1, + }, + }); + + await closeToolboxForTab(tab); + + await extension.unload(); + + // Verify that the extension preference branch has been removed once the extension + // has been uninstalled. + prefBranch = Services.prefs.getBranch(extensionPrefBranch); + is( + prefBranch.getPrefType("enabled"), + prefBranch.PREF_INVALID, + "The preference branch for the extension should have been removed" + ); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_devtools_page_panels_switch_toolbox_host() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888/" + ); + + function devtools_panel() { + const hasDevToolsAPINamespace = "devtools" in browser; + + browser.test.sendMessage("devtools_panel_loaded", { + hasDevToolsAPINamespace, + panelLoadedURL: window.location.href, + }); + } + + async function devtools_page() { + const panel = await browser.devtools.panels.create( + "Test Panel Switch Host", + "fake-icon.png", + "devtools_panel.html" + ); + + panel.onShown.addListener(panelWindow => { + browser.test.sendMessage( + "devtools_panel_shown", + panelWindow.location.href + ); + }); + + panel.onHidden.addListener(() => { + browser.test.sendMessage("devtools_panel_hidden"); + }); + + browser.test.sendMessage("devtools_panel_created"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + devtools_page: "devtools_page.html", + }, + files: { + "devtools_page.html": createPage("devtools_page.js"), + "devtools_page.js": devtools_page, + "devtools_panel.html": createPage("devtools_panel.js", "DEVTOOLS PANEL"), + "devtools_panel.js": devtools_panel, + }, + }); + + await extension.startup(); + + let toolbox = await openToolboxForTab(tab); + await extension.awaitMessage("devtools_panel_created"); + + const toolboxAdditionalTools = toolbox.getAdditionalTools(); + + is( + toolboxAdditionalTools.length, + 1, + "Got the expected number of toolbox specific panel registered." + ); + + const panelDef = toolboxAdditionalTools[0]; + const panelId = panelDef.id; + + info("Selecting the addon devtools panel"); + await gDevTools.showToolboxForTab(tab, { toolId: panelId }); + + info("Wait for the panel to show and load for the first time"); + const panelShownURL = await extension.awaitMessage("devtools_panel_shown"); + + const { panelLoadedURL, hasDevToolsAPINamespace } = + await extension.awaitMessage("devtools_panel_loaded"); + + is( + panelShownURL, + panelLoadedURL, + "Got the expected panel URL on the first load" + ); + ok( + hasDevToolsAPINamespace, + "The devtools panel has the devtools API on the first load" + ); + + const originalToolboxHostType = toolbox.hostType; + + info("Switch the toolbox from docked on bottom to docked on right"); + toolbox.switchHost("right"); + + info( + "Wait for the panel to emit hide, show and load messages once docked on side" + ); + await extension.awaitMessage("devtools_panel_hidden"); + const dockedOnSideShownURL = await extension.awaitMessage( + "devtools_panel_shown" + ); + + is( + dockedOnSideShownURL, + panelShownURL, + "Got the expected panel url once the panel shown event has been emitted on toolbox host changed" + ); + + const dockedOnSideLoaded = await extension.awaitMessage( + "devtools_panel_loaded" + ); + + is( + dockedOnSideLoaded.panelLoadedURL, + panelShownURL, + "Got the expected panel url once the panel has been reloaded on toolbox host changed" + ); + ok( + dockedOnSideLoaded.hasDevToolsAPINamespace, + "The devtools panel has the devtools API once the toolbox host has been changed" + ); + + info("Switch the toolbox from docked on bottom to the original dock mode"); + toolbox.switchHost(originalToolboxHostType); + + info( + "Wait for the panel test messages once toolbox dock mode has been restored" + ); + await extension.awaitMessage("devtools_panel_hidden"); + await extension.awaitMessage("devtools_panel_shown"); + await extension.awaitMessage("devtools_panel_loaded"); + + await closeToolboxForTab(tab); + + await extension.unload(); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_devtools_page_invalid_panel_urls() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "http://mochi.test:8888/" + ); + + async function devtools_page() { + const matchInvalidPanelURL = /must be a relative URL/; + const matchInvalidIconURL = + /be one of \[""\], or match the format "strictRelativeUrl"/; + + // Invalid panel urls (validated by the schema wrappers, throws on invalid urls). + const invalid_panels = [ + { + panel: "about:about", + icon: "icon.png", + expectError: matchInvalidPanelURL, + }, + { + panel: "about:addons", + icon: "icon.png", + expectError: matchInvalidPanelURL, + }, + { + panel: "http://mochi.test:8888", + icon: "icon.png", + expectError: matchInvalidPanelURL, + }, + // Invalid icon urls (validated inside the API method because of the empty icon string + // which have to be resolved to the default icon, reject the returned promise). + { + panel: "panel.html", + icon: "about:about", + expectError: matchInvalidIconURL, + }, + { + panel: "panel.html", + icon: "http://mochi.test:8888", + expectError: matchInvalidIconURL, + }, + ]; + + const valid_panels = [ + { panel: "panel.html", icon: "icon.png" }, + { panel: "./panel.html", icon: "icon.png" }, + { panel: "/panel.html", icon: "icon.png" }, + { panel: "/panel.html", icon: "" }, + ]; + + let valid_panels_length = valid_panels.length; + + const test_cases = [].concat(invalid_panels, valid_panels); + + browser.test.onMessage.addListener(async msg => { + if (msg !== "start_test_panel_create") { + return; + } + + for (let { panel, icon, expectError } of test_cases) { + browser.test.log( + `Testing devtools.panels.create for ${JSON.stringify({ + panel, + icon, + })}` + ); + + if (expectError) { + // Verify that invalid panel urls throw. + browser.test.assertThrows( + () => browser.devtools.panels.create("Test Panel", icon, panel), + expectError, + "Got the expected rejection on creating a devtools panel with " + + `panel url ${panel} and icon ${icon}` + ); + } else { + // Verify that with valid panel and icon urls the panel is created and loaded + // as expected. + try { + const pane = await browser.devtools.panels.create( + "Test Panel", + icon, + panel + ); + + valid_panels_length--; + + // Wait the panel to be loaded. + const oncePanelLoaded = new Promise(resolve => { + pane.onShown.addListener(paneWin => { + browser.test.assertTrue( + paneWin.location.href.endsWith("/panel.html"), + `The panel has loaded the expected extension URL with ${panel}` + ); + resolve(); + }); + }); + + // Ask the privileged code to select the last created panel. + const done = valid_panels_length === 0; + browser.test.sendMessage("select-devtools-panel", done); + await oncePanelLoaded; + } catch (err) { + browser.test.fail( + "Unexpected failure on creating a devtools panel with " + + `panel url ${panel} and icon ${icon}` + ); + throw err; + } + } + } + + browser.test.sendMessage("test_invalid_devtools_panel_urls_done"); + }); + + browser.test.sendMessage("devtools_page_ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + devtools_page: "devtools_page.html", + icons: { + 32: "icon.png", + }, + }, + files: { + "devtools_page.html": createPage("devtools_page.js"), + "devtools_page.js": devtools_page, + "panel.html": createPage("panel.js", "DEVTOOLS PANEL"), + "panel.js": "", + "icon.png": imageBuffer, + "default-icon.png": imageBuffer, + }, + }); + + await extension.startup(); + + let toolbox = await openToolboxForTab(tab); + info("developer toolbox opened"); + + await extension.awaitMessage("devtools_page_ready"); + + extension.sendMessage("start_test_panel_create"); + + let done = false; + + while (!done) { + info("Waiting test extension request to select the last created panel"); + done = await extension.awaitMessage("select-devtools-panel"); + + const toolboxAdditionalTools = toolbox.getAdditionalTools(); + const lastTool = toolboxAdditionalTools[toolboxAdditionalTools.length - 1]; + + gDevTools.showToolboxForTab(tab, { toolId: lastTool.id }); + info("Last created panel selected"); + } + + await extension.awaitMessage("test_invalid_devtools_panel_urls_done"); + + await closeToolboxForTab(tab); + + await extension.unload(); + + BrowserTestUtils.removeTab(tab); +}); -- cgit v1.2.3