Bug 1330494 - Part 2: Use the ExtensionsSettingsStore to handle precedence for extensions using chrome_url_overrides, r?aswan
MozReview-Commit-ID: 5sKtIXjdSmC
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -218,16 +218,17 @@ extensions.registerModules({
paths: [
["tabs"],
],
},
urlOverrides: {
url: "chrome://browser/content/ext-url-overrides.js",
schema: "chrome://browser/content/schemas/url_overrides.json",
scopes: ["addon_parent"],
+ events: ["startup"],
manifest: ["chrome_url_overrides"],
paths: [
["urlOverrides"],
],
},
windows: {
url: "chrome://browser/content/ext-windows.js",
schema: "chrome://browser/content/schemas/windows.json",
--- a/browser/components/extensions/ext-url-overrides.js
+++ b/browser/components/extensions/ext-url-overrides.js
@@ -1,53 +1,70 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
+ "resource://gre/modules/ExtensionSettingsStore.jsm");
+
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
-// Bug 1320736 tracks creating a generic precedence manager for handling
-// multiple addons modifying the same properties, and bug 1330494 has been filed
-// to track utilizing this manager for chrome_url_overrides. Until those land,
-// the edge cases surrounding multiple addons using chrome_url_overrides will
-// be ignored and precedence will be first come, first serve.
-let overrides = {
- // A queue of extensions in line to override the newtab page (sorted oldest to newest).
- newtab: [],
-};
+const STORE_TYPE = "url_overrides";
+const NEW_TAB_SETTING_NAME = "newTabURL";
this.urlOverrides = class extends ExtensionAPI {
- onManifestEntry(entryName) {
+ processNewTabSetting(action) {
+ let {extension} = this;
+ let item = ExtensionSettingsStore[action](extension, STORE_TYPE, NEW_TAB_SETTING_NAME);
+ if (item) {
+ aboutNewTabService.newTabURL = item.value || item.initialValue;
+ }
+ }
+
+ async onStartup() {
let {extension} = this;
let {manifest} = extension;
- if (manifest.chrome_url_overrides.newtab) {
- let newtab = manifest.chrome_url_overrides.newtab;
- let url = extension.baseURI.resolve(newtab);
+ // Record the setting if it exists in the manifest.
+ if (manifest.chrome_url_overrides && manifest.chrome_url_overrides.newtab) {
+ let url = extension.baseURI.resolve(manifest.chrome_url_overrides.newtab);
- // Only set the newtab URL if no other extension is overriding it.
- if (!overrides.newtab.length) {
- aboutNewTabService.newTabURL = url;
+ let item = await ExtensionSettingsStore.addSetting(
+ extension, STORE_TYPE, NEW_TAB_SETTING_NAME, url,
+ () => aboutNewTabService.newTabURL);
+
+ // If the extension was just re-enabled, change the setting to enabled.
+ // This is required because addSetting above is used for both add and update.
+ if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"]
+ .includes(extension.startupReason)) {
+ item = ExtensionSettingsStore.enable(extension, STORE_TYPE, NEW_TAB_SETTING_NAME);
}
- overrides.newtab.push({id: extension.id, url});
+ // Set the newTabURL to the current value of the setting.
+ if (item) {
+ aboutNewTabService.newTabURL = item.value || item.initialValue;
+ }
+ // If the setting exists for the extension, but is missing from the manifest,
+ // remove it.
+ } else if (ExtensionSettingsStore.hasSetting(
+ extension, STORE_TYPE, NEW_TAB_SETTING_NAME)) {
+ this.processNewTabSetting("removeSetting");
}
}
- onShutdown(reason) {
- let {extension} = this;
+ onShutdown(shutdownReason) {
+ switch (shutdownReason) {
+ case "ADDON_DISABLE":
+ case "ADDON_DOWNGRADE":
+ case "ADDON_UPGRADE":
+ this.processNewTabSetting("disable");
+ break;
- let i = overrides.newtab.findIndex(o => o.id === extension.id);
- if (i !== -1) {
- overrides.newtab.splice(i, 1);
-
- if (overrides.newtab.length) {
- aboutNewTabService.newTabURL = overrides.newtab[0].url;
- } else {
- aboutNewTabService.resetNewTabURL();
- }
+ case "ADDON_UNINSTALL":
+ this.processNewTabSetting("removeSetting");
+ break;
}
}
};
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -1,110 +1,47 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
-XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
- "@mozilla.org/browser/aboutnewtab-service;1",
- "nsIAboutNewTabService");
-
const NEWTAB_URI_1 = "webext-newtab-1.html";
-const NEWTAB_URI_2 = "webext-newtab-2.html";
-const NEWTAB_URI_3 = "webext-newtab-3.html";
-
-add_task(async function test_multiple_extensions_overriding_newtab_page() {
- is(aboutNewTabService.newTabURL, "about:newtab",
- "Default newtab url should be about:newtab");
-
- let ext1 = ExtensionTestUtils.loadExtension({
- manifest: {"chrome_url_overrides": {}},
- });
-
- let ext2 = ExtensionTestUtils.loadExtension({
- manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI_1}},
- });
-
- let ext3 = ExtensionTestUtils.loadExtension({
- manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI_2}},
- });
-
- let ext4 = ExtensionTestUtils.loadExtension({
- manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI_3}},
- });
-
- await ext1.startup();
-
- is(aboutNewTabService.newTabURL, "about:newtab",
- "Default newtab url should still be about:newtab");
-
- await ext2.startup();
-
- ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
- "Newtab url should be overriden by the second extension.");
-
- await ext1.unload();
-
- ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
- "Newtab url should still be overriden by the second extension.");
-
- await ext3.startup();
-
- ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
- "Newtab url should still be overriden by the second extension.");
-
- await ext2.unload();
-
- ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
- "Newtab url should be overriden by the third extension.");
-
- await ext4.startup();
-
- ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
- "Newtab url should be overriden by the third extension.");
-
- await ext4.unload();
-
- ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
- "Newtab url should be overriden by the third extension.");
-
- await ext3.unload();
-
- is(aboutNewTabService.newTabURL, "about:newtab",
- "Newtab url should be reset to about:newtab");
-});
add_task(async function test_sending_message_from_newtab_page() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"chrome_url_overrides": {
- newtab: NEWTAB_URI_2,
+ newtab: NEWTAB_URI_1,
},
},
+ useAddonManager: "temporary",
files: {
- [NEWTAB_URI_2]: `
+ [NEWTAB_URI_1]: `
<!DOCTYPE html>
<head>
<meta charset="utf-8"/></head>
<html>
<body>
<script src="newtab.js"></script>
</body>
</html>
`,
"newtab.js": function() {
window.onload = () => {
- browser.test.sendMessage("from-newtab-page");
+ browser.test.sendMessage("from-newtab-page", window.location.href);
};
},
},
});
await extension.startup();
// Simulate opening the newtab open as a user would.
BrowserOpenTab();
- await extension.awaitMessage("from-newtab-page");
+ let url = await extension.awaitMessage("from-newtab-page");
+ ok(url.endsWith(NEWTAB_URI_1),
+ "Newtab url is overriden by the extension.");
+
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await extension.unload();
});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab.js
@@ -0,0 +1,123 @@
+/* -*- 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");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+Cu.import("resource://testing-common/AddonTestUtils.jsm");
+
+const {
+ createAppInfo,
+ promiseShutdownManager,
+ promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function awaitEvent(eventName) {
+ return new Promise(resolve => {
+ Management.once(eventName, (e, ...args) => resolve(...args));
+ });
+}
+
+add_task(async function test_multiple_extensions_overriding_newtab_page() {
+ const NEWTAB_URI_1 = "webext-newtab-1.html";
+ const NEWTAB_URI_2 = "webext-newtab-2.html";
+ const EXT_2_ID = "ext2@tests.mozilla.org";
+
+ equal(aboutNewTabService.newTabURL, "about:newtab",
+ "Default newtab url is about:newtab");
+
+ await promiseStartupManager();
+
+ let ext1 = ExtensionTestUtils.loadExtension({
+ manifest: {"chrome_url_overrides": {}},
+ useAddonManager: "temporary",
+ });
+
+ let ext2 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "chrome_url_overrides": {newtab: NEWTAB_URI_1},
+ applications: {
+ gecko: {
+ id: EXT_2_ID,
+ },
+ },
+ },
+ useAddonManager: "temporary",
+ });
+
+ let ext3 = ExtensionTestUtils.loadExtension({
+ manifest: {"chrome_url_overrides": {newtab: NEWTAB_URI_2}},
+ useAddonManager: "temporary",
+ });
+
+ await ext1.startup();
+ equal(aboutNewTabService.newTabURL, "about:newtab",
+ "Default newtab url is still about:newtab");
+
+ await ext2.startup();
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
+ "Newtab url is overriden by the second extension.");
+
+ // Disable the second extension.
+ let addon = await AddonManager.getAddonByID(EXT_2_ID);
+ let disabledPromise = awaitEvent("shutdown");
+ addon.userDisabled = true;
+ await disabledPromise;
+ equal(aboutNewTabService.newTabURL, "about:newtab",
+ "Newtab url is about:newtab after second extension is disabled.");
+
+ // Re-enable the second extension.
+ let enabledPromise = awaitEvent("ready");
+ addon.userDisabled = false;
+ await enabledPromise;
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
+ "Newtab url is overriden by the second extension.");
+
+ await ext1.unload();
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
+ "Newtab url is still overriden by the second extension.");
+
+ await ext3.startup();
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
+ "Newtab url is overriden by the third extension.");
+
+ // Disable the second extension.
+ disabledPromise = awaitEvent("shutdown");
+ addon.userDisabled = true;
+ await disabledPromise;
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
+ "Newtab url is still overriden by the third extension.");
+
+ // Re-enable the second extension.
+ enabledPromise = awaitEvent("ready");
+ addon.userDisabled = false;
+ await enabledPromise;
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_2),
+ "Newtab url is still overriden by the third extension.");
+
+ await ext3.unload();
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI_1),
+ "Newtab url reverts to being overriden by the second extension.");
+
+ await ext2.unload();
+ equal(aboutNewTabService.newTabURL, "about:newtab",
+ "Newtab url is reset to about:newtab");
+
+ await promiseShutdownManager();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_url_overrides_newtab_update.js
@@ -0,0 +1,116 @@
+/* -*- 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");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+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_url_overrides_newtab_update() {
+ const EXTENSION_ID = "test_url_overrides_update@tests.mozilla.org";
+ const NEWTAB_URI = "webext-newtab-1.html";
+ const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+
+ 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_url_overrides-2.0.xpi"
+ }
+ ]
+ }
+ }
+ }`);
+ });
+
+ let webExtensionFile = createTempWebExtensionFile({
+ manifest: {
+ version: "2.0",
+ applications: {
+ gecko: {
+ id: EXTENSION_ID,
+ },
+ },
+ },
+ });
+
+ testServer.registerFile("/addons/test_url_overrides-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_url_overrides: {newtab: NEWTAB_URI},
+ },
+ });
+
+ let defaultNewTabURL = aboutNewTabService.newTabURL;
+ equal(aboutNewTabService.newTabURL, defaultNewTabURL,
+ `Default newtab url is ${defaultNewTabURL}.`);
+
+ await extension.startup();
+
+ equal(extension.version, "1.0", "The installed addon has the expected version.");
+ ok(aboutNewTabService.newTabURL.endsWith(NEWTAB_URI),
+ "Newtab url is overriden by the extension.");
+
+ let update = await promiseFindAddonUpdates(extension.addon);
+ let install = update.updateAvailable;
+
+ await promiseCompleteAllInstalls([install]);
+
+ await extension.awaitStartup();
+
+ equal(extension.version, "2.0", "The updated addon has the expected version.");
+ equal(aboutNewTabService.newTabURL, defaultNewTabURL,
+ "Newtab url 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,8 +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_geckoProfiler_control.js]
[test_ext_history.js]
-[test_ext_geckoProfiler_control.js]
+[test_ext_url_overrides_newtab.js]
+[test_ext_url_overrides_newtab_update.js]
+