Bug 1404584 - Respond to manifest keys being removed for homepage and default search engine, r?kmag r?mkaply draft
authorBob Silverberg <bsilverberg@mozilla.com>
Wed, 04 Oct 2017 17:13:29 -0400
changeset 680202 8b0e1062b47964e2ba227079e9097c80cd8d0aa9
parent 680024 196dadb2fe500e75c6fbddcac78106648676cf10
child 735785 626d108cf212ba9fd0610107b071138cd55779f9
push id84420
push userbmo:bob.silverberg@gmail.com
push dateFri, 13 Oct 2017 16:49:29 +0000
reviewerskmag, mkaply
bugs1404584
milestone58.0a1
Bug 1404584 - Respond to manifest keys being removed for homepage and default search engine, r?kmag r?mkaply When an extension removes either chrome_settings_overrides.homepage or chrome_settings_overrides.search_provider.is_default from the manifest and then updates, currently the setting is not reverted. This patch fixes that. MozReview-Commit-ID: 7oeNRgQT5hH
browser/components/extensions/ext-chrome-settings-overrides.js
browser/components/extensions/test/xpcshell/test_ext_chrome_settings_override_update.js
browser/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/components/extensions/Extension.jsm
--- a/browser/components/extensions/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -69,19 +69,24 @@ this.chrome_settings_overrides = class e
     if (manifest.chrome_settings_overrides.homepage) {
       ExtensionPreferencesManager.setSetting(extension, "homepage_override",
                                              manifest.chrome_settings_overrides.homepage);
     }
     if (manifest.chrome_settings_overrides.search_provider) {
       await searchInitialized();
       extension.callOnClose({
         close: () => {
-          if (extension.shutdownReason == "ADDON_DISABLE" ||
-              extension.shutdownReason == "ADDON_UNINSTALL") {
+          if (["ADDON_UNINSTALL", "ADDON_DISABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"]
+              .includes(extension.shutdownReason)) {
             switch (extension.shutdownReason) {
+              case "ADDON_DOWNGRADE":
+              case "ADDON_UPGRADE":
+                this.processDefaultSearchSetting("disable");
+                return;
+
               case "ADDON_DISABLE":
                 this.processDefaultSearchSetting("disable");
                 break;
 
               case "ADDON_UNINSTALL":
                 this.processDefaultSearchSetting("removeSetting");
                 break;
             }
@@ -153,17 +158,18 @@ this.chrome_settings_overrides = class e
   async setDefault(engineName) {
     let {extension} = this;
     if (extension.startupReason === "ADDON_INSTALL") {
       let item = await ExtensionSettingsStore.addSetting(
         extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => {
           return Services.search.currentEngine.name;
         });
       Services.search.currentEngine = Services.search.getEngineByName(item.value);
-    } else if (extension.startupReason === "ADDON_ENABLE") {
+    } else if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"]
+               .includes(extension.startupReason)) {
       this.processDefaultSearchSetting("enable");
     }
   }
 
   addSearchEngine(searchProvider) {
     let {extension} = this;
     let isCurrent = false;
     let index = -1;
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_override_update.js
@@ -0,0 +1,136 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+
+Cu.import("resource://testing-common/AddonTestUtils.jsm");
+
+const {
+  createAppInfo,
+  createTempWebExtensionFile,
+  promiseCompleteAllInstalls,
+  promiseFindAddonUpdates,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+// Allow for unsigned addons.
+AddonTestUtils.overrideCertDB();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+add_task(async function test_overrides_homepage_update() {
+  const EXTENSION_ID = "test_overrides_update@tests.mozilla.org";
+  const HOMEPAGE_URI = "webext-homepage-1.html";
+  const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+
+  const HOMEPAGE_URL_PREF = "browser.startup.homepage";
+
+  const getHomePageURL = () => {
+    return Services.prefs.getComplexValue(
+      HOMEPAGE_URL_PREF, Ci.nsIPrefLocalizedString).data;
+  };
+
+  const testServer = createHttpServer();
+  const port = testServer.identity.primaryPort;
+
+  function promisePrefChanged(value) {
+    return new Promise((resolve, reject) => {
+      Services.prefs.addObserver(HOMEPAGE_URL_PREF, function observer() {
+        if (getHomePageURL().endsWith(value)) {
+          Services.prefs.removeObserver(HOMEPAGE_URL_PREF, observer);
+          resolve();
+        }
+      });
+    });
+  }
+
+  // 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_override-2.0.xpi"
+            }
+          ]
+        }
+      }
+    }`);
+  });
+
+  let webExtensionFile = createTempWebExtensionFile({
+    manifest: {
+      version: "2.0",
+      applications: {
+        gecko: {
+          id: EXTENSION_ID,
+        },
+      },
+    },
+  });
+
+  testServer.registerFile("/addons/test_override-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`,
+        },
+      },
+      "chrome_settings_overrides": {
+        "homepage": HOMEPAGE_URI,
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+  });
+
+  let defaultHomepageURL = getHomePageURL();
+  let defaultEngineName = Services.search.currentEngine.name;
+
+  let prefPromise = promisePrefChanged(HOMEPAGE_URI);
+  await extension.startup();
+  await prefPromise;
+
+  equal(extension.version, "1.0", "The installed addon has the expected version.");
+  ok(getHomePageURL().endsWith(HOMEPAGE_URI),
+     "Home page url is overriden by the extension.");
+  equal(Services.search.currentEngine.name, "DuckDuckGo",
+        "Default engine is overriden by the extension");
+
+  let update = await promiseFindAddonUpdates(extension.addon);
+  let install = update.updateAvailable;
+  await promiseCompleteAllInstalls([install]);
+
+  prefPromise = promisePrefChanged(defaultHomepageURL);
+  await extension.awaitStartup();
+  await prefPromise;
+
+  equal(extension.version, "2.0", "The updated addon has the expected version.");
+  equal(getHomePageURL(), defaultHomepageURL,
+        "Home page url reverted to the default after update.");
+  equal(Services.search.currentEngine.name, defaultEngineName,
+        "Default engine reverted to the default after update.");
+
+  await extension.unload();
+
+  await promiseShutdownManager();
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -1,11 +1,11 @@
 [test_ext_bookmarks.js]
 [test_ext_browsingData.js]
 [test_ext_browsingData_cookies_cache.js]
 [test_ext_browsingData_downloads.js]
 [test_ext_browsingData_passwords.js]
 [test_ext_browsingData_settings.js]
+[test_ext_chrome_settings_override_update.js]
 [test_ext_geckoProfiler_control.js]
 [test_ext_history.js]
 [test_ext_url_overrides_newtab.js]
 [test_ext_url_overrides_newtab_update.js]
-
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -44,16 +44,17 @@ Cu.importGlobalProperties(["fetch"]);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm",
   ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
+  ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.jsm",
   ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
   ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
   FileSource: "resource://gre/modules/L10nRegistry.jsm",
   L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
   Log: "resource://gre/modules/Log.jsm",
   MessageChannel: "resource://gre/modules/MessageChannel.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   OS: "resource://gre/modules/osfile.jsm",
@@ -1354,17 +1355,17 @@ this.Extension = class extends Extension
         this.defaultLocale);
 
       locale = matches[0];
     }
 
     return super.initLocale(locale);
   }
 
