Bug 1457474 - Add 'Manage Extension' in browserAction context menu
MozReview-Commit-ID: 3iA7fCeXLVs
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6385,16 +6385,39 @@ function UpdateDownloadsAutoHide(popup)
}
}
function onDownloadsAutoHideChange(event) {
let autoHide = event.target.getAttribute("checked") == "true";
Services.prefs.setBoolPref("browser.download.autohideButton", autoHide);
}
+function getUnwrappedTriggerNode(popup) {
+ // Toolbar buttons are wrapped in customize mode. Unwrap if necessary.
+ let {triggerNode} = popup;
+ if (triggerNode && gCustomizeMode.isWrappedToolbarItem(triggerNode)) {
+ return triggerNode.firstChild;
+ }
+ return triggerNode;
+}
+
+function UpdateManageExtension(popup) {
+ let checkbox = popup.querySelector(".customize-context-manageExtension");
+ let separator = checkbox.nextElementSibling;
+ let node = getUnwrappedTriggerNode(popup);
+ let isWebExt = node && node.hasAttribute("data-extensionid");
+ checkbox.hidden = separator.hidden = !isWebExt;
+}
+
+function openAboutAddonsForContextAction(popup) {
+ let id = getUnwrappedTriggerNode(popup).getAttribute("data-extensionid");
+ let viewID = "addons://detail/" + encodeURIComponent(id);
+ BrowserOpenAddonsMgr(viewID);
+}
+
var gPageStyleMenu = {
// This maps from a <browser> element (or, more specifically, a
// browser's permanentKey) to an Object that contains the most recent
// information about the browser content's stylesheets. That Object
// is populated via the PageStyle:StyleSheets message from the content
// process. The Object should have the following structure:
//
// filteredStyleSheets (Array):
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -328,17 +328,23 @@
oncommand="SidebarUI.reversePosition()"/>
<toolbarseparator/>
<toolbarbutton label="&sidebarMenuClose.label;"
class="subviewbutton"
oncommand="SidebarUI.hide()"/>
</panel>
<menupopup id="toolbar-context-menu"
- onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator')); UpdateDownloadsAutoHide(this)">
+ onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator')); UpdateDownloadsAutoHide(this); UpdateManageExtension(this)">
+ <menuitem oncommand="openAboutAddonsForContextAction(this.parentElement)"
+ accesskey="&customizeMenu.manageExtension.accesskey;"
+ label="&customizeMenu.manageExtension.label;"
+ contexttype="toolbaritem"
+ class="customize-context-manageExtension"/>
+ <menuseparator/>
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
accesskey="&customizeMenu.pinToOverflowMenu.accesskey;"
label="&customizeMenu.pinToOverflowMenu.label;"
contexttype="toolbaritem"
class="customize-context-moveToPanel"/>
<menuitem oncommand="onDownloadsAutoHideChange(event)"
type="checkbox"
accesskey="&customizeMenu.autoHideDownloadsButton.accesskey;"
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -24,17 +24,23 @@
accesskey="&overflowCustomizeToolbar.accesskey;"
label="&overflowCustomizeToolbar.label;"/>
</panelview>
</panelmultiview>
<!-- This menu is here because not having it in the menu in which it's used flickers
when hover styles overlap. See https://bugzilla.mozilla.org/show_bug.cgi?id=1378427 .
-->
<menupopup id="customizationPanelItemContextMenu"
- onpopupshowing="gCustomizeMode.onPanelContextMenuShowing(event)">
+ onpopupshowing="gCustomizeMode.onPanelContextMenuShowing(event); UpdateManageExtension(this)">
+ <menuitem oncommand="openAboutAddonsForContextAction(this.parentElement)"
+ accesskey="&customizeMenu.manageExtension.accesskey;"
+ label="&customizeMenu.manageExtension.label;"
+ contexttype="toolbaritem"
+ class="customize-context-manageExtension"/>
+ <menuseparator/>
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
id="customizationPanelItemContextMenuPin"
accesskey="&customizeMenu.pinToOverflowMenu.accesskey;"
label="&customizeMenu.pinToOverflowMenu.label;"
closemenu="single"
class="customize-context-moveToPanel"/>
<menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
id="customizationPanelItemContextMenuUnpin"
--- a/browser/components/extensions/parent/ext-browserAction.js
+++ b/browser/components/extensions/parent/ext-browserAction.js
@@ -175,16 +175,17 @@ this.browserAction = class extends Exten
view.remove();
}
},
onCreated: node => {
node.classList.add("badged-button");
node.classList.add("webextension-browser-action");
node.setAttribute("constrain-size", "true");
+ node.setAttribute("data-extensionid", this.extension.id);
node.onmousedown = event => this.handleEvent(event);
node.onmouseover = event => this.handleEvent(event);
node.onmouseout = event => this.handleEvent(event);
this.updateButton(node, this.globals, true);
},
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -99,8 +99,151 @@ add_task(async function browseraction_po
let item = contentAreaContextMenu.querySelector("#context-viewimageinfo");
ok(!item.hidden);
ok(item.disabled);
await closeContextMenu(contentAreaContextMenu);
await extension.unload();
});
+
+add_task(async function browseraction_contextmenu_manage_extension() {
+ let id = "addon_id@example.com";
+ let buttonId = `${makeWidgetId(id)}-browser-action`;
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "applications": {
+ "gecko": {id},
+ },
+ "browser_action": {},
+ "options_ui": {
+ "page": "options.html",
+ },
+ },
+ useAddonManager: "temporary",
+ files: {
+ "options.html": `<script src="options.js"></script>`,
+ "options.js": `browser.test.sendMessage("options-loaded");`,
+ },
+ });
+
+ function openContextMenu(menuId, targetId) {
+ return openChromeContextMenu(menuId, "#" + CSS.escape(targetId));
+ }
+
+ function checkVisibility(menu, visible) {
+ let manageExtension = menu.querySelector(".customize-context-manageExtension");
+ let separator = manageExtension.nextElementSibling;
+
+ info(`Check visibility`);
+ is(manageExtension.hidden, !visible, `Manage Extension should be ${visible ? "visible" : "hidden"}`);
+ is(separator.hidden, !visible, `Separator after Manage Extension should be ${visible ? "visible" : "hidden"}`);
+ }
+
+ async function testContextMenu(menuId, customizing) {
+ info(`Open browserAction context menu in ${menuId}`);
+ let menu = await openContextMenu(menuId, buttonId);
+ await checkVisibility(menu, true);
+
+ info(`Choosing 'Manage Extension' in ${menuId} should load options`);
+ let optionsLoaded = extension.awaitMessage("options-loaded");
+ let manageExtension = menu.querySelector(".customize-context-manageExtension");
+ await closeChromeContextMenu(menuId, manageExtension);
+ await optionsLoaded;
+
+ info(`Remove the opened tab, and await customize mode to be restored if necessary`);
+ let tab = gBrowser.selectedTab;
+ is(tab.linkedBrowser.currentURI.spec, "about:addons");
+ if (customizing) {
+ let customizationReady = BrowserTestUtils.waitForEvent(gNavToolbox, "customizationready");
+ gBrowser.removeTab(tab);
+ await customizationReady;
+ } else {
+ gBrowser.removeTab(tab);
+ }
+
+ return menu;
+ }
+
+ function waitForElementShown(element) {
+ let win = element.ownerGlobal;
+ let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ return BrowserTestUtils.waitForCondition(() => {
+ info("Waiting for overflow button to have non-0 size");
+ let bounds = dwu.getBoundsWithoutFlushing(element);
+ return bounds.width > 0 && bounds.height > 0;
+ });
+ }
+
+ async function main(customizing) {
+ if (customizing) {
+ info("Enter customize mode");
+ let customizationReady = BrowserTestUtils.waitForEvent(gNavToolbox, "customizationready");
+ gCustomizeMode.enter();
+ await customizationReady;
+ }
+
+ info("Test toolbar context menu in browserAction");
+ let toolbarCtxMenu = await testContextMenu("toolbar-context-menu", customizing);
+
+ info("Check toolbar context menu in another button");
+ let otherButtonId = "home-button";
+ await openContextMenu(toolbarCtxMenu.id, otherButtonId);
+ checkVisibility(toolbarCtxMenu, false);
+ toolbarCtxMenu.hidePopup();
+
+ info("Check toolbar context menu without triggerNode");
+ toolbarCtxMenu.openPopup();
+ checkVisibility(toolbarCtxMenu, false);
+ toolbarCtxMenu.hidePopup();
+
+ info("Pin the browserAction and another button to the overflow menu");
+ CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
+ CustomizableUI.addWidgetToArea(otherButtonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
+
+ info("Wait until the overflow menu is ready");
+ let overflowButton = document.getElementById("nav-bar-overflow-button");
+ let icon = document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
+ await waitForElementShown(icon);
+
+ if (!customizing) {
+ info("Open overflow menu");
+ let menu = document.getElementById("widget-overflow");
+ let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ overflowButton.click();
+ await shown;
+ }
+
+ info("Check overflow menu context menu in another button");
+ let overflowMenuCtxMenu = await openContextMenu("customizationPanelItemContextMenu", otherButtonId);
+ checkVisibility(overflowMenuCtxMenu, false);
+ overflowMenuCtxMenu.hidePopup();
+
+ info("Test overflow menu context menu in browserAction");
+ await testContextMenu(overflowMenuCtxMenu.id, customizing);
+
+ info("Restore initial state");
+ CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR);
+ CustomizableUI.addWidgetToArea(otherButtonId, CustomizableUI.AREA_NAVBAR);
+
+ if (customizing) {
+ info("Exit customize mode");
+ let afterCustomization = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization");
+ gCustomizeMode.exit();
+ await afterCustomization;
+ }
+ }
+
+ await extension.startup();
+
+ info("Add a dummy tab to prevent about:addons from being loaded in the initial about:blank tab");
+ let dummyTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com", true, true);
+
+ info("Run tests in normal mode");
+ await main(false);
+
+ info("Run tests in customize mode");
+ await main(true);
+
+ info("Close the dummy tab and finish");
+ gBrowser.removeTab(dummyTab);
+ await extension.unload();
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -423,16 +423,18 @@ These should match what Safari and other
<!ENTITY customizeMenu.removeFromToolbar.label "Remove from Toolbar">
<!ENTITY customizeMenu.removeFromToolbar.accesskey "R">
<!ENTITY customizeMenu.removeFromMenu.label "Remove from Menu">
<!ENTITY customizeMenu.removeFromMenu.accesskey "R">
<!ENTITY customizeMenu.addMoreItems.label "Add More Items…">
<!ENTITY customizeMenu.addMoreItems.accesskey "A">
<!ENTITY customizeMenu.autoHideDownloadsButton.label "Auto-Hide in Toolbar">
<!ENTITY customizeMenu.autoHideDownloadsButton.accesskey "A">
+<!ENTITY customizeMenu.manageExtension.label "Manage Extension">
+<!ENTITY customizeMenu.manageExtension.accesskey "E">
<!-- LOCALIZATION NOTE (moreMenu.label) This label is used in the new Photon
app (hamburger) menu. When clicked, it opens a subview that contains
secondary commands. -->
<!ENTITY moreMenu.label "More">
<!ENTITY openCmd.commandkey "l">
<!ENTITY urlbar.placeholder2 "Search or enter address">