Bug 1544928 - Add Report extension in browserAction context menu. r=flod,jaws,mstriemer
authorLuca Greco <lgreco@mozilla.com>
Mon, 06 May 2019 18:38:19 +0000
changeset 472750 8f6b0e4004271985c3e14d28e9f3c608dc1d498e
parent 472749 d15c62c6826bf796cabf60ea8ea23988aedea17a
child 472751 362072ce25dbe0311da24e701c293416c2eda3ec
push id35978
push usershindli@mozilla.com
push dateTue, 07 May 2019 09:44:39 +0000
treeherdermozilla-central@7aee5a30dd15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflod, jaws, mstriemer
bugs1544928
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1544928 - Add Report extension in browserAction context menu. r=flod,jaws,mstriemer Depends on D27294 Differential Revision: https://phabricator.services.mozilla.com/D29120
browser/base/content/browser.js
browser/base/content/browser.xul
browser/components/customizableui/content/panelUI.inc.xul
browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
browser/locales/en-US/chrome/browser/browser.dtd
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -390,16 +390,22 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     showFxaToolbarMenu(aNewVal);
   });
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "gFxaToolbarAccessed",
   "identity.fxaccounts.toolbar.accessed", false, (aPref, aOldVal, aNewVal) => {
     showFxaToolbarMenu(gFxaToolbarEnabled);
   });
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gHtmlAboutAddonsEnabled",
+  "extensions.htmlaboutaddons.enabled", false);
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "gAddonAbuseReportEnabled",
+  "extensions.abuseReport.enabled", false);
+
 customElements.setElementCreationCallback("translation-notification", () => {
   Services.scriptloader.loadSubScript(
     "chrome://browser/content/translation-notification.js", window);
 });
 
 var gBrowser;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
@@ -6643,20 +6649,29 @@ var ToolbarContextMenu = {
   _getExtensionId(popup) {
     let node = this._getUnwrappedTriggerNode(popup);
     return node && node.getAttribute("data-extensionid");
   },
 
   async updateExtension(popup) {
     let removeExtension = popup.querySelector(".customize-context-removeExtension");
     let manageExtension = popup.querySelector(".customize-context-manageExtension");
-    let separator = removeExtension.nextElementSibling;
+    let reportExtension = popup.querySelector(".customize-context-reportExtension");
+    let separator = reportExtension.nextElementSibling;
     let id = this._getExtensionId(popup);
     let addon = id && await AddonManager.getAddonByID(id);
-    removeExtension.hidden = manageExtension.hidden = separator.hidden = !addon;
+
+    for (let element of [removeExtension, manageExtension, separator]) {
+      element.hidden = !addon;
+    }
+
+    reportExtension.hidden = !addon ||
+                             !gAddonAbuseReportEnabled ||
+                             !gHtmlAboutAddonsEnabled;
+
     if (addon) {
       removeExtension.disabled = !(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);
     }
   },
 
   async removeExtensionForContextAction(popup) {
     let id = this._getExtensionId(popup);
     let addon = id && await AddonManager.getAddonByID(id);
@@ -6670,16 +6685,29 @@ var ToolbarContextMenu = {
       value: response ? "cancelled" : "accepted",
       extra: {addonId: addon.id},
     });
     if (response == 0) {
       addon.uninstall();
     }
   },
 
