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
--- 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);