diff options
Diffstat (limited to '')
5 files changed, 895 insertions, 0 deletions
diff --git a/browser/base/content/test/pageActions/browser.ini b/browser/base/content/test/pageActions/browser.ini new file mode 100644 index 0000000000..b19464fc48 --- /dev/null +++ b/browser/base/content/test/pageActions/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + head.js + +[browser_PageActions_bookmark.js] +[browser_PageActions_overflow.js] +[browser_PageActions_removeExtension.js] diff --git a/browser/base/content/test/pageActions/browser_PageActions_bookmark.js b/browser/base/content/test/pageActions/browser_PageActions_bookmark.js new file mode 100644 index 0000000000..a77095f2cd --- /dev/null +++ b/browser/base/content/test/pageActions/browser_PageActions_bookmark.js @@ -0,0 +1,130 @@ +"use strict"; + +add_task(async function starButtonCtrlClick() { + // Open a unique page. + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let url = "http://example.com/browser_page_action_star_button"; + await BrowserTestUtils.withNewTab(url, async () => { + StarUI._createPanelIfNeeded(); + // The button ignores activation while the bookmarked status is being + // updated. So, wait for it to finish updating. + await TestUtils.waitForCondition( + () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING + ); + + const popup = document.getElementById("editBookmarkPanel"); + const starButtonBox = document.getElementById("star-button-box"); + + let shownPromise = promisePanelShown(popup); + EventUtils.synthesizeMouseAtCenter(starButtonBox, { ctrlKey: true }); + await shownPromise; + ok(true, "Panel shown after button pressed"); + + let hiddenPromise = promisePanelHidden(popup); + document.getElementById("editBookmarkPanelRemoveButton").click(); + await hiddenPromise; + }); +}); + +add_task(async function bookmark() { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + const url = "http://example.com/browser_page_action_menu"; + + const win = await BrowserTestUtils.openNewBrowserWindow(); + registerCleanupFunction(async () => { + await BrowserTestUtils.closeWindow(win); + }); + + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url }, + async () => { + // The bookmark button should not be starred. + const bookmarkButton = + win.BrowserPageActions.urlbarButtonNodeForActionID("bookmark"); + Assert.ok(!bookmarkButton.hasAttribute("starred")); + + info("Click the button."); + // The button ignores activation while the bookmarked status is being + // updated. So, wait for it to finish updating. + await TestUtils.waitForCondition( + () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING + ); + const starUIPanel = win.StarUI.panel; + let panelShown = BrowserTestUtils.waitForPopupEvent(starUIPanel, "shown"); + EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}, win); + await panelShown; + is( + await PlacesUtils.bookmarks.fetch({ url }), + null, + "Bookmark has not been created before save." + ); + + // The bookmark button should now be starred. + Assert.equal(bookmarkButton.firstChild.getAttribute("starred"), "true"); + + info("Save the bookmark."); + const onItemAddedPromise = PlacesTestUtils.waitForNotification( + "bookmark-added", + events => events.some(event => event.url == url) + ); + starUIPanel.hidePopup(); + await onItemAddedPromise; + + info("Click it again."); + // The button ignores activation while the bookmarked status is being + // updated. So, wait for it to finish updating. + await TestUtils.waitForCondition( + () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING + ); + panelShown = BrowserTestUtils.waitForPopupEvent(starUIPanel, "shown"); + EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}, win); + await panelShown; + + info("Remove the bookmark."); + const onItemRemovedPromise = PlacesTestUtils.waitForNotification( + "bookmark-removed", + events => events.some(event => event.url == url) + ); + win.StarUI._element("editBookmarkPanelRemoveButton").click(); + await onItemRemovedPromise; + } + ); +}); + +add_task(async function bookmarkNoEditDialog() { + const url = + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://example.com/browser_page_action_menu_no_edit_dialog"; + + await SpecialPowers.pushPrefEnv({ + set: [["browser.bookmarks.editDialog.showForNewBookmarks", false]], + }); + const win = await BrowserTestUtils.openNewBrowserWindow(); + registerCleanupFunction(async () => { + await BrowserTestUtils.closeWindow(win); + await PlacesUtils.bookmarks.eraseEverything(); + }); + + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url }, + async () => { + info("Click the button."); + // The button ignores activation while the bookmarked status is being + // updated. So, wait for it to finish updating. + await TestUtils.waitForCondition( + () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING + ); + const bookmarkButton = win.document.getElementById( + BrowserPageActions.urlbarButtonNodeIDForActionID("bookmark") + ); + + // The bookmark should be saved immediately after clicking the star. + const onItemAddedPromise = PlacesTestUtils.waitForNotification( + "bookmark-added", + events => events.some(event => event.url == url) + ); + EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}, win); + await onItemAddedPromise; + } + ); +}); diff --git a/browser/base/content/test/pageActions/browser_PageActions_overflow.js b/browser/base/content/test/pageActions/browser_PageActions_overflow.js new file mode 100644 index 0000000000..463dd336c4 --- /dev/null +++ b/browser/base/content/test/pageActions/browser_PageActions_overflow.js @@ -0,0 +1,257 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test() { + // We use an extension that shows a page action. We must add this additional + // action because otherwise the meatball menu would not appear as an overflow + // for a single action. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: ["<all_urls>"] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + let actionId = ExtensionCommon.makeWidgetId(extension.id); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + Assert.greater(win.outerWidth, 700, "window is bigger than 700px"); + BrowserTestUtils.loadURIString( + win.gBrowser, + "data:text/html,<h1>A Page</h1>" + ); + await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(win); + + info("Check page action buttons are visible, the meatball button is not"); + let addonButton = + win.BrowserPageActions.urlbarButtonNodeForActionID(actionId); + Assert.ok(BrowserTestUtils.is_visible(addonButton)); + let starButton = + win.BrowserPageActions.urlbarButtonNodeForActionID("bookmark"); + Assert.ok(BrowserTestUtils.is_visible(starButton)); + let meatballButton = win.document.getElementById("pageActionButton"); + Assert.ok(!BrowserTestUtils.is_visible(meatballButton)); + + info( + "Shrink the window, check page action buttons are not visible, the meatball menu is visible" + ); + let originalOuterWidth = win.outerWidth; + await promiseStableResize(500, win); + Assert.ok(!BrowserTestUtils.is_visible(addonButton)); + Assert.ok(!BrowserTestUtils.is_visible(starButton)); + Assert.ok(BrowserTestUtils.is_visible(meatballButton)); + + info( + "Remove the extension, check the only page action button is visible, the meatball menu is not visible" + ); + let promiseUninstalled = promiseAddonUninstalled(extension.id); + await extension.unload(); + await promiseUninstalled; + Assert.ok(BrowserTestUtils.is_visible(starButton)); + Assert.ok(!BrowserTestUtils.is_visible(meatballButton)); + Assert.deepEqual( + win.BrowserPageActions.urlbarButtonNodeForActionID(actionId), + null + ); + + await promiseStableResize(originalOuterWidth, win); + await BrowserTestUtils.closeWindow(win); +}); + +add_task(async function bookmark() { + // We use an extension that shows a page action. We must add this additional + // action because otherwise the meatball menu would not appear as an overflow + // for a single action. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: ["<all_urls>"] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + const url = "data:text/html,<h1>A Page</h1>"; + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + BrowserTestUtils.loadURIString(win.gBrowser, url); + await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(win); + + info("Shrink the window if necessary, check the meatball menu is visible"); + let originalOuterWidth = win.outerWidth; + await promiseStableResize(500, win); + + let meatballButton = win.document.getElementById("pageActionButton"); + Assert.ok(BrowserTestUtils.is_visible(meatballButton)); + + // Open the panel. + await promisePageActionPanelOpen(win); + + // The bookmark button should read "Bookmark Current Tab…" and not be starred. + let bookmarkButton = win.document.getElementById("pageAction-panel-bookmark"); + await TestUtils.waitForCondition( + () => bookmarkButton.label === "Bookmark Current Tab…" + ); + Assert.ok(!bookmarkButton.hasAttribute("starred")); + + // Click the button. + let hiddenPromise = promisePageActionPanelHidden(win); + let showPromise = BrowserTestUtils.waitForPopupEvent( + win.StarUI.panel, + "shown" + ); + EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}, win); + await hiddenPromise; + await showPromise; + win.StarUI.panel.hidePopup(); + + // Open the panel again. + await promisePageActionPanelOpen(win); + + // The bookmark button should now read "Edit This Bookmark…" and be starred. + await TestUtils.waitForCondition( + () => bookmarkButton.label === "Edit This Bookmark…" + ); + Assert.ok(bookmarkButton.hasAttribute("starred")); + Assert.equal(bookmarkButton.getAttribute("starred"), "true"); + + // Click it again. + hiddenPromise = promisePageActionPanelHidden(win); + showPromise = BrowserTestUtils.waitForPopupEvent(win.StarUI.panel, "shown"); + EventUtils.synthesizeMouseAtCenter(bookmarkButton, {}, win); + await hiddenPromise; + await showPromise; + + let onItemRemovedPromise = PlacesTestUtils.waitForNotification( + "bookmark-removed", + events => events.some(event => event.url == url) + ); + + // Click the remove-bookmark button in the panel. + win.StarUI._element("editBookmarkPanelRemoveButton").click(); + + // Wait for the bookmark to be removed before continuing. + await onItemRemovedPromise; + + // Open the panel again. + await promisePageActionPanelOpen(win); + + // The bookmark button should read "Bookmark Current Tab…" and not be starred. + await TestUtils.waitForCondition( + () => bookmarkButton.label === "Bookmark Current Tab…" + ); + Assert.ok(!bookmarkButton.hasAttribute("starred")); + + // Done. + hiddenPromise = promisePageActionPanelHidden(); + win.BrowserPageActions.panelNode.hidePopup(); + await hiddenPromise; + + info("Remove the extension"); + let promiseUninstalled = promiseAddonUninstalled(extension.id); + await extension.unload(); + await promiseUninstalled; + + await promiseStableResize(originalOuterWidth, win); + await BrowserTestUtils.closeWindow(win); +}); + +add_task(async function test_disabledPageAction_hidden_in_protonOverflowMenu() { + // Make sure the overflow menu urlbar button is visible (indipendently from + // the current size of the Firefox window). + BrowserPageActions.mainButtonNode.style.visibility = "visible"; + registerCleanupFunction(() => { + BrowserPageActions.mainButtonNode.style.removeProperty("visibility"); + }); + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { page_action: {} }, + async background() { + const { browser } = this; + const [tab] = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + browser.test.assertTrue(tab, "Got an active tab as expected"); + browser.test.onMessage.addListener(async msg => { + switch (msg) { + case "show-pageAction": + await browser.pageAction.show(tab.id); + break; + case "hide-pageAction": + await browser.pageAction.hide(tab.id); + break; + default: + browser.test.fail(`Unexpected message received: ${msg}`); + } + browser.test.sendMessage(`${msg}:done`); + }); + }, + }); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await BrowserTestUtils.withNewTab("http://example.com", async browser => { + const win = browser.ownerGlobal; + const promisePageActionPanelClosed = async () => { + let popupHiddenPromise = promisePageActionPanelHidden(win); + win.BrowserPageActions.panelNode.hidePopup(); + await popupHiddenPromise; + }; + + await extension.startup(); + const widgetId = ExtensionCommon.makeWidgetId(extension.id); + + info( + "Show pageAction and verify it is visible in the urlbar overflow menu" + ); + extension.sendMessage("show-pageAction"); + await extension.awaitMessage("show-pageAction:done"); + await promisePageActionPanelOpen(win); + let pageActionNode = + win.BrowserPageActions.panelButtonNodeForActionID(widgetId); + ok( + pageActionNode && BrowserTestUtils.is_visible(pageActionNode), + "enabled pageAction should be visible in the urlbar overflow menu" + ); + + info("Hide pageAction and verify it is hidden in the urlbar overflow menu"); + extension.sendMessage("hide-pageAction"); + await extension.awaitMessage("hide-pageAction:done"); + + await BrowserTestUtils.waitForCondition( + () => !win.BrowserPageActions.panelButtonNodeForActionID(widgetId), + "Wait for the disabled pageAction to be removed from the urlbar overflow menu" + ); + + await promisePageActionPanelClosed(); + + info("Reopen the urlbar overflow menu"); + await promisePageActionPanelOpen(win); + ok( + !win.BrowserPageActions.panelButtonNodeForActionID(widgetId), + "Disabled pageAction is still removed as expected" + ); + + await promisePageActionPanelClosed(); + await extension.unload(); + }); + + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/base/content/test/pageActions/browser_PageActions_removeExtension.js b/browser/base/content/test/pageActions/browser_PageActions_removeExtension.js new file mode 100644 index 0000000000..329be2db17 --- /dev/null +++ b/browser/base/content/test/pageActions/browser_PageActions_removeExtension.js @@ -0,0 +1,338 @@ +"use strict"; + +// Initialization. Must run first. +add_setup(async function () { + // The page action urlbar button, and therefore the panel, is only shown when + // the current tab is actionable -- i.e., a normal web page. about:blank is + // not, so open a new tab first thing, and close it when this test is done. + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: "http://example.com/", + }); + + // The prompt service is mocked later, so set it up to be restored. + let { prompt } = Services; + + registerCleanupFunction(async () => { + BrowserTestUtils.removeTab(tab); + Services.prompt = prompt; + }); +}); + +add_task(async function contextMenu_removeExtension_panel() { + // We use an extension that shows a page action so that we can test the + // "remove extension" item in the context menu. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: ["<all_urls>"] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + + let actionId = ExtensionCommon.makeWidgetId(extension.id); + + const url = "data:text/html,<h1>A Page</h1>"; + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + BrowserTestUtils.loadURIString(win.gBrowser, url); + await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + + info("Shrink the window if necessary, check the meatball menu is visible"); + let originalOuterWidth = win.outerWidth; + await promiseStableResize(500, win); + + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(win); + + let meatballButton = win.document.getElementById("pageActionButton"); + Assert.ok(BrowserTestUtils.is_visible(meatballButton)); + + // Open the panel. + await promisePageActionPanelOpen(win); + + info("Open the context menu"); + let panelButton = win.BrowserPageActions.panelButtonNodeForActionID(actionId); + let contextMenuPromise = promisePanelShown("pageActionContextMenu", win); + EventUtils.synthesizeMouseAtCenter( + panelButton, + { + type: "contextmenu", + button: 2, + }, + win + ); + let contextMenu = await contextMenuPromise; + + let removeExtensionItem = getRemoveExtensionItem(win); + Assert.ok(removeExtensionItem, "'Remove' item exists"); + Assert.ok(!removeExtensionItem.hidden, "'Remove' item is visible"); + Assert.ok(!removeExtensionItem.disabled, "'Remove' item is not disabled"); + + // Click the "remove extension" item, a prompt should be displayed and then + // the add-on should be uninstalled. We mock the prompt service to confirm + // the removal of the add-on. + contextMenuPromise = promisePanelHidden("pageActionContextMenu", win); + let addonUninstalledPromise = promiseAddonUninstalled(extension.id); + mockPromptService(); + contextMenu.activateItem(removeExtensionItem); + await Promise.all([contextMenuPromise, addonUninstalledPromise]); + + // Done, clean up. + await extension.unload(); + + await promiseStableResize(originalOuterWidth, win); + await BrowserTestUtils.closeWindow(win); +}); + +add_task(async function contextMenu_removeExtension_urlbar() { + // We use an extension that shows a page action so that we can test the + // "remove extension" item in the context menu. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: ["<all_urls>"] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(); + + let actionId = ExtensionCommon.makeWidgetId(extension.id); + + // Open the context menu on the action's urlbar button. + let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(actionId); + let contextMenuPromise = promisePanelShown("pageActionContextMenu"); + EventUtils.synthesizeMouseAtCenter(urlbarButton, { + type: "contextmenu", + button: 2, + }); + let contextMenu = await contextMenuPromise; + + let menuItems = collectContextMenuItems(); + Assert.equal(menuItems.length, 2, "Context menu has two children"); + let removeExtensionItem = getRemoveExtensionItem(); + Assert.ok(removeExtensionItem, "'Remove' item exists"); + Assert.ok(!removeExtensionItem.hidden, "'Remove' item is visible"); + Assert.ok(!removeExtensionItem.disabled, "'Remove' item is not disabled"); + let manageExtensionItem = getManageExtensionItem(); + Assert.ok(manageExtensionItem, "'Manage' item exists"); + Assert.ok(!manageExtensionItem.hidden, "'Manage' item is visible"); + Assert.ok(!manageExtensionItem.disabled, "'Manage' item is not disabled"); + + // Click the "remove extension" item, a prompt should be displayed and then + // the add-on should be uninstalled. We mock the prompt service to cancel the + // removal of the add-on. + contextMenuPromise = promisePanelHidden("pageActionContextMenu"); + let promptService = mockPromptService(); + let promptCancelledPromise = new Promise(resolve => { + promptService.confirmEx = () => resolve(); + }); + contextMenu.activateItem(removeExtensionItem); + await Promise.all([contextMenuPromise, promptCancelledPromise]); + + // Done, clean up. + await extension.unload(); + + // urlbar tests that run after this one can break if the mouse is left over + // the area where the urlbar popup appears, which seems to happen due to the + // above synthesized mouse events. Move it over the urlbar. + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { type: "mousemove" }); + gURLBar.focus(); +}); + +add_task(async function contextMenu_removeExtension_disabled_in_urlbar() { + // We use an extension that shows a page action so that we can test the + // "remove extension" item in the context menu. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: ["<all_urls>"] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(); + // Add a policy to prevent the add-on from being uninstalled. + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: { + Extensions: { + Locked: [extension.id], + }, + }, + }); + + let actionId = ExtensionCommon.makeWidgetId(extension.id); + + // Open the context menu on the action's urlbar button. + let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(actionId); + let contextMenuPromise = promisePanelShown("pageActionContextMenu"); + EventUtils.synthesizeMouseAtCenter(urlbarButton, { + type: "contextmenu", + button: 2, + }); + let contextMenu = await contextMenuPromise; + + let menuItems = collectContextMenuItems(); + Assert.equal(menuItems.length, 2, "Context menu has two children"); + let removeExtensionItem = getRemoveExtensionItem(); + Assert.ok(removeExtensionItem, "'Remove' item exists"); + Assert.ok(!removeExtensionItem.hidden, "'Remove' item is visible"); + Assert.ok(removeExtensionItem.disabled, "'Remove' item is disabled"); + let manageExtensionItem = getManageExtensionItem(); + Assert.ok(manageExtensionItem, "'Manage' item exists"); + Assert.ok(!manageExtensionItem.hidden, "'Manage' item is visible"); + Assert.ok(!manageExtensionItem.disabled, "'Manage' item is not disabled"); + + // Hide the context menu. + contextMenuPromise = promisePanelHidden("pageActionContextMenu"); + contextMenu.hidePopup(); + await contextMenuPromise; + + // Done, clean up. + await extension.unload(); + await EnterprisePolicyTesting.setupPolicyEngineWithJson(""); + + // urlbar tests that run after this one can break if the mouse is left over + // the area where the urlbar popup appears, which seems to happen due to the + // above synthesized mouse events. Move it over the urlbar. + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { type: "mousemove" }); + gURLBar.focus(); +}); + +add_task(async function contextMenu_removeExtension_disabled_in_panel() { + // We use an extension that shows a page action so that we can test the + // "remove extension" item in the context menu. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: ["<all_urls>"] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + // Add a policy to prevent the add-on from being uninstalled. + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: { + Extensions: { + Locked: [extension.id], + }, + }, + }); + + let actionId = ExtensionCommon.makeWidgetId(extension.id); + + const url = "data:text/html,<h1>A Page</h1>"; + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + BrowserTestUtils.loadURIString(win.gBrowser, url); + await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + + info("Shrink the window if necessary, check the meatball menu is visible"); + let originalOuterWidth = win.outerWidth; + await promiseStableResize(500, win); + + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(win); + + let meatballButton = win.document.getElementById("pageActionButton"); + Assert.ok(BrowserTestUtils.is_visible(meatballButton)); + + // Open the panel. + await promisePageActionPanelOpen(win); + + info("Open the context menu"); + let panelButton = win.BrowserPageActions.panelButtonNodeForActionID(actionId); + let contextMenuPromise = promisePanelShown("pageActionContextMenu", win); + EventUtils.synthesizeMouseAtCenter( + panelButton, + { + type: "contextmenu", + button: 2, + }, + win + ); + let contextMenu = await contextMenuPromise; + + let removeExtensionItem = getRemoveExtensionItem(win); + Assert.ok(removeExtensionItem, "'Remove' item exists"); + Assert.ok(!removeExtensionItem.hidden, "'Remove' item is visible"); + Assert.ok(removeExtensionItem.disabled, "'Remove' item is disabled"); + + // Hide the context menu. + contextMenuPromise = promisePanelHidden("pageActionContextMenu", win); + contextMenu.hidePopup(); + await contextMenuPromise; + + // Done, clean up. + await extension.unload(); + await EnterprisePolicyTesting.setupPolicyEngineWithJson(""); + + await promiseStableResize(originalOuterWidth, win); + await BrowserTestUtils.closeWindow(win); +}); + +function promiseAddonUninstalled(addonId) { + return new Promise(resolve => { + let listener = {}; + listener.onUninstalled = addon => { + if (addon.id == addonId) { + AddonManager.removeAddonListener(listener); + resolve(); + } + }; + AddonManager.addAddonListener(listener); + }); +} + +function mockPromptService() { + let promptService = { + // The prompt returns 1 for cancelled and 0 for accepted. + _response: 0, + QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]), + confirmEx: () => promptService._response, + }; + + Services.prompt = promptService; + + return promptService; +} + +function getRemoveExtensionItem(win = window) { + return win.document.querySelector( + "#pageActionContextMenu > menuitem[label='Remove Extension']" + ); +} + +function getManageExtensionItem(win = window) { + return win.document.querySelector( + "#pageActionContextMenu > menuitem[label='Manage Extension…']" + ); +} + +function collectContextMenuItems(win = window) { + let contextMenu = win.document.getElementById("pageActionContextMenu"); + return Array.prototype.filter.call(contextMenu.children, node => { + return win.getComputedStyle(node).visibility == "visible"; + }); +} diff --git a/browser/base/content/test/pageActions/head.js b/browser/base/content/test/pageActions/head.js new file mode 100644 index 0000000000..15801c490e --- /dev/null +++ b/browser/base/content/test/pageActions/head.js @@ -0,0 +1,163 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +ChromeUtils.defineESModuleGetters(this, { + EnterprisePolicyTesting: + "resource://testing-common/EnterprisePolicyTesting.sys.mjs", + ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs", + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", + TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", + sinon: "resource://testing-common/Sinon.sys.mjs", +}); + +async function promisePageActionPanelOpen(win = window, eventDict = {}) { + await BrowserTestUtils.waitForCondition(() => { + // Wait for the main page action button to become visible. It's hidden for + // some URIs, so depending on when this is called, it may not yet be quite + // visible. It's up to the caller to make sure it will be visible. + info("Waiting for main page action button to have non-0 size"); + let bounds = win.windowUtils.getBoundsWithoutFlushing( + win.BrowserPageActions.mainButtonNode + ); + return bounds.width > 0 && bounds.height > 0; + }); + + // Wait for the panel to become open, by clicking the button if necessary. + info("Waiting for main page action panel to be open"); + if (win.BrowserPageActions.panelNode.state == "open") { + return; + } + let shownPromise = promisePageActionPanelShown(win); + EventUtils.synthesizeMouseAtCenter( + win.BrowserPageActions.mainButtonNode, + eventDict, + win + ); + await shownPromise; + info("Wait for items in the panel to become visible."); + await promisePageActionViewChildrenVisible( + win.BrowserPageActions.mainViewNode, + win + ); +} + +function promisePageActionPanelShown(win = window) { + return promisePanelShown(win.BrowserPageActions.panelNode, win); +} + +function promisePageActionPanelHidden(win = window) { + return promisePanelHidden(win.BrowserPageActions.panelNode, win); +} + +function promisePanelShown(panelIDOrNode, win = window) { + return promisePanelEvent(panelIDOrNode, "popupshown", win); +} + +function promisePanelHidden(panelIDOrNode, win = window) { + return promisePanelEvent(panelIDOrNode, "popuphidden", win); +} + +function promisePanelEvent(panelIDOrNode, eventType, win = window) { + return new Promise(resolve => { + let panel = panelIDOrNode; + if (typeof panel == "string") { + panel = win.document.getElementById(panelIDOrNode); + if (!panel) { + throw new Error(`Panel with ID "${panelIDOrNode}" does not exist.`); + } + } + if ( + (eventType == "popupshown" && panel.state == "open") || + (eventType == "popuphidden" && panel.state == "closed") + ) { + executeSoon(() => resolve(panel)); + return; + } + panel.addEventListener( + eventType, + () => { + executeSoon(() => resolve(panel)); + }, + { once: true } + ); + }); +} + +async function promisePageActionViewChildrenVisible( + panelViewNode, + win = window +) { + info( + "promisePageActionViewChildrenVisible waiting for a child node to be visible" + ); + await new Promise(win.requestAnimationFrame); + let dwu = win.windowUtils; + return TestUtils.waitForCondition(() => { + let bodyNode = panelViewNode.firstElementChild; + for (let childNode of bodyNode.children) { + let bounds = dwu.getBoundsWithoutFlushing(childNode); + if (bounds.width > 0 && bounds.height > 0) { + return true; + } + } + return false; + }); +} + +function promiseAddonUninstalled(addonId) { + return new Promise(resolve => { + let listener = {}; + listener.onUninstalled = addon => { + if (addon.id == addonId) { + AddonManager.removeAddonListener(listener); + resolve(); + } + }; + AddonManager.addAddonListener(listener); + }); +} + +async function promiseAnimationFrame(win = window) { + await new Promise(resolve => win.requestAnimationFrame(resolve)); + await win.promiseDocumentFlushed(() => {}); +} + +async function promisePopupNotShown(id, win = window) { + let deferred = PromiseUtils.defer(); + function listener(e) { + deferred.reject("Unexpected popupshown"); + } + let panel = win.document.getElementById(id); + panel.addEventListener("popupshown", listener); + try { + await Promise.race([ + deferred.promise, + new Promise(resolve => { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + win.setTimeout(resolve, 300); + }), + ]); + } finally { + panel.removeEventListener("popupshown", listener); + } +} + +// TODO (Bug 1700780): Why is this necessary? Without this trick the test +// fails intermittently on Ubuntu. +function promiseStableResize(expectedWidth, win = window) { + let deferred = PromiseUtils.defer(); + let id; + function listener() { + win.clearTimeout(id); + info(`Got resize event: ${win.innerWidth} x ${win.innerHeight}`); + if (win.innerWidth <= expectedWidth) { + id = win.setTimeout(() => { + win.removeEventListener("resize", listener); + deferred.resolve(); + }, 100); + } + } + win.addEventListener("resize", listener); + win.resizeTo(expectedWidth, win.outerHeight); + return deferred.promise; +} |