summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/pageActions
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /browser/base/content/test/pageActions
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/base/content/test/pageActions')
-rw-r--r--browser/base/content/test/pageActions/browser.ini7
-rw-r--r--browser/base/content/test/pageActions/browser_PageActions_bookmark.js130
-rw-r--r--browser/base/content/test/pageActions/browser_PageActions_overflow.js257
-rw-r--r--browser/base/content/test/pageActions/browser_PageActions_removeExtension.js338
-rw-r--r--browser/base/content/test/pageActions/head.js163
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;
+}