-  updatePermissions(reason) {
+  async updatePermissionsAndSettings(reason) {
     const {principal} = this;
 
     const testPermission = perm =>
       Services.perms.testPermissionFromPrincipal(principal, perm);
 
     // Only update storage permissions when the extension changes in
     // some way.
     if (reason !== "APP_STARTUP" && reason !== "APP_SHUTDOWN") {
@@ -1393,16 +1394,44 @@ this.Extension = class extends Extension
                                           Services.perms.ALLOW_ACTION,
                                           Services.perms.EXPIRE_SESSION);
         }
       } else if (reason !== "APP_STARTUP" &&
                  testPermission("geo") === Services.perms.ALLOW_ACTION) {
         Services.perms.removeFromPrincipal(principal, "geo");
       }
     }
+
+    // Remove settings for manifest keys that have been removed.
+    let settings = [
+      {
+        storeType: "default_search",
+        settingName: "defaultSearch",
+        keyName: "search_provider",
+      },
+      {
+        storeType: "prefs",
+        settingName: "homepage_override",
+        keyName: "homepage",
+      },
+    ];
+
+    if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(reason)) {
+      for (let setting of settings) {
+        if (this.manifest.chrome_settings_overrides === null
+           || this.manifest.chrome_settings_overrides[setting.keyName] === null) {
+          await ExtensionSettingsStore.initialize();
+          if (ExtensionSettingsStore.hasSetting(
+              this, setting.storeType, setting.settingName)) {
+            ExtensionSettingsStore.removeSetting(
+              this, setting.storeType, setting.settingName);
+          }
+        }
+      }
+    }
   }
 
   startup() {
     this.startupPromise = this._startup();
 
     return this.startupPromise;
   }
 
@@ -1444,17 +1473,17 @@ this.Extension = class extends Extension
         return;
       }
 
       GlobalManager.init(this);
 
       this.policy.active = false;
       this.policy = processScript.initExtension(this);
 
-      this.updatePermissions(this.startupReason);
+      await this.updatePermissionsAndSettings(this.startupReason);
 
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
       // and it is used to run code that needs to be executed before
       // any of the "startup" listeners.
       this.emit("startup", this);
       Management.emit("startup", this);
 
@@ -1555,17 +1584,17 @@ this.Extension = class extends Extension
       StartupCache.clearAddonData(this.id);
     }
 
     let data = Services.ppmm.initialProcessData;
     data["Extension:Extensions"] = data["Extension:Extensions"].filter(e => e.id !== this.id);
 
     Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
-    this.updatePermissions(this.shutdownReason);
+    await this.updatePermissionsAndSettings(this.shutdownReason);
 
     if (!this.manifest) {
       this.policy.active = false;
 
       return this.cleanupGeneratedFile();
     }
 
     GlobalManager.uninit(this);