Bug 1390160 - Show that a WebExtension is managing how cookies are handled draft
authorBob Silverberg <bsilverberg@mozilla.com>
Thu, 18 Jan 2018 17:48:00 -0500
changeset 723187 c5e53d817552bd69f42b7e891ac52e101cb7598e
parent 723064 3d23e6d98a09a3395bf2b0d9cb03dd4be358c715
child 746789 1407ac3f9ced82c6f502e5e54b006ea190d25ef5
push id96351
push userbmo:bob.silverberg@gmail.com
push dateMon, 22 Jan 2018 17:38:09 +0000
bugs1390160
milestone60.0a1
Bug 1390160 - Show that a WebExtension is managing how cookies are handled Work in progress! Not ready for review! The latest version disables the setting if mode is changed away from custom, but it doesn't deal with reinstating the prefs if a user switches back to "custom". We might not need that. MozReview-Commit-ID: AAXl9ZqHtWw
browser/components/preferences/in-content/preferences.js
browser/components/preferences/in-content/privacy.js
browser/components/preferences/in-content/privacy.xul
browser/locales/en-US/chrome/browser/preferences/preferences.properties
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -43,16 +43,33 @@ function init_category_if_required(categ
     throw "Unknown in-content prefs category! Can't init " + category;
   }
   if (categoryInfo.inited) {
     return;
   }
   categoryInfo.init();
 }
 
