Bug 1537542 - Hide addon preferences if the addon cannot access the current window. r=aswan a=pascalc
authorLuca Greco <lgreco@mozilla.com>
Tue, 26 Mar 2019 12:30:39 +0000
changeset 525765 d42c58c97c84eb7c55a6d8776cdd3ac11e66bd27
parent 525764 06fcdd66507750c295355a287d9b815a58bb0227
child 525766 01fc4edd840ae80b5819054861d3e3e727162d4c
push id2032
push userffxbld-merge
push dateMon, 13 May 2019 09:36:57 +0000
treeherdermozilla-release@455c1065dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, pascalc
bugs1537542
milestone67.0
Bug 1537542 - Hide addon preferences if the addon cannot access the current window. r=aswan a=pascalc Differential Revision: https://phabricator.services.mozilla.com/D24534
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/test/browser/browser_webext_incognito.js
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1575,16 +1575,27 @@ var gViewController = {
     if (!cmd.isEnabled(aAddon))
       return;
     cmd.doCommand(aAddon);
   },
 
   onEvent() {},
 };
 
+async function isAddonAllowedInCurrentWindow(aAddon) {
+  if (allowPrivateBrowsingByDefault ||
+      aAddon.type !== "extension" ||
+      !PrivateBrowsingUtils.isContentWindowPrivate(window)) {
+    return true;
+  }
+
+  const perms = await ExtensionPermissions.get(aAddon.id);
+  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) {
@@ -2986,32 +2997,37 @@ var gDetailView = {
     }
 
     // Only type = "extension" will ever get privateBrowsingAllowed, other types have
     // no code that would be affected by the setting.  The permission is read directly
     // from ExtensionPermissions so we can get it whether or not the extension is
     // currently active.
     let privateBrowsingRow = document.getElementById("detail-privateBrowsing-row");
     let privateBrowsingFooterRow = document.getElementById("detail-privateBrowsing-row-footer");
+
     if (allowPrivateBrowsingByDefault || aAddon.type != "extension" ||
         !(aAddon.permissions & AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS)) {
       this._privateBrowsing.hidden = true;
       privateBrowsingRow.hidden = true;
       privateBrowsingFooterRow.hidden = true;
       this._privateBrowsing.value = "0";
     } else {
       let perms = await ExtensionPermissions.get(aAddon.id);
       this._privateBrowsing.hidden = false;
       privateBrowsingRow.hidden = false;
       privateBrowsingFooterRow.hidden = false;
       this._privateBrowsing.value = perms.permissions.includes("internal:privateBrowsingAllowed") ? "1" : "0";
     }
 
-    document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
-      !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
+    // While updating the addon details view, also check if the preferences button should be disabled because
+    // we are in a private window and the addon is not allowed to access it.
+    let hidePreferences = (!aIsRemote &&
+      !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon)) ||
+      !await isAddonAllowedInCurrentWindow(aAddon);
+    document.getElementById("detail-prefs-btn").hidden = hidePreferences;
 
     var gridRows = document.querySelectorAll("#detail-grid rows row");
     let first = true;
     for (let gridRow of gridRows) {
       if (first && window.getComputedStyle(gridRow).getPropertyValue("display") != "none") {
         gridRow.setAttribute("first-row", true);
         first = false;
       } else {
@@ -3281,16 +3297,22 @@ var gDetailView = {
 
     var rows = document.getElementById("detail-rows");
 
     if (this._addon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) {
       whenViewLoaded(async () => {
         const addon = this._addon;
         await addon.startupPromise;
 
+        // Do not create the inline addon options if about:addons is opened in a private window
+        // and the addon is not allowed to access it.
+        if (!await isAddonAllowedInCurrentWindow(addon)) {
+          return;
+        }
+
         const browserContainer = await this.createOptionsBrowser(rows);
 
         if (browserContainer) {
           // Make sure the browser is unloaded as soon as we change views,
           // rather than waiting for the next detail view to load.
           document.addEventListener("ViewChanged", function() {
             // Do not remove the addon options container if the view changed
             // event is not related to a change to the current selected view
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -902,16 +902,21 @@
             isLegacyExtension(this.mAddon);
           this.setAttribute("legacy", legacyWarning);
           document.getAnonymousElementByAttribute(this, "anonid", "legacy").href = SUPPORT_URL + "webextensions";
 
           if (!allowPrivateBrowsingByDefault) {
             ExtensionPermissions.get(this.mAddon.id).then((perms) => {
               let allowed = perms.permissions.includes("internal:privateBrowsingAllowed");
               this.setAttribute("privateBrowsing", allowed);
+              if (!allowed && PrivateBrowsingUtils.isContentWindowPrivate(window)) {
+                // Hide the preferences button if the current window is
+                // private and the addon is not allowed to access it.
+                this._preferencesBtn.hidden = true;
+              }
             });
           }
 
           if (!("applyBackgroundUpdates" in this.mAddon) ||
               (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
                (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
                 !AddonManager.autoUpdateDefault))) {
             AddonManager.getAllInstalls().then(aInstallsList => {
--- a/toolkit/mozapps/extensions/test/browser/browser_webext_incognito.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webext_incognito.js
@@ -135,8 +135,142 @@ add_task(async function test_addon() {
   assertTelemetryMatches([
     ["action", "aboutAddons", "on", {...expectedExtras, addonId: "@test-default"}],
     ["action", "aboutAddons", "off", {...expectedExtras, addonId: "@test-override"}],
     ["action", "aboutAddons", "off", {...expectedExtras, addonId: "@test-override-permanent"}],
   ], {filterMethods: ["action"]});
 
   Services.prefs.clearUserPref("extensions.allowPrivateBrowsingByDefault");
 });
+
+add_task(async function test_addon_preferences_button() {
+  await SpecialPowers.pushPrefEnv({set: [["extensions.allowPrivateBrowsingByDefault", false]]});
+
+  let addons = new Map([
+    ["test-inline-options@mozilla.com", {
+      useAddonManager: "temporary",
+      manifest: {
+        name: "Extension with inline options",
+        applications: {gecko: {id: "test-inline-options@mozilla.com"}},
+        options_ui: {page: "options.html", open_in_tab: false},
+      },
+    }],
+    ["test-newtab-options@mozilla.com", {
+      useAddonManager: "temporary",
+      manifest: {
+        name: "Extension with options page in a new tab",
+        applications: {gecko: {id: "test-newtab-options@mozilla.com"}},
+        options_ui: {page: "options.html", open_in_tab: true},
+      },
+    }],
+    ["test-not-allowed@mozilla.com", {
+      useAddonManager: "temporary",
+      manifest: {
+        name: "Extension not allowed in PB windows",
+        incognito: "not_allowed",
+        applications: {gecko: {id: "test-not-allowed@mozilla.com"}},
+        options_ui: {page: "options.html", open_in_tab: true},
+      },
+    }],
+  ]);
+
+  async function runTest(openInPrivateWin) {
+    const win = await BrowserTestUtils.openNewBrowserWindow({
+      private: openInPrivateWin,
+    });
+
+    gManagerWindow = await open_manager(
+      "addons://list/extension", undefined, undefined, undefined, win);
+
+    const doc = gManagerWindow.document;
+    const checkPrefsVisibility = (id, hasInlinePrefs, expectVisible) => {
+      if (!hasInlinePrefs) {
+        const detailsPrefBtn = doc.getElementById("detail-prefs-btn");
+        is(!detailsPrefBtn.hidden, expectVisible,
+           `The ${id} prefs button in the addon details has the expected visibility`);
+      } else {
+        const hasInlineOptionsBrowser = !!doc.getElementById("addon-options");
+        is(hasInlineOptionsBrowser, expectVisible,
+           `The ${id} inline prefs in the addon details has the expected visibility`);
+      }
+    };
+
+    const setAddonPrivateBrowsingAccess = async (id, allowPrivateBrowsing) => {
+      const privateBrowsing = doc.getElementById("detail-privateBrowsing");
+
+      is(privateBrowsing.value,
+         allowPrivateBrowsing ? "0" : "1",
+         `Private browsing should be initially ${allowPrivateBrowsing ? "off" : "on"}`);
+
+      // Get the DOM element we want to click on (to allow or disallow the
+      // addon on private browsing windows).
+      const controlEl = allowPrivateBrowsing ?
+        privateBrowsing.firstChild : privateBrowsing.lastChild;
+
+      EventUtils.synthesizeMouseAtCenter(controlEl, { clickCount: 1 }, gManagerWindow);
+
+      // Wait the private browsing access to be reflected in the about:addons
+      // addon details page.
+      await TestUtils.waitForCondition(
+        () => privateBrowsing.value == allowPrivateBrowsing ? "1" : "0",
+        "Waiting privateBrowsing value to be updated");
+
+      is(privateBrowsing.value,
+         allowPrivateBrowsing ? "1" : "0",
+         `Private browsing should be initially ${allowPrivateBrowsing ? "on" : "off"}`);
+
+      is(await hasPrivateAllowed(id), allowPrivateBrowsing,
+         `Private browsing permission ${allowPrivateBrowsing ? "added" : "removed"}`);
+    };
+
+    const extensions = [];
+    for (const definition of addons.values()) {
+      const extension = ExtensionTestUtils.loadExtension(definition);
+      extensions.push(extension);
+      await extension.startup();
+    }
+
+    const items = get_test_items();
+
+    for (const [id, definition] of addons.entries()) {
+      // Check the preferences button in the addon list page.
+      is(items[id]._preferencesBtn.hidden, openInPrivateWin,
+        `The ${id} prefs button in the addon list has the expected visibility`);
+
+      // Check the preferences button or inline frame in the addon
+      // details page.
+      info(`Opening addon details for ${id}`);
+      const hasInlinePrefs = !definition.manifest.options_ui.open_in_tab;
+      const onceViewChanged = BrowserTestUtils.waitForEvent(gManagerWindow, "ViewChanged");
+      gManagerWindow.loadView(`addons://detail/${encodeURIComponent(id)}`);
+      await onceViewChanged;
+
+      checkPrefsVisibility(id, hasInlinePrefs, !openInPrivateWin);
+
+      // While testing in a private window, also check that the preferences
+      // are going to be visible when we toggle the PB access for the addon.
+      if (openInPrivateWin && definition.manifest.incognito !== "not_allowed") {
+        await Promise.all([
+          BrowserTestUtils.waitForEvent(gManagerWindow, "ViewChanged"),
+          setAddonPrivateBrowsingAccess(id, true),
+        ]);
+        checkPrefsVisibility(id, hasInlinePrefs, true);
+
+        await Promise.all([
+          BrowserTestUtils.waitForEvent(gManagerWindow, "ViewChanged"),
+          setAddonPrivateBrowsingAccess(id, false),
+        ]);
+        checkPrefsVisibility(id, hasInlinePrefs, false);
+      }
+    }
+
+    for (const extension of extensions) {
+      await extension.unload();
+    }
+
+    await close_manager(gManagerWindow);
+    await BrowserTestUtils.closeWindow(win);
+  }
+
+  // run tests in private and non-private windows.
+  await runTest(true);
+  await runTest(false);
+});