summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js')
-rw-r--r--browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js938
1 files changed, 938 insertions, 0 deletions
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
new file mode 100644
index 0000000000..cf43401c0c
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js
@@ -0,0 +1,938 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs",
+});
+
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+loadTestSubscript("head_unified_extensions.js");
+
+// We expect this rejection when the abuse report dialog window is
+// being forcefully closed as part of the related test task.
+PromiseTestUtils.allowMatchingRejectionsGlobally(/report dialog closed/);
+
+const promiseExtensionUninstalled = extensionId => {
+ return new Promise(resolve => {
+ let listener = {};
+ listener.onUninstalled = addon => {
+ if (addon.id == extensionId) {
+ AddonManager.removeAddonListener(listener);
+ resolve();
+ }
+ };
+ AddonManager.addAddonListener(listener);
+ });
+};
+
+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])"
+ );
+ is(visibleItems.length, expected, `expected ${expected} visible menu items`);
+}
+
+function assertOrderOfWidgetsInPanel(extensions, win = window) {
+ const widgetIds = CustomizableUI.getWidgetIdsInArea(
+ CustomizableUI.AREA_ADDONS
+ ).filter(
+ widgetId => !!CustomizableUI.getWidget(widgetId).forWindow(win).node
+ );
+ const widgetIdsFromExtensions = extensions.map(ext =>
+ AppUiTestInternals.getBrowserActionWidgetId(ext.id)
+ );
+
+ Assert.deepEqual(
+ widgetIds,
+ widgetIdsFromExtensions,
+ "expected extensions to be ordered"
+ );
+}
+
+async function moveWidgetUp(extension, win = window) {
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id, win);
+ const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.activateItem(
+ contextMenu.querySelector(".unified-extensions-context-menu-move-widget-up")
+ );
+ await hidden;
+}
+
+async function moveWidgetDown(extension, win = window) {
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id, win);
+ const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.activateItem(
+ contextMenu.querySelector(
+ ".unified-extensions-context-menu-move-widget-down"
+ )
+ );
+ await hidden;
+}
+
+async function pinToToolbar(extension, win = window) {
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id, win);
+ const pinToToolbarItem = contextMenu.querySelector(
+ ".unified-extensions-context-menu-pin-to-toolbar"
+ );
+ ok(pinToToolbarItem, "expected 'pin to toolbar' menu item");
+
+ const hidden = BrowserTestUtils.waitForEvent(
+ win.gUnifiedExtensions.panel,
+ "popuphidden",
+ true
+ );
+ contextMenu.activateItem(pinToToolbarItem);
+ await hidden;
+}
+
+async function assertMoveContextMenuItems(
+ ext,
+ { expectMoveUpHidden, expectMoveDownHidden, expectOrder },
+ win = window
+) {
+ const extName = WebExtensionPolicy.getByID(ext.id).name;
+ info(`Assert Move context menu items visibility for ${extName}`);
+ const contextMenu = await openUnifiedExtensionsContextMenu(ext.id, win);
+ const moveUp = contextMenu.querySelector(
+ ".unified-extensions-context-menu-move-widget-up"
+ );
+ const moveDown = contextMenu.querySelector(
+ ".unified-extensions-context-menu-move-widget-down"
+ );
+ ok(moveUp, "expected 'move up' item in the context menu");
+ ok(moveDown, "expected 'move down' item in the context menu");
+
+ is(
+ BrowserTestUtils.is_hidden(moveUp),
+ expectMoveUpHidden,
+ `expected 'move up' item to be ${expectMoveUpHidden ? "hidden" : "visible"}`
+ );
+ is(
+ BrowserTestUtils.is_hidden(moveDown),
+ expectMoveDownHidden,
+ `expected 'move down' item to be ${
+ expectMoveDownHidden ? "hidden" : "visible"
+ }`
+ );
+ const expectedVisibleItems =
+ 5 + (+(expectMoveUpHidden ? 0 : 1) + (expectMoveDownHidden ? 0 : 1));
+ assertVisibleContextMenuItems(contextMenu, expectedVisibleItems);
+ if (expectOrder) {
+ assertOrderOfWidgetsInPanel(expectOrder, win);
+ }
+ await closeChromeContextMenu(contextMenu.id, null, win);
+}
+
+add_task(async function test_context_menu() {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel.
+ await openExtensionsPanel();
+
+ // Get the menu button of the extension and verify the mouseover/mouseout
+ // behavior. We expect a help message (in the message deck) to be selected
+ // (and therefore displayed) when the menu button is hovered/focused.
+ const item = getUnifiedExtensionsItem(extension.id);
+ ok(item, "expected an item for the extension");
+
+ const messageDeck = item.querySelector(
+ ".unified-extensions-item-message-deck"
+ );
+ ok(messageDeck, "expected message deck");
+ is(
+ messageDeck.selectedIndex,
+ gUnifiedExtensions.MESSAGE_DECK_INDEX_DEFAULT,
+ "expected selected message in the deck to be the default message"
+ );
+
+ const hoverMenuButtonMessage = item.querySelector(
+ ".unified-extensions-item-message-hover-menu-button"
+ );
+ Assert.deepEqual(
+ document.l10n.getAttributes(hoverMenuButtonMessage),
+ { id: "unified-extensions-item-message-manage", args: null },
+ "expected correct l10n attributes for the hover message"
+ );
+
+ const menuButton = item.querySelector(".unified-extensions-item-menu-button");
+ ok(menuButton, "expected menu button");
+
+ let hovered = BrowserTestUtils.waitForEvent(menuButton, "mouseover");
+ EventUtils.synthesizeMouseAtCenter(menuButton, { type: "mouseover" });
+ await hovered;
+ is(
+ messageDeck.selectedIndex,
+ gUnifiedExtensions.MESSAGE_DECK_INDEX_MENU_HOVER,
+ "expected selected message in the deck to be the message when hovering the menu button"
+ );
+
+ let notHovered = BrowserTestUtils.waitForEvent(menuButton, "mouseout");
+ // Move mouse somewhere else...
+ EventUtils.synthesizeMouseAtCenter(item, { type: "mouseover" });
+ await notHovered;
+ is(
+ messageDeck.selectedIndex,
+ gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER,
+ "expected selected message in the deck to be the hover message"
+ );
+
+ // Open the context menu for the extension.
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+ const doc = contextMenu.ownerDocument;
+
+ const manageButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-manage-extension"
+ );
+ ok(manageButton, "expected manage button");
+ is(manageButton.hidden, false, "expected manage button to be visible");
+ is(manageButton.disabled, false, "expected manage button to be enabled");
+ Assert.deepEqual(
+ doc.l10n.getAttributes(manageButton),
+ { id: "unified-extensions-context-menu-manage-extension", args: null },
+ "expected correct l10n attributes for manage button"
+ );
+
+ const removeButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-remove-extension"
+ );
+ ok(removeButton, "expected remove button");
+ is(removeButton.hidden, false, "expected remove button to be visible");
+ is(removeButton.disabled, false, "expected remove button to be enabled");
+ Assert.deepEqual(
+ doc.l10n.getAttributes(removeButton),
+ { id: "unified-extensions-context-menu-remove-extension", args: null },
+ "expected correct l10n attributes for remove button"
+ );
+
+ const reportButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-report-extension"
+ );
+ ok(reportButton, "expected report button");
+ is(reportButton.hidden, false, "expected report button to be visible");
+ is(reportButton.disabled, false, "expected report button to be enabled");
+ Assert.deepEqual(
+ doc.l10n.getAttributes(reportButton),
+ { id: "unified-extensions-context-menu-report-extension", args: null },
+ "expected correct l10n attributes for report button"
+ );
+
+ await closeChromeContextMenu(contextMenu.id, null);
+ await closeExtensionsPanel();
+
+ await extension.unload();
+});
+
+add_task(
+ async function test_context_menu_report_button_hidden_when_abuse_report_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.abuseReport.enabled", false]],
+ });
+
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel, then open the contextMenu for the extension.
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const reportButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-report-extension"
+ );
+ ok(reportButton, "expected report button");
+ is(reportButton.hidden, true, "expected report button to be hidden");
+
+ await closeChromeContextMenu(contextMenu.id, null);
+ await closeExtensionsPanel();
+
+ await extension.unload();
+ }
+);
+
+add_task(
+ async function test_context_menu_remove_button_disabled_when_extension_cannot_be_uninstalled() {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ Extensions: {
+ Locked: [extension.id],
+ },
+ },
+ });
+
+ // Open the extension panel, then open the context menu for the extension.
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const removeButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-remove-extension"
+ );
+ ok(removeButton, "expected remove button");
+ is(removeButton.disabled, true, "expected remove button to be disabled");
+
+ await closeChromeContextMenu(contextMenu.id, null);
+ await closeExtensionsPanel();
+
+ await extension.unload();
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson("");
+ }
+);
+
+add_task(async function test_manage_extension() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:robots" },
+ async () => {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel, then open the context menu for the extension.
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const manageButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-manage-extension"
+ );
+ ok(manageButton, "expected manage button");
+
+ // Click the "manage extension" context menu item, and wait until the menu is
+ // closed and about:addons is open.
+ const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ const aboutAddons = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:addons",
+ true
+ );
+ contextMenu.activateItem(manageButton);
+ const [aboutAddonsTab] = await Promise.all([aboutAddons, hidden]);
+
+ // Close the tab containing about:addons because we don't need it anymore.
+ BrowserTestUtils.removeTab(aboutAddonsTab);
+
+ await extension.unload();
+ }
+ );
+});
+
+add_task(async function test_report_extension() {
+ SpecialPowers.pushPrefEnv({
+ set: [["extensions.abuseReport.enabled", true]],
+ });
+
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ // Open the extension panel, then open the context menu for the extension.
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const reportButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-report-extension"
+ );
+ ok(reportButton, "expected report button");
+
+ // Click the "report extension" context menu item, and wait until the menu is
+ // closed and about:addons is open with the "abuse report dialog".
+ const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ const abuseReportOpen = BrowserTestUtils.waitForCondition(
+ () => AbuseReporter.getOpenDialog(),
+ "wait for the abuse report dialog to have been opened"
+ );
+ 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"
+ );
+ is(
+ reportDialogParams.report.reportEntryPoint,
+ "unified_context_menu",
+ "abuse report dialog has the expected reportEntryPoint"
+ );
+
+ 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;
+ });
+
+ await extension.unload();
+});
+
+add_task(async function test_remove_extension() {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel, then open the context menu for the extension.
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const removeButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-remove-extension"
+ );
+ ok(removeButton, "expected remove button");
+
+ // Set up a mock prompt service that returns 0 to indicate that the user
+ // pressed the OK button.
+ const { prompt } = Services;
+ const promptService = {
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+ confirmEx() {
+ return 0;
+ },
+ };
+ Services.prompt = promptService;
+ registerCleanupFunction(() => {
+ Services.prompt = prompt;
+ });
+
+ // Click the "remove extension" context menu item, and wait until the menu is
+ // closed and the extension is uninstalled.
+ const uninstalled = promiseExtensionUninstalled(extension.id);
+ const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.activateItem(removeButton);
+ await Promise.all([uninstalled, hidden]);
+
+ await extension.unload();
+ // Restore prompt service.
+ Services.prompt = prompt;
+});
+
+add_task(async function test_remove_extension_cancelled() {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel, then open the context menu for the extension.
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const removeButton = contextMenu.querySelector(
+ ".unified-extensions-context-menu-remove-extension"
+ );
+ ok(removeButton, "expected remove button");
+
+ // Set up a mock prompt service that returns 1 to indicate that the user
+ // refused to uninstall the extension.
+ const { prompt } = Services;
+ const promptService = {
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+ confirmEx() {
+ return 1;
+ },
+ };
+ Services.prompt = promptService;
+ registerCleanupFunction(() => {
+ Services.prompt = prompt;
+ });
+
+ // Click the "remove extension" context menu item, and wait until the menu is
+ // closed.
+ const hidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.activateItem(removeButton);
+ await hidden;
+
+ // Re-open the panel to make sure the extension is still there.
+ await openExtensionsPanel();
+ const item = getUnifiedExtensionsItem(extension.id);
+ is(
+ item.querySelector(".unified-extensions-item-name").textContent,
+ "an extension",
+ "expected extension to still be listed"
+ );
+ await closeExtensionsPanel();
+
+ await extension.unload();
+ // Restore prompt service.
+ Services.prompt = prompt;
+});
+
+add_task(async function test_open_context_menu_on_click() {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel.
+ await openExtensionsPanel();
+
+ const button = getUnifiedExtensionsItem(extension.id).querySelector(
+ ".unified-extensions-item-menu-button"
+ );
+ ok(button, "expected menu button");
+
+ const contextMenu = document.getElementById(
+ "unified-extensions-context-menu"
+ );
+ ok(contextMenu, "expected menu");
+
+ // Open the context menu with a "right-click".
+ const shown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(button, { type: "contextmenu" });
+ await shown;
+
+ await closeChromeContextMenu(contextMenu.id, null);
+ await closeExtensionsPanel();
+
+ await extension.unload();
+});
+
+add_task(async function test_open_context_menu_with_keyboard() {
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel.
+ await openExtensionsPanel();
+
+ const button = getUnifiedExtensionsItem(extension.id).querySelector(
+ ".unified-extensions-item-menu-button"
+ );
+ ok(button, "expected menu button");
+ // Make this button focusable because those (toolbar) buttons are only made
+ // focusable when a user is navigating with the keyboard, which isn't exactly
+ // what we are doing in this test.
+ button.setAttribute("tabindex", "-1");
+
+ const contextMenu = document.getElementById(
+ "unified-extensions-context-menu"
+ );
+ ok(contextMenu, "expected menu");
+
+ // Open the context menu by focusing the button and pressing the SPACE key.
+ let shown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ button.focus();
+ is(button, document.activeElement, "expected button to be focused");
+ EventUtils.synthesizeKey(" ", {});
+ await shown;
+
+ await closeChromeContextMenu(contextMenu.id, null);
+
+ if (AppConstants.platform != "macosx") {
+ // Open the context menu by focusing the button and pressing the ENTER key.
+ // TODO(emilio): Maybe we should harmonize this behavior across platforms,
+ // we're inconsistent right now.
+ shown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ button.focus();
+ is(button, document.activeElement, "expected button to be focused");
+ EventUtils.synthesizeKey("KEY_Enter", {});
+ await shown;
+ await closeChromeContextMenu(contextMenu.id, null);
+ }
+
+ await closeExtensionsPanel();
+
+ await extension.unload();
+});
+
+add_task(async function test_context_menu_without_browserActionFor_global() {
+ const { ExtensionParent } = ChromeUtils.importESModule(
+ "resource://gre/modules/ExtensionParent.sys.mjs"
+ );
+ const { browserActionFor } = ExtensionParent.apiManager.global;
+ const cleanup = () => {
+ ExtensionParent.apiManager.global.browserActionFor = browserActionFor;
+ };
+ registerCleanupFunction(cleanup);
+ // This is needed to simulate the case where the browserAction API hasn't
+ // been loaded yet (since it is lazy-loaded). That could happen when only
+ // extensions without browser actions are installed. In which case, the
+ // `global.browserActionFor()` function would not be defined yet.
+ delete ExtensionParent.apiManager.global.browserActionFor;
+
+ const [extension] = createExtensions([{ name: "an extension" }]);
+ await extension.startup();
+
+ // Open the extension panel and then the context menu for the extension that
+ // has been loaded above. We expect the context menu to be displayed and no
+ // error caused by the lack of `global.browserActionFor()`.
+ await openExtensionsPanel();
+ // This promise rejects with an error if the implementation does not handle
+ // the case where `global.browserActionFor()` is undefined.
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+ assertVisibleContextMenuItems(contextMenu, 3);
+
+ await closeChromeContextMenu(contextMenu.id, null);
+ await closeExtensionsPanel();
+
+ await extension.unload();
+
+ cleanup();
+});
+
+add_task(async function test_page_action_context_menu() {
+ const extWithMenuPageAction = ExtensionTestUtils.loadExtension({
+ manifest: {
+ page_action: {},
+ permissions: ["contextMenus"],
+ },
+ background() {
+ browser.contextMenus.create(
+ {
+ id: "some-menu-id",
+ title: "Click me!",
+ contexts: ["all"],
+ },
+ () => browser.test.sendMessage("menu-created")
+ );
+ },
+ useAddonManager: "temporary",
+ });
+ const extWithoutMenu1 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name: "extension without any menu",
+ },
+ useAddonManager: "temporary",
+ });
+
+ const extensions = [extWithMenuPageAction, extWithoutMenu1];
+
+ await Promise.all(extensions.map(extension => extension.startup()));
+
+ await extWithMenuPageAction.awaitMessage("menu-created");
+
+ await openExtensionsPanel();
+
+ info("extension with page action and a menu");
+ // This extension declares a page action so its menu shouldn't be added to
+ // the unified extensions context menu.
+ let contextMenu = await openUnifiedExtensionsContextMenu(
+ extWithMenuPageAction.id
+ );
+ assertVisibleContextMenuItems(contextMenu, 3);
+ await closeChromeContextMenu(contextMenu.id, null);
+
+ info("extension with no browser action and no menu");
+ // There is no context menu created by this extension, so there should only
+ // be 3 menu items corresponding to the default manage/remove/report items.
+ contextMenu = await openUnifiedExtensionsContextMenu(extWithoutMenu1.id);
+ assertVisibleContextMenuItems(contextMenu, 3);
+ await closeChromeContextMenu(contextMenu.id, null);
+
+ await closeExtensionsPanel();
+
+ await Promise.all(extensions.map(extension => extension.unload()));
+});
+
+add_task(async function test_pin_to_toolbar() {
+ const [extension] = createExtensions([
+ { name: "an extension", browser_action: {} },
+ ]);
+ await extension.startup();
+
+ // Open the extension panel, then open the context menu for the extension and
+ // pin the extension to the toolbar.
+ await openExtensionsPanel();
+ await pinToToolbar(extension);
+
+ // Undo the 'pin to toolbar' action.
+ await CustomizableUI.reset();
+ await extension.unload();
+});
+
+add_task(async function test_contextmenu_command_closes_panel() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name: "an extension",
+ browser_action: {},
+ permissions: ["contextMenus"],
+ },
+ background() {
+ browser.contextMenus.create(
+ {
+ id: "some-menu-id",
+ title: "Click me!",
+ contexts: ["all"],
+ },
+ () => browser.test.sendMessage("menu-created")
+ );
+ },
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+ await extension.awaitMessage("menu-created");
+
+ await openExtensionsPanel();
+ const contextMenu = await openUnifiedExtensionsContextMenu(extension.id);
+
+ const firstMenuItem = contextMenu.querySelector("menuitem");
+ is(
+ firstMenuItem?.getAttribute("label"),
+ "Click me!",
+ "expected custom menu item as first child"
+ );
+
+ const hidden = BrowserTestUtils.waitForEvent(
+ gUnifiedExtensions.panel,
+ "popuphidden",
+ true
+ );
+ contextMenu.activateItem(firstMenuItem);
+ await hidden;
+
+ await extension.unload();
+});
+
+add_task(async function test_contextmenu_reorder_extensions() {
+ const [ext1, ext2, ext3] = createExtensions([
+ { name: "ext1", browser_action: {} },
+ { name: "ext2", browser_action: {} },
+ { name: "ext3", browser_action: {} },
+ ]);
+ await Promise.all([ext1.startup(), ext2.startup(), ext3.startup()]);
+
+ await openExtensionsPanel();
+
+ // First extension in the list should only have "Move Down".
+ await assertMoveContextMenuItems(ext1, {
+ expectMoveUpHidden: true,
+ expectMoveDownHidden: false,
+ });
+
+ // Second extension in the list should have "Move Up" and "Move Down".
+ await assertMoveContextMenuItems(ext2, {
+ expectMoveUpHidden: false,
+ expectMoveDownHidden: false,
+ });
+
+ // Third extension in the list should only have "Move Up".
+ await assertMoveContextMenuItems(ext3, {
+ expectMoveUpHidden: false,
+ expectMoveDownHidden: true,
+ expectOrder: [ext1, ext2, ext3],
+ });
+
+ // Let's move some extensions now. We'll start by moving ext1 down until it
+ // is positioned at the end of the list.
+ info("Move down ext1 action to the bottom of the list");
+ await moveWidgetDown(ext1);
+ assertOrderOfWidgetsInPanel([ext2, ext1, ext3]);
+ await moveWidgetDown(ext1);
+
+ // Verify that the extension 1 has the right context menu items now that it
+ // is located at the end of the list.
+ await assertMoveContextMenuItems(ext1, {
+ expectMoveUpHidden: false,
+ expectMoveDownHidden: true,
+ expectOrder: [ext2, ext3, ext1],
+ });
+
+ info("Move up ext1 action to the top of the list");
+ await moveWidgetUp(ext1);
+ assertOrderOfWidgetsInPanel([ext2, ext1, ext3]);
+
+ await moveWidgetUp(ext1);
+ assertOrderOfWidgetsInPanel([ext1, ext2, ext3]);
+
+ // Move the last extension up.
+ info("Move up ext3 action");
+ await moveWidgetUp(ext3);
+ assertOrderOfWidgetsInPanel([ext1, ext3, ext2]);
+
+ // Move the last extension up (again).
+ info("Move up ext2 action to the top of the list");
+ await moveWidgetUp(ext2);
+ assertOrderOfWidgetsInPanel([ext1, ext2, ext3]);
+
+ // Move the second extension up.
+ await moveWidgetUp(ext2);
+ assertOrderOfWidgetsInPanel([ext2, ext1, ext3]);
+
+ // Pin an extension to the toolbar, which should remove it from the panel.
+ info("Pin ext1 action to the toolbar");
+ await pinToToolbar(ext1);
+ await openExtensionsPanel();
+ assertOrderOfWidgetsInPanel([ext2, ext3]);
+ await closeExtensionsPanel();
+
+ await Promise.all([ext1.unload(), ext2.unload(), ext3.unload()]);
+ await CustomizableUI.reset();
+});
+
+add_task(async function test_contextmenu_only_one_widget() {
+ const [extension] = createExtensions([{ name: "ext1", browser_action: {} }]);
+ await extension.startup();
+
+ await openExtensionsPanel();
+ await assertMoveContextMenuItems(extension, {
+ expectMoveUpHidden: true,
+ expectMoveDownHidden: true,
+ });
+ await closeExtensionsPanel();
+
+ await extension.unload();
+ await CustomizableUI.reset();
+});
+
+add_task(
+ async function test_contextmenu_reorder_extensions_with_private_window() {
+ // We want a panel in private mode that looks like this one (ext2 is not
+ // allowed in PB mode):
+ //
+ // - ext1
+ // - ext3
+ //
+ // But if we ask CUI to list the widgets in the panel, it would list:
+ //
+ // - ext1
+ // - ext2
+ // - ext3
+ //
+ const ext1 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name: "ext1",
+ browser_specific_settings: { gecko: { id: "ext1@reorder-private" } },
+ browser_action: {},
+ },
+ useAddonManager: "temporary",
+ incognitoOverride: "spanning",
+ });
+ await ext1.startup();
+
+ const ext2 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name: "ext2",
+ browser_specific_settings: { gecko: { id: "ext2@reorder-private" } },
+ browser_action: {},
+ },
+ useAddonManager: "temporary",
+ incognitoOverride: "not_allowed",
+ });
+ await ext2.startup();
+
+ const ext3 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name: "ext3",
+ browser_specific_settings: { gecko: { id: "ext3@reorder-private" } },
+ browser_action: {},
+ },
+ useAddonManager: "temporary",
+ incognitoOverride: "spanning",
+ });
+ await ext3.startup();
+
+ // Make sure all extension widgets are in the correct order.
+ assertOrderOfWidgetsInPanel([ext1, ext2, ext3]);
+
+ const privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ await openExtensionsPanel(privateWin);
+
+ // First extension in the list should only have "Move Down".
+ await assertMoveContextMenuItems(
+ ext1,
+ {
+ expectMoveUpHidden: true,
+ expectMoveDownHidden: false,
+ expectOrder: [ext1, ext3],
+ },
+ privateWin
+ );
+
+ // Second extension in the list (which is ext3) should only have "Move Up".
+ await assertMoveContextMenuItems(
+ ext3,
+ {
+ expectMoveUpHidden: false,
+ expectMoveDownHidden: true,
+ expectOrder: [ext1, ext3],
+ },
+ privateWin
+ );
+
+ // In private mode, we should only have two CUI widget nodes in the panel.
+ assertOrderOfWidgetsInPanel([ext1, ext3], privateWin);
+
+ info("Move ext1 down");
+ await moveWidgetDown(ext1, privateWin);
+ // The new order in a regular window should be:
+ assertOrderOfWidgetsInPanel([ext2, ext3, ext1]);
+ // ... while the order in the private window should be:
+ assertOrderOfWidgetsInPanel([ext3, ext1], privateWin);
+
+ // Verify that the extension 1 has the right context menu items now that it
+ // is located at the end of the list in PB mode.
+ await assertMoveContextMenuItems(
+ ext1,
+ {
+ expectMoveUpHidden: false,
+ expectMoveDownHidden: true,
+ expectOrder: [ext3, ext1],
+ },
+ privateWin
+ );
+
+ // Verify that the extension 3 has the right context menu items now that it
+ // is located at the top of the list in PB mode.
+ await assertMoveContextMenuItems(
+ ext3,
+ {
+ expectMoveUpHidden: true,
+ expectMoveDownHidden: false,
+ expectOrder: [ext3, ext1],
+ },
+ privateWin
+ );
+
+ info("Move ext3 extension down");
+ await moveWidgetDown(ext3, privateWin);
+ // The new order in a regular window should be:
+ assertOrderOfWidgetsInPanel([ext2, ext1, ext3]);
+ // ... while the order in the private window should be:
+ assertOrderOfWidgetsInPanel([ext1, ext3], privateWin);
+
+ // Pin an extension to the toolbar, which should remove it from the panel.
+ info("Pin ext1 to the toolbar");
+ await pinToToolbar(ext1, privateWin);
+ await openExtensionsPanel(privateWin);
+
+ // The new order in a regular window should be:
+ assertOrderOfWidgetsInPanel([ext2, ext3]);
+ await assertMoveContextMenuItems(
+ ext3,
+ {
+ expectMoveUpHidden: true,
+ expectMoveDownHidden: true,
+ // ... while the order in the private window should be:
+ expectOrder: [ext3],
+ },
+ privateWin
+ );
+
+ await closeExtensionsPanel(privateWin);
+
+ await Promise.all([ext1.unload(), ext2.unload(), ext3.unload()]);
+ await CustomizableUI.reset();
+
+ await BrowserTestUtils.closeWindow(privateWin);
+ }
+);