Bug 1341277 - Part 3: Update ext-privacy.js to support disabling and re-enabling settings. r=aswan
authorBob Silverberg <bsilverberg@mozilla.com>
Thu, 23 Feb 2017 09:45:37 -0500
changeset 394639 ce4a71e11833f4ade0976842576e158224876ede
parent 394638 02d52f2c1d5534b3ac784ffe3dcf1c27ad6d7281
child 394640 b93c92e29ed6ee69d30ad3120f689ed3e92edf29
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1341277
milestone54.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 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]