Bug 1532726 - Open add-on prefs in tab about:addons HTML r=flod,rpl
authorMark Striemer <mstriemer@mozilla.com>
Mon, 13 May 2019 19:04:06 +0000
changeset 535531 5c3bfb23d0cee0bff3751d0c089c1c5c28dbcf82
parent 535530 21e44ad9c6df3e31355ab73e4fdfb23be8fe255b
child 535532 9053b1f316ff269aba9b7b4bcbea0b97f9ca733c
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflod, rpl
bugs1532726
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 1532726 - Open add-on prefs in tab about:addons HTML r=flod,rpl Differential Revision: https://phabricator.services.mozilla.com/D29786
modules/libpref/init/all.js
toolkit/locales/en-US/toolkit/about/aboutAddons.ftl
toolkit/mozapps/extensions/content/aboutaddons.html
toolkit/mozapps/extensions/content/aboutaddons.js
toolkit/mozapps/extensions/content/aboutaddonsCommon.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/test/browser/browser.ini
toolkit/mozapps/extensions/test/browser/browser_html_options_ui_in_tab.js
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5190,16 +5190,18 @@ pref("extensions.webextensions.enablePer
 
 // Maximum age in milliseconds of performance counters in children
 // When reached, the counters are sent to the main process and
 // reset, so we reduce memory footprint.
 pref("extensions.webextensions.performanceCountersMaxAge", 5000);
 
 // The HTML about:addons page.
 pref("extensions.htmlaboutaddons.enabled", false);
+// Whether to allow the inline options browser in HTML about:addons page.
+pref("extensions.htmlaboutaddons.inline-options.enabled", false);
 
 // Report Site Issue button
 // Note that on enabling the button in other release channels, make sure to
 // disable it in problematic tests, see disableNonReleaseActions() inside
 // browser/modules/test/browser/head.js
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
 #if MOZ_UPDATE_CHANNEL != release && MOZ_UPDATE_CHANNEL != esr
 pref("extensions.webcompat-reporter.enabled", true);
--- a/toolkit/locales/en-US/toolkit/about/aboutAddons.ftl
+++ b/toolkit/locales/en-US/toolkit/about/aboutAddons.ftl
@@ -379,16 +379,21 @@ manage-addon-button = Manage
 find-more-addons = Find more add-ons
 
 ## Add-on actions
 report-addon-button = Report
 remove-addon-button = Remove
 disable-addon-button = Disable
 enable-addon-button = Enable
 expand-addon-button = More Options
+preferences-addon-button =
+    { PLATFORM() ->
+        [windows] Options
+       *[other] Preferences
+    }
 
 addons-enabled-heading = Enabled
 addons-disabled-heading = Disabled
 
 ask-to-activate-button = Ask to Activate
 always-activate-button = Always Activate
 never-activate-button = Never Activate
 
--- a/toolkit/mozapps/extensions/content/aboutaddons.html
+++ b/toolkit/mozapps/extensions/content/aboutaddons.html
@@ -19,16 +19,17 @@
     <div id="main">
     </div>
 
     <template name="addon-options">
       <panel-list>
         <panel-item action="toggle-disabled"></panel-item>
         <panel-item data-l10n-id="remove-addon-button" action="remove"></panel-item>
         <panel-item data-l10n-id="install-update-button" action="install-update" badged></panel-item>
+        <panel-item data-l10n-id="preferences-addon-button" action="preferences"></panel-item>
         <panel-item-separator></panel-item-separator>
         <panel-item data-l10n-id="report-addon-button" action="report"></panel-item>
         <panel-item-separator></panel-item-separator>
         <panel-item data-l10n-id="expand-addon-button" action="expand"></panel-item>
       </panel-list>
     </template>
 
     <template name="plugin-options">
--- a/toolkit/mozapps/extensions/content/aboutaddons.js
+++ b/toolkit/mozapps/extensions/content/aboutaddons.js
@@ -119,16 +119,31 @@ function hasPermission(addon, permission
   return !!(addon.permissions & PERMISSION_MASKS[permission]);
 }
 
 function isPending(addon, action) {
   const amAction = AddonManager["PENDING_" + action.toUpperCase()];
   return !!(addon.pendingOperations & amAction);
 }
 
+// Don't change how we handle this while the page is open.
+const INLINE_OPTIONS_ENABLED = Services.prefs.getBoolPref(
+  "extensions.htmlaboutaddons.inline-options.enabled");
+const OPTIONS_TYPE_MAP = {
+  [AddonManager.OPTIONS_TYPE_TAB]: "tab",
+  [AddonManager.OPTIONS_TYPE_INLINE_BROWSER]:
+    INLINE_OPTIONS_ENABLED ? "inline" : "tab",
+};
+
+// Check if an add-on has the provided options type, accounting for the pref
+// to disable inline options.
+function getOptionsType(addon, type) {
+  return OPTIONS_TYPE_MAP[addon.optionsType];
+}
+
 /**
  * This function is set in initialize() by the parent about:addons window. It
  * is a helper for gViewController.loadView().
  *
  * @param {string} type The view type to load.
  * @param {string} param The (optional) param for the view.
  */
 let loadViewFn;
@@ -561,16 +576,20 @@ class AddonOptions extends HTMLElement {
     toggleDisabledButton.hidden = !hasPermission(addon, toggleDisabledAction);
 
     // Set the update button and badge the menu if there's an update.
     this.querySelector('[action="install-update"]').hidden = !updateInstall;
 
     // Hide the expand button if we're expanded.
     this.querySelector('[action="expand"]').hidden = card.expanded;
 
+    // Show the preferences option if needed.
+    this.querySelector('[action="preferences"]').hidden =
+      getOptionsType(addon) != "tab";
+
     // Update the separators visibility based on the updated visibility
     // of the actions in the panel-list.
     this.updateSeparatorsVisibility();
   }
 }
 customElements.define("addon-options", AddonOptions);
 
 class PluginOptions extends HTMLElement {
@@ -927,16 +946,21 @@ class AddonCard extends HTMLElement {
           this.updateInstall = null;
           break;
         case "contribute":
           windowRoot.ownerGlobal.openUILinkIn(addon.contributionURL, "tab", {
             triggeringPrincipal:
               Services.scriptSecurityManager.createNullPrincipal({}),
           });
           break;
+        case "preferences":
+          if (getOptionsType(addon) == "tab") {
+            openOptionsInTab(addon.optionsURL);
+          }
+          break;
         case "remove":
           {
             this.panel.hide();
             let {
               remove, report,
             } = windowRoot.ownerGlobal.promptRemoveExtension(addon);
             if (remove) {
               await addon.uninstall(true);
--- a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
+++ b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* eslint max-len: ["error", 80] */
 
 "use strict";
 
-/* exported attachUpdateHandler, getBrowserElement */
+/* exported attachUpdateHandler, getBrowserElement, openOptionsInTab */
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this, "WEBEXT_PERMISSION_PROMPTS",
   "extensions.webextPermissionPrompts", false);
 
@@ -59,8 +59,20 @@ function attachUpdateHandler(install) {
             reject,
           },
         },
       };
       Services.obs.notifyObservers(subject, "webextension-permission-prompt");
     });
   };
 }