+function debounce(func, wait) {
+  let timer = null;
+
+  return function() {
+    if (timer) {
+      clearTimeout(timer);
+    }
+
+    let args = arguments;
+    let context = this;
+    timer = setTimeout(() => {
+      timer = null;
+      func.apply(context, args);
+    }, wait);
+  };
+}
+
 function register_module(categoryName, categoryObject) {
   gCategoryInits.set(categoryName, {
     inited: false,
     init() {
       categoryObject.init();
       this.inited = true;
     }
   });
@@ -423,25 +440,26 @@ function appendSearchKeywords(aId, keywo
 
 const PREF_SETTING_TYPE = "prefs";
 
 let extensionControlledContentIds = {
   "privacy.containers": "browserContainersExtensionContent",
   "homepage_override": "browserHomePageExtensionContent",
   "newTabURL": "browserNewTabExtensionContent",
   "defaultSearch": "browserDefaultSearchExtensionContent",
+  "websites.cookieConfig": "cookiesExtensionContent",
   get "websites.trackingProtectionMode"() {
     return {
       button: "trackingProtectionExtensionContentButton",
       section:
         trackingprotectionUiEnabled ?
           "trackingProtectionExtensionContentLabel" :
           "trackingProtectionPBMExtensionContentLabel",
     };
-  }
+  },
 };
 
 let extensionControlledIds = {};
 
 /**
   * Check if a pref is being managed by an extension.
   */
 async function getControllingExtensionInfo(type, settingName) {
@@ -472,20 +490,34 @@ async function handleControllingExtensio
   // outside of the regular lifecycle. If the extension isn't currently installed
   // then we should treat the setting as not being controlled.
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1411046 for an example.
   if (addon) {
     extensionControlledIds[settingName] = info.id;
     showControllingExtension(settingName, addon);
   } else {
     let elements = getControllingExtensionEls(settingName);
+    if (elements.section.classList.contains("extension-controlled-disabled")) {
+      return false;
+    }
     if (extensionControlledIds[settingName]
         && !document.hidden
         && elements.button) {
-      showEnableExtensionMessage(settingName);
+      let showEnableMessage = true;
+      if (settingName === "websites.cookieConfig") {
+        // If a user manually changed history control away from custom, do not
+        // display the extension re-enable message.
+        let historyModeControl = document.getElementById("historyMode");
+        if (historyModeControl.value && historyModeControl.value !== "custom") {
+          showEnableMessage = false;
+        }
+      }
+      if (showEnableMessage) {
+        showEnableExtensionMessage(settingName);
+      }
     } else {
       hideControllingExtension(settingName);
     }
     delete extensionControlledIds[settingName];
   }
 
   return !!addon;
 }
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -22,16 +22,19 @@ XPCOMUtils.defineLazyPreferenceGetter(th
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 
 const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode";
 const TRACKING_PROTECTION_PREFS = ["privacy.trackingprotection.enabled",
                                    "privacy.trackingprotection.pbmode.enabled"];
+const COOKIE_CONFIG_KEY = "websites.cookieConfig";
+const COOKIE_CONFIG_PREFS = ["network.cookie.cookieBehavior",
+                             "network.cookie.lifetimePolicy"];
 
 XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
   try {
     let alertsService = Cc["@mozilla.org/alerts-service;1"]
       .getService(Ci.nsIAlertsService)
       .QueryInterface(Ci.nsIAlertsDoNotDisturb);
     // This will throw if manualDoNotDisturb isn't implemented.
     alertsService.manualDoNotDisturb;
@@ -223,16 +226,81 @@ var gPrivacyPane = {
     window.addEventListener("unload", () => {
       for (let pref of TRACKING_PROTECTION_PREFS) {
         Services.prefs.removeObserver(pref, trackingProtectionObserver);
       }
     });
   },
 
   /**
+   * Set up handlers for showing and hiding controlling extension info
+   * for cookie prefs.
+   */
+  _initCookieExtensionControl() {
+    let cookieConfigObserver = {
+      observe(subject, topic, data) {
+        gPrivacyPane._updateCookiesUI();
+      },
+    };
+    cookieConfigObserver.observe = debounce(cookieConfigObserver.observe, 1);
+
+    for (let pref of COOKIE_CONFIG_PREFS) {
+      Services.prefs.addObserver(pref, cookieConfigObserver);
+    }
+    window.addEventListener("unload", () => {
+      for (let pref of COOKIE_CONFIG_PREFS) {
+        Services.prefs.removeObserver(pref, cookieConfigObserver);
+      }
+    });
+  },
+
+  /**
+   * Update the cookies UI to deal with extension control.
+   */
+  _updateCookiesUI() {
+    let isLocked = COOKIE_CONFIG_PREFS.some(
+      pref => Services.prefs.prefIsLocked(pref));
+
+    function setInputsDisabledState(isControlled) {
+      let disabled = isLocked || isControlled;
+      for (let id of ["acceptCookies", "cookieExceptions",
+                      "acceptThirdPartyLabel", "acceptThirdPartyMenu",
+                      "keepUntil", "keepCookiesUntil", "showCookiesButton"]) {
+        document.getElementById(id).disabled = disabled;
+      }
+    }
+
+    if (isLocked) {
+      // An extension can't control this setting if either pref is locked.
+      hideControllingExtension(COOKIE_CONFIG_KEY);
+      setInputsDisabledState(false);
+    } else {
+      handleControllingExtension(
+        PREF_SETTING_TYPE,
+        COOKIE_CONFIG_KEY)
+          .then(setInputsDisabledState);
+    }
+  },
+
+  /**
+   * Disable the setting from an extension that is controlling cookies if
+   * the mode was changed away from "custom".
+   */
+  _updateCookiesExtension() {
+    getControllingExtensionInfo(PREF_SETTING_TYPE, COOKIE_CONFIG_KEY).then(
+      info => {
+        if (info && info.id &&
+            document.getElementById("historyMode").value !== "custom") {
+          ExtensionSettingsStore.setByUser(PREF_SETTING_TYPE, COOKIE_CONFIG_KEY);
+        }
+      }
+    );
+  },
+
+  /**
    * Initialize autocomplete to ensure prefs are in sync.
    */
   _initAutocomplete() {
     Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
       .getService(Components.interfaces.mozIPlacesAutoComplete);
   },
 
   /**
@@ -241,35 +309,38 @@ var gPrivacyPane = {
    */
   init() {
     function setEventListener(aId, aEventType, aCallback) {
       document.getElementById(aId)
         .addEventListener(aEventType, aCallback.bind(gPrivacyPane));
     }
 
     this._updateSanitizeSettingsButton();
+    this._updateCookiesUI();
     this.initializeHistoryMode();
     this.updateHistoryModePane();
     this.updatePrivacyMicroControls();
     this.initAutoStartPrivateBrowsingReverter();
     this._initTrackingProtection();
     this._initTrackingProtectionPBM();
     this._initTrackingProtectionExtensionControl();
+    this._initCookieExtensionControl();
     this._initAutocomplete();
 
     Preferences.get("privacy.sanitize.sanitizeOnShutdown").on("change",
       gPrivacyPane._updateSanitizeSettingsButton.bind(gPrivacyPane));
     Preferences.get("browser.privatebrowsing.autostart").on("change",
       gPrivacyPane.updatePrivacyMicroControls.bind(gPrivacyPane));
     Preferences.get("privacy.trackingprotection.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
     Preferences.get("privacy.trackingprotection.pbmode.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
     setEventListener("historyMode", "command", function() {
       gPrivacyPane.updateHistoryModePane();
+      gPrivacyPane._updateCookiesExtension();
       gPrivacyPane.updateHistoryModePrefs();
       gPrivacyPane.updatePrivacyMicroControls();
       gPrivacyPane.updateAutostart();
     });
     setEventListener("historyRememberClear", "click", function(event) {
       if (event.button == 0) {
         gPrivacyPane.clearPrivateDataNow(false);
       }
@@ -290,16 +361,19 @@ var gPrivacyPane = {
     setEventListener("openSearchEnginePreferences", "click", function(event) {
       if (event.button == 0) {
         gotoPref("search");
       }
       return false;
     });
     setEventListener("privateBrowsingAutoStart", "command",
       gPrivacyPane.updateAutostart);
+    setEventListener("disableCookiesExtension", "command",
+      makeDisableControllingExtension(
+        PREF_SETTING_TYPE, COOKIE_CONFIG_KEY));
     setEventListener("cookieExceptions", "command",
       gPrivacyPane.showCookieExceptions);
     setEventListener("showCookiesButton", "command",
       gPrivacyPane.showCookies);
     setEventListener("clearDataSettings", "command",
       gPrivacyPane.showClearPrivateDataSettings);
     setEventListener("disableTrackingProtectionExtension", "command",
       makeDisableControllingExtension(
@@ -618,16 +692,19 @@ var gPrivacyPane = {
         selectedIndex = 1;
         break;
       case "custom":
         selectedIndex = 2;
         break;
     }
     document.getElementById("historyPane").selectedIndex = selectedIndex;
     Preferences.get("privacy.history.custom").value = selectedIndex == 2;
+
+    // Update the extension controlled message.
+    this._updateCookiesUI();
   },
 
   /**
    * Update the private browsing auto-start pref and the history mode
    * micro-management prefs based on the history mode menulist
    */
   updateHistoryModePrefs() {
     let pref = Preferences.get("browser.privatebrowsing.autostart");
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -138,16 +138,22 @@
           <checkbox id="rememberHistory"
                     label="&rememberHistory2.label;"
                     accesskey="&rememberHistory2.accesskey;"
                     preference="places.history.enabled"/>
           <checkbox id="rememberForms"
                     label="&rememberSearchForm.label;"
                     accesskey="&rememberSearchForm.accesskey;"
                     preference="browser.formfill.enable"/>
+          <hbox id="cookiesExtensionContent" align="center">
+            <description control="disableCookiesExtension" flex="1" />
+            <button id="disableCookiesExtension"
+                    class="extension-controlled-button accessory-button"
+                    label="&disableExtension.label;" />
+          </hbox>
           <hbox id="cookiesBox">
             <checkbox id="acceptCookies" label="&acceptCookies2.label;"
                       preference="network.cookie.cookieBehavior"
                       accesskey="&acceptCookies2.accesskey;"
                       onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
                       onsynctopreference="return gPrivacyPane.writeAcceptCookies();"
                       flex="1" />
             <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -293,14 +293,18 @@ extensionControlled.defaultSearch = An e
 # 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.
 
 # LOCALIZATION NOTE (extensionControlled.websites.trackingProtectionMode):
 # This string is shown to notify the user that their tracking protection preferences are being controlled by an extension.
 extensionControlled.websites.trackingProtectionMode = An extension, %S, is controlling tracking protection.
 
+# LOCALIZATION NOTE (extensionControlled.websites.cookieConfig):
+# This string is shown to notify the user that their cookie settings are being controlled by an extension.
+extensionControlled.websites.cookieConfig = An extension, %S, is controlling cookies from websites.
+
 # LOCALIZATION NOTE (extensionControlled.enable):
 # %1$S is replaced with the icon for the add-ons menu.
 # %2$S is replaced with the icon for the toolbar menu.
 # This string is shown to notify the user how to enable an extension that they disabled.
 # Note, this string will be used as raw markup. Avoid characters like <, >, &
 extensionControlled.enable = To enable the extension go to %1$S Add-ons in the %2$S menu.