From 8dd16259287f58f9273002717ec4d27e97127719 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:43:14 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- .../extensions/ExtensionControlledPopup.sys.mjs | 2 - browser/components/extensions/extension.css | 2 +- .../components/extensions/parent/ext-browser.js | 19 +- browser/components/extensions/parent/ext-menus.js | 14 +- .../extensions/parent/ext-sidebarAction.js | 169 ++---- .../extensions/test/browser/browser.toml | 2 + .../browser_ext_browserAction_contextMenu.js | 117 +--- .../browser_ext_commands_execute_browser_action.js | 236 +++++++- .../test/browser/browser_ext_commands_update.js | 8 +- .../test/browser/browser_ext_contextMenus.js | 2 +- .../browser/browser_ext_contextMenus_bookmarks.js | 2 +- .../browser_ext_contextMenus_targetUrlPatterns.js | 18 +- .../test/browser/browser_ext_getViews.js | 38 -- .../browser_ext_menus_replace_menu_permissions.js | 8 +- .../browser/browser_ext_menus_targetElement.js | 8 + .../test/browser/browser_ext_mousewheel_zoom.js | 2 +- .../test/browser/browser_ext_openPanel.js | 6 +- .../test/browser/browser_ext_originControls.js | 6 + .../browser/browser_ext_pageAction_show_matches.js | 18 +- .../browser/browser_ext_popup_select_in_oopif.js | 2 + .../browser/browser_ext_runtime_getContexts.js | 597 +++++++++++++++++++++ .../browser_ext_sessions_getRecentlyClosed.js | 16 +- .../browser/browser_ext_sessions_restoreTab.js | 6 +- .../test/browser/browser_ext_sidebarAction.js | 60 ++- .../browser/browser_ext_sidebarAction_click.js | 2 +- .../browser/browser_ext_sidebarAction_context.js | 2 +- .../browser/browser_ext_sidebarAction_httpAuth.js | 2 +- .../browser/browser_ext_sidebarAction_incognito.js | 7 +- .../browser/browser_ext_sidebarAction_runtime.js | 9 +- .../browser/browser_ext_sidebarAction_windows.js | 2 +- .../test/browser/browser_unified_extensions.js | 3 + .../browser_unified_extensions_accessibility.js | 6 + .../browser_unified_extensions_context_menu.js | 108 +--- browser/components/extensions/test/browser/head.js | 52 +- 34 files changed, 1145 insertions(+), 406 deletions(-) create mode 100644 browser/components/extensions/test/browser/browser_ext_runtime_getContexts.js (limited to 'browser/components/extensions') diff --git a/browser/components/extensions/ExtensionControlledPopup.sys.mjs b/browser/components/extensions/ExtensionControlledPopup.sys.mjs index b07a8214f3..2d9a9fb584 100644 --- a/browser/components/extensions/ExtensionControlledPopup.sys.mjs +++ b/browser/components/extensions/ExtensionControlledPopup.sys.mjs @@ -235,8 +235,6 @@ export class ExtensionControlledPopup { return; } - win.ownerGlobal.ensureCustomElements("moz-support-link"); - // Find the elements we need. let doc = win.document; let panel = ExtensionControlledPopup._getAndMaybeCreatePanel(doc); diff --git a/browser/components/extensions/extension.css b/browser/components/extensions/extension.css index f05e2f12a1..431f0148ae 100644 --- a/browser/components/extensions/extension.css +++ b/browser/components/extensions/extension.css @@ -21,7 +21,7 @@ body { background: transparent; box-sizing: border-box; - color: #222426; + color: light-dark(#222426, CanvasText); cursor: default; display: flex; flex-direction: column; diff --git a/browser/components/extensions/parent/ext-browser.js b/browser/components/extensions/parent/ext-browser.js index d2f72d4f46..e7a516dcd3 100644 --- a/browser/components/extensions/parent/ext-browser.js +++ b/browser/components/extensions/parent/ext-browser.js @@ -82,7 +82,7 @@ global.openOptionsPage = extension => { extension.id )}/preferences`; - return window.BrowserOpenAddonsMgr(viewId); + return window.BrowserAddonUI.openAddonsMgr(viewId); }; global.makeWidgetId = id => { @@ -709,6 +709,23 @@ class TabTracker extends TabTrackerBase { }; } + getBrowserDataForContext(context) { + if (["tab", "background"].includes(context.viewType)) { + return this.getBrowserData(context.xulBrowser); + } else if (["popup", "sidebar"].includes(context.viewType)) { + // popups and sidebars are nested inside a browser element + // (with url "chrome://browser/content/webext-panels.xhtml") + // and so we look for the corresponding topChromeWindow to + // determine the windowId the panel belongs to. + const chromeWindow = + context.xulBrowser?.ownerGlobal?.browsingContext?.topChromeWindow; + const windowId = chromeWindow ? windowTracker.getId(chromeWindow) : -1; + return { tabId: -1, windowId }; + } + + return { tabId: -1, windowId: -1 }; + } + get activeTab() { let window = windowTracker.topWindow; if (window && window.gBrowser) { diff --git a/browser/components/extensions/parent/ext-menus.js b/browser/components/extensions/parent/ext-menus.js index a5b27bff7d..e4ad9f4747 100644 --- a/browser/components/extensions/parent/ext-menus.js +++ b/browser/components/extensions/parent/ext-menus.js @@ -1100,11 +1100,11 @@ const menuTracker = { ); sidebarHeader.addEventListener("SidebarShown", menuTracker.onSidebarShown); - await window.SidebarUI.promiseInitialized; + await window.SidebarController.promiseInitialized; if ( !window.closed && - window.SidebarUI.currentID === "viewBookmarksSidebar" + window.SidebarController.currentID === "viewBookmarksSidebar" ) { menuTracker.onSidebarShown({ currentTarget: sidebarHeader }); } @@ -1121,8 +1121,8 @@ const menuTracker = { ); sidebarHeader.removeEventListener("SidebarShown", this.onSidebarShown); - if (window.SidebarUI.currentID === "viewBookmarksSidebar") { - let sidebarBrowser = window.SidebarUI.browser; + if (window.SidebarController.currentID === "viewBookmarksSidebar") { + let sidebarBrowser = window.SidebarController.browser; sidebarBrowser.removeEventListener("load", this.onSidebarShown); const menu = sidebarBrowser.contentDocument.getElementById("placesContext"); @@ -1134,10 +1134,10 @@ const menuTracker = { // The event target is an element in a browser window, so |window| will be // the browser window that contains the sidebar. const window = event.currentTarget.ownerGlobal; - if (window.SidebarUI.currentID === "viewBookmarksSidebar") { - let sidebarBrowser = window.SidebarUI.browser; + if (window.SidebarController.currentID === "viewBookmarksSidebar") { + let sidebarBrowser = window.SidebarController.browser; if (sidebarBrowser.contentDocument.readyState !== "complete") { - // SidebarUI.currentID may be updated before the bookmark sidebar's + // SidebarController.currentID may be updated before the bookmark sidebar's // document has finished loading. This sometimes happens when the // sidebar is automatically shown when a new window is opened. sidebarBrowser.addEventListener("load", menuTracker.onSidebarShown, { diff --git a/browser/components/extensions/parent/ext-sidebarAction.js b/browser/components/extensions/parent/ext-sidebarAction.js index 197456abd9..b2c009014e 100644 --- a/browser/components/extensions/parent/ext-sidebarAction.js +++ b/browser/components/extensions/parent/ext-sidebarAction.js @@ -17,8 +17,6 @@ var { IconDetails } = ExtensionParent; // WeakMap[Extension -> SidebarAction] let sidebarActionMap = new WeakMap(); -const sidebarURL = "chrome://browser/content/webext-panels.xhtml"; - /** * Responsible for the sidebar_action section of the manifest as well * as the associated sidebar browser. @@ -40,7 +38,6 @@ this.sidebarAction = class extends ExtensionAPI { let widgetId = makeWidgetId(extension.id); this.id = `${widgetId}-sidebar-action`; this.menuId = `menubar_menu_${this.id}`; - this.switcherMenuId = `sidebarswitcher_menu_${this.id}`; this.browserStyle = options.browser_style; @@ -66,23 +63,6 @@ this.sidebarAction = class extends ExtensionAPI { }; windowTracker.addOpenListener(this.windowOpenListener); - this.updateHeader = event => { - let window = event.target.ownerGlobal; - let details = this.tabContext.get(window.gBrowser.selectedTab); - let header = window.document.getElementById("sidebar-switcher-target"); - if (window.SidebarUI.currentID === this.id) { - this.setMenuIcon(header, details); - } - }; - - this.windowCloseListener = window => { - let header = window.document.getElementById("sidebar-switcher-target"); - if (header) { - header.removeEventListener("SidebarShown", this.updateHeader); - } - }; - windowTracker.addCloseListener(this.windowCloseListener); - sidebarActionMap.set(extension, this); } @@ -91,7 +71,7 @@ this.sidebarAction = class extends ExtensionAPI { } onShutdown(isAppShutdown) { - sidebarActionMap.delete(this.this); + sidebarActionMap.delete(this.extension); this.tabContext.shutdown(); @@ -102,26 +82,18 @@ this.sidebarAction = class extends ExtensionAPI { } for (let window of windowTracker.browserWindows()) { - let { document, SidebarUI } = window; - if (SidebarUI.currentID === this.id) { - SidebarUI.hide(); - } - document.getElementById(this.menuId)?.remove(); - document.getElementById(this.switcherMenuId)?.remove(); - let header = document.getElementById("sidebar-switcher-target"); - header.removeEventListener("SidebarShown", this.updateHeader); - SidebarUI.sidebars.delete(this.id); + let { SidebarController } = window; + SidebarController.removeExtension(this.id); } windowTracker.removeOpenListener(this.windowOpenListener); - windowTracker.removeCloseListener(this.windowCloseListener); } static onUninstall(id) { const sidebarId = `${makeWidgetId(id)}-sidebar-action`; for (let window of windowTracker.browserWindows()) { - let { SidebarUI } = window; - if (SidebarUI.lastOpenedId === sidebarId) { - SidebarUI.lastOpenedId = null; + let { SidebarController } = window; + if (SidebarController.lastOpenedId === sidebarId) { + SidebarController.lastOpenedId = null; } } } @@ -135,12 +107,12 @@ this.sidebarAction = class extends ExtensionAPI { let install = this.extension.startupReason === "ADDON_INSTALL"; for (let window of windowTracker.browserWindows()) { this.updateWindow(window); - let { SidebarUI } = window; + let { SidebarController } = window; if ( (install && this.extension.manifest.sidebar_action.open_at_install) || - SidebarUI.lastOpenedId == this.id + SidebarController.lastOpenedId == this.id ) { - SidebarUI.show(this.id); + SidebarController.show(this.id); } } } @@ -149,60 +121,29 @@ this.sidebarAction = class extends ExtensionAPI { if (!this.extension.canAccessWindow(window)) { return; } - let { document, SidebarUI } = window; - let keyId = `ext-key-id-${this.id}`; - - SidebarUI.sidebars.set(this.id, { - title: details.title, - url: sidebarURL, + this.panel = details.panel; + let { SidebarController } = window; + SidebarController.registerExtension(this.id, { + icon: this.getMenuIcon(details), menuId: this.menuId, - switcherMenuId: this.switcherMenuId, - // The following properties are specific to extensions + title: details.title, extensionId: this.extension.id, - panel: details.panel, - browserStyle: this.browserStyle, + onload: () => + SidebarController.browser.contentWindow.loadPanel( + this.extension.id, + this.panel, + this.browserStyle + ), }); - - let header = document.getElementById("sidebar-switcher-target"); - header.addEventListener("SidebarShown", this.updateHeader); - - // Insert a menuitem for View->Show Sidebars. - let menuitem = document.createXULElement("menuitem"); - menuitem.setAttribute("id", this.menuId); - menuitem.setAttribute("type", "checkbox"); - menuitem.setAttribute("label", details.title); - menuitem.setAttribute("oncommand", `SidebarUI.toggle("${this.id}");`); - menuitem.setAttribute("class", "menuitem-iconic webextension-menuitem"); - menuitem.setAttribute("key", keyId); - this.setMenuIcon(menuitem, details); - - // Insert a toolbarbutton for the sidebar dropdown selector. - let switcherMenuitem = menuitem.cloneNode(); - switcherMenuitem.setAttribute("id", this.switcherMenuId); - switcherMenuitem.removeAttribute("type"); - - document.getElementById("viewSidebarMenu").appendChild(menuitem); - let separator = document.getElementById("sidebar-extensions-separator"); - separator.parentNode.insertBefore(switcherMenuitem, separator); - - return menuitem; } - setMenuIcon(menuitem, details) { + getMenuIcon(details) { let getIcon = size => IconDetails.escapeUrl( IconDetails.getPreferredIcon(details.icon, this.extension, size).icon ); - menuitem.setAttribute( - "style", - ` - --webextension-menuitem-image: image-set( - url("${getIcon(16)}"), - url("${getIcon(32)}") 2x - ); - ` - ); + return `image-set(url("${getIcon(16)}"), url("${getIcon(32)}") 2x)`; } /** @@ -214,34 +155,26 @@ this.sidebarAction = class extends ExtensionAPI { * Tab specific sidebar configuration. */ updateButton(window, tabData) { - let { document, SidebarUI } = window; + let { document, SidebarController } = window; let title = tabData.title || this.extension.name; - let menu = document.getElementById(this.menuId); - if (!menu) { - menu = this.createMenuItem(window, tabData); + if (!document.getElementById(this.menuId)) { + // Menu items are added when new windows are opened, or from onReady (when + // an extension has fully started). The menu item may be missing at this + // point if the extension updates the sidebar during its startup. + this.createMenuItem(window, tabData); } - - let urlChanged = tabData.panel !== SidebarUI.sidebars.get(this.id).panel; + let urlChanged = tabData.panel !== this.panel; if (urlChanged) { - SidebarUI.sidebars.get(this.id).panel = tabData.panel; - } - - menu.setAttribute("label", title); - this.setMenuIcon(menu, tabData); - - let button = document.getElementById(this.switcherMenuId); - button.setAttribute("label", title); - this.setMenuIcon(button, tabData); - - // Update the sidebar if this extension is the current sidebar. - if (SidebarUI.currentID === this.id) { - SidebarUI.title = title; - let header = document.getElementById("sidebar-switcher-target"); - this.setMenuIcon(header, tabData); - if (SidebarUI.isOpen && urlChanged) { - SidebarUI.show(this.id); - } + this.panel = tabData.panel; } + SidebarController.setExtensionAttributes( + this.id, + { + icon: this.getMenuIcon(tabData), + label: title, + }, + urlChanged + ); } /** @@ -382,9 +315,9 @@ this.sidebarAction = class extends ExtensionAPI { * @param {ChromeWindow} window */ triggerAction(window) { - let { SidebarUI } = window; - if (SidebarUI && this.extension.canAccessWindow(window)) { - SidebarUI.toggle(this.id); + let { SidebarController } = window; + if (SidebarController && this.extension.canAccessWindow(window)) { + SidebarController.toggle(this.id); } } @@ -394,9 +327,9 @@ this.sidebarAction = class extends ExtensionAPI { * @param {ChromeWindow} window */ open(window) { - let { SidebarUI } = window; - if (SidebarUI && this.extension.canAccessWindow(window)) { - SidebarUI.show(this.id); + let { SidebarController } = window; + if (SidebarController && this.extension.canAccessWindow(window)) { + SidebarController.show(this.id); } } @@ -407,7 +340,7 @@ this.sidebarAction = class extends ExtensionAPI { */ close(window) { if (this.isOpen(window)) { - window.SidebarUI.hide(); + window.SidebarController.hide(); } } @@ -417,15 +350,15 @@ this.sidebarAction = class extends ExtensionAPI { * @param {ChromeWindow} window */ toggle(window) { - let { SidebarUI } = window; - if (!SidebarUI || !this.extension.canAccessWindow(window)) { + let { SidebarController } = window; + if (!SidebarController || !this.extension.canAccessWindow(window)) { return; } if (!this.isOpen(window)) { - SidebarUI.show(this.id); + SidebarController.show(this.id); } else { - SidebarUI.hide(); + SidebarController.hide(); } } @@ -436,8 +369,8 @@ this.sidebarAction = class extends ExtensionAPI { * @returns {boolean} */ isOpen(window) { - let { SidebarUI } = window; - return SidebarUI.isOpen && this.id == SidebarUI.currentID; + let { SidebarController } = window; + return SidebarController.isOpen && this.id == SidebarController.currentID; } getAPI(context) { diff --git a/browser/components/extensions/test/browser/browser.toml b/browser/components/extensions/test/browser/browser.toml index 570a51eeb4..b5b4322ffe 100644 --- a/browser/components/extensions/test/browser/browser.toml +++ b/browser/components/extensions/test/browser/browser.toml @@ -392,6 +392,8 @@ run-if = ["crashreporter"] ["browser_ext_request_permissions.js"] +["browser_ext_runtime_getContexts.js"] + ["browser_ext_runtime_onPerformanceWarning.js"] ["browser_ext_runtime_openOptionsPage.js"] diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js index 26d1536de1..32de8b95f2 100644 --- a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js +++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js @@ -2,10 +2,6 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -ChromeUtils.defineESModuleGetters(this, { - AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs", -}); - XPCOMUtils.defineLazyPreferenceGetter( this, "ABUSE_REPORT_ENABLED", @@ -546,35 +542,6 @@ async function browseraction_contextmenu_report_extension_helper() { useAddonManager: "temporary", }); - async function testReportDialog(viaUnifiedContextMenu) { - const reportDialogWindow = await BrowserTestUtils.waitForCondition( - () => AbuseReporter.getOpenDialog(), - "Wait for the abuse report dialog to have been opened" - ); - - const reportDialogParams = reportDialogWindow.arguments[0].wrappedJSObject; - is( - reportDialogParams.report.addon.id, - id, - "Abuse report dialog has the expected addon id" - ); - is( - reportDialogParams.report.reportEntryPoint, - viaUnifiedContextMenu ? "unified_context_menu" : "toolbar_context_menu", - "Abuse report dialog has the expected reportEntryPoint" - ); - - info("Wait the report dialog to complete rendering"); - await reportDialogParams.promiseReportPanel; - info("Close the report dialog"); - reportDialogWindow.close(); - is( - await reportDialogParams.promiseReport, - undefined, - "Report resolved as user cancelled when the window is closed" - ); - } - async function testContextMenu(menuId, customizing) { info(`Open browserAction context menu in ${menuId}`); let menu = await openContextMenu(menuId, buttonId); @@ -591,54 +558,27 @@ async function browseraction_contextmenu_report_extension_helper() { let aboutAddonsBrowser; - if (AbuseReporter.amoFormEnabled) { - const reportURL = Services.urlFormatter - .formatURLPref("extensions.abuseReport.amoFormURL") - .replace("%addonID%", id); - - const promiseReportTab = BrowserTestUtils.waitForNewTab( - gBrowser, - reportURL, - /* waitForLoad */ false, - // Expect it to be the next tab opened - /* waitForAnyTab */ false - ); - await closeChromeContextMenu(menuId, reportExtension); - const reportTab = await promiseReportTab; - // Remove the report tab and expect the selected tab - // to become the about:addons tab. - BrowserTestUtils.removeTab(reportTab); - is( - gBrowser.selectedBrowser.currentURI.spec, - "about:blank", - "Expect about:addons tab to not have been opened (amoFormEnabled=true)" - ); - } else { - // When running in customizing mode "about:addons" will load in a new tab, - // otherwise it will replace the existing blank tab. - const onceAboutAddonsTab = customizing - ? BrowserTestUtils.waitForNewTab(gBrowser, "about:addons") - : BrowserTestUtils.waitForCondition(() => { - return gBrowser.currentURI.spec === "about:addons"; - }, "Wait an about:addons tab to be opened"); - await closeChromeContextMenu(menuId, reportExtension); - await onceAboutAddonsTab; - const browser = gBrowser.selectedBrowser; - is( - browser.currentURI.spec, - "about:addons", - "Got about:addons tab selected (amoFormEnabled=false)" - ); - // Do not wait for the about:addons tab to be loaded if its - // document is already readyState==complete. - // This prevents intermittent timeout failures while running - // this test in optimized builds. - if (browser.contentDocument?.readyState != "complete") { - await BrowserTestUtils.browserLoaded(browser); - } - await testReportDialog(usingUnifiedContextMenu); - aboutAddonsBrowser = browser; - } + const reportURL = Services.urlFormatter + .formatURLPref("extensions.abuseReport.amoFormURL") + .replace("%addonID%", id); + + const promiseReportTab = BrowserTestUtils.waitForNewTab( + gBrowser, + reportURL, + /* waitForLoad */ false, + // Expect it to be the next tab opened + /* waitForAnyTab */ false + ); + await closeChromeContextMenu(menuId, reportExtension); + const reportTab = await promiseReportTab; + // Remove the report tab and expect the selected tab + // to become the about:addons tab. + BrowserTestUtils.removeTab(reportTab); + is( + gBrowser.selectedBrowser.currentURI.spec, + "about:blank", + "Expect about:addons tab to not have been opened" + ); // Close the new about:addons tab when running in customize mode, // or cancel the abuse report if the about:addons page has been @@ -729,22 +669,7 @@ add_task(async function test_unified_extensions_ui() { await browseraction_contextmenu_manage_extension_helper(); await browseraction_contextmenu_remove_extension_helper(); await test_no_toolbar_pinning_on_builtin_helper(); -}); - -add_task(async function test_report_amoFormEnabled() { - await SpecialPowers.pushPrefEnv({ - set: [["extensions.abuseReport.amoFormEnabled", true]], - }); - await browseraction_contextmenu_report_extension_helper(); - await SpecialPowers.popPrefEnv(); -}); - -add_task(async function test_report_amoFormDisabled() { - await SpecialPowers.pushPrefEnv({ - set: [["extensions.abuseReport.amoFormEnabled", false]], - }); await browseraction_contextmenu_report_extension_helper(); - await SpecialPowers.popPrefEnv(); }); /** diff --git a/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js b/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js index 526dfbbeeb..9ca2a9c666 100644 --- a/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js +++ b/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js @@ -2,10 +2,9 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -add_task(async function testTabSwitchActionContext() { - await SpecialPowers.pushPrefEnv({ - set: [["extensions.manifestV3.enabled", true]], - }); +ChromeUtils.defineESModuleGetters(this, { + ExtensionSettingsStore: + "resource://gre/modules/ExtensionSettingsStore.sys.mjs", }); async function testExecuteBrowserActionWithOptions(options = {}) { @@ -192,3 +191,232 @@ add_task( }); } ); + +add_task(async function test_fallback_to_execute_browser_action_in_mv3() { + // Make sure the mouse isn't hovering over the browserAction widget. + EventUtils.synthesizeMouseAtCenter( + gURLBar.textbox, + { type: "mouseover" }, + window + ); + + const EXTENSION_ID = "@test-action"; + const extMV2 = ExtensionTestUtils.loadExtension({ + manifest: { + manifest_version: 2, + browser_action: {}, + browser_specific_settings: { gecko: { id: EXTENSION_ID } }, + commands: { + _execute_browser_action: { + suggested_key: { + default: "Alt+1", + }, + }, + }, + }, + async background() { + await browser.commands.update({ + name: "_execute_browser_action", + shortcut: "Alt+Shift+2", + }); + browser.test.sendMessage("command-update"); + }, + useAddonManager: "temporary", + }); + await extMV2.startup(); + await extMV2.awaitMessage("command-update"); + + let storedCommands = ExtensionSettingsStore.getAllForExtension( + EXTENSION_ID, + "commands" + ); + Assert.deepEqual( + storedCommands, + ["_execute_browser_action"], + "expected a stored command" + ); + + const extDataMV3 = { + manifest: { + manifest_version: 3, + action: {}, + browser_specific_settings: { gecko: { id: EXTENSION_ID } }, + commands: { + _execute_action: { + suggested_key: { + default: "Alt+3", + }, + }, + }, + }, + background() { + browser.action.onClicked.addListener(() => { + browser.test.notifyPass("execute-action-on-clicked-fired"); + }); + + browser.test.onMessage.addListener(async (msg, data) => { + switch (msg) { + case "verify": { + const commands = await browser.commands.getAll(); + browser.test.assertDeepEq( + data, + commands, + "expected correct commands" + ); + browser.test.sendMessage(`${msg}-done`); + break; + } + + case "update": + await browser.commands.update(data); + browser.test.sendMessage(`${msg}-done`); + break; + + case "reset": + await browser.commands.reset("_execute_action"); + browser.test.sendMessage(`${msg}-done`); + break; + + default: + browser.test.fail(`unexpected message: ${msg}`); + } + }); + + browser.test.sendMessage("ready"); + }, + useAddonManager: "temporary", + }; + const extMV3 = ExtensionTestUtils.loadExtension(extDataMV3); + await extMV3.startup(); + await extMV3.awaitMessage("ready"); + + // We should have the shortcut value from the previous extension. + extMV3.sendMessage("verify", [ + { + name: "_execute_action", + description: null, + shortcut: "Alt+Shift+2", + }, + ]); + await extMV3.awaitMessage("verify-done"); + + // Execute the shortcut from the MV2 extension. + await SimpleTest.promiseFocus(window); + EventUtils.synthesizeKey("2", { + altKey: true, + shiftKey: true, + }); + await extMV3.awaitFinish("execute-action-on-clicked-fired"); + + // Update the shortcut. + extMV3.sendMessage("update", { + name: "_execute_action", + shortcut: "Alt+Shift+4", + }); + await extMV3.awaitMessage("update-done"); + + extMV3.sendMessage("verify", [ + { + name: "_execute_action", + description: null, + shortcut: "Alt+Shift+4", + }, + ]); + await extMV3.awaitMessage("verify-done"); + + // At this point, we should have the old and new commands in storage. + storedCommands = ExtensionSettingsStore.getAllForExtension( + EXTENSION_ID, + "commands" + ); + Assert.deepEqual( + storedCommands, + ["_execute_browser_action", "_execute_action"], + "expected two stored commands" + ); + + // Disarm any pending writes before we modify the JSONFile directly. + await ExtensionSettingsStore._reloadFile( + true // saveChanges + ); + + let jsonFileName = "extension-settings.json"; + let storePath = PathUtils.join(PathUtils.profileDir, jsonFileName); + + let settingsStoreData = await IOUtils.readJSON(storePath); + Assert.deepEqual( + Array.from(Object.keys(settingsStoreData.commands)), + ["_execute_browser_action", "_execute_action"], + "expected command hortcuts data to be found in extension-settings.json" + ); + + // Reverse the order of _execute_action and _execute_browser_action stored + // in the settings store. + settingsStoreData.commands = { + _execute_action: settingsStoreData.commands._execute_action, + _execute_browser_action: settingsStoreData.commands._execute_browser_action, + }; + + Assert.deepEqual( + Array.from(Object.keys(settingsStoreData.commands)), + ["_execute_action", "_execute_browser_action"], + "expected command shortcuts order to be reversed in extension-settings.json data" + ); + + // Write the extension-settings.json data and reload it. + await IOUtils.writeJSON(storePath, settingsStoreData); + await ExtensionSettingsStore._reloadFile( + false // saveChanges + ); + + // Restart the extension to verify that the loaded command is the right one. + const updatedExtMV3 = ExtensionTestUtils.loadExtension(extDataMV3); + await updatedExtMV3.startup(); + await updatedExtMV3.awaitMessage("ready"); + + // We should *still* have two stored commands. + storedCommands = ExtensionSettingsStore.getAllForExtension( + EXTENSION_ID, + "commands" + ); + Assert.deepEqual( + storedCommands, + ["_execute_action", "_execute_browser_action"], + "expected two stored commands" + ); + + updatedExtMV3.sendMessage("verify", [ + { + name: "_execute_action", + description: null, + shortcut: "Alt+Shift+4", + }, + ]); + await updatedExtMV3.awaitMessage("verify-done"); + + updatedExtMV3.sendMessage("reset"); + await updatedExtMV3.awaitMessage("reset-done"); + + // Resetting the shortcut should take the default value from the latest + // extension version. + updatedExtMV3.sendMessage("verify", [ + { + name: "_execute_action", + description: null, + shortcut: "Alt+3", + }, + ]); + await updatedExtMV3.awaitMessage("verify-done"); + + // At this point, we should no longer have any stored commands, since we are + // using the default. + storedCommands = ExtensionSettingsStore.getAllForExtension( + EXTENSION_ID, + "commands" + ); + Assert.deepEqual(storedCommands, [], "expected no stored command"); + + await extMV2.unload(); + await extMV3.unload(); + await updatedExtMV3.unload(); +}); diff --git a/browser/components/extensions/test/browser/browser_ext_commands_update.js b/browser/components/extensions/test/browser/browser_ext_commands_update.js index 5e1f05e346..9598328d26 100644 --- a/browser/components/extensions/test/browser/browser_ext_commands_update.js +++ b/browser/components/extensions/test/browser/browser_ext_commands_update.js @@ -401,11 +401,11 @@ add_task(async function updateSidebarCommand() { await extension.awaitMessage("sidebar"); // Show and hide the switcher panel to generate the initial shortcuts. - let switcherShown = promisePopupShown(SidebarUI._switcherPanel); - SidebarUI.showSwitcherPanel(); + let switcherShown = promisePopupShown(SidebarController._switcherPanel); + SidebarController.showSwitcherPanel(); await switcherShown; - let switcherHidden = promisePopupHidden(SidebarUI._switcherPanel); - SidebarUI.hideSwitcherPanel(); + let switcherHidden = promisePopupHidden(SidebarController._switcherPanel); + SidebarController.hideSwitcherPanel(); await switcherHidden; let menuitemId = `sidebarswitcher_menu_${makeWidgetId( diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus.js b/browser/components/extensions/test/browser/browser_ext_contextMenus.js index 77e4bf0827..0ed5d6ce9e 100644 --- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js @@ -724,7 +724,7 @@ add_task(async function test_bookmark_sidebar_contextmenu() { await extension.startup(); let bookmarkGuid = await extension.awaitMessage("bookmark-created"); - let sidebar = window.SidebarUI.browser; + let sidebar = window.SidebarController.browser; let menu = sidebar.contentDocument.getElementById("placesContext"); tree.selectItems([bookmarkGuid]); let shown = BrowserTestUtils.waitForEvent(menu, "popupshown"); diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_bookmarks.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_bookmarks.js index 24411731f7..3c0a1269a9 100644 --- a/browser/components/extensions/test/browser/browser_ext_contextMenus_bookmarks.js +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_bookmarks.js @@ -54,7 +54,7 @@ add_task(async function test_bookmark_sidebar_contextmenu() { expectedVirtualID, ] of expected_bookmarkID_2_virtualID) { info(`Testing context menu for Bookmark ID "${expectedBookmarkID}"`); - let sidebar = window.SidebarUI.browser; + let sidebar = window.SidebarController.browser; let menu = sidebar.contentDocument.getElementById("placesContext"); tree.selectItems([expectedBookmarkID]); diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_targetUrlPatterns.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_targetUrlPatterns.js index 0f353600d7..ae1ec31827 100644 --- a/browser/components/extensions/test/browser/browser_ext_contextMenus_targetUrlPatterns.js +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_targetUrlPatterns.js @@ -221,7 +221,7 @@ add_task(async function privileged_are_allowed_to_use_restrictedSchemes() { manifest: { permissions: ["tabs", "contextMenus", "mozillaAddons"], }, - async background() { + background() { browser.contextMenus.create({ id: "privileged-extension", title: "Privileged Extension", @@ -229,6 +229,7 @@ add_task(async function privileged_are_allowed_to_use_restrictedSchemes() { documentUrlPatterns: ["about:reader*"], }); + let articleReady = Promise.withResolvers(); browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { if ( changeInfo.status === "complete" && @@ -236,6 +237,9 @@ add_task(async function privileged_are_allowed_to_use_restrictedSchemes() { ) { browser.test.sendMessage("readerModeEntered"); } + if (tab.isArticle && tab.url.includes("/readerModeArticle.html")) { + articleReady.resolve(); + } }); browser.test.onMessage.addListener(async msg => { @@ -244,8 +248,20 @@ add_task(async function privileged_are_allowed_to_use_restrictedSchemes() { return; } + browser.test.log("Waiting for tab.isArticle to be true"); + await articleReady.promise; + browser.test.log("Toggling reader mode"); browser.tabs.toggleReaderMode(); }); + + browser.tabs.query( + { url: "*://example.com/*/readerModeArticle.html" }, + tabs => { + if (tabs[0].isArticle) { + articleReady.resolve(); + } + } + ); }, }); diff --git a/browser/components/extensions/test/browser/browser_ext_getViews.js b/browser/components/extensions/test/browser/browser_ext_getViews.js index 1af190e753..20db9c86c8 100644 --- a/browser/components/extensions/test/browser/browser_ext_getViews.js +++ b/browser/components/extensions/test/browser/browser_ext_getViews.js @@ -78,44 +78,6 @@ function genericChecker() { browser.test.sendMessage(kind + "-ready"); } -async function promiseBrowserContentUnloaded(browser) { - // Wait until the content has unloaded before resuming the test, to avoid - // calling extension.getViews too early (and having intermittent failures). - const MSG_WINDOW_DESTROYED = "Test:BrowserContentDestroyed"; - let unloadPromise = new Promise(resolve => { - Services.ppmm.addMessageListener(MSG_WINDOW_DESTROYED, function listener() { - Services.ppmm.removeMessageListener(MSG_WINDOW_DESTROYED, listener); - resolve(); - }); - }); - - await ContentTask.spawn( - browser, - MSG_WINDOW_DESTROYED, - MSG_WINDOW_DESTROYED => { - let innerWindowId = this.content.windowGlobalChild.innerWindowId; - let observer = subject => { - if ( - innerWindowId === subject.QueryInterface(Ci.nsISupportsPRUint64).data - ) { - Services.obs.removeObserver(observer, "inner-window-destroyed"); - - // Use process message manager to ensure that the message is delivered - // even after the 's message manager is disconnected. - Services.cpmm.sendAsyncMessage(MSG_WINDOW_DESTROYED); - } - }; - // Observe inner-window-destroyed, like ExtensionPageChild, to ensure that - // the ExtensionPageContextChild instance has been unloaded when we resolve - // the unloadPromise. - Services.obs.addObserver(observer, "inner-window-destroyed"); - } - ); - - // Return an object so that callers can use "await". - return { unloadPromise }; -} - add_task(async function () { let win1 = await BrowserTestUtils.openNewBrowserWindow(); let win2 = await BrowserTestUtils.openNewBrowserWindow(); diff --git a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js index c9627c5ae9..7960dd74dc 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu_permissions.js @@ -189,7 +189,9 @@ add_task(async function overrideContext_permissions() { // permissions.request requires user input, export helper. await SpecialPowers.spawn( - SidebarUI.browser.contentDocument.getElementById("webext-panels-browser"), + SidebarController.browser.contentDocument.getElementById( + "webext-panels-browser" + ), [], () => { const { ExtensionCommon } = ChromeUtils.importESModule( @@ -212,7 +214,9 @@ add_task(async function overrideContext_permissions() { await BrowserTestUtils.synthesizeMouseAtCenter( "a", { type: "contextmenu" }, - SidebarUI.browser.contentDocument.getElementById("webext-panels-browser") + SidebarController.browser.contentDocument.getElementById( + "webext-panels-browser" + ) ); } while (await extension.awaitMessage("continue_test")); diff --git a/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js b/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js index 4fc9ebba82..9035b6eecb 100644 --- a/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js +++ b/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js @@ -308,6 +308,14 @@ add_task(async function independentMenusInDifferentTabs() { gBrowser.selectedTab = tab2; let targetElementId2 = await extension.openAndCloseMenu("#editabletext"); + if (targetElementId === targetElementId2) { + // targetElementId is only guaranteed to be unique within a tab, so odds are + // that by unlucky coincidence, that the discovered ID accidentally overlaps + // with the actual ID. + info(`Got same targetElementId ${targetElementId}, retrying for a new one`); + targetElementId2 = await extension.openAndCloseMenu("#editabletext"); + } + Assert.notEqual(targetElementId, targetElementId2, "targetElementId differ"); await extension.checkIsValid( targetElementId2, diff --git a/browser/components/extensions/test/browser/browser_ext_mousewheel_zoom.js b/browser/components/extensions/test/browser/browser_ext_mousewheel_zoom.js index 9c5f447a76..1c9224ca00 100644 --- a/browser/components/extensions/test/browser/browser_ext_mousewheel_zoom.js +++ b/browser/components/extensions/test/browser/browser_ext_mousewheel_zoom.js @@ -120,7 +120,7 @@ async function test_mousewheel_zoom(test) { const sidebar = document.getElementById("sidebar-box"); ok(!sidebar.hidden, "Sidebar box is visible"); - browser = SidebarUI.browser.contentWindow.gBrowser.selectedBrowser; + browser = SidebarController.browser.contentWindow.gBrowser.selectedBrowser; } else if (test == TESTS.BROWSER_ACTION) { browser = await openBrowserActionPanel(extension, undefined, true); } else if (test == TESTS.PAGE_ACTION) { diff --git a/browser/components/extensions/test/browser/browser_ext_openPanel.js b/browser/components/extensions/test/browser/browser_ext_openPanel.js index ed96bf2520..c56c0a31b6 100644 --- a/browser/components/extensions/test/browser/browser_ext_openPanel.js +++ b/browser/components/extensions/test/browser/browser_ext_openPanel.js @@ -136,16 +136,16 @@ add_task(async function test_openPopup_requires_user_interaction() { {}, gBrowser.selectedBrowser ); - await TestUtils.waitForCondition(() => !SidebarUI.isOpen); + await TestUtils.waitForCondition(() => !SidebarController.isOpen); await click("#toggleSidebarAction"); - await TestUtils.waitForCondition(() => SidebarUI.isOpen); + await TestUtils.waitForCondition(() => SidebarController.isOpen); await BrowserTestUtils.synthesizeMouseAtCenter( "#toggleSidebarAction", {}, gBrowser.selectedBrowser ); - await TestUtils.waitForCondition(() => !SidebarUI.isOpen); + await TestUtils.waitForCondition(() => !SidebarController.isOpen); BrowserTestUtils.removeTab(gBrowser.selectedTab); await extension.unload(); diff --git a/browser/components/extensions/test/browser/browser_ext_originControls.js b/browser/components/extensions/test/browser/browser_ext_originControls.js index 176eef08bc..6bcae3ef8f 100644 --- a/browser/components/extensions/test/browser/browser_ext_originControls.js +++ b/browser/components/extensions/test/browser/browser_ext_originControls.js @@ -20,6 +20,12 @@ const l10n = new Localization( true ); +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.originControls.grantByDefault", false]], + }); +}); + async function makeExtension({ useAddonManager = "temporary", manifest_version = 3, diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js b/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js index fd589acdbd..71f13a4691 100644 --- a/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_show_matches.js @@ -268,10 +268,14 @@ add_task(async function test_pageAction_restrictScheme_false() { }, }, background: function () { - browser.tabs.onUpdated.addListener(async (tabId, changeInfo) => { + let articleReady = Promise.withResolvers(); + browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { if (changeInfo.url && changeInfo.url.startsWith("about:reader")) { browser.test.sendMessage("readerModeEntered"); } + if (tab.isArticle && tab.url.includes("/readerModeArticle.html")) { + articleReady.resolve(); + } }); browser.test.onMessage.addListener(async msg => { @@ -280,8 +284,20 @@ add_task(async function test_pageAction_restrictScheme_false() { return; } + browser.test.log("Waiting for tab.isArticle to be true"); + await articleReady.promise; + browser.test.log("Toggling reader mode"); browser.tabs.toggleReaderMode(); }); + + browser.tabs.query( + { url: "*://example.com/*/readerModeArticle.html" }, + tabs => { + if (tabs[0].isArticle) { + articleReady.resolve(); + } + } + ); }, }); diff --git a/browser/components/extensions/test/browser/browser_ext_popup_select_in_oopif.js b/browser/components/extensions/test/browser/browser_ext_popup_select_in_oopif.js index fa2c414047..bdc5145dc5 100644 --- a/browser/components/extensions/test/browser/browser_ext_popup_select_in_oopif.js +++ b/browser/components/extensions/test/browser/browser_ext_popup_select_in_oopif.js @@ -70,6 +70,8 @@ add_task(async function testPopupSelectPopup() { }); const selectRect = await SpecialPowers.spawn(iframe, [], async () => { + await SpecialPowers.contentTransformsReceived(content.window); + await ContentTaskUtils.waitForCondition(() => { return content.document.querySelector("select"); }); diff --git a/browser/components/extensions/test/browser/browser_ext_runtime_getContexts.js b/browser/components/extensions/test/browser/browser_ext_runtime_getContexts.js new file mode 100644 index 0000000000..4d28cd5f87 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_runtime_getContexts.js @@ -0,0 +1,597 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +loadTestSubscript("head_devtools.js"); + +async function genericChecker() { + const params = new URLSearchParams(window.location.search); + const kind = params.get("kind"); + + browser.test.onMessage.addListener(async (msg, ...args) => { + if (msg == `${kind}-get-contexts-invalid-params`) { + browser.test.assertThrows( + () => browser.runtime.getContexts({ unknownParamName: true }), + /Type error for parameter filter \(Unexpected property "unknownParamName"\)/, + "Got the expected error on unexpected filter property" + ); + browser.test.sendMessage(`${msg}:done`); + } else if (msg == `${kind}-get-contexts`) { + const filter = args[0]; + try { + const result = await browser.runtime.getContexts(filter); + browser.test.sendMessage(`${msg}:result`, result); + } catch (err) { + // In case of unexpected errors, log a failure and let the test + // to continue to avoid it to only fail after timing out. + browser.test.fail(`browser.runtime.getContexts call rejected: ${err}`); + browser.test.sendMessage(`${msg}:result`, []); + } + } else if (msg == `${kind}-history-push-state`) { + const pushStateURL = args[0]; + window.history.pushState({}, "", pushStateURL); + browser.test.sendMessage(`${msg}:done`); + } else if (msg == `${kind}-create-iframe`) { + const iframeUrl = args[0]; + const iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + document.body.appendChild(iframe); + } else if (msg == `${kind}-open-options-page`) { + browser.runtime.openOptionsPage(); + } + }); + + if (kind === "devtools-page") { + await browser.devtools.panels.create( + "Test DevTool Panel", + "fake-icon.png", + "page.html?kind=devtools-panel" + ); + } + + browser.test.log(`${kind} extension page loaded`); + browser.test.sendMessage(`${kind}-loaded`); +} + +async function triggerActionPopup(extension, win, callback) { + // Window needs focus to open popups. + await focusWindow(win); + await clickBrowserAction(extension, win); + let browser = await awaitExtensionPanel(extension, win); + + await callback(); + + let { unloadPromise } = await promiseBrowserContentUnloaded(browser); + closeBrowserAction(extension, win); + await unloadPromise; +} + +const byWindowId = (a, b) => a.windowId - b.windowId; +const byTabId = (a, b) => a.tabId - b.tabId; +const byFrameId = (a, b) => a.frameId - b.frameId; +const byContextType = (a, b) => a.contextType.localeCompare(b.contextType); + +const assertValidContextId = contextId => { + Assert.equal( + typeof contextId, + "string", + "contextId should be set to a string" + ); + Assert.notEqual( + contextId.length, + 0, + "contextId should be set to a non-zero length string" + ); +}; + +const assertGetContextsResult = ( + actual, + expected, + msg, + { assertContextId = false } = {} +) => { + const actualCopy = assertContextId ? actual : actual.map(it => ({ ...it })); + if (!assertContextId) { + actualCopy.forEach(it => delete it.contextId); + } + for (let [idx, expectedProps] of expected.entries()) { + Assert.deepEqual(actualCopy[idx], expectedProps, msg); + } + Assert.equal( + actualCopy.length, + expected.length, + "Got the expected number of extension contexts" + ); +}; + +add_task(async function test_runtime_getContexts() { + const EXT_ID = "runtime-getContexts@mochitest"; + let extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", // To automatically show sidebar on load. + incognitoOverride: "spanning", + manifest: { + manifest_version: 3, + browser_specific_settings: { gecko: { id: EXT_ID } }, + + action: { + default_popup: "page.html?kind=action", + default_area: "navbar", + }, + + sidebar_action: { + default_panel: "page.html?kind=sidebar", + }, + + options_ui: { + page: "page.html?kind=options", + }, + + devtools_page: "page.html?kind=devtools-page", + + background: { + page: "page.html?kind=background", + }, + }, + + files: { + "page.html": ` + + + + + + + `, + + "page.js": genericChecker, + }, + }); + + const { + Management: { + global: { tabTracker, windowTracker }, + }, + } = ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs"); + + let firstWin = window; + let secondWin = await BrowserTestUtils.openNewBrowserWindow(); + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + + await extension.startup(); + await extension.awaitMessage("background-loaded"); + + // Expect 3 sidebars (2 non-private and 1 private windows). + await extension.awaitMessage("sidebar-loaded"); + await extension.awaitMessage("sidebar-loaded"); + await extension.awaitMessage("sidebar-loaded"); + + let firstWinId = windowTracker.getId(firstWin); + let secondWinId = windowTracker.getId(secondWin); + let privateWinId = windowTracker.getId(privateWin); + + const getGetContextsResults = async ({ filter, sortBy }) => { + extension.sendMessage("background-get-contexts", filter); + let results = await extension.awaitMessage( + "background-get-contexts:result" + ); + if (sortBy) { + results.sort(sortBy); + } + return results; + }; + + const resolveExtPageUrl = urlPath => + WebExtensionPolicy.getByID(EXT_ID).extension.baseURI.resolve(urlPath); + + const documentOrigin = resolveExtPageUrl("/").slice(0, -1); + + const getExpectedExtensionContext = ({ + contextId, + contextType, + documentUrl, + incognito = false, + frameId = 0, + tabId = -1, + windowId = -1, + }) => { + let props = { + contextType, + documentOrigin, + documentUrl, + incognito, + frameId, + tabId, + windowId, + }; + if (contextId) { + props.contextId = contextId; + } + return props; + }; + + let expected = [ + getExpectedExtensionContext({ + contextType: "BACKGROUND", + documentUrl: resolveExtPageUrl("page.html?kind=background"), + }), + + getExpectedExtensionContext({ + contextType: "SIDE_PANEL", + documentUrl: resolveExtPageUrl("page.html?kind=sidebar"), + windowId: firstWinId, + }), + + getExpectedExtensionContext({ + contextType: "SIDE_PANEL", + documentUrl: resolveExtPageUrl("page.html?kind=sidebar"), + windowId: secondWinId, + }), + + getExpectedExtensionContext({ + contextType: "SIDE_PANEL", + documentUrl: resolveExtPageUrl("page.html?kind=sidebar"), + windowId: privateWinId, + incognito: true, + }), + ].sort(byWindowId); + + info("Test getContexts error on unsupported getContexts filter property"); + extension.sendMessage("background-get-contexts-invalid-params"); + await extension.awaitMessage("background-get-contexts-invalid-params:done"); + + info("Test getContexts with a valid empty filter"); + let actual = await getGetContextsResults({ filter: {}, sortBy: byWindowId }); + + assertGetContextsResult( + actual, + expected, + "Got the expected results from runtime.getContexts (with an empty filter)" + ); + + for (const ctx of actual) { + info(`Validate contextId for context ${ctx.contextType} ${ctx.contextId}`); + assertValidContextId(ctx.contextId); + } + + await BrowserTestUtils.withNewTab( + { + gBrowser: secondWin.gBrowser, + url: resolveExtPageUrl("page.html?kind=tab"), + }, + async browser => { + info("Wait the extension page to be fully loaded in the new tab"); + await extension.awaitMessage("tab-loaded"); + + const tabId = tabTracker.getBrowserData(browser).tabId; + + const expectedTabContext = getExpectedExtensionContext({ + contextType: "TAB", + documentUrl: resolveExtPageUrl("page.html?kind=tab"), + windowId: secondWinId, + tabId, + incognito: false, + }); + + info("Test getContexts with contextTypes TAB filter"); + let actual = await getGetContextsResults({ + filter: { contextTypes: ["TAB"] }, + }); + assertGetContextsResult( + actual, + [expectedTabContext], + "Got the expected results from runtime.getContexts (with contextTypes TAB filter)" + ); + assertValidContextId(actual[0].contextId); + const initialTabContextId = actual[0].contextId; + + info("Test getContexts with contextTypes TabIds filter"); + actual = await getGetContextsResults({ + filter: { tabIds: [tabId] }, + }); + assertGetContextsResult( + actual, + [expectedTabContext], + "Got the expected results from runtime.getContexts (with tabIds filter)" + ); + + info("Test getContexts with contextTypes WindowIds filter"); + actual = await getGetContextsResults({ + filter: { windowIds: [secondWinId] }, + sortBy: byTabId, + }); + assertGetContextsResult( + actual, + [ + expectedTabContext, + expected.find(it => it.windowId === secondWinId), + ].sort(byTabId), + "Got the expected results from runtime.getContexts (with windowIds filter)" + ); + + info("Test getContexts after navigating the tab"); + const newTabURL = resolveExtPageUrl("page.html?kind=tab&navigated=true"); + browser.loadURI(Services.io.newURI(newTabURL), { + triggeringPrincipal: + Services.scriptSecurityManager.getSystemPrincipal(), + }); + await extension.awaitMessage("tab-loaded"); + + actual = await getGetContextsResults({ + filter: { + contextTypes: ["TAB"], + windowIds: [secondWinId], + }, + }); + Assert.equal(actual.length, 1, "Expect 1 tab extension context"); + Assert.equal( + actual[0].documentUrl, + newTabURL, + "Expect documentUrl to match the new loaded url" + ); + Assert.equal(actual[0].frameId, 0, "Got expected frameId"); + Assert.equal( + actual[0].tabId, + expectedTabContext.tabId, + "Got expected tabId" + ); + Assert.notEqual( + actual[0].contextId, + initialTabContextId, + "Expect contextId to change on navigated tab" + ); + } + ); + + await triggerActionPopup(extension, privateWin, async () => { + info("Wait the extension page to be fully loaded in the action popup"); + await extension.awaitMessage("action-loaded"); + + const expectedPopupContext = getExpectedExtensionContext({ + contextType: "POPUP", + documentUrl: resolveExtPageUrl("page.html?kind=action"), + windowId: privateWinId, + tabId: -1, + incognito: true, + }); + + info("Test getContexts with contextTypes POPUP filter"); + let actual = await getGetContextsResults({ + filter: { + contextTypes: ["POPUP"], + }, + }); + assertGetContextsResult( + actual, + [expectedPopupContext], + "Got the expected results from runtime.getContexts (with contextTypes POPUP filter)" + ); + + info("Test getContexts with incognito true filter"); + actual = await getGetContextsResults({ + filter: { incognito: true }, + sortBy: byContextType, + }); + assertGetContextsResult( + actual.sort(byContextType), + [expectedPopupContext, ...expected.filter(it => it.incognito)].sort( + byContextType + ), + "Got the expected results from runtime.getContexts (with contextTypes incognito true filter)" + ); + }); + + info("Test getContexts with existing background iframes"); + extension.sendMessage( + `background-create-iframe`, + resolveExtPageUrl("page.html?kind=background-subframe") + ); + await extension.awaitMessage(`background-subframe-loaded`); + + actual = await getGetContextsResults({ + filter: { contextTypes: ["BACKGROUND"] }, + }); + + Assert.equal( + actual.length, + 2, + "Expect 2 background extension contexts to be found" + ); + const bgTopFrame = actual.find( + it => it.documentUrl === resolveExtPageUrl("page.html?kind=background") + ); + const bgSubFrame = actual.find( + it => + it.documentUrl === resolveExtPageUrl("page.html?kind=background-subframe") + ); + + assertValidContextId(bgTopFrame.contextId); + assertValidContextId(bgSubFrame.contextId); + Assert.notEqual( + bgTopFrame.contextId, + bgSubFrame.contextId, + "Expect background top and sub frame to have different contextIds" + ); + + Assert.equal( + bgTopFrame.frameId, + 0, + "Expect background top frame to have frameId 0" + ); + ok( + typeof bgSubFrame.frameId === "number" && bgSubFrame.frameId > 0, + "Expect background sub frame to have a non zero frameId" + ); + Assert.equal( + bgSubFrame.windowId, + bgSubFrame.windowId, + "Expect background top frame to have same windowId as the top frame" + ); + Assert.equal( + bgSubFrame.tabId, + bgTopFrame.tabId, + "Expect background top frame to have same tabId as the top frame" + ); + + info("Test getContexts with existing sidebars iframes"); + extension.sendMessage( + `sidebar-create-iframe`, + resolveExtPageUrl("page.html?kind=sidebar-subframe") + ); + // Expect 3 sidebar subframe to be created. + await extension.awaitMessage(`sidebar-subframe-loaded`); + await extension.awaitMessage(`sidebar-subframe-loaded`); + await extension.awaitMessage(`sidebar-subframe-loaded`); + + actual = await getGetContextsResults({ + filter: { contextTypes: ["SIDE_PANEL"], windowIds: [firstWinId] }, + sortBy: byFrameId, + }); + Assert.equal( + actual.length, + 2, + "Expect 2 sidebar extension contexts to be found for the first window" + ); + // NOTE: we have already asserted that the sidebar top level frame has frameId 0. + Assert.greater( + actual.find( + it => + it.documentUrl == resolveExtPageUrl("page.html?kind=sidebar-subframe") + )?.frameId, + 0, + "Expect sidebar subframe to have the expected frameId" + ); + // NOTE: we have already asserted that the top level frame have tabId -1. + Assert.equal( + actual[0].tabId, + actual[1].tabId, + "Expect iframe and top level sidebar frame to have the same tabId" + ); + + actual = await getGetContextsResults({ + filter: { contextTypes: ["SIDE_PANEL"], windowIds: [secondWinId] }, + sortBy: byFrameId, + }); + Assert.equal( + actual.length, + 2, + "Expect 2 sidebar extension contexts to be found for the second window" + ); + + actual = await getGetContextsResults({ + filter: { contextTypes: ["SIDE_PANEL"], incognito: true }, + }); + Assert.equal( + actual.length, + 2, + "Expect 2 sidebar extension contexts to be found for private windows" + ); + + info("Test getContexts after background history push state"); + let pushStateURLPath = "/page.html?kind=background&pushedState=1"; + extension.sendMessage("background-history-push-state", pushStateURLPath); + await extension.awaitMessage("background-history-push-state:done"); + + actual = await getGetContextsResults({ + filter: { contextTypes: ["BACKGROUND"], frameIds: [0] }, + }); + Assert.equal( + actual.length, + 1, + "Expect 1 top level background context to be found" + ); + Assert.equal( + actual[0].contextId, + bgTopFrame.contextId, + "Expect top level background contextId to NOT be changed" + ); + Assert.equal( + actual[0].documentUrl, + resolveExtPageUrl(pushStateURLPath), + "Expect top level background documentUrl to change due to history.pushState" + ); + + await BrowserTestUtils.closeWindow(privateWin); + await BrowserTestUtils.closeWindow(secondWin); + + info( + "Test getContexts after opening an options page embedded in an about:addons tab" + ); + await BrowserTestUtils.withNewTab("about:addons", async () => { + extension.sendMessage("background-open-options-page"); + await extension.awaitMessage("options-loaded"); + const { selectedBrowser } = firstWin.gBrowser; + Assert.equal( + selectedBrowser.currentURI.spec, + "about:addons", + "Expect an about:addons tab to be current active tab" + ); + let optionsTabId = tabTracker.getBrowserData(selectedBrowser).tabId; + + actual = await getGetContextsResults({ + filter: { windowIds: [firstWinId], tabIds: [optionsTabId] }, + }); + assertGetContextsResult( + actual, + [ + getExpectedExtensionContext({ + contextType: "TAB", + documentUrl: resolveExtPageUrl("page.html?kind=options"), + windowId: firstWinId, + tabId: optionsTabId, + }), + ], + "Got the expected results from runtime.getContexts for an options_page" + ); + }); + + info("Test getContexts with an extension devtools page and devtools panel"); + await BrowserTestUtils.withNewTab("https://example.com", async () => { + const tab = gBrowser.selectedTab; + const toolbox = await openToolboxForTab(tab); + + info("Wait for the devtools page to be loaded"); + await extension.awaitMessage("devtools-page-loaded"); + + Assert.equal( + toolbox.getAdditionalTools()?.length, + 1, + "Expecte extension devtools panel to be registered" + ); + let panelId = toolbox.getAdditionalTools()[0].id; + await gDevTools.showToolboxForTab(tab, { toolId: panelId }); + + info("Wait for the devtools panel to be loaded"); + await extension.awaitMessage("devtools-panel-loaded"); + + actual = await getGetContextsResults({ filter: {} }); + // Expect the backgrond page and its subframe to still be returned. + Assert.equal( + actual.filter(ctx => ctx.contextType === "BACKGROUND").length, + 2, + "Expect the existing 2 background context types" + ); + // Expect the side_panel page and its subframe to still be returned. + Assert.equal( + actual.filter(ctx => ctx.contextType === "SIDE_PANEL").length, + 2, + "Expect the existing 2 side_panel context types" + ); + // Expect no other context to be listed in the getContexts results + // (devtools page and panel are currently expected to not be + // part of getContexts results). + Assert.deepEqual( + actual.filter( + ctx => !["BACKGROUND", "SIDE_PANEL"].includes(ctx.contextType) + ), + [], + "DevTools page and panel are not listed in getContexts results" + ); + + await closeToolboxForTab(gBrowser.selectedTab); + }); + + await extension.unload(); +}); diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js index 7dd41ecfe9..715cf14458 100644 --- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js +++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js @@ -7,7 +7,7 @@ requestLongerTimeout(2); loadTestSubscript("head_sessions.js"); add_task(async function test_sessions_get_recently_closed() { - async function openAndCloseWindow(url = "http://example.com", tabUrls) { + async function openAndCloseWindow(url = "https://example.com", tabUrls) { let win = await BrowserTestUtils.openNewBrowserWindow(); BrowserTestUtils.startLoadingURIString(win.gBrowser.selectedBrowser, url); await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); @@ -77,13 +77,13 @@ add_task(async function test_sessions_get_recently_closed() { let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, - "http://example.com" + "https://example.com" ); BrowserTestUtils.removeTab(tab); tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, - "http://example.com" + "https://example.com" ); BrowserTestUtils.removeTab(tab); @@ -123,7 +123,7 @@ add_task(async function test_sessions_get_recently_closed_navigated() { .then(recentlyClosed => { let tab = recentlyClosed[0].window.tabs[0]; browser.test.assertEq( - "http://example.com/", + "https://example.com/", tab.url, "Tab in closed window has the expected url." ); @@ -144,7 +144,7 @@ add_task(async function test_sessions_get_recently_closed_navigated() { // Test with a window with navigation history. let win = await BrowserTestUtils.openNewBrowserWindow(); - for (let url of ["about:robots", "about:mozilla", "http://example.com/"]) { + for (let url of ["about:robots", "about:mozilla", "https://example.com/"]) { BrowserTestUtils.startLoadingURIString(win.gBrowser.selectedBrowser, url); await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); } @@ -178,7 +178,7 @@ add_task( "The second tab with empty.xpi has no url field due to empty history." ); browser.test.assertEq( - "http://example.com/", + "https://example.com/", win.tabs[2].url, "The third tab is example.com." ); @@ -195,7 +195,7 @@ add_task( // Test with a window with empty history. let xpi = - "http://example.com/browser/browser/components/extensions/test/browser/empty.xpi"; + "https://example.com/browser/browser/components/extensions/test/browser/empty.xpi"; let newWin = await BrowserTestUtils.openNewBrowserWindow(); await BrowserTestUtils.openNewForegroundTab({ gBrowser: newWin.gBrowser, @@ -205,7 +205,7 @@ add_task( }); await BrowserTestUtils.openNewForegroundTab({ gBrowser: newWin.gBrowser, - url: "http://example.com/", + url: "https://example.com/", }); await BrowserTestUtils.closeWindow(newWin); diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_restoreTab.js b/browser/components/extensions/test/browser/browser_ext_sessions_restoreTab.js index c89e9d39dc..46a925039a 100644 --- a/browser/components/extensions/test/browser/browser_ext_sessions_restoreTab.js +++ b/browser/components/extensions/test/browser/browser_ext_sessions_restoreTab.js @@ -15,7 +15,7 @@ ChromeUtils.defineESModuleGetters(this, { // Check that we can restore a tab modified by an extension. add_task(async function test_restoringModifiedTab() { function background() { - browser.tabs.create({ url: "http://example.com/" }); + browser.tabs.create({ url: "https://example.com/" }); browser.test.onMessage.addListener(msg => { if (msg == "change-tab") { browser.tabs.executeScript({ code: 'location.href += "?changedTab";' }); @@ -32,14 +32,14 @@ add_task(async function test_restoringModifiedTab() { background, }); - const contentScriptTabURL = "http://example.com/?changedTab"; + const contentScriptTabURL = "https://example.com/?changedTab"; let win = await BrowserTestUtils.openNewBrowserWindow({}); // Open and close a tabs. let tabPromise = BrowserTestUtils.waitForNewTab( win.gBrowser, - "http://example.com/", + "https://example.com/", true ); await extension.startup(); diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction.js index 8498f73071..ac2d19cf23 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction.js @@ -7,6 +7,7 @@ requestLongerTimeout(2); let extData = { manifest: { sidebar_action: { + default_icon: "default.png", default_panel: "sidebar.html", }, }, @@ -29,6 +30,9 @@ let extData = { browser.test.sendMessage("sidebar"); }; }, + + "default.png": imageBuffer, + "1.png": imageBuffer, }, background: function () { @@ -44,6 +48,8 @@ let extData = { let { arg = {}, result } = data; let isOpen = await browser.sidebarAction.isOpen(arg); browser.test.assertEq(result, isOpen, "expected value from isOpen"); + } else if (msg === "set-icon") { + await browser.sidebarAction.setIcon({ path: data }); } browser.test.sendMessage("done"); }); @@ -148,7 +154,7 @@ add_task(async function sidebar_isOpen() { info("Test extension1's sidebar is opened on install"); await extension1.awaitMessage("sidebar"); await sendMessage(extension1, "isOpen", { result: true }); - let sidebar1ID = SidebarUI.currentID; + let sidebar1ID = SidebarController.currentID; info("Load extension2"); let extension2 = ExtensionTestUtils.loadExtension(getExtData()); @@ -160,7 +166,7 @@ add_task(async function sidebar_isOpen() { await sendMessage(extension2, "isOpen", { result: true }); info("Switch back to extension1's sidebar"); - SidebarUI.show(sidebar1ID); + SidebarController.show(sidebar1ID); await extension1.awaitMessage("sidebar"); await sendMessage(extension1, "isOpen", { result: true }); await sendMessage(extension2, "isOpen", { result: false }); @@ -195,7 +201,7 @@ add_task(async function sidebar_isOpen() { newWin.close(); info("Close the sidebar in the original window"); - SidebarUI.hide(); + SidebarController.hide(); await sendMessage(extension1, "isOpen", { result: false }); await sendMessage(extension2, "isOpen", { result: false }); @@ -223,11 +229,15 @@ add_task(async function testShortcuts() { async function toggleSwitcherPanel(win = window) { // Open and close the switcher panel to trigger shortcut content rendering. - let switcherPanelShown = promisePopupShown(win.SidebarUI._switcherPanel); - win.SidebarUI.showSwitcherPanel(); + let switcherPanelShown = promisePopupShown( + win.SidebarController._switcherPanel + ); + win.SidebarController.showSwitcherPanel(); await switcherPanelShown; - let switcherPanelHidden = promisePopupHidden(win.SidebarUI._switcherPanel); - win.SidebarUI.hideSwitcherPanel(); + let switcherPanelHidden = promisePopupHidden( + win.SidebarController._switcherPanel + ); + win.SidebarController.hideSwitcherPanel(); await switcherPanelHidden; } @@ -301,3 +311,39 @@ add_task(async function testShortcuts() { await BrowserTestUtils.closeWindow(win); }); + +add_task(async function sidebar_switcher_panel_icon_update() { + info("Load extension"); + const extension = ExtensionTestUtils.loadExtension(getExtData()); + await extension.startup(); + + info("Test extension's sidebar is opened on install"); + await extension.awaitMessage("sidebar"); + await sendMessage(extension, "isOpen", { result: true }); + const sidebarID = SidebarController.currentID; + + const item = SidebarController._switcherPanel.querySelector( + ".webextension-menuitem" + ); + let iconUrl = `moz-extension://${extension.uuid}/default.png`; + is( + item.style.getPropertyValue("--webextension-menuitem-image"), + `image-set(url("${iconUrl}"), url("${iconUrl}") 2x)`, + "Extension has the correct icon." + ); + SidebarController.hide(); + await sendMessage(extension, "isOpen", { result: false }); + + await sendMessage(extension, "set-icon", "1.png"); + await SidebarController.show(sidebarID); + await extension.awaitMessage("sidebar"); + await sendMessage(extension, "isOpen", { result: true }); + iconUrl = `moz-extension://${extension.uuid}/1.png`; + is( + item.style.getPropertyValue("--webextension-menuitem-image"), + `image-set(url("${iconUrl}"), url("${iconUrl}") 2x)`, + "Extension has updated icon." + ); + + await extension.unload(); +}); diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_click.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_click.js index 621d2d1180..a82f6b8186 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_click.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_click.js @@ -54,7 +54,7 @@ add_task(async function test_sidebar_click_isAppTab_behavior() { await extension.awaitMessage("sidebar-ready"); // This test fails if docShell.isAppTab has not been set to true. - let content = SidebarUI.browser.contentWindow; + let content = SidebarController.browser.contentWindow; // Wait for the layout to be flushed, otherwise this test may // fail intermittently if synthesizeMouseAtCenter is being called diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js index 7057037a5e..93d34d4487 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js @@ -124,7 +124,7 @@ async function runTests(options) { }); // Wait for initial sidebar load. - SidebarUI.browser.addEventListener( + SidebarController.browser.addEventListener( "load", async () => { // Wait for the background page listeners to be ready and diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_httpAuth.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_httpAuth.js index d50d96b822..f8ab238148 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_httpAuth.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_httpAuth.js @@ -42,7 +42,7 @@ add_task(async function sidebar_httpAuthPrompt() { // Wait for the http auth prompt and close it with accept button. let promptPromise = PromptTestUtils.handleNextPrompt( - SidebarUI.browser.contentWindow, + SidebarController.browser.contentWindow, { modalType: Services.prompt.MODAL_TYPE_WINDOW, promptType: "promptUserAndPass", diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_incognito.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_incognito.js index 221447cf2e..39269d9d06 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_incognito.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_incognito.js @@ -124,11 +124,14 @@ add_task(async function test_sidebarAction_not_allowed() { await extension.startup(); let sidebarID = `${makeWidgetId(extension.id)}-sidebar-action`; - ok(SidebarUI.sidebars.has(sidebarID), "sidebar exists in non-private window"); + ok( + SidebarController.sidebars.has(sidebarID), + "sidebar exists in non-private window" + ); let winData = await getIncognitoWindow(); - let hasSidebar = winData.win.SidebarUI.sidebars.has(sidebarID); + let hasSidebar = winData.win.SidebarController.sidebars.has(sidebarID); ok(!hasSidebar, "sidebar does not exist in private window"); // Test API access to private window data. extension.sendMessage(winData.details); diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_runtime.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_runtime.js index 55c83ee0b1..8ac1dd76a1 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_runtime.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_runtime.js @@ -52,9 +52,10 @@ add_task(async function test_sidebar_disconnect() { await connected; // Bug 1445080 fixes currentURI, test to avoid future breakage. - let currentURI = window.SidebarUI.browser.contentDocument.getElementById( - "webext-panels-browser" - ).currentURI; + let currentURI = + window.SidebarController.browser.contentDocument.getElementById( + "webext-panels-browser" + ).currentURI; is(currentURI.scheme, "moz-extension", "currentURI is set correctly"); // switching sidebar to another extension @@ -68,7 +69,7 @@ add_task(async function test_sidebar_disconnect() { // switching sidebar to built-in sidebar let disconnected = extension2.awaitMessage("disconnected"); - window.SidebarUI.show("viewBookmarksSidebar"); + window.SidebarController.show("viewBookmarksSidebar"); await disconnected; await extension.unload(); diff --git a/browser/components/extensions/test/browser/browser_ext_sidebarAction_windows.js b/browser/components/extensions/test/browser/browser_ext_sidebarAction_windows.js index 58f2b07797..c8f3f043ec 100644 --- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_windows.js +++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_windows.js @@ -49,7 +49,7 @@ add_task(async function sidebar_windows() { let secondSidebar = extension.awaitMessage("sidebar"); - // SidebarUI relies on window.opener being set, which is normal behavior when + // SidebarController relies on window.opener being set, which is normal behavior when // using menu or key commands to open a new browser window. let win = await BrowserTestUtils.openNewBrowserWindow(); diff --git a/browser/components/extensions/test/browser/browser_unified_extensions.js b/browser/components/extensions/test/browser/browser_unified_extensions.js index 2c65f47c4e..b83d4c2e23 100644 --- a/browser/components/extensions/test/browser/browser_unified_extensions.js +++ b/browser/components/extensions/test/browser/browser_unified_extensions.js @@ -44,6 +44,9 @@ add_setup(async function () { // panel, which could happen when a previous test file resizes the current // window. await ensureMaximizedWindow(window); + await SpecialPowers.pushPrefEnv({ + set: [["extensions.originControls.grantByDefault", false]], + }); }); add_task(async function test_button_enabled_by_pref() { diff --git a/browser/components/extensions/test/browser/browser_unified_extensions_accessibility.js b/browser/components/extensions/test/browser/browser_unified_extensions_accessibility.js index 44d861d97c..8439cf30dd 100644 --- a/browser/components/extensions/test/browser/browser_unified_extensions_accessibility.js +++ b/browser/components/extensions/test/browser/browser_unified_extensions_accessibility.js @@ -5,6 +5,12 @@ loadTestSubscript("head_unified_extensions.js"); +add_setup(async () => { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.originControls.grantByDefault", false]], + }); +}); + add_task(async function test_keyboard_navigation_activeScript() { const extension1 = ExtensionTestUtils.loadExtension({ manifest: { diff --git a/browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js b/browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js index d04d85e535..a7bdc8273c 100644 --- a/browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js +++ b/browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js @@ -5,10 +5,6 @@ requestLongerTimeout(2); -ChromeUtils.defineESModuleGetters(this, { - AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs", -}); - const { EnterprisePolicyTesting } = ChromeUtils.importESModule( "resource://testing-common/EnterprisePolicyTesting.sys.mjs" ); @@ -31,21 +27,6 @@ const promiseExtensionUninstalled = extensionId => { }); }; -function waitClosedWindow(win) { - return new Promise(resolve => { - function onWindowClosed() { - if (win && !win.closed) { - // If a specific window reference has been passed, then check - // that the window is closed before resolving the promise. - return; - } - Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed"); - resolve(); - } - Services.obs.addObserver(onWindowClosed, "xul-window-destroyed"); - }); -} - function assertVisibleContextMenuItems(contextMenu, expected) { let visibleItems = contextMenu.querySelectorAll( ":is(menuitem, menuseparator):not([hidden])" @@ -366,90 +347,33 @@ add_task(async function test_report_extension() { // closed and about:addons is open with the "abuse report dialog". const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); - if (AbuseReporter.amoFormEnabled) { - const reportURL = Services.urlFormatter - .formatURLPref("extensions.abuseReport.amoFormURL") - .replace("%addonID%", extension.id); - - const promiseReportTab = BrowserTestUtils.waitForNewTab( - gBrowser, - reportURL, - /* waitForLoad */ false, - // Do not expect it to be the next tab opened - /* waitForAnyTab */ true - ); - contextMenu.activateItem(reportButton); - const [reportTab] = await Promise.all([promiseReportTab, hidden]); - // Remove the report tab and expect the selected tab - // to become the about:addons tab. - BrowserTestUtils.removeTab(reportTab); - if (AbuseReporter.amoFormEnabled) { - is( - gBrowser.selectedBrowser.currentURI.spec, - "about:blank", - "Expect about:addons tab to have not been opened (amoFormEnabled=true)" - ); - } else { - is( - gBrowser.selectedBrowser.currentURI.spec, - "about:addons", - "Got about:addons tab selected (amoFormEnabled=false)" - ); - } - return; - } + const reportURL = Services.urlFormatter + .formatURLPref("extensions.abuseReport.amoFormURL") + .replace("%addonID%", extension.id); - const abuseReportOpen = BrowserTestUtils.waitForCondition( - () => AbuseReporter.getOpenDialog(), - "wait for the abuse report dialog to have been opened" + const promiseReportTab = BrowserTestUtils.waitForNewTab( + gBrowser, + reportURL, + /* waitForLoad */ false, + // Do not expect it to be the next tab opened + /* waitForAnyTab */ true ); contextMenu.activateItem(reportButton); - const [reportDialogWindow] = await Promise.all([abuseReportOpen, hidden]); - - const reportDialogParams = - reportDialogWindow.arguments[0].wrappedJSObject; - is( - reportDialogParams.report.addon.id, - extension.id, - "abuse report dialog has the expected addon id" - ); + const [reportTab] = await Promise.all([promiseReportTab, hidden]); + // Remove the report tab and expect the selected tab + // to become the about:addons tab. + BrowserTestUtils.removeTab(reportTab); is( - reportDialogParams.report.reportEntryPoint, - "unified_context_menu", - "abuse report dialog has the expected reportEntryPoint" + gBrowser.selectedBrowser.currentURI.spec, + "about:blank", + "Expect about:addons tab to have not been opened" ); - - let promiseClosedWindow = waitClosedWindow(); - reportDialogWindow.close(); - // Wait for the report dialog window to be completely closed - // (to prevent an intermittent failure due to a race between - // the dialog window being closed and the test tasks that follows - // opening the unified extensions button panel to not lose the - // focus and be suddently closed before the task has done with - // its assertions, see Bug 1782304). - await promiseClosedWindow; }); } const [ext] = createExtensions([{ name: "an extension" }]); await ext.startup(); - - info("Test report with amoFormEnabled=true"); - - await SpecialPowers.pushPrefEnv({ - set: [["extensions.abuseReport.amoFormEnabled", true]], - }); await runReportTest(ext); - await SpecialPowers.popPrefEnv(); - - info("Test report with amoFormEnabled=false"); - - await SpecialPowers.pushPrefEnv({ - set: [["extensions.abuseReport.amoFormEnabled", false]], - }); - await runReportTest(ext); - await SpecialPowers.popPrefEnv(); - await ext.unload(); }); diff --git a/browser/components/extensions/test/browser/head.js b/browser/components/extensions/test/browser/head.js index 344eb3cca7..2d6835a034 100644 --- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -31,6 +31,7 @@ * loadTestSubscript awaitBrowserLoaded * getScreenAt roundCssPixcel getCssAvailRect isRectContained * getToolboxBackgroundColor + * promiseBrowserContentUnloaded */ // There are shutdown issues for which multiple rejections are left uncaught. @@ -167,6 +168,44 @@ function promiseAnimationFrame(win = window) { return AppUiTestInternals.promiseAnimationFrame(win); } +async function promiseBrowserContentUnloaded(browser) { + // Wait until the content has unloaded before resuming the test, to avoid + // calling extension.getViews too early (and having intermittent failures). + const MSG_WINDOW_DESTROYED = "Test:BrowserContentDestroyed"; + let unloadPromise = new Promise(resolve => { + Services.ppmm.addMessageListener(MSG_WINDOW_DESTROYED, function listener() { + Services.ppmm.removeMessageListener(MSG_WINDOW_DESTROYED, listener); + resolve(); + }); + }); + + await ContentTask.spawn( + browser, + MSG_WINDOW_DESTROYED, + MSG_WINDOW_DESTROYED => { + let innerWindowId = this.content.windowGlobalChild.innerWindowId; + let observer = subject => { + if ( + innerWindowId === subject.QueryInterface(Ci.nsISupportsPRUint64).data + ) { + Services.obs.removeObserver(observer, "inner-window-destroyed"); + + // Use process message manager to ensure that the message is delivered + // even after the 's message manager is disconnected. + Services.cpmm.sendAsyncMessage(MSG_WINDOW_DESTROYED); + } + }; + // Observe inner-window-destroyed, like ExtensionPageChild, to ensure that + // the ExtensionPageContextChild instance has been unloaded when we resolve + // the unloadPromise. + Services.obs.addObserver(observer, "inner-window-destroyed"); + } + ); + + // Return an object so that callers can use "await". + return { unloadPromise }; +} + function promisePopupHidden(popup) { return new Promise(resolve => { let onPopupHidden = () => { @@ -437,10 +476,11 @@ async function openContextMenuInPopup( } async function openContextMenuInSidebar(selector = "body") { - let contentAreaContextMenu = SidebarUI.browser.contentDocument.getElementById( - "contentAreaContextMenu" - ); - let browser = SidebarUI.browser.contentDocument.getElementById( + let contentAreaContextMenu = + SidebarController.browser.contentDocument.getElementById( + "contentAreaContextMenu" + ); + let browser = SidebarController.browser.contentDocument.getElementById( "webext-panels-browser" ); let popupShownPromise = BrowserTestUtils.waitForEvent( @@ -452,7 +492,9 @@ async function openContextMenuInSidebar(selector = "body") { // fail intermittently if synthesizeMouseAtCenter is being called // while the sidebar is still opening and the browser window layout // being recomputed. - await SidebarUI.browser.contentWindow.promiseDocumentFlushed(() => {}); + await SidebarController.browser.contentWindow.promiseDocumentFlushed( + () => {} + ); info("Opening context menu in sidebarAction panel"); await BrowserTestUtils.synthesizeMouseAtCenter( -- cgit v1.2.3