+
+function openOptionsInTab(optionsURL) {
+  let mainWindow = window.windowRoot.ownerGlobal;
+  if ("switchToTabHavingURI" in mainWindow) {
+    mainWindow.switchToTabHavingURI(optionsURL, true, {
+      relatedToCurrent: true,
+      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+    });
+    return true;
+  }
+  return false;
+}
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -152,17 +152,17 @@ function initialize(event) {
 
   let helpButton = document.getElementById("helpButton");
   let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "addons-help";
   helpButton.setAttribute("href", helpUrl);
   helpButton.addEventListener("click", () => recordLinkTelemetry("support"));
 
   document.getElementById("preferencesButton")
     .addEventListener("click", () => {
-      let mainWindow = getMainWindow();
+      let mainWindow = window.windowRoot.ownerGlobal;
       recordLinkTelemetry("about:preferences");
       if ("switchToTabHavingURI" in mainWindow) {
         mainWindow.switchToTabHavingURI("about:preferences", true, {
           triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
         });
       }
     });
 
@@ -380,23 +380,16 @@ function setSearchLabel(type) {
     searchLabel.hidden = false;
   } else {
     searchLabel.textContent = "";
     searchLabel.hidden = true;
   }
 }
 
 /**
- * Obtain the main DOMWindow for the current context.
- */
-function getMainWindow() {
-  return window.docShell.rootTreeItem.domWindow;
-}
-
-/**
  * A wrapper around the HTML5 session history service that allows the browser
  * back/forward controls to work within the manager
  */
 var HTML5History = {
   get index() {
     return window.docShell
                  .QueryInterface(Ci.nsIWebNavigation)
                  .sessionHistory.index;
@@ -1392,17 +1385,17 @@ var gViewController = {
       },
     },
 
     cmd_debugAddons: {
       isEnabled() {
         return true;
       },
       doCommand() {
-        let mainWindow = getMainWindow();
+        let mainWindow = window.windowRoot.ownerGlobal;
         recordLinkTelemetry("about:debugging");
         if ("switchToTabHavingURI" in mainWindow) {
           mainWindow.switchToTabHavingURI("about:debugging#addons", true, {
             triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
           });
         }
       },
     },
@@ -1559,28 +1552,16 @@ async function isAddonAllowedInCurrentWi
   return perms.permissions.includes("internal:privateBrowsingAllowed");
 }
 
 function hasInlineOptions(aAddon) {
   return aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER ||
          aAddon.type == "plugin";
 }
 
