/* 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); } );