/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // This verifies that delaying an update works for WebExtensions. // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); if (AppConstants.platform == "win" && AppConstants.DEBUG) { // Shutdown timing is flaky in this test, and remote extensions // sometimes wind up leaving the XPI locked at the point when we try // to remove it. Services.prefs.setBoolPref("extensions.webextensions.remote", false); } PromiseTestUtils.allowMatchingRejectionsGlobally( /Message manager disconnected/ ); /* globals browser*/ const profileDir = gProfD.clone(); profileDir.append("extensions"); const stageDir = profileDir.clone(); stageDir.append("staged"); const IGNORE_ID = "test_delay_update_ignore_webext@tests.mozilla.org"; const COMPLETE_ID = "test_delay_update_complete_webext@tests.mozilla.org"; const DEFER_ID = "test_delay_update_defer_webext@tests.mozilla.org"; const STAGED_ID = "test_delay_update_staged_webext@tests.mozilla.org"; const STAGED_NO_UPDATE_URL_ID = "test_delay_update_staged_webext_no_update_url@tests.mozilla.org"; const NOUPDATE_ID = "test_no_update_webext@tests.mozilla.org"; // Create and configure the HTTP server. var testserver = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); testserver.registerDirectory("/data/", do_get_file("data")); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42"); BootstrapMonitor.init(); const ADDONS = { test_delay_update_complete_webextension_v2: { "manifest.json": { manifest_version: 2, name: "Delay Upgrade", version: "2.0", browser_specific_settings: { gecko: { id: COMPLETE_ID }, }, }, }, test_delay_update_defer_webextension_v2: { "manifest.json": { manifest_version: 2, name: "Delay Upgrade", version: "2.0", browser_specific_settings: { gecko: { id: DEFER_ID }, }, }, }, test_delay_update_staged_webextension_v2: { "manifest.json": { manifest_version: 2, name: "Delay Upgrade", version: "2.0", browser_specific_settings: { gecko: { id: STAGED_ID, update_url: `http://example.com/data/test_delay_updates_staged.json`, strict_min_version: "1", strict_max_version: "41", }, }, }, }, test_delay_update_staged_webextension_no_update_url_v2: { "manifest.json": { manifest_version: 2, name: "Delay Upgrade", version: "2.0", browser_specific_settings: { gecko: { id: STAGED_NO_UPDATE_URL_ID, strict_min_version: "1", strict_max_version: "41", }, }, }, }, test_delay_update_ignore_webextension_v2: { "manifest.json": { manifest_version: 2, name: "Delay Upgrade", version: "2.0", browser_specific_settings: { gecko: { id: IGNORE_ID }, }, }, }, }; const XPIS = {}; for (let [name, files] of Object.entries(ADDONS)) { XPIS[name] = AddonTestUtils.createTempXPIFile(files); testserver.registerFile(`/addons/${name}.xpi`, XPIS[name]); } // add-on registers upgrade listener, and ignores update. add_task(async function delay_updates_ignore() { await promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", browser_specific_settings: { gecko: { id: IGNORE_ID, update_url: `http://example.com/data/test_delay_updates_ignore.json`, }, }, }, background() { browser.runtime.onUpdateAvailable.addListener(details => { if (details) { if (details.version) { // This should be the version of the pending update. browser.test.assertEq("2.0", details.version, "correct version"); browser.test.notifyPass("delay"); } } else { browser.test.fail("no details object passed"); } }); browser.test.sendMessage("ready"); }, }); await Promise.all([extension.startup(), extension.awaitMessage("ready")]); BootstrapMonitor.checkInstalled(IGNORE_ID, "1.0"); let addon = await promiseAddonByID(IGNORE_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); let update = await promiseFindAddonUpdates(addon); let install = update.updateAvailable; await promiseCompleteAllInstalls([install]); Assert.equal(install.state, AddonManager.STATE_POSTPONED); BootstrapMonitor.checkInstalled(IGNORE_ID, "1.0"); // addon upgrade has been delayed let addon_postponed = await promiseAddonByID(IGNORE_ID); Assert.notEqual(addon_postponed, null); Assert.equal(addon_postponed.version, "1.0"); Assert.equal(addon_postponed.name, "Generated extension"); Assert.ok(addon_postponed.isCompatible); Assert.ok(!addon_postponed.appDisabled); Assert.ok(addon_postponed.isActive); Assert.equal(addon_postponed.type, "extension"); await extension.awaitFinish("delay"); // restarting allows upgrade to proceed await promiseRestartManager(); let addon_upgraded = await promiseAddonByID(IGNORE_ID); await extension.awaitStartup(); BootstrapMonitor.checkUpdated(IGNORE_ID, "2.0"); Assert.notEqual(addon_upgraded, null); Assert.equal(addon_upgraded.version, "2.0"); Assert.equal(addon_upgraded.name, "Delay Upgrade"); Assert.ok(addon_upgraded.isCompatible); Assert.ok(!addon_upgraded.appDisabled); Assert.ok(addon_upgraded.isActive); Assert.equal(addon_upgraded.type, "extension"); await extension.unload(); await promiseShutdownManager(); }); // add-on registers upgrade listener, and allows update. add_task(async function delay_updates_complete() { await promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", browser_specific_settings: { gecko: { id: COMPLETE_ID, update_url: `http://example.com/data/test_delay_updates_complete.json`, }, }, }, background() { browser.runtime.onUpdateAvailable.addListener(details => { browser.test.notifyPass("reload"); browser.runtime.reload(); }); browser.test.sendMessage("ready"); }, }); await Promise.all([extension.startup(), extension.awaitMessage("ready")]); let addon = await promiseAddonByID(COMPLETE_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); let update = await promiseFindAddonUpdates(addon); let install = update.updateAvailable; let promiseInstalled = promiseAddonEvent("onInstalled"); await promiseCompleteAllInstalls([install]); await extension.awaitFinish("reload"); // addon upgrade has been allowed let [addon_allowed] = await promiseInstalled; await extension.awaitStartup(); Assert.notEqual(addon_allowed, null); Assert.equal(addon_allowed.version, "2.0"); Assert.equal(addon_allowed.name, "Delay Upgrade"); Assert.ok(addon_allowed.isCompatible); Assert.ok(!addon_allowed.appDisabled); Assert.ok(addon_allowed.isActive); Assert.equal(addon_allowed.type, "extension"); await new Promise(executeSoon); if (stageDir.exists()) { do_throw( "Staging directory should not exist for formerly-postponed extension" ); } await extension.unload(); await promiseShutdownManager(); }); // add-on registers upgrade listener, initially defers update then allows upgrade add_task(async function delay_updates_defer() { await promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", browser_specific_settings: { gecko: { id: DEFER_ID, update_url: `http://example.com/data/test_delay_updates_defer.json`, }, }, }, background() { browser.runtime.onUpdateAvailable.addListener(details => { // Upgrade will only proceed when "allow" message received. browser.test.onMessage.addListener(msg => { if (msg == "allow") { browser.test.notifyPass("allowed"); browser.runtime.reload(); } else { browser.test.fail(`wrong message: ${msg}`); } }); browser.test.sendMessage("truly ready"); }); browser.test.sendMessage("ready"); }, }); await Promise.all([extension.startup(), extension.awaitMessage("ready")]); let addon = await promiseAddonByID(DEFER_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); let update = await promiseFindAddonUpdates(addon); let install = update.updateAvailable; let promiseInstalled = promiseAddonEvent("onInstalled"); await promiseCompleteAllInstalls([install]); Assert.equal(install.state, AddonManager.STATE_POSTPONED); // upgrade is initially postponed let addon_postponed = await promiseAddonByID(DEFER_ID); Assert.notEqual(addon_postponed, null); Assert.equal(addon_postponed.version, "1.0"); Assert.equal(addon_postponed.name, "Generated extension"); Assert.ok(addon_postponed.isCompatible); Assert.ok(!addon_postponed.appDisabled); Assert.ok(addon_postponed.isActive); Assert.equal(addon_postponed.type, "extension"); // add-on will not allow upgrade until message is received await extension.awaitMessage("truly ready"); extension.sendMessage("allow"); await extension.awaitFinish("allowed"); // addon upgrade has been allowed let [addon_allowed] = await promiseInstalled; await extension.awaitStartup(); Assert.notEqual(addon_allowed, null); Assert.equal(addon_allowed.version, "2.0"); Assert.equal(addon_allowed.name, "Delay Upgrade"); Assert.ok(addon_allowed.isCompatible); Assert.ok(!addon_allowed.appDisabled); Assert.ok(addon_allowed.isActive); Assert.equal(addon_allowed.type, "extension"); await promiseRestartManager(); // restart changes nothing addon_allowed = await promiseAddonByID(DEFER_ID); await extension.awaitStartup(); Assert.notEqual(addon_allowed, null); Assert.equal(addon_allowed.version, "2.0"); Assert.equal(addon_allowed.name, "Delay Upgrade"); Assert.ok(addon_allowed.isCompatible); Assert.ok(!addon_allowed.appDisabled); Assert.ok(addon_allowed.isActive); Assert.equal(addon_allowed.type, "extension"); await extension.unload(); await promiseShutdownManager(); }); // add-on registers upgrade listener to deny update, completes after restart, // even though the updated XPI is incompatible - the information returned // by the update server defined in its manifest returns a compatible range add_task(async function delay_updates_staged() { await promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", browser_specific_settings: { gecko: { id: STAGED_ID, update_url: `http://example.com/data/test_delay_updates_staged.json`, }, }, }, background() { browser.runtime.onUpdateAvailable.addListener(details => { browser.test.sendMessage("denied"); }); browser.test.sendMessage("ready"); }, }); await Promise.all([extension.startup(), extension.awaitMessage("ready")]); let addon = await promiseAddonByID(STAGED_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); let update = await promiseFindAddonUpdates(addon); let install = update.updateAvailable; await promiseCompleteAllInstalls([install]); Assert.equal(install.state, AddonManager.STATE_POSTPONED); // upgrade is initially postponed let addon_postponed = await promiseAddonByID(STAGED_ID); Assert.notEqual(addon_postponed, null); Assert.equal(addon_postponed.version, "1.0"); Assert.equal(addon_postponed.name, "Generated extension"); Assert.ok(addon_postponed.isCompatible); Assert.ok(!addon_postponed.appDisabled); Assert.ok(addon_postponed.isActive); Assert.equal(addon_postponed.type, "extension"); // add-on reports an available upgrade, but denied it till next restart await extension.awaitMessage("denied"); await promiseRestartManager(); await extension.awaitStartup(); // add-on should have been updated during restart let addon_upgraded = await promiseAddonByID(STAGED_ID); Assert.notEqual(addon_upgraded, null); Assert.equal(addon_upgraded.version, "2.0"); Assert.equal(addon_upgraded.name, "Delay Upgrade"); Assert.ok(addon_upgraded.isCompatible); Assert.ok(!addon_upgraded.appDisabled); Assert.ok(addon_upgraded.isActive); Assert.equal(addon_upgraded.type, "extension"); await extension.unload(); await promiseShutdownManager(); }); // add-on registers upgrade listener to deny update, does not complete after // restart, because the updated XPI is incompatible - there is no update server // defined in its manifest, which could return a compatible range add_task(async function delay_updates_staged_no_update_url() { await promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", browser_specific_settings: { gecko: { id: STAGED_NO_UPDATE_URL_ID, update_url: `http://example.com/data/test_delay_updates_staged.json`, }, }, }, background() { browser.runtime.onUpdateAvailable.addListener(details => { browser.test.sendMessage("denied"); }); browser.test.sendMessage("ready"); }, }); await Promise.all([extension.startup(), extension.awaitMessage("ready")]); let addon = await promiseAddonByID(STAGED_NO_UPDATE_URL_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); let update = await promiseFindAddonUpdates(addon); let install = update.updateAvailable; await promiseCompleteAllInstalls([install]); Assert.equal(install.state, AddonManager.STATE_POSTPONED); // upgrade is initially postponed let addon_postponed = await promiseAddonByID(STAGED_NO_UPDATE_URL_ID); Assert.notEqual(addon_postponed, null); Assert.equal(addon_postponed.version, "1.0"); Assert.equal(addon_postponed.name, "Generated extension"); Assert.ok(addon_postponed.isCompatible); Assert.ok(!addon_postponed.appDisabled); Assert.ok(addon_postponed.isActive); Assert.equal(addon_postponed.type, "extension"); // add-on reports an available upgrade, but denied it till next restart await extension.awaitMessage("denied"); await promiseRestartManager(); await extension.awaitStartup(); // add-on should not have been updated during restart let addon_upgraded = await promiseAddonByID(STAGED_NO_UPDATE_URL_ID); Assert.notEqual(addon_upgraded, null); Assert.equal(addon_upgraded.version, "1.0"); Assert.equal(addon_upgraded.name, "Generated extension"); Assert.ok(addon_upgraded.isCompatible); Assert.ok(!addon_upgraded.appDisabled); Assert.ok(addon_upgraded.isActive); Assert.equal(addon_upgraded.type, "extension"); await extension.unload(); await promiseShutdownManager(); }); // browser.runtime.reload() without a pending upgrade should just reload. add_task(async function runtime_reload() { await promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", manifest: { version: "1.0", browser_specific_settings: { gecko: { id: NOUPDATE_ID, update_url: `http://example.com/data/test_no_update.json`, }, }, }, background() { browser.test.onMessage.addListener(msg => { if (msg == "reload") { browser.runtime.reload(); } else { browser.test.fail(`wrong message: ${msg}`); } }); browser.test.sendMessage("ready"); }, }); await Promise.all([extension.startup(), extension.awaitMessage("ready")]); let addon = await promiseAddonByID(NOUPDATE_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); await promiseFindAddonUpdates(addon); extension.sendMessage("reload"); // Wait for extension to restart, to make sure reload works. await AddonTestUtils.promiseWebExtensionStartup(NOUPDATE_ID); await extension.awaitMessage("ready"); addon = await promiseAddonByID(NOUPDATE_ID); Assert.notEqual(addon, null); Assert.equal(addon.version, "1.0"); Assert.equal(addon.name, "Generated extension"); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); await extension.unload(); await promiseShutdownManager(); });