summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js751
1 files changed, 751 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js
new file mode 100644
index 0000000000..616dc1fb50
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js
@@ -0,0 +1,751 @@
+"use strict";
+
+// Delay loading until createAppInfo is called and setup.
+ChromeUtils.defineESModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+});
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+
+// The app and platform version here should be >= of the version set in the extensions.webExtensionsMinPlatformVersion preference,
+// otherwise test_persistent_listener_after_staged_update will fail because no compatible updates will be found.
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "42",
+ "42"
+);
+
+let { promiseShutdownManager, promiseStartupManager, promiseRestartManager } =
+ AddonTestUtils;
+
+const server = createHttpServer({ hosts: ["example.com"] });
+server.registerDirectory("/data/", do_get_file("data"));
+
+let scopes = AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION;
+Services.prefs.setIntPref("extensions.enabledScopes", scopes);
+
+function trackEvents(wrapper) {
+ let events = new Map();
+ for (let event of ["background-script-event", "start-background-script"]) {
+ events.set(event, false);
+ wrapper.extension.once(event, () => events.set(event, true));
+ }
+ return events;
+}
+
+/**
+ * That that we get the expected events
+ *
+ * @param {Extension} extension
+ * @param {Map} events
+ * @param {object} expect
+ * @param {boolean} expect.background delayed startup event expected
+ * @param {boolean} expect.started background has already started
+ * @param {boolean} expect.delayedStart startup is delayed, notify start and
+ * expect the starting event
+ * @param {boolean} expect.request wait for the request event
+ */
+async function testPersistentRequestStartup(extension, events, expect = {}) {
+ equal(
+ events.get("background-script-event"),
+ !!expect.background,
+ "Should have gotten a background script event"
+ );
+ equal(
+ events.get("start-background-script"),
+ !!expect.started,
+ "Background script should be started"
+ );
+
+ if (!expect.started) {
+ AddonTestUtils.notifyEarlyStartup();
+ await ExtensionParent.browserPaintedPromise;
+
+ equal(
+ events.get("start-background-script"),
+ !!expect.delayedStart,
+ "Should have gotten start-background-script event"
+ );
+ }
+
+ if (expect.request) {
+ await extension.awaitMessage("got-request");
+ ok(true, "Background page loaded and received webRequest event");
+ }
+}
+
+// Test that a non-blocking listener does not start the background on
+// startup, but that it does work after startup.
+add_task(async function test_nonblocking() {
+ await promiseStartupManager();
+
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ permissions: ["webRequest", "http://example.com/"],
+ },
+
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] }
+ );
+ browser.test.sendMessage("ready");
+ },
+ });
+
+ // First install runs background immediately, this sets persistent listeners
+ await extension.startup();
+ await extension.awaitMessage("ready");
+
+ // Restart to get APP_STARTUP, the background should not start
+ await promiseRestartManager({ lateStartup: false });
+ await extension.awaitStartup();
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ });
+
+ // Test an early startup event
+ let events = trackEvents(extension);
+
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+
+ await testPersistentRequestStartup(extension, events, {
+ background: false,
+ delayedStart: false,
+ request: false,
+ });
+
+ AddonTestUtils.notifyLateStartup();
+ await extension.awaitMessage("ready");
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ });
+
+ // Test an event after startup
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+
+ await testPersistentRequestStartup(extension, events, {
+ background: false,
+ started: true,
+ request: true,
+ });
+
+ await extension.unload();
+
+ await promiseShutdownManager();
+});
+
+// Test that a non-blocking listener does not start the background on
+// startup, but that it does work after startup.
+add_task(async function test_eventpage_nonblocking() {
+ Services.prefs.setBoolPref("extensions.eventPages.enabled", true);
+ await promiseStartupManager();
+
+ let id = "event-nonblocking@test";
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ browser_specific_settings: { gecko: { id } },
+ permissions: ["webRequest", "http://example.com/"],
+ background: { persistent: false },
+ },
+
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] }
+ );
+ },
+ });
+
+ // First install runs background immediately, this sets persistent listeners
+ await extension.startup();
+
+ // Restart to get APP_STARTUP, the background should not start
+ await promiseRestartManager({ lateStartup: false });
+ await extension.awaitStartup();
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ });
+
+ // Test an early startup event
+ let events = trackEvents(extension);
+
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+
+ await testPersistentRequestStartup(extension, events);
+
+ await AddonTestUtils.notifyLateStartup();
+ // After late startup, event page listeners should be primed.
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: true,
+ });
+
+ // We should not have seen any events yet.
+ await testPersistentRequestStartup(extension, events);
+
+ // Test an event after startup
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+
+ // Now the event page should be started and we'll see the request.
+ await testPersistentRequestStartup(extension, events, {
+ background: true,
+ started: true,
+ request: true,
+ });
+
+ await extension.unload();
+
+ await promiseShutdownManager();
+ Services.prefs.setBoolPref("extensions.eventPages.enabled", false);
+});
+
+// Tests that filters are handled properly: if we have a blocking listener
+// with a filter, a request that does not match the filter does not get
+// suspended and does not start the background page.
+add_task(async function test_persistent_blocking() {
+ await promiseStartupManager();
+
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "http://test1.example.com/",
+ ],
+ },
+
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.fail("Listener should not have been called");
+ },
+ { urls: ["http://test1.example.com/*"] },
+ ["blocking"]
+ );
+ },
+ });
+
+ await extension.startup();
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ });
+
+ await promiseRestartManager({ lateStartup: false });
+ await extension.awaitStartup();
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: true,
+ });
+
+ let events = trackEvents(extension);
+
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+
+ await testPersistentRequestStartup(extension, events, {
+ background: false,
+ delayedStart: false,
+ request: false,
+ });
+
+ AddonTestUtils.notifyLateStartup();
+
+ await extension.unload();
+ await promiseShutdownManager();
+});
+
+// Tests that moving permission to optional retains permission and that the
+// persistent listeners are used as expected.
+add_task(async function test_persistent_listener_after_sideload_upgrade() {
+ let id = "permission-sideload-upgrade@test";
+ let extensionData = {
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ browser_specific_settings: { gecko: { id } },
+ permissions: ["webRequest", "webRequestBlocking", "http://example.com/"],
+ },
+
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] },
+ ["blocking"]
+ );
+ },
+ };
+ let xpi = AddonTestUtils.createTempWebExtensionFile(extensionData);
+
+ let extension = ExtensionTestUtils.expectExtension(id);
+ await AddonTestUtils.manuallyInstall(xpi);
+ await promiseStartupManager();
+ await extension.awaitStartup();
+ // Sideload install does not prime listeners
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ });
+
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+ await extension.awaitMessage("got-request");
+
+ await promiseShutdownManager();
+
+ // Prepare a sideload update for the extension.
+ extensionData.manifest.version = "2.0";
+ extensionData.manifest.permissions = ["http://example.com/"];
+ extensionData.manifest.optional_permissions = [
+ "webRequest",
+ "webRequestBlocking",
+ ];
+ xpi = AddonTestUtils.createTempWebExtensionFile(extensionData);
+ await AddonTestUtils.manuallyInstall(xpi);
+
+ await promiseStartupManager();
+ await extension.awaitStartup();
+ // Upgrades start the background when the extension is loaded, so
+ // primed listeners are cleared already and background events are
+ // already completed.
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ persisted: true,
+ });
+
+ await extension.unload();
+ await promiseShutdownManager();
+});
+
+// Utility to install builtin addon
+async function installBuiltinExtension(extensionData) {
+ let xpi = await AddonTestUtils.createTempWebExtensionFile(extensionData);
+
+ // The built-in location requires a resource: URL that maps to a
+ // jar: or file: URL. This would typically be something bundled
+ // into omni.ja but for testing we just use a temp file.
+ let base = Services.io.newURI(`jar:file:${xpi.path}!/`);
+ let resProto = Services.io
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ resProto.setSubstitution("ext-test", base);
+ return AddonManager.installBuiltinAddon("resource://ext-test/");
+}
+
+function promisePostponeInstall(install) {
+ return new Promise((resolve, reject) => {
+ let listener = {
+ onInstallFailed: () => {
+ install.removeListener(listener);
+ reject(new Error("extension installation should not have failed"));
+ },
+ onInstallEnded: () => {
+ install.removeListener(listener);
+ reject(
+ new Error(
+ `extension installation should not have ended for ${install.addon.id}`
+ )
+ );
+ },
+ onInstallPostponed: () => {
+ install.removeListener(listener);
+ resolve();
+ },
+ };
+
+ install.addListener(listener);
+ install.install();
+ });
+}
+
+// Tests that moving permission to optional retains permission and that the
+// persistent listeners are used as expected.
+add_task(
+ async function test_persistent_listener_after_builtin_location_upgrade() {
+ let id = "permission-builtin-upgrade@test";
+ let extensionData = {
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ browser_specific_settings: { gecko: { id } },
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "http://example.com/",
+ ],
+ },
+
+ async background() {
+ browser.runtime.onUpdateAvailable.addListener(() => {
+ browser.test.sendMessage("postponed");
+ });
+
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] },
+ ["blocking"]
+ );
+ },
+ };
+ await promiseStartupManager();
+ // If we use an extension wrapper via ExtensionTestUtils.expectExtension
+ // it will continue to handle messages even after the update, resulting
+ // in errors when it receives additional messages without any awaitMessage.
+ let promiseExtension = AddonTestUtils.promiseWebExtensionStartup(id);
+ await installBuiltinExtension(extensionData);
+ let extv1 = await promiseExtension;
+ assertPersistentListeners(
+ { extension: extv1 },
+ "webRequest",
+ "onBeforeRequest",
+ {
+ primed: false,
+ }
+ );
+
+ // Prepare an update for the extension.
+ extensionData.manifest.version = "2.0";
+ let xpi = AddonTestUtils.createTempWebExtensionFile(extensionData);
+ let install = await AddonManager.getInstallForFile(xpi);
+
+ // Install the update and wait for the onUpdateAvailable event to complete.
+ let promiseUpdate = new Promise(resolve =>
+ extv1.once("test-message", (kind, msg) => {
+ if (msg == "postponed") {
+ resolve();
+ }
+ })
+ );
+ await Promise.all([promisePostponeInstall(install), promiseUpdate]);
+ await promiseShutdownManager();
+
+ // restarting allows upgrade to proceed
+ let extension = ExtensionTestUtils.expectExtension(id);
+ await promiseStartupManager();
+ await extension.awaitStartup();
+ // Upgrades start the background when the extension is loaded, so
+ // primed listeners are cleared already and background events are
+ // already completed.
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ persisted: true,
+ });
+
+ await extension.unload();
+
+ // remove the builtin addon which will have restarted now.
+ let addon = await AddonManager.getAddonByID(id);
+ await addon.uninstall();
+
+ await promiseShutdownManager();
+ }
+);
+
+// Tests that moving permission to optional during a staged upgrade retains permission
+// and that the persistent listeners are used as expected.
+add_task(async function test_persistent_listener_after_staged_upgrade() {
+ AddonManager.checkUpdateSecurity = false;
+ let id = "persistent-staged-upgrade@test";
+
+ // register an update file.
+ AddonTestUtils.registerJSON(server, "/test_update.json", {
+ addons: {
+ "persistent-staged-upgrade@test": {
+ updates: [
+ {
+ version: "2.0",
+ update_link:
+ "http://example.com/addons/test_settings_staged_restart.xpi",
+ },
+ ],
+ },
+ },
+ });
+
+ let extensionData = {
+ useAddonManager: "permanent",
+ manifest: {
+ version: "2.0",
+ browser_specific_settings: {
+ gecko: { id, update_url: `http://example.com/test_update.json` },
+ },
+ permissions: ["http://example.com/"],
+ optional_permissions: ["webRequest", "webRequestBlocking"],
+ },
+
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] },
+ ["blocking"]
+ );
+ browser.webRequest.onSendHeaders.addListener(
+ details => {
+ browser.test.sendMessage("got-sendheaders");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] }
+ );
+ // Force a staged updated.
+ browser.runtime.onUpdateAvailable.addListener(async details => {
+ if (details && details.version) {
+ // This should be the version of the pending update.
+ browser.test.assertEq("2.0", details.version, "correct version");
+ browser.test.sendMessage("delay");
+ }
+ });
+ },
+ };
+
+ // Prepare the update first.
+ server.registerFile(
+ `/addons/test_settings_staged_restart.xpi`,
+ AddonTestUtils.createTempWebExtensionFile(extensionData)
+ );
+
+ // Prepare the extension that will be updated.
+ extensionData.manifest.version = "1.0";
+ extensionData.manifest.permissions = [
+ "webRequest",
+ "webRequestBlocking",
+ "http://example.com/",
+ ];
+ delete extensionData.manifest.optional_permissions;
+ extensionData.background = function () {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] },
+ ["blocking"]
+ );
+ browser.webRequest.onBeforeSendHeaders.addListener(
+ details => {
+ browser.test.sendMessage("got-beforesendheaders");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] }
+ );
+ browser.webRequest.onSendHeaders.addListener(
+ details => {
+ browser.test.sendMessage("got-sendheaders");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] }
+ );
+ // Force a staged updated.
+ browser.runtime.onUpdateAvailable.addListener(async details => {
+ if (details && details.version) {
+ // This should be the version of the pending update.
+ browser.test.assertEq("2.0", details.version, "correct version");
+ browser.test.sendMessage("delay");
+ }
+ });
+ };
+
+ await promiseStartupManager();
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ });
+ assertPersistentListeners(extension, "webRequest", "onBeforeSendHeaders", {
+ primed: false,
+ });
+ assertPersistentListeners(extension, "webRequest", "onSendHeaders", {
+ primed: false,
+ });
+
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+ await extension.awaitMessage("got-request");
+ await extension.awaitMessage("got-beforesendheaders");
+ await extension.awaitMessage("got-sendheaders");
+ ok(true, "Initial version received webRequest event");
+
+ let addon = await AddonManager.getAddonByID(id);
+ Assert.equal(addon.version, "1.0", "1.0 is loaded");
+
+ let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
+ let install = update.updateAvailable;
+ Assert.ok(install, `install is available ${update.error}`);
+
+ await AddonTestUtils.promiseCompleteAllInstalls([install]);
+
+ Assert.equal(
+ install.state,
+ AddonManager.STATE_POSTPONED,
+ "update is staged for install"
+ );
+ await extension.awaitMessage("delay");
+
+ await promiseShutdownManager();
+
+ // restarting allows upgrade to proceed
+ await promiseStartupManager();
+ await extension.awaitStartup();
+
+ // Upgrades start the background when the extension is loaded, so
+ // primed listeners are cleared already and background events are
+ // already completed.
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ persisted: true,
+ });
+ // this was removed in the upgrade background, should not be persisted.
+ assertPersistentListeners(extension, "webRequest", "onBeforeSendHeaders", {
+ primed: false,
+ persisted: false,
+ });
+ assertPersistentListeners(extension, "webRequest", "onSendHeaders", {
+ primed: false,
+ persisted: true,
+ });
+
+ await extension.unload();
+ await promiseShutdownManager();
+ AddonManager.checkUpdateSecurity = true;
+});
+
+// Tests that removing the permission releases the persistent listener.
+add_task(async function test_persistent_listener_after_permission_removal() {
+ AddonManager.checkUpdateSecurity = false;
+ let id = "persistent-staged-remove@test";
+
+ // register an update file.
+ AddonTestUtils.registerJSON(server, "/test_remove.json", {
+ addons: {
+ "persistent-staged-remove@test": {
+ updates: [
+ {
+ version: "2.0",
+ update_link:
+ "http://example.com/addons/test_settings_staged_remove.xpi",
+ },
+ ],
+ },
+ },
+ });
+
+ let extensionData = {
+ useAddonManager: "permanent",
+ manifest: {
+ version: "2.0",
+ browser_specific_settings: {
+ gecko: { id, update_url: `http://example.com/test_remove.json` },
+ },
+ permissions: ["tabs", "http://example.com/"],
+ },
+
+ background() {
+ browser.test.sendMessage("loaded");
+ },
+ };
+
+ // Prepare the update first.
+ server.registerFile(
+ `/addons/test_settings_staged_remove.xpi`,
+ AddonTestUtils.createTempWebExtensionFile(extensionData)
+ );
+
+ await promiseStartupManager();
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ version: "1.0",
+ browser_specific_settings: {
+ gecko: { id, update_url: `http://example.com/test_remove.json` },
+ },
+ permissions: ["webRequest", "webRequestBlocking", "http://example.com/"],
+ },
+
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("got-request");
+ },
+ { urls: ["http://example.com/data/file_sample.html"] },
+ ["blocking"]
+ );
+ // Force a staged updated.
+ browser.runtime.onUpdateAvailable.addListener(async details => {
+ if (details && details.version) {
+ // This should be the version of the pending update.
+ browser.test.assertEq("2.0", details.version, "correct version");
+ browser.test.sendMessage("delay");
+ }
+ });
+ },
+ });
+
+ await extension.startup();
+
+ await ExtensionTestUtils.fetch(
+ "http://example.com/",
+ "http://example.com/data/file_sample.html"
+ );
+ await extension.awaitMessage("got-request");
+ ok(true, "Initial version received webRequest event");
+
+ let addon = await AddonManager.getAddonByID(id);
+ Assert.equal(addon.version, "1.0", "1.0 is loaded");
+
+ let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
+ let install = update.updateAvailable;
+ Assert.ok(install, `install is available ${update.error}`);
+
+ await AddonTestUtils.promiseCompleteAllInstalls([install]);
+
+ Assert.equal(
+ install.state,
+ AddonManager.STATE_POSTPONED,
+ "update is staged for install"
+ );
+ await extension.awaitMessage("delay");
+
+ await promiseShutdownManager();
+
+ // restarting allows upgrade to proceed
+ await promiseStartupManager({ lateStartup: false });
+ await extension.awaitStartup();
+ await extension.awaitMessage("loaded");
+
+ // Upgrades start the background when the extension is loaded, so
+ // primed listeners are cleared already and background events are
+ // already completed.
+ assertPersistentListeners(extension, "webRequest", "onBeforeRequest", {
+ primed: false,
+ persisted: false,
+ });
+
+ await extension.unload();
+ await promiseShutdownManager();
+ AddonManager.checkUpdateSecurity = true;
+});