/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const ID = "addon@tests.mozilla.org"; createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42"); function waitForBootstrapEvent(expectedEvent, addonId) { return new Promise(resolve => { function listener(msg, { method, params, reason }) { if (params.id === addonId && method === expectedEvent) { resolve({ params, method, reason }); AddonTestUtils.off("bootstrap-method", listener); } else { info(`Ignoring bootstrap event: ${method} for ${params.id}`); } } AddonTestUtils.on("bootstrap-method", listener); }); } async function checkEvent(promise, { reason, params }) { let event = await promise; info(`Checking bootstrap event ${event.method} for ${event.params.id}`); equal( event.reason, reason, `Expected bootstrap reason ${getReasonName(reason)} got ${getReasonName( event.reason )}` ); for (let [param, value] of Object.entries(params)) { equal(event.params[param], value, `Expected value for params.${param}`); } } BootstrapMonitor.init(); const XPIS = {}; add_task(async function setup() { for (let n of [1, 2]) { XPIS[n] = await createTempWebExtensionFile({ manifest: { name: "Test", version: `${n}.0`, browser_specific_settings: { gecko: { id: ID } }, }, }); } }); // Install a temporary add-on with no existing add-on present. // Restart and make sure it has gone away. add_task(async function test_new_temporary() { await promiseStartupManager(); let extInstallCalled = false; AddonManager.addInstallListener({ onExternalInstall: aInstall => { Assert.equal(aInstall.id, ID); Assert.equal(aInstall.version, "1.0"); extInstallCalled = true; }, }); let installingCalled = false; let installedCalled = false; AddonManager.addAddonListener({ onInstalling: aInstall => { Assert.equal(aInstall.id, ID); Assert.equal(aInstall.version, "1.0"); installingCalled = true; }, onInstalled: aInstall => { Assert.equal(aInstall.id, ID); Assert.equal(aInstall.version, "1.0"); installedCalled = true; }, onInstallStarted: aInstall => { do_throw("onInstallStarted called unexpectedly"); }, }); await AddonManager.installTemporaryAddon(XPIS[1]); Assert.ok(extInstallCalled); Assert.ok(installingCalled); Assert.ok(installedCalled); const install = BootstrapMonitor.checkInstalled(ID, "1.0"); equal(install.reason, BOOTSTRAP_REASONS.ADDON_INSTALL); BootstrapMonitor.checkStarted(ID, "1.0"); let info = BootstrapMonitor.started.get(ID); Assert.equal(info.reason, BOOTSTRAP_REASONS.ADDON_INSTALL); let addon = await promiseAddonByID(ID); checkAddon(ID, addon, { version: "1.0", name: "Test", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: true, }); let onShutdown = waitForBootstrapEvent("shutdown", ID); let onUninstall = waitForBootstrapEvent("uninstall", ID); await promiseRestartManager(); let shutdown = await onShutdown; equal(shutdown.reason, BOOTSTRAP_REASONS.ADDON_UNINSTALL); let uninstall = await onUninstall; equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UNINSTALL); BootstrapMonitor.checkNotInstalled(ID); BootstrapMonitor.checkNotStarted(ID); addon = await promiseAddonByID(ID); Assert.equal(addon, null); await promiseRestartManager(); }); // Install a temporary add-on over the top of an existing add-on. // Restart and make sure the existing add-on comes back. add_task(async function test_replace_temporary() { await promiseInstallFile(XPIS[2]); let addon = await promiseAddonByID(ID); BootstrapMonitor.checkInstalled(ID, "2.0"); BootstrapMonitor.checkStarted(ID, "2.0"); checkAddon(ID, addon, { version: "2.0", name: "Test", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: false, }); let tempdir = gTmpD.clone(); for (let newversion of ["1.0", "3.0"]) { for (let packed of [false, true]) { // ugh, file locking issues with xpis on windows if (packed && AppConstants.platform == "win") { continue; } let files = ExtensionTestCommon.generateFiles({ manifest: { name: "Test", version: newversion, browser_specific_settings: { gecko: { id: ID } }, }, }); let target = await AddonTestUtils.promiseWriteFilesToExtension( tempdir.path, ID, files, !packed ); let onShutdown = waitForBootstrapEvent("shutdown", ID); let onUpdate = waitForBootstrapEvent("update", ID); let onStartup = waitForBootstrapEvent("startup", ID); await AddonManager.installTemporaryAddon(target); let reason = Services.vc.compare(newversion, "2.0") < 0 ? BOOTSTRAP_REASONS.ADDON_DOWNGRADE : BOOTSTRAP_REASONS.ADDON_UPGRADE; await checkEvent(onShutdown, { reason, params: { version: "2.0", }, }); await checkEvent(onUpdate, { reason, params: { version: newversion, oldVersion: "2.0", }, }); await checkEvent(onStartup, { reason, params: { version: newversion, oldVersion: "2.0", }, }); addon = await promiseAddonByID(ID); let signedState = packed ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_UNKNOWN; // temporary add-on is installed and started checkAddon(ID, addon, { version: newversion, name: "Test", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState, temporarilyInstalled: true, }); // Now restart, the temporary addon will go away which should // be the opposite action (ie, if the temporary addon was an // upgrade, then removing it is a downgrade and vice versa) reason = reason == BOOTSTRAP_REASONS.ADDON_UPGRADE ? BOOTSTRAP_REASONS.ADDON_DOWNGRADE : BOOTSTRAP_REASONS.ADDON_UPGRADE; onShutdown = waitForBootstrapEvent("shutdown", ID); onUpdate = waitForBootstrapEvent("update", ID); onStartup = waitForBootstrapEvent("startup", ID); await promiseRestartManager(); await checkEvent(onShutdown, { reason, params: { version: newversion, }, }); await checkEvent(onUpdate, { reason, params: { version: "2.0", oldVersion: newversion, }, }); await checkEvent(onStartup, { // We don't actually propagate the upgrade/downgrade reason across // the browser restart when a temporary addon is removed. See // bug 1359558 for detailed reasoning. reason: BOOTSTRAP_REASONS.APP_STARTUP, params: { version: "2.0", }, }); BootstrapMonitor.checkInstalled(ID, "2.0"); BootstrapMonitor.checkStarted(ID, "2.0"); addon = await promiseAddonByID(ID); // existing add-on is back checkAddon(ID, addon, { version: "2.0", name: "Test", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: false, }); Services.obs.notifyObservers(target, "flush-cache-entry"); target.remove(true); } } // remove original add-on await addon.uninstall(); BootstrapMonitor.checkNotInstalled(ID); BootstrapMonitor.checkNotStarted(ID); await promiseRestartManager(); }); // Test that loading from the same path multiple times work add_task(async function test_samefile() { // File locking issues on Windows, ugh if (AppConstants.platform == "win") { return; } // test that a webextension works let webext = createTempWebExtensionFile({ manifest: { version: "1.0", name: "Test WebExtension 1 (temporary)", browser_specific_settings: { gecko: { id: ID, }, }, }, }); let addon = await AddonManager.installTemporaryAddon(webext); // temporary add-on is installed and started checkAddon(ID, addon, { version: "1.0", name: "Test WebExtension 1 (temporary)", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: true, }); Services.obs.notifyObservers(webext, "flush-cache-entry"); webext.remove(false); webext = createTempWebExtensionFile({ manifest: { version: "2.0", name: "Test WebExtension 1 (temporary)", browser_specific_settings: { gecko: { id: ID, }, }, }, }); addon = await AddonManager.installTemporaryAddon(webext); // temporary add-on is installed and started checkAddon(ID, addon, { version: "2.0", name: "Test WebExtension 1 (temporary)", isCompatible: true, appDisabled: false, isActive: true, type: "extension", isWebExtension: true, signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: true, }); await addon.uninstall(); }); // Install a temporary add-on over the top of an existing add-on. // Uninstall it and make sure the existing add-on comes back. add_task(async function test_replace_permanent() { await promiseInstallWebExtension({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "1.0", name: "Test Bootstrap 1", }, }); BootstrapMonitor.checkInstalled(ID, "1.0"); BootstrapMonitor.checkStarted(ID, "1.0"); let unpacked_addon = gTmpD.clone(); unpacked_addon.append(ID); let files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "2.0", name: "Test Bootstrap 1 (temporary)", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpacked_addon.path, files); let extInstallCalled = false; AddonManager.addInstallListener({ onExternalInstall: aInstall => { Assert.equal(aInstall.id, ID); Assert.equal(aInstall.version, "2.0"); extInstallCalled = true; }, }); let installingCalled = false; let installedCalled = false; AddonManager.addAddonListener({ onInstalling: aInstall => { Assert.equal(aInstall.id, ID); if (!installingCalled) { Assert.equal(aInstall.version, "2.0"); } installingCalled = true; }, onInstalled: aInstall => { Assert.equal(aInstall.id, ID); if (!installedCalled) { Assert.equal(aInstall.version, "2.0"); } installedCalled = true; }, onInstallStarted: aInstall => { do_throw("onInstallStarted called unexpectedly"); }, }); let addon = await AddonManager.installTemporaryAddon(unpacked_addon); Assert.ok(extInstallCalled); Assert.ok(installingCalled); Assert.ok(installedCalled); BootstrapMonitor.checkInstalled(ID); BootstrapMonitor.checkStarted(ID); // temporary add-on is installed and started checkAddon(ID, addon, { version: "2.0", name: "Test Bootstrap 1 (temporary)", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_UNKNOWN, temporarilyInstalled: true, }); await addon.uninstall(); BootstrapMonitor.checkInstalled(ID); BootstrapMonitor.checkStarted(ID); addon = await promiseAddonByID(ID); // existing add-on is back checkAddon(ID, addon, { version: "1.0", name: "Test Bootstrap 1", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: false, }); unpacked_addon.remove(true); await addon.uninstall(); BootstrapMonitor.checkNotInstalled(ID); BootstrapMonitor.checkNotStarted(ID); await promiseRestartManager(); }); // Install a temporary add-on as a version upgrade over the top of an // existing temporary add-on. add_task(async function test_replace_temporary() { const unpackedAddon = gTmpD.clone(); unpackedAddon.append(ID); let files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "1.0", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpackedAddon.path, files); await AddonManager.installTemporaryAddon(unpackedAddon); // Increment the version number, re-install it, and make sure it // gets marked as an upgrade. files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "2.0", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpackedAddon.path, files); const onShutdown = waitForBootstrapEvent("shutdown", ID); const onUpdate = waitForBootstrapEvent("update", ID); const onStartup = waitForBootstrapEvent("startup", ID); await AddonManager.installTemporaryAddon(unpackedAddon); await checkEvent(onShutdown, { reason: BOOTSTRAP_REASONS.ADDON_UPGRADE, params: { version: "1.0", }, }); await checkEvent(onUpdate, { reason: BOOTSTRAP_REASONS.ADDON_UPGRADE, params: { version: "2.0", oldVersion: "1.0", }, }); await checkEvent(onStartup, { reason: BOOTSTRAP_REASONS.ADDON_UPGRADE, params: { version: "2.0", oldVersion: "1.0", }, }); const addon = await promiseAddonByID(ID); await addon.uninstall(); unpackedAddon.remove(true); await promiseRestartManager(); }); // Install a temporary add-on as a version downgrade over the top of an // existing temporary add-on. add_task(async function test_replace_temporary_downgrade() { const unpackedAddon = gTmpD.clone(); unpackedAddon.append(ID); let files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "1.0", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpackedAddon.path, files); await AddonManager.installTemporaryAddon(unpackedAddon); // Decrement the version number, re-install, and make sure // it gets marked as a downgrade. files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "0.8", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpackedAddon.path, files); const onShutdown = waitForBootstrapEvent("shutdown", ID); const onUpdate = waitForBootstrapEvent("update", ID); const onStartup = waitForBootstrapEvent("startup", ID); await AddonManager.installTemporaryAddon(unpackedAddon); await checkEvent(onShutdown, { reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE, params: { version: "1.0", }, }); await checkEvent(onUpdate, { reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE, params: { oldVersion: "1.0", version: "0.8", }, }); await checkEvent(onStartup, { reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE, params: { version: "0.8", }, }); const addon = await promiseAddonByID(ID); await addon.uninstall(); unpackedAddon.remove(true); await promiseRestartManager(); }); // Installing a temporary add-on over an existing add-on with the same // version number should be installed as an upgrade. add_task(async function test_replace_same_version() { const unpackedAddon = gTmpD.clone(); unpackedAddon.append(ID); let files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, version: "1.0", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpackedAddon.path, files); const onInitialInstall = waitForBootstrapEvent("install", ID); const onInitialStartup = waitForBootstrapEvent("startup", ID); await AddonManager.installTemporaryAddon(unpackedAddon); await checkEvent(onInitialInstall, { reason: BOOTSTRAP_REASONS.ADDON_INSTALL, params: { version: "1.0", }, }); await checkEvent(onInitialStartup, { reason: BOOTSTRAP_REASONS.ADDON_INSTALL, params: { version: "1.0", }, }); let info = BootstrapMonitor.started.get(ID); Assert.equal(info.reason, BOOTSTRAP_REASONS.ADDON_INSTALL); // Install it again. const onShutdown = waitForBootstrapEvent("shutdown", ID); const onUpdate = waitForBootstrapEvent("update", ID); const onStartup = waitForBootstrapEvent("startup", ID); await AddonManager.installTemporaryAddon(unpackedAddon); await checkEvent(onShutdown, { reason: BOOTSTRAP_REASONS.ADDON_UPGRADE, params: { version: "1.0", }, }); await checkEvent(onUpdate, { reason: BOOTSTRAP_REASONS.ADDON_UPGRADE, params: { oldVersion: "1.0", version: "1.0", }, }); await checkEvent(onStartup, { reason: BOOTSTRAP_REASONS.ADDON_UPGRADE, params: { version: "1.0", }, }); const addon = await promiseAddonByID(ID); await addon.uninstall(); unpackedAddon.remove(true); await promiseRestartManager(); }); // Install a temporary add-on over the top of an existing disabled add-on. // After restart, the existing add-on should continue to be installed and disabled. add_task(async function test_replace_permanent_disabled() { await promiseInstallFile(XPIS[1]); let addon = await promiseAddonByID(ID); BootstrapMonitor.checkInstalled(ID, "1.0"); BootstrapMonitor.checkStarted(ID, "1.0"); await addon.disable(); BootstrapMonitor.checkInstalled(ID, "1.0"); BootstrapMonitor.checkNotStarted(ID); let unpacked_addon = gTmpD.clone(); unpacked_addon.append(ID); let files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } }, name: "Test", version: "2.0", }, }); await AddonTestUtils.promiseWriteFilesToDir(unpacked_addon.path, files); let extInstallCalled = false; AddonManager.addInstallListener({ onExternalInstall: aInstall => { Assert.equal(aInstall.id, ID); Assert.equal(aInstall.version, "2.0"); extInstallCalled = true; }, }); let tempAddon = await AddonManager.installTemporaryAddon(unpacked_addon); Assert.ok(extInstallCalled); BootstrapMonitor.checkInstalled(ID, "2.0"); BootstrapMonitor.checkStarted(ID); // temporary add-on is installed and started checkAddon(ID, tempAddon, { version: "2.0", name: "Test", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_UNKNOWN, temporarilyInstalled: true, }); await tempAddon.uninstall(); unpacked_addon.remove(true); await addon.enable(); await new Promise(executeSoon); addon = await promiseAddonByID(ID); BootstrapMonitor.checkInstalled(ID, "1.0"); BootstrapMonitor.checkStarted(ID); // existing add-on is back checkAddon(ID, addon, { version: "1.0", name: "Test", isCompatible: true, appDisabled: false, isActive: true, type: "extension", signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, temporarilyInstalled: false, }); await addon.uninstall(); BootstrapMonitor.checkNotInstalled(ID); BootstrapMonitor.checkNotStarted(ID); await promiseRestartManager(); }); // Tests that XPIs with a .zip extension work when loaded temporarily. add_task(async function test_zip_extension() { let xpi = createTempWebExtensionFile({ background() { /* globals browser */ browser.test.sendMessage("started", "Hello."); }, }); xpi.moveTo(null, xpi.leafName.replace(/\.xpi$/, ".zip")); let extension = ExtensionTestUtils.loadExtensionXPI(xpi); await extension.startup(); let msg = await extension.awaitMessage("started"); equal(msg, "Hello.", "Got expected background script message"); await extension.unload(); });