Bug 1537542 - Hide addon preferences if the addon cannot access the current window. r=aswan
authorLuca Greco <lgreco@mozilla.com>
Tue, 26 Mar 2019 12:30:39 +0000
changeset 466090 6e7b485a2f314633b7f059095207ad3655d83759
parent 466089 53b8b3bf4ac1527e0a20022dbf46362af5ca568a
child 466091 58fe84489519e891e049ad1eccee5f7ad046c1ba
push id81434
push userluca.greco@alcacoop.it
push dateTue, 26 Mar 2019 12:32:17 +0000
treeherderautoland@6e7b485a2f31 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1537542
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 1537542 - Hide addon preferences if the addon cannot access the current window. r=aswan 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);
+});