Bug 1397100 - Disable container about:preference checkbox if a container addon is enabled. r?felipc r?bsilverberg draft
authorJonathan Kingston <jkt@mozilla.com>
Mon, 18 Sep 2017 08:11:31 +0100
changeset 666339 b905fb519ca5b827bdd912a72d3b5124a933dbcb
parent 666327 bb61660397587c7ed85f2c4f7671a72e9da4075e
child 732077 4472d4239ba4d03c8974d4797e5243ca8df17460
push id80378
push userbmo:jkt@mozilla.com
push dateMon, 18 Sep 2017 16:43:15 +0000
reviewersfelipc, bsilverberg
bugs1397100
milestone57.0a1
Bug 1397100 - Disable container about:preference checkbox if a container addon is enabled. r?felipc r?bsilverberg MozReview-Commit-ID: DtJX3FiE0e0
browser/app/profile/firefox.js
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/main.xul
browser/components/preferences/in-content/privacy.js
browser/locales/en-US/chrome/browser/preferences/preferences.properties
toolkit/components/extensions/ext-contextualIdentities.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1533,16 +1533,17 @@ pref("privacy.userContext.longPressBehav
 #else
 pref("privacy.userContext.enabled", false);
 pref("privacy.userContext.ui.enabled", false);
 pref("privacy.usercontext.about_newtab_segregation.enabled", false);
 
 // 0 disables long press, 1 when clicked, the menu is shown, 2 the menu is shown after X milliseconds.
 pref("privacy.userContext.longPressBehavior", 0);
 #endif
+pref("privacy.userContext.extension", "");
 
 // Start the browser in e10s mode
 pref("browser.tabs.remote.autostart", false);
 pref("browser.tabs.remote.desktopbehavior", true);
 
 #if !defined(RELEASE_OR_BETA) || defined(MOZ_DEV_EDITION)
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -29,16 +29,19 @@ const TYPE_MAYBE_VIDEO_FEED = "applicati
 const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
 const TYPE_PDF = "application/pdf";
 
 const PREF_PDFJS_DISABLED = "pdfjs.disabled";
 const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
 
 const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
 
+// Pref for when containers is being controlled
+const PREF_CONTAINERS_EXTENSION = "privacy.userContext.extension";
+
 // Preferences that affect which entries to show in the list.
 const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
 const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
   "browser.download.hide_plugins_without_extensions";
 
 /*
  * Preferences where we store handling information about the feed type.
  *
@@ -238,16 +241,18 @@ var gMainPane = {
     setEventListener("useCurrent", "command",
       gMainPane.setHomePageToCurrent);
     setEventListener("useBookmark", "command",
       gMainPane.setHomePageToBookmark);
     setEventListener("restoreDefaultHomePage", "command",
       gMainPane.restoreDefaultHomePage);
     setEventListener("disableHomePageExtension", "command",
                      gMainPane.makeDisableControllingExtension("homepage_override"));
+    setEventListener("disableContainersExtension", "command",
+                     gMainPane.makeDisableControllingExtension("privacy.containers"));
     setEventListener("chooseLanguage", "command",
       gMainPane.showLanguages);
     setEventListener("translationAttributionImage", "click",
       gMainPane.openTranslationProviderAttribution);
     setEventListener("translateButton", "command",
       gMainPane.showTranslationExceptions);
     setEventListener("font.language.group", "change",
       gMainPane._rebuildFonts);
@@ -459,35 +464,60 @@ var gMainPane = {
     ]);
 
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
       .getService(Components.interfaces.nsIObserverService)
       .notifyObservers(window, "main-pane-loaded");
   },
 
+  // CONTAINERS
+
+  /*
+   * preferences:
+   *
+   * privacy.userContext.enabled
+   * - true if containers is enabled
+   */
+
+  /**
+   * Enables/disables the Settings button used to configure containers
+   */
+  readBrowserContainersCheckbox() {
+    const pref = document.getElementById("privacy.userContext.enabled");
+    const settings = document.getElementById("browserContainersSettings");
+
+    settings.disabled = !pref.value;
+    const containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
+    const containersCheckbox = document.getElementById("browserContainersCheckbox");
+    containersCheckbox.checked = containersEnabled;
+    handleControllingExtension("privacy.containers")
+      .then((isControlled) => {
+        containersCheckbox.disabled = isControlled;
+      });
+  },
+
   /**
    * Show the Containers UI depending on the privacy.userContext.ui.enabled pref.
    */
   initBrowserContainers() {
     if (!Services.prefs.getBoolPref("privacy.userContext.ui.enabled")) {
       // The browserContainersGroup element has its own internal padding that
       // is visible even if the browserContainersbox is visible, so hide the whole
       // groupbox if the feature is disabled to prevent a gap in the preferences.
       document.getElementById("browserContainersbox").setAttribute("data-hidden-from-search", "true");
       return;
     }
-
-    let link = document.getElementById("browserContainersLearnMore");
+    this._prefSvc.addObserver(PREF_CONTAINERS_EXTENSION, this);
+
+    const link = document.getElementById("browserContainersLearnMore");
     link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "containers";
 
     document.getElementById("browserContainersbox").hidden = false;
-
-    document.getElementById("browserContainersCheckbox").checked =
-      Services.prefs.getBoolPref("privacy.userContext.enabled");
+    this.readBrowserContainersCheckbox();
   },
 
   isE10SEnabled() {
     let e10sEnabled;
     try {
       let e10sStatus = Components.classes["@mozilla.org/supports-PRUint64;1"]
         .createInstance(Ci.nsISupportsPRUint64);
       let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
@@ -1342,16 +1372,18 @@ var gMainPane = {
     this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
     this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
     this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_READER, this);
 
     this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
     this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
     this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
     this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_READER, this);
+
+    this._prefSvc.removeObserver(PREF_CONTAINERS_EXTENSION, this);
   },
 
 
   // nsISupports
 
   QueryInterface(aIID) {
     if (aIID.equals(Ci.nsIObserver) ||
       aIID.equals(Ci.nsIDOMEventListener ||
@@ -1361,16 +1393,20 @@ var gMainPane = {
     throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
 
   // nsIObserver
 
   observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
+      if (aData == PREF_CONTAINERS_EXTENSION) {
+        this.readBrowserContainersCheckbox();
+        return;
+      }
       // Rebuild the list when there are changes to preferences that influence
       // whether or not to show certain entries in the list.
       if (!this._storingAction) {
         // These two prefs alter the list of visible types, so we have to rebuild
         // that list when they change.
         if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
           aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
           this._rebuildVisibleTypes();
@@ -2599,16 +2635,17 @@ function getLocalHandlerApp(aFile) {
     createInstance(Ci.nsILocalHandlerApp);
   localHandlerApp.name = getFileDisplayName(aFile);
   localHandlerApp.executable = aFile;
 
   return localHandlerApp;
 }
 
 let extensionControlledContentIds = {
+  "privacy.containers": "browserContainersExtensionContent",
   "homepage_override": "browserHomePageExtensionContent",
 };
 
 /**
   * Check if a pref is being managed by an extension.
   */
 function getControllingExtensionId(settingName) {
   return ExtensionPreferencesManager.getControllingExtensionId(settingName);
@@ -2627,17 +2664,17 @@ async function handleControllingExtensio
     hideControllingExtension(prefName);
   }
 
   return !!controllingExtensionId;
 }
 
 async function showControllingExtension(settingName, extensionId) {
   let extensionControlledContent = getControllingExtensionEl(settingName);
-  // Tell the user what extension is controlling the homepage.
+  // Tell the user what extension is controlling the setting.
   let addon = await AddonManager.getAddonByID(extensionId);
   const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
   let stringParts = document
     .getElementById("bundlePreferences")
     .getString(`extensionControlled.${settingName}`)
     .split("%S");
   let description = extensionControlledContent.querySelector("description");
 
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -419,37 +419,45 @@
               preference="browser.tabs.loadInBackground"/>
 
 #ifdef XP_WIN
     <checkbox id="showTabsInTaskbar" label="&showTabsInTaskbar.label;"
               accesskey="&showTabsInTaskbar.accesskey;"
               preference="browser.taskbar.previews.enable"/>
 #endif
 
-    <hbox id="browserContainersbox" hidden="true" align="center">
-      <checkbox id="browserContainersCheckbox"
-                label="&browserContainersEnabled.label;"
-                accesskey="&browserContainersEnabled.accesskey;"
-                preference="privacy.userContext.enabled"
-                onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
-      <label id="browserContainersLearnMore" class="learnMore text-link">
-        &browserContainersLearnMore.label;
-      </label>
-      <spacer flex="1"/>
-      <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
-      <hbox>
-        <button id="browserContainersSettings"
-                class="accessory-button"
-                label="&browserContainersSettings.label;"
-                accesskey="&browserContainersSettings.accesskey;"
-                searchkeywords="&addButton.label;
-                                &preferencesButton.label;
-                                &removeButton.label;"/>
+    <vbox id="browserContainersbox" hidden="true">
+      <hbox id="browserContainersExtensionContent" align="center">
+        <description control="disableContainersExtension" flex="1" />
+        <button id="disableContainersExtension"
+                class="extension-controlled-button accessory-button"
+                label="&disableExtension.label;" />
       </hbox>
-    </hbox>
+      <hbox align="center">
+        <checkbox id="browserContainersCheckbox"
+                  label="&browserContainersEnabled.label;"
+                  accesskey="&browserContainersEnabled.accesskey;"
+                  preference="privacy.userContext.enabled"
+                  onsyncfrompreference="return gMainPane.readBrowserContainersCheckbox();"/>
+        <label id="browserContainersLearnMore" class="learnMore text-link">
+          &browserContainersLearnMore.label;
+        </label>
+        <spacer flex="1"/>
+        <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
+        <hbox>
+          <button id="browserContainersSettings"
+                  class="accessory-button"
+                  label="&browserContainersSettings.label;"
+                  accesskey="&browserContainersSettings.accesskey;"
+                  searchkeywords="&addButton.label;
+                                  &preferencesButton.label;
+                                  &removeButton.label;"/>
+        </hbox>
+      </hbox>
+    </vbox>
 </groupbox>
 
 <hbox id="languageAndAppearanceCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
   <label class="header-name" flex="1">&languageAndAppearance.label;</label>
 </hbox>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -804,35 +804,16 @@ var gPrivacyPane = {
    */
   _updateSanitizeSettingsButton() {
     var settingsButton = document.getElementById("clearDataSettings");
     var sanitizeOnShutdownPref = document.getElementById("privacy.sanitize.sanitizeOnShutdown");
 
     settingsButton.disabled = !sanitizeOnShutdownPref.value;
   },
 
-  // CONTAINERS
-
-  /*
-   * preferences:
-   *
-   * privacy.userContext.enabled
-   * - true if containers is enabled
-   */
-
-  /**
-   * Enables/disables the Settings button used to configure containers
-   */
-  readBrowserContainersCheckbox() {
-    var pref = document.getElementById("privacy.userContext.enabled");
-    var settings = document.getElementById("browserContainersSettings");
-
-    settings.disabled = !pref.value;
-  },
-
   toggleDoNotDisturbNotifications(event) {
     AlertsServiceDND.manualDoNotDisturb = event.target.checked;
   },
 
   // GEOLOCATION
 
   /**
    * Displays the location exceptions dialog where specific site location
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -276,8 +276,13 @@ searchResults.sorryMessageUnix=Sorry! There are no results in Preferences for ā€œ%Sā€.
 searchResults.needHelp2=Need help? Visit <html:a id="need-help-link" target="_blank" href="%1$S">%2$S Support</html:a>
 
 # LOCALIZATION NOTE %S is the default value of the `dom.ipc.processCount` pref.
 defaultContentProcessCount=%S (default)
 
 # LOCALIZATION NOTE (extensionControlled.homepage_override):
 # This string is shown to notify the user that their home page is being controlled by an extension.
 extensionControlled.homepage_override = An extension, %S, controls your home page.
+
+# LOCALIZATION NOTE (extensionControlled.privacy.containers):
+# This string is shown to notify the user that Container Tabs are being enabled by an extension
+# %S is the container addon controlling it
+extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
\ No newline at end of file
--- a/toolkit/components/extensions/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/ext-contextualIdentities.js
@@ -14,16 +14,17 @@ var {
   ExtensionError,
 } = ExtensionUtils;
 
 const CONTAINER_PREF_INSTALL_DEFAULTS = {
   "privacy.userContext.enabled": true,
   "privacy.userContext.longPressBehavior": 2,
   "privacy.userContext.ui.enabled": true,
   "privacy.usercontext.about_newtab_segregation.enabled": true,
+  "privacy.userContext.extension": undefined,
 };
 
 const CONTAINERS_ENABLED_SETTING_NAME = "privacy.containers";
 
 const CONTAINER_COLORS = new Map([
   ["blue", "#37adff"],
   ["turquoise", "#00c79a"],
   ["green", "#51cd00"],
@@ -103,34 +104,36 @@ const convertIdentityFromObserver = wrap
 
   return result;
 };
 
 ExtensionPreferencesManager.addSetting(CONTAINERS_ENABLED_SETTING_NAME, {
   prefNames: Object.keys(CONTAINER_PREF_INSTALL_DEFAULTS),
 
   setCallback(value) {
-    if (value === true) {
-      return CONTAINER_PREF_INSTALL_DEFAULTS;
+    if (value !== true) {
+      return Object.assign(CONTAINER_PREF_INSTALL_DEFAULTS, {
+        "privacy.userContext.extension": value,
+      });
     }
 
     let prefs = {};
     for (let pref of this.prefNames) {
       prefs[pref] = undefined;
     }
     return prefs;
   },
 });
 
 this.contextualIdentities = class extends ExtensionAPI {
   onStartup() {
     let {extension} = this;
 
     if (extension.hasPermission("contextualIdentities")) {
-      ExtensionPreferencesManager.setSetting(extension, CONTAINERS_ENABLED_SETTING_NAME, true);
+      ExtensionPreferencesManager.setSetting(extension, CONTAINERS_ENABLED_SETTING_NAME, extension.id);
     }
   }
 
   getAPI(context) {
     let self = {
       contextualIdentities: {
         async get(cookieStoreId) {
           checkAPIEnabled();