Bug 1407209 - Add observer for preference changes whilst extensions are being updated. r?bsilverberg r?baku r?aswan draft
authorJonathan Kingston <jkt@mozilla.com>
Mon, 16 Oct 2017 18:44:50 +0100
changeset 681655 ef3c42bf335942394d7a3eb13c15cc68bea31dd9
parent 681625 f27105b62753c71ecadad2f8d632ec7e5ac96bbd
child 736207 a32f7fc08f986567d0fa203d3d9db324014da14f
push id84894
push userbmo:jkt@mozilla.com
push dateTue, 17 Oct 2017 16:46:05 +0000
reviewersbsilverberg, baku, aswan
bugs1407209
milestone58.0a1
Bug 1407209 - Add observer for preference changes whilst extensions are being updated. r?bsilverberg r?baku r?aswan MozReview-Commit-ID: 5CqpYDc4tCg
toolkit/components/contextualidentity/ContextualIdentityService.jsm
toolkit/components/extensions/ExtensionPreferencesManager.jsm
toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -110,27 +110,39 @@ function _ContextualIdentityService(path
 
   _path: null,
   _dataReady: false,
 
   _saver: null,
 
   init(path) {
     this._path = path;
+    this._webExtensionUpdating = false;
 
     Services.prefs.addObserver(CONTEXTUAL_IDENTITY_ENABLED_PREF, this);
+    Services.obs.addObserver(this, "web-extension-preferences-replacing");
+    Services.obs.addObserver(this, "web-extension-preferences-replaced");
   },
 
-  // observe() is only used to listen to container enabling pref
-  async observe() {
-    const contextualIdentitiesEnabled = Services.prefs.getBoolPref(CONTEXTUAL_IDENTITY_ENABLED_PREF);
-    if (!contextualIdentitiesEnabled) {
-      await this.closeContainerTabs();
-      this.notifyAllContainersCleared();
-      this.resetDefault();
+  async observe(aSubject, aTopic) {
+    switch (aTopic) {
+      case "web-extension-preferences-replacing":
+        this._webExtensionUpdating = true;
+        break;
+      case "web-extension-preferences-replaced":
+        this._webExtensionUpdating = false;
+        // We want to check the pref when the extension has been replaced too
+      case "nsPref:changed":
+        const contextualIdentitiesEnabled = Services.prefs.getBoolPref(CONTEXTUAL_IDENTITY_ENABLED_PREF);
+        if (!contextualIdentitiesEnabled && !this._webExtensionUpdating) {
+          await this.closeContainerTabs();
+          this.notifyAllContainersCleared();
+          this.resetDefault();
+        }
+        break;
     }
   },
 
   load() {
     OS.File.read(this._path).then(bytes => {
       // If synchronous loading happened in the meantime, exit now.
       if (this._dataReady) {
         return;
--- a/toolkit/components/extensions/ExtensionPreferencesManager.jsm
+++ b/toolkit/components/extensions/ExtensionPreferencesManager.jsm
@@ -20,44 +20,57 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["ExtensionPreferencesManager"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
                                   "resource://gre/modules/ExtensionSettingsStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "defaultPreferences", function() {
   return new Preferences({defaultBranch: true});
 });
 
+const ADDON_REPLACE_REASONS = new Set([
+  "ADDON_DOWNGRADE",
+  "ADDON_UPGRADE",
+]);
+
 /* eslint-disable mozilla/balanced-listeners */
 Management.on("shutdown", (type, extension) => {
   switch (extension.shutdownReason) {
     case "ADDON_DISABLE":
     case "ADDON_DOWNGRADE":
     case "ADDON_UPGRADE":
+      if (ADDON_REPLACE_REASONS.has(extension.shutdownReason)) {
+        Services.obs.notifyObservers(null, "web-extension-preferences-replacing");
+      }
       this.ExtensionPreferencesManager.disableAll(extension);
       break;
 
     case "ADDON_UNINSTALL":
       this.ExtensionPreferencesManager.removeAll(extension);
       break;
   }
 });
 
-Management.on("startup", (type, extension) => {
+Management.on("startup", async (type, extension) => {
   if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(extension.startupReason)) {
-    this.ExtensionPreferencesManager.enableAll(extension);
+    const enablePromise = this.ExtensionPreferencesManager.enableAll(extension);
+    if (ADDON_REPLACE_REASONS.has(extension.startupReason)) {
+      await enablePromise;
+      Services.obs.notifyObservers(null, "web-extension-preferences-replaced");
+    }
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 const STORE_TYPE = "prefs";
 
 // Definitions of settings, each of which correspond to a different API.
 let settingsMap = new Map();
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -120,17 +120,17 @@ add_task(async function test_contextualI
 
   Services.prefs.clearUserPref(CONTAINERS_PREF);
 });
 
 add_task(async function test_contextualIdentity_with_permissions() {
   const CONTAINERS_PREF = "privacy.userContext.enabled";
   const initial = Services.prefs.getBoolPref(CONTAINERS_PREF);
 
-  async function background(ver) {
+  async function background() {
     let ci;
     await browser.test.assertRejects(browser.contextualIdentities.get("foobar"), "Invalid contextual identity: foobar", "API should reject here");
     await browser.test.assertRejects(browser.contextualIdentities.update("foobar", {name: "testing"}), "Invalid contextual identity: foobar", "API should reject for unknown updates");
     await browser.test.assertRejects(browser.contextualIdentities.remove("foobar"), "Invalid contextual identity: foobar", "API should reject for removing unknown containers");
 
     ci = await browser.contextualIdentities.get("firefox-container-1");
     browser.test.assertTrue(!!ci, "We have an identity");
     browser.test.assertTrue("name" in ci, "We have an identity.name");
@@ -260,17 +260,17 @@ add_task(async function test_contextualI
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), initial, "Pref should now be initial state");
 
   Services.prefs.clearUserPref(CONTAINERS_PREF);
 });
 
 add_task(async function test_contextualIdentity_extensions_enable_containers() {
   const CONTAINERS_PREF = "privacy.userContext.enabled";
   const initial = Services.prefs.getBoolPref(CONTAINERS_PREF);
-  async function background(ver) {
+  async function background() {
     let ci = await browser.contextualIdentities.get("firefox-container-1");
     browser.test.assertTrue(!!ci, "We have an identity");
 
     browser.test.notifyPass("contextualIdentities");
   }
   function makeExtension(id) {
     return ExtensionTestUtils.loadExtension({
       useAddonManager: "temporary",
@@ -323,8 +323,59 @@ add_task(async function test_contextualI
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled 1");
   await extension3.unload();
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled 2");
   await extension2.unload();
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled 3");
 
   Services.prefs.clearUserPref(CONTAINERS_PREF);
 });
+
+add_task(async function test_contextualIdentity_preference_change() {
+  const CONTAINERS_PREF = "privacy.userContext.enabled";
+  async function background() {
+    let extensionInfo = await browser.management.getSelf();
+    if (extensionInfo.version == "1.0.0") {
+      const containers = await browser.contextualIdentities.query({});
+      browser.test.assertEq(containers.length, 4, "We still have the original containers");
+      await browser.contextualIdentities.create({
+        name: "foobar",
+        color: "red",
+        icon: "circle",
+      });
+    }
+    const containers = await browser.contextualIdentities.query({});
+    browser.test.assertEq(containers.length, 5, "We have a new container");
+    if (extensionInfo.version == "1.1.0") {
+      await browser.contextualIdentities.remove(containers[4].cookieStoreId);
+    }
+    browser.test.notifyPass("contextualIdentities");
+  }
+  function makeExtension(id, version) {
+    return ExtensionTestUtils.loadExtension({
+      useAddonManager: "temporary",
+      background,
+      manifest: {
+        version,
+        applications: {
+          gecko: {id},
+        },
+        permissions: ["contextualIdentities"],
+      },
+    });
+  }
+
+  Services.prefs.setBoolPref(CONTAINERS_PREF, false);
+  let extension = makeExtension("containers-pref-test@mozilla.org", "1.0.0");
+  await extension.startup();
+  await extension.awaitFinish("contextualIdentities");
+  equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled, whatever it's initial state");
+
+  let extension2 = makeExtension("containers-pref-test@mozilla.org", "1.1.0");
+  await extension2.startup();
+  await extension2.awaitFinish("contextualIdentities");
+
+  const prefChange = waitForPrefChange(CONTAINERS_PREF);
+  await extension.unload();
+  await prefChange;
+
+  Services.prefs.clearUserPref(CONTAINERS_PREF);
+});