+  async reportExtensionForContextAction(popup, reportEntryPoint) {
+    let id = this._getExtensionId(popup);
+    let addon = id && await AddonManager.getAddonByID(id);
+    if (!addon) {
+      return;
+    }
+    const win = await BrowserOpenAddonsMgr("addons://list/extension");
+    win.openAbuseReport({
+      addonId: addon.id,
+      reportEntryPoint,
+    });
+  },
+
   openAboutAddonsForContextAction(popup) {
     let id = this._getExtensionId(popup);
     if (id) {
       let viewID = "addons://detail/" + encodeURIComponent(id);
       BrowserOpenAddonsMgr(viewID);
       AMTelemetry.recordActionEvent({
         object: "browserAction",
         action: "manage",
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -397,16 +397,21 @@
                 label="&customizeMenu.manageExtension.label;"
                 contexttype="toolbaritem"
                 class="customize-context-manageExtension"/>
       <menuitem oncommand="ToolbarContextMenu.removeExtensionForContextAction(this.parentElement)"
                 accesskey="&customizeMenu.removeExtension.accesskey;"
                 label="&customizeMenu.removeExtension.label;"
                 contexttype="toolbaritem"
                 class="customize-context-removeExtension"/>
+      <menuitem oncommand="ToolbarContextMenu.reportExtensionForContextAction(this.parentElement, 'toolbar_context_menu')"
+                accesskey="&customizeMenu.reportExtension.accesskey;"
+                label="&customizeMenu.reportExtension.label;"
+                contexttype="toolbaritem"
+                class="customize-context-reportExtension"/>
       <menuseparator/>
       <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
                 accesskey="&customizeMenu.pinToOverflowMenu.accesskey;"
                 label="&customizeMenu.pinToOverflowMenu.label;"
                 contexttype="toolbaritem"
                 class="customize-context-moveToPanel"/>
       <menuitem id="toolbar-context-autohide-downloads-button"
                 oncommand="ToolbarContextMenu.onDownloadsAutoHideChange(event);"
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -35,16 +35,21 @@
               label="&customizeMenu.manageExtension.label;"
               contexttype="toolbaritem"
               class="customize-context-manageExtension"/>
     <menuitem oncommand="ToolbarContextMenu.removeExtensionForContextAction(this.parentElement)"
               accesskey="&customizeMenu.removeExtension.accesskey;"
               label="&customizeMenu.removeExtension.label;"
               contexttype="toolbaritem"
               class="customize-context-removeExtension"/>
+    <menuitem oncommand="ToolbarContextMenu.reportExtensionForContextAction(this.parentElement, 'toolbar_context_menu')"
+              accesskey="&customizeMenu.reportExtension.accesskey;"
+              label="&customizeMenu.reportExtension.label;"
+              contexttype="toolbaritem"
+              class="customize-context-reportExtension"/>
     <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)"
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -1,12 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "ABUSE_REPORT_ENABLED",
+                                      "extensions.abuseReport.enabled", false);
+XPCOMUtils.defineLazyPreferenceGetter(this, "HTML_ABOUTADDONS_ENABLED",
+                                      "extensions.htmlaboutaddons.enabled", false);
+
 let extData = {
   manifest: {
     "permissions": ["contextMenus"],
     "browser_action": {
       "default_popup": "popup.html",
     },
   },
   useAddonManager: "temporary",
@@ -171,22 +176,26 @@ add_task(async function browseraction_co
       "options.html": `<script src="options.js"></script>`,
       "options.js": `browser.test.sendMessage("options-loaded");`,
     },
   });
 
   function checkVisibility(menu, visible) {
     let removeExtension = menu.querySelector(".customize-context-removeExtension");
     let manageExtension = menu.querySelector(".customize-context-manageExtension");
-    let separator = removeExtension.nextElementSibling;
+    let reportExtension = menu.querySelector(".customize-context-reportExtension");
+    let separator = reportExtension.nextElementSibling;
 
-    info(`Check visibility`);
+    info(`Check visibility: ${visible}`);
     let expected = visible ? "visible" : "hidden";
     is(removeExtension.hidden, !visible, `Remove Extension should be ${expected}`);
     is(manageExtension.hidden, !visible, `Manage Extension should be ${expected}`);
+    is(reportExtension.hidden,
+       !ABUSE_REPORT_ENABLED || !HTML_ABOUTADDONS_ENABLED || !visible,
+       `Report Extension should be ${expected}`);
     is(separator.hidden, !visible, `Separator after Manage Extension should be ${expected}`);
   }
 
   async function testContextMenu(menuId, customizing) {
     info(`Open browserAction context menu in ${menuId}`);
     let menu = await openContextMenu(menuId, buttonId, win);
     await checkVisibility(menu, true);
 
@@ -294,16 +303,59 @@ add_task(async function browseraction_co
 
   info("Close the dummy tab and finish");
   win.gBrowser.removeTab(dummyTab);
   await extension.unload();
 
   await BrowserTestUtils.closeWindow(win);
 });
 
+async function runTestContextMenu({
+  buttonId, customizing, testContextMenu, win,
+}) {
+  if (customizing) {
+    info("Enter customize mode");
+    let customizationReady = BrowserTestUtils.waitForEvent(win.gNavToolbox, "customizationready");
+    win.gCustomizeMode.enter();
+    await customizationReady;
+  }
+
+  info("Test toolbar context menu in browserAction");
+  await testContextMenu("toolbar-context-menu", customizing);
+
+  info("Pin the browserAction and another button to the overflow menu");
+  CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
+
+  info("Wait until the overflow menu is ready");
+  let overflowButton = win.document.getElementById("nav-bar-overflow-button");
+  let icon = win.document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
+  await waitForElementShown(icon);
+
+  if (!customizing) {
+    info("Open overflow menu");
+    let menu = win.document.getElementById("widget-overflow");
+    let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+    overflowButton.click();
+    await shown;
+  }
+
+  info("Test overflow menu context menu in browserAction");
+  await testContextMenu("customizationPanelItemContextMenu", customizing);
+
+  info("Restore initial state");
+  CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR);
+
+  if (customizing) {
+    info("Exit customize mode");
+    let afterCustomization = BrowserTestUtils.waitForEvent(win.gNavToolbox, "aftercustomization");
+    win.gCustomizeMode.exit();
+    await afterCustomization;
+  }
+}
+
 add_task(async function browseraction_contextmenu_remove_extension() {
   // Do the customize mode shuffle in a separate window, because it interferes
   // with other tests.
   let win = await BrowserTestUtils.openNewBrowserWindow();
   let id = "addon_id@example.com";
   let name = "Awesome Add-on";
   let buttonId = `${makeWidgetId(id)}-browser-action`;
   let extension = ExtensionTestUtils.loadExtension({
@@ -340,69 +392,28 @@ add_task(async function browseraction_co
     let removeExtension = menu.querySelector(".customize-context-removeExtension");
     await closeChromeContextMenu(menuId, removeExtension, win);
     is(promptService._confirmExArgs[1], `Remove ${name}`);
     is(promptService._confirmExArgs[2], `Remove ${name} from ${brand}?`);
     is(promptService._confirmExArgs[4], "Remove");
     return menu;
   }
 
-  async function main(customizing) {
-    if (customizing) {
-      info("Enter customize mode");
-      let customizationReady = BrowserTestUtils.waitForEvent(win.gNavToolbox, "customizationready");
-      win.gCustomizeMode.enter();
-      await customizationReady;
-    }
-
-    info("Test toolbar context menu in browserAction");
-    await testContextMenu("toolbar-context-menu", customizing);
-
-    info("Pin the browserAction and another button to the overflow menu");
-    CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
-
-    info("Wait until the overflow menu is ready");
-    let overflowButton = win.document.getElementById("nav-bar-overflow-button");
-    let icon = win.document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
-    await waitForElementShown(icon);
-
-    if (!customizing) {
-      info("Open overflow menu");
-      let menu = win.document.getElementById("widget-overflow");
-      let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
-      overflowButton.click();
-      await shown;
-    }
-
-    info("Test overflow menu context menu in browserAction");
-    await testContextMenu("customizationPanelItemContextMenu", customizing);
-
-    info("Restore initial state");
-    CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR);
-
-    if (customizing) {
-      info("Exit customize mode");
-      let afterCustomization = BrowserTestUtils.waitForEvent(win.gNavToolbox, "aftercustomization");
-      win.gCustomizeMode.exit();
-      await afterCustomization;
-    }
-  }
-
   await extension.startup();
 
   info("Run tests in normal mode");
-  await main(false);
+  await runTestContextMenu({buttonId, customizing: false, testContextMenu, win});
 
   assertTelemetryMatches([
     ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
     ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
   ]);
 
   info("Run tests in customize mode");
-  await main(true);
+  await runTestContextMenu({buttonId, customizing: true, testContextMenu, win});
 
   assertTelemetryMatches([
     ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
     ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
   ]);
 
   let addon = await AddonManager.getAddonByID(id);
   ok(addon, "Addon is still installed");
@@ -426,8 +437,97 @@ add_task(async function browseraction_co
 
   addon = await AddonManager.getAddonByID(id);
   ok(!addon, "Addon has been uninstalled");
 
   await extension.unload();
 
   await BrowserTestUtils.closeWindow(win);
 });
+
+// This test case verify reporting an extension from the browserAction
+// context menu (when the browserAction is in the toolbox and in the
+// overwflow menu, and repeat the test with and without the customize
+// mode enabled).
+add_task(async function browseraction_contextmenu_report_extension() {
+  SpecialPowers.pushPrefEnv({
+    set: [
+      ["extensions.htmlaboutaddons.enabled", true],
+      ["extensions.abuseReport.enabled", true],
+    ],
+  });
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  let id = "addon_id@example.com";
+  let name = "Bad Add-on";
+  let buttonId = `${makeWidgetId(id)}-browser-action`;
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      name,
+      author: "Bad author",
+      applications: {
+        gecko: {id},
+      },
+      browser_action: {},
+    },
+    useAddonManager: "temporary",
+  });
+
+  async function testContextMenu(menuId, customizing) {
+    info(`Open browserAction context menu in ${menuId}`);
+    let menu = await openContextMenu(menuId, buttonId, win);
+
+    info(`Choosing 'Report Extension' in ${menuId} should show confirm dialog`);
+    let reportExtension = menu.querySelector(".customize-context-reportExtension");
+    ok(!reportExtension.hidden, "Report extension should be visibile");
+
+    // When running in customizing mode "about:addons" will load in a new tab,
+    // otherwise it will replace the existing blank tab.
+    const onceAboutAddonsTab = customizing ?
+      BrowserTestUtils.waitForNewTab(win.gBrowser, "about:addons") :
+      BrowserTestUtils.waitForCondition(() => {
+        return win.gBrowser.currentURI.spec === "about:addons";
+      }, "Wait an about:addons tab to be opened");
+
+    await closeChromeContextMenu(menuId, reportExtension, win);
+    await onceAboutAddonsTab;
+
+    const browser = win.gBrowser.selectedBrowser;
+    is(browser.currentURI.spec, "about:addons",
+       "Got about:addons tab selected");
+
+    await BrowserTestUtils.browserLoaded(browser);
+
+    const abuseReportFrame = await BrowserTestUtils.waitForCondition(() => {
+      return browser.contentDocument.querySelector("addon-abuse-report-xulframe");
+    }, "Wait the abuse report frame");
+
+    ok(!abuseReportFrame.hidden, "Abuse report frame has the expected visibility");
+    is(abuseReportFrame.report.addon.id, id,
+       "Abuse report frame has the expected addon id");
+    is(abuseReportFrame.report.reportEntryPoint, "toolbar_context_menu",
+       "Abuse report frame has the expected reportEntryPoint");
+
+    // Close the new about:addons tab when running in customize mode,
+    // or cancel the abuse report if the about:addons page has been
+    // loaded in the existing blank tab.
+    if (customizing) {
+      info("Closing the about:addons tab");
+      BrowserTestUtils.removeTab(win.gBrowser.selectedTab);
+    } else {
+      info("Navigate the about:addons tab to about:blank");
+      await BrowserTestUtils.loadURI(browser, "about:blank");
+    }
+
+    return menu;
+  }
+
+  await extension.startup();
+
+  info("Run tests in normal mode");
+  await runTestContextMenu({buttonId, customizing: false, testContextMenu, win});
+
+  info("Run tests in customize mode");
+  await runTestContextMenu({buttonId, customizing: true, testContextMenu, win});
+
+  await extension.unload();
+
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -431,16 +431,21 @@ These should match what Safari and other
 <!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">
 <!ENTITY customizeMenu.removeExtension.label "Remove Extension">
 <!ENTITY customizeMenu.removeExtension.accesskey "v">
+<!-- LOCALIZATION NOTE (reportExtension.label) This label is used in the extensions
+     toolbar buttons context menus, a user can use this command to submit to Mozilla
+     an abuse report related to that extension. "Report" is a verb. -->
+<!ENTITY customizeMenu.reportExtension.label "Report Extension">
+<!ENTITY customizeMenu.reportExtension.accesskey "o">
 
 <!-- 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">