Bug 1341277 - Part 3: Update ext-privacy.js to support disabling and re-enabling settings. r?aswan draft
authorBob Silverberg <bsilverberg@mozilla.com>
Thu, 23 Feb 2017 09:45:37 -0500
changeset 492037 adc63f0385cc88fa4c482ac84cb9f47ef12216c3
parent 492036 abb391e6fa06972c50e9788f0896891fb43d9dd7
child 547616 31b0faaaefed3848f2b8d9369d9d9fb1f40f1d9d
push id47492
push userbmo:bob.silverberg@gmail.com
push dateThu, 02 Mar 2017 17:11:21 +0000
reviewersaswan
bugs1341277
milestone54.0a1
Bug 1341277 - Part 3: Update ext-privacy.js to support disabling and re-enabling settings. r?aswan MozReview-Commit-ID: 4Yf0uxsoXHP
toolkit/components/extensions/ext-privacy.js
toolkit/components/extensions/test/xpcshell/test_ext_privacy.js
toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js
toolkit/components/extensions/test/xpcshell/test_ext_privacy_update.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/ext-privacy.js
+++ b/toolkit/components/extensions/ext-privacy.js
@@ -8,54 +8,64 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Preferences.jsm");
 
 Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 const {
   ExtensionError,
 } = ExtensionUtils;
 
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("startup", async (type, extension) => {
+  if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(extension.startupReason)) {
+    await ExtensionPreferencesManager.enableAll(extension);
+  }
+});
+
+extensions.on("shutdown", async (type, extension) => {
+  switch (extension.shutdownReason) {
+    case "ADDON_DISABLE":
+    case "ADDON_DOWNGRADE":
+    case "ADDON_UPGRADE":
+      await ExtensionPreferencesManager.disableAll(extension);
+      break;
+
+    case "ADDON_UNINSTALL":
+      await ExtensionPreferencesManager.removeAll(extension);
+      break;
+  }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
 function checkScope(scope) {
   if (scope && scope !== "regular") {
     throw new ExtensionError(
       `Firefox does not support the ${scope} settings scope.`);
   }
 }
 
-function getAPI(extension, context, name, callback) {
-  let anythingSet = false;
+function getAPI(extension, name, callback) {
   return {
     async get(details) {
       return {
         levelOfControl: details.incognito ?
           "not_controllable" :
           await ExtensionPreferencesManager.getLevelOfControl(
             extension, name),
         value: await callback(),
       };
     },
     async set(details) {
       checkScope(details.scope);
-      if (!anythingSet) {
-        anythingSet = true;
-        context.callOnClose({
-          close: async () => {
-            if (["ADDON_DISABLE", "ADDON_UNINSTALL"].includes(extension.shutdownReason)) {
-              await ExtensionPreferencesManager.unsetAll(extension);
-              anythingSet = false;
-            }
-          },
-        });
-      }
       return await ExtensionPreferencesManager.setSetting(
         extension, name, details.value);
     },
     async clear(details) {
       checkScope(details.scope);
-      return await ExtensionPreferencesManager.unsetSetting(
+      return await ExtensionPreferencesManager.removeSetting(
         extension, name);
     },
   };
 }
 
 // Add settings objects for supported APIs to the preferences manager.
 ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", {
   prefNames: [
@@ -120,25 +130,25 @@ ExtensionPreferencesManager.addSetting("
   },
 });
 
 extensions.registerSchemaAPI("privacy.network", "addon_parent", context => {
   let {extension} = context;
   return {
     privacy: {
       network: {
-        networkPredictionEnabled: getAPI(extension, context,
+        networkPredictionEnabled: getAPI(extension,
           "network.networkPredictionEnabled",
           () => {
             return Preferences.get("network.predictor.enabled") &&
               Preferences.get("network.prefetch-next") &&
               Preferences.get("network.http.speculative-parallel-limit") > 0 &&
               !Preferences.get("network.dns.disablePrefetch");
           }),
-        webRTCIPHandlingPolicy: getAPI(extension, context,
+        webRTCIPHandlingPolicy: getAPI(extension,
           "network.webRTCIPHandlingPolicy",
           () => {
             if (Preferences.get("media.peerconnection.ice.proxy_only")) {
               return "disable_non_proxied_udp";
             }
 
             let default_address_only =
               Preferences.get("media.peerconnection.ice.default_address_only");
@@ -148,17 +158,17 @@ extensions.registerSchemaAPI("privacy.ne
               }
               return "default_public_and_private_interfaces";
             }
 
             return "default";
           }),
       },
       websites: {
-        hyperlinkAuditingEnabled: getAPI(extension, context,
+        hyperlinkAuditingEnabled: getAPI(extension,
           "websites.hyperlinkAuditingEnabled",
           () => {
             return Preferences.get("browser.send_pings");
           }),
       },
     },
   };
 });
--- a/toolkit/components/extensions/test/xpcshell/test_ext_privacy.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy.js
@@ -15,17 +15,17 @@ const {
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 add_task(async function test_privacy() {
-  // Create a object to hold the values to which we will initialize the prefs.
+  // Create an object to hold the values to which we will initialize the prefs.
   const SETTINGS = {
     "network.networkPredictionEnabled": {
       "network.predictor.enabled": true,
       "network.prefetch-next": true,
       "network.http.speculative-parallel-limit": 10,
       "network.dns.disablePrefetch": false,
     },
     "websites.hyperlinkAuditingEnabled": {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js
@@ -0,0 +1,185 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
+                                  "resource://gre/modules/ExtensionPreferencesManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+
+const {
+  createAppInfo,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function awaitEvent(eventName) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      if (_eventName === eventName) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+function awaitPrefChange(prefName) {
+  return new Promise(resolve => {
+    let listener = (args) => {
+      Preferences.ignore(prefName, listener);
+      resolve();
+    };
+
+    Preferences.observe(prefName, listener);
+  });
+}
+
+add_task(async function test_disable() {
+  const OLD_ID = "old_id@tests.mozilla.org";
+  const NEW_ID = "new_id@tests.mozilla.org";
+
+  const PREF_TO_WATCH = "network.http.speculative-parallel-limit";
+
+  // Create an object to hold the values to which we will initialize the prefs.
+  const PREFS = {
+    "network.predictor.enabled": true,
+    "network.prefetch-next": true,
+    "network.http.speculative-parallel-limit": 10,
+    "network.dns.disablePrefetch": false,
+  };
+
+  // Set prefs to our initial values.
+  for (let pref in PREFS) {
+    Preferences.set(pref, PREFS[pref]);
+  }
+
+  do_register_cleanup(() => {
+    // Reset the prefs.
+    for (let pref in PREFS) {
+      Preferences.reset(pref);
+    }
+  });
+
+  function checkPrefs(expected) {
+    for (let pref in PREFS) {
+      let msg = `${pref} set correctly.`;
+      let expectedValue = expected ? PREFS[pref] : !PREFS[pref];
+      if (pref === "network.http.speculative-parallel-limit") {
+        expectedValue = expected ? ExtensionPreferencesManager.getDefaultValue(pref) : 0;
+      }
+      equal(Preferences.get(pref), expectedValue, msg);
+    }
+  }
+
+  async function background() {
+    browser.test.onMessage.addListener(async (msg, data) => {
+      await browser.privacy.network.networkPredictionEnabled.set(data);
+      let settingData = await browser.privacy.network.networkPredictionEnabled.get({});
+      browser.test.sendMessage("privacyData", settingData);
+    });
+  }
+
+  // Create an array of extensions to install.
+  let testExtensions = [
+    ExtensionTestUtils.loadExtension({
+      background,
+      manifest: {
+        applications: {
+          gecko: {
+            id: OLD_ID,
+          },
+        },
+        permissions: ["privacy"],
+      },
+      useAddonManager: "temporary",
+    }),
+
+    ExtensionTestUtils.loadExtension({
+      background,
+      manifest: {
+        applications: {
+          gecko: {
+            id: NEW_ID,
+          },
+        },
+        permissions: ["privacy"],
+      },
+      useAddonManager: "temporary",
+    }),
+  ];
+
+  await promiseStartupManager();
+
+  for (let extension of testExtensions) {
+    await extension.startup();
+  }
+
+  // Set the value to true for the older extension.
+  testExtensions[0].sendMessage("set", {value: true});
+  let data = await testExtensions[0].awaitMessage("privacyData");
+  ok(data.value, "Value set to true for the older extension.");
+
+  // Set the value to false for the newest extension.
+  testExtensions[1].sendMessage("set", {value: false});
+  data = await testExtensions[1].awaitMessage("privacyData");
+  ok(!data.value, "Value set to false for the newest extension.");
+
+  // Verify the prefs have been set to match the "false" setting.
+  checkPrefs(false);
+
+  // Disable the newest extension.
+  let disabledPromise = awaitPrefChange(PREF_TO_WATCH);
+  let newAddon = await AddonManager.getAddonByID(NEW_ID);
+  newAddon.userDisabled = true;
+  await disabledPromise;
+
+  // Verify the prefs have been set to match the "true" setting.
+  checkPrefs(true);
+
+  // Disable the older extension.
+  disabledPromise = awaitPrefChange(PREF_TO_WATCH);
+  let oldAddon = await AddonManager.getAddonByID(OLD_ID);
+  oldAddon.userDisabled = true;
+  await disabledPromise;
+
+  // Verify the prefs have reverted back to their initial values.
+  for (let pref in PREFS) {
+    equal(Preferences.get(pref), PREFS[pref], `${pref} reset correctly.`);
+  }
+
+  // Re-enable the newest extension.
+  let enabledPromise = awaitEvent("ready");
+  newAddon.userDisabled = false;
+  await enabledPromise;
+
+  // Verify the prefs have been set to match the "false" setting.
+  checkPrefs(false);
+
+  // Re-enable the older extension.
+  enabledPromise = awaitEvent("ready");
+  oldAddon.userDisabled = false;
+  await enabledPromise;
+
+  // Verify the prefs have remained set to match the "false" setting.
+  checkPrefs(false);
+
+  for (let extension of testExtensions) {
+    await extension.unload();
+  }
+
+  await promiseShutdownManager();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy_update.js
@@ -0,0 +1,180 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+
+const {
+  createAppInfo,
+  createTempWebExtensionFile,
+  promiseAddonEvent,
+  promiseCompleteAllInstalls,
+  promiseFindAddonUpdates,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+// Allow for unsigned addons.
+AddonTestUtils.overrideCertDB();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function awaitEvent(eventName) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      if (_eventName === eventName) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+add_task(async function test_privacy_update() {
+  // Create a object to hold the values to which we will initialize the prefs.
+  const PREFS = {
+    "network.predictor.enabled": true,
+    "network.prefetch-next": true,
+    "network.http.speculative-parallel-limit": 10,
+    "network.dns.disablePrefetch": false,
+  };
+
+  const EXTENSION_ID = "test_privacy_addon_update@tests.mozilla.org";
+  const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+
+  // Set prefs to our initial values.
+  for (let pref in PREFS) {
+    Preferences.set(pref, PREFS[pref]);
+  }
+
+  do_register_cleanup(() => {
+    // Reset the prefs.
+    for (let pref in PREFS) {
+      Preferences.reset(pref);
+    }
+  });
+
+  async function background() {
+    browser.test.onMessage.addListener(async (msg, data) => {
+      let settingData;
+      switch (msg) {
+        case "get":
+          settingData = await browser.privacy.network.networkPredictionEnabled.get({});
+          browser.test.sendMessage("privacyData", settingData);
+          break;
+
+        case "set":
+          await browser.privacy.network.networkPredictionEnabled.set(data);
+          settingData = await browser.privacy.network.networkPredictionEnabled.get({});
+          browser.test.sendMessage("privacyData", settingData);
+          break;
+      }
+    });
+  }
+
+  const testServer = createHttpServer();
+  const port = testServer.identity.primaryPort;
+
+  // The test extension uses an insecure update url.
+  Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+  testServer.registerPathHandler("/test_update.json", (request, response) => {
+    response.write(`{
+      "addons": {
+        "${EXTENSION_ID}": {
+          "updates": [
+            {
+              "version": "2.0",
+              "update_link": "http://localhost:${port}/addons/test_privacy-2.0.xpi"
+            }
+          ]
+        }
+      }
+    }`);
+  });
+
+  let webExtensionFile = createTempWebExtensionFile({
+    manifest: {
+      version: "2.0",
+      applications: {
+        gecko: {
+          id: EXTENSION_ID,
+        },
+      },
+      permissions: ["privacy"],
+    },
+    background,
+  });
+
+  testServer.registerFile("/addons/test_privacy-2.0.xpi", webExtensionFile);
+
+  await promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      "version": "1.0",
+      "applications": {
+        "gecko": {
+          "id": EXTENSION_ID,
+          "update_url": `http://localhost:${port}/test_update.json`,
+        },
+      },
+      permissions: ["privacy"],
+    },
+    background,
+  });
+
+  await extension.startup();
+
+  // Change the value to false.
+  extension.sendMessage("set", {value: false});
+  let data = await extension.awaitMessage("privacyData");
+  ok(!data.value, "get returns expected value after setting.");
+
+  let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+  equal(addon.version, "1.0", "The installed addon has the expected version.");
+
+  let update = await promiseFindAddonUpdates(addon);
+  let install = update.updateAvailable;
+
+  let promiseInstalled = promiseAddonEvent("onInstalled");
+  await promiseCompleteAllInstalls([install]);
+
+  let startupPromise = awaitEvent("ready");
+
+  let [updated_addon] = await promiseInstalled;
+  equal(updated_addon.version, "2.0", "The updated addon has the expected version.");
+
+  extension.extension = await startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get");
+  data = await extension.awaitMessage("privacyData");
+  ok(!data.value, "get returns expected value after updating.");
+
+  // Verify the prefs are still set to match the "false" setting.
+  for (let pref in PREFS) {
+    let msg = `${pref} set correctly.`;
+    let expectedValue = pref === "network.http.speculative-parallel-limit" ? 0 : !PREFS[pref];
+    equal(Preferences.get(pref), expectedValue, msg);
+  }
+
+  await extension.unload();
+
+  await updated_addon.uninstall();
+
+  await promiseShutdownManager();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -44,16 +44,18 @@ skip-if = release_or_beta
 [test_ext_management.js]
 [test_ext_management_uninstall_self.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_privacy.js]
+[test_ext_privacy_disable.js]
+[test_ext_privacy_update.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]
 [test_ext_runtime_getPlatformInfo.js]
 [test_ext_runtime_onInstalled_and_onStartup.js]
 [test_ext_runtime_sendMessage.js]
 [test_ext_runtime_sendMessage_errors.js]
 [test_ext_runtime_sendMessage_no_receiver.js]
 [test_ext_runtime_sendMessage_self.js]