From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser_unified_extensions_context_menu.js | 938 +++++++++++++++++++++ 1 file changed, 938 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js (limited to 'browser/components/extensions/test/browser/browser_unified_extensions_context_menu.js') 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); + } +); -- cgit v1.2.3