-function openOptionsInTab(optionsURL) {
-  let mainWindow = getMainWindow();
-  if ("switchToTabHavingURI" in mainWindow) {
-    mainWindow.switchToTabHavingURI(optionsURL, true, {
-      relatedToCurrent: true,
-      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-    });
-    return true;
-  }
-  return false;
-}
-
 function formatDate(aDate) {
   const dtOptions = { year: "numeric", month: "long", day: "numeric" };
   return aDate.toLocaleDateString(undefined, dtOptions);
 }
 
 
 function hasPermission(aAddon, aPerm) {
   var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -79,16 +79,17 @@ skip-if = true # Bug 1449071 - Frequent 
 skip-if = os == 'linux' && !debug # Bug 1398766
 [browser_html_abuse_report.js]
 [browser_html_detail_view.js]
 [browser_html_discover_view.js]
 [browser_html_discover_view_clientid.js]
 [browser_html_discover_view_prefs.js]
 [browser_html_list_view.js]
 [browser_html_message_bar.js]
+[browser_html_options_ui_in_tab.js]
 [browser_html_plugins.js]
 skip-if = (os == 'win' && processor == 'aarch64') # aarch64 has no plugin support, bug 1525174 and 1547495
 [browser_html_recent_updates.js]
 [browser_html_updates.js]
 [browser_inlinesettings_browser.js]
 skip-if = os == 'mac' || os == 'linux' # Bug 1483347
 [browser_installssl.js]
 skip-if = verify
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui_in_tab.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint max-len: ["error", 80] */
+
+"use strict";
+
+add_task(async function enableHtmlViews() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["extensions.htmlaboutaddons.enabled", true],
+      ["extensions.htmlaboutaddons.inline-options.enabled", true],
+    ],
+  });
+});
+
+async function testOptionsInTab({id, options_ui_options}) {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      name: "Prefs extension",
+      applications: {gecko: {id}},
+      options_ui: {
+        page: "options.html",
+        ...options_ui_options,
+      },
+    },
+    background() {
+      browser.test.sendMessage(
+        "options-url", browser.runtime.getURL("options.html"));
+    },
+    files: {
+      "options.html": `<script src="options.js"></script>`,
+      "options.js": () => {
+        browser.test.sendMessage("options-loaded");
+      },
+    },
+    useAddonManager: "temporary",
+  });
+  await extension.startup();
+  let optionsUrl = await extension.awaitMessage("options-url");
+
+  let win = await loadInitialView("extension");
+  let doc = win.document;
+  let aboutAddonsTab = gBrowser.selectedTab;
+
+  let card = doc.querySelector(`addon-card[addon-id="${id}"]`);
+
+  let prefsBtn = card.querySelector('panel-item[action="preferences"]');
+  ok(!prefsBtn.hidden, "The button is not hidden");
+
+  info("Open the preferences page from list");
+  let tabLoaded = BrowserTestUtils.waitForNewTab(gBrowser, optionsUrl);
+  prefsBtn.click();
+  BrowserTestUtils.removeTab(await tabLoaded);
+  await extension.awaitMessage("options-loaded");
+
+  info("Load details page");
+  let loaded = waitForViewLoad(win);
+  card.querySelector('[action="expand"]').click();
+  await loaded;
+
+  // Find the expanded card.
+  card = doc.querySelector(`addon-card[addon-id="${id}"]`);
+
+  info("Check that the button is still visible");
+  prefsBtn = card.querySelector('panel-item[action="preferences"]');
+  ok(!prefsBtn.hidden, "The button is not hidden");
+
+  info("Open the preferences page from details");
+  tabLoaded = BrowserTestUtils.waitForNewTab(gBrowser, optionsUrl);
+  prefsBtn.click();
+  let prefsTab = await tabLoaded;
+  await extension.awaitMessage("options-loaded");
+
+  info("Switch back to about:addons and open prefs again");
+  await BrowserTestUtils.switchTab(gBrowser, aboutAddonsTab);
+  let tabSwitched = BrowserTestUtils.waitForEvent(gBrowser, "TabSwitchDone");
+  prefsBtn.click();
+  await tabSwitched;
+  is(gBrowser.selectedTab, prefsTab, "The prefs tab was selected");
+
+  BrowserTestUtils.removeTab(prefsTab);
+
+  await closeView(win);
+  await extension.unload();
+}
+
+add_task(async function testPreferencesLink() {
+  let id = "prefs@mochi.test";
+  await testOptionsInTab({id, options_ui_options: {open_in_tab: true}});
+});
+
+add_task(async function testPreferencesInlineDisabled() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.inline-options.enabled", false]],
+  });
+
+  let id = "inline-disabled@mochi.test";
+  await testOptionsInTab({id, options_ui_options: {}});
+
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function testNoPreferences() {
+  let id = "no-prefs@mochi.test";
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      name: "No Prefs extension",
+      applications: {gecko: {id}},
+    },
+    useAddonManager: "temporary",
+  });
+  await extension.startup();
+
+  let win = await loadInitialView("extension");
+  let doc = win.document;
+
+  let card = doc.querySelector(`addon-card[addon-id="${id}"]`);
+
+  info("Check button on list");
+  let prefsBtn = card.querySelector('panel-item[action="preferences"]');
+  ok(prefsBtn.hidden, "The button is hidden");
+
+  info("Load details page");
+  let loaded = waitForViewLoad(win);
+  card.querySelector('[action="expand"]').click();
+  await loaded;
+
+  // Find the expanded card.
+  card = doc.querySelector(`addon-card[addon-id="${id}"]`);
+
+  info("Check that the button is still hidden on detail");
+  prefsBtn = card.querySelector('panel-item[action="preferences"]');
+  ok(prefsBtn.hidden, "The button is hidden");
+
+  await closeView(win);
+  await extension.unload();
+});