From a2347b9b5b5ad471c3dc1c70ebf43d4ca4ecfa1f Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Tue, 3 Nov 2015 10:07:08 -0800 Subject: [PATCH] Bug 1209341 - allow loading unsigned restartless add-ons at runtime. r=mossop --- toolkit/mozapps/extensions/AddonManager.jsm | 29 +- .../mozapps/extensions/content/extensions.js | 3 + .../extensions/internal/XPIProvider.jsm | 125 ++++- .../extensions/internal/XPIProviderUtils.js | 2 +- .../xpcshell/data/test_temporary/bootstrap.js | 1 + .../test/xpcshell/test_temporary.js | 445 ++++++++++++++++++ .../extensions/test/xpcshell/xpcshell.ini | 1 + 7 files changed, 602 insertions(+), 4 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_temporary/bootstrap.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_temporary.js diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 184bf3b1f3c..4a294c7f348 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2315,6 +2315,27 @@ var AddonManagerInternal = { } }, + /** + * Starts installation of a temporary add-on from a local directory. + * @param aDirectory + * The directory of the add-on to be temporarily installed + * @return a Promise that rejects if the add-on is not restartless + * or an add-on with the same ID is already temporarily installed + */ + installTemporaryAddon: function AMI_installTemporaryAddon(aFile) { + if (!gStarted) + throw Components.Exception("AddonManager is not initialized", + Cr.NS_ERROR_NOT_INITIALIZED); + + if (!(aFile instanceof Ci.nsIFile)) + throw Components.Exception("aFile must be a nsIFile", + Cr.NS_ERROR_INVALID_ARG); + + return AddonManagerInternal._getProviderByName("XPIProvider") + .installTemporaryAddon(aFile); + }, + + /** * Gets an icon from the icon set provided by the add-on * that is closest to the specified size. @@ -3026,8 +3047,10 @@ this.AddonManager = { SCOPE_APPLICATION: 4, // Installed for all users of the computer. SCOPE_SYSTEM: 8, + // Installed temporarily + SCOPE_TEMPORARY: 16, // The combination of all scopes. - SCOPE_ALL: 15, + SCOPE_ALL: 31, // 1-15 are different built-in views for the add-on type VIEW_TYPE_LIST: "list", @@ -3207,6 +3230,10 @@ this.AddonManager = { aInstalls); }, + installTemporaryAddon: function AM_installTemporaryAddon(aDirectory) { + return AddonManagerInternal.installTemporaryAddon(aDirectory); + }, + addManagerListener: function AM_addManagerListener(aListener) { AddonManagerInternal.addManagerListener(aListener); }, diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index 532dca658a2..6289e251660 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -202,6 +202,9 @@ function loadView(aViewId) { } function isCorrectlySigned(aAddon) { + // temporary add-ons do not require signing + if (aAddon.scope == AddonManager.SCOPE_TEMPORARY) + return true; if (aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING) return false; if (aAddon.foreignInstall && aAddon.signedState < AddonManager.SIGNEDSTATE_SIGNED) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 7553201b584..3c0fbe9268a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -146,6 +146,7 @@ const KEY_APP_GLOBAL = "app-global"; const KEY_APP_SYSTEM_LOCAL = "app-system-local"; const KEY_APP_SYSTEM_SHARE = "app-system-share"; const KEY_APP_SYSTEM_USER = "app-system-user"; +const KEY_APP_TEMPORARY = "app-temporary"; const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions"; const XPI_PERMISSION = "install"; @@ -646,6 +647,12 @@ function canRunInSafeMode(aAddon) { // Even though the updated system add-ons aren't generally run in safe mode we // include them here so their uninstall functions get called when switching // back to the default set. + + // TODO product should make the call about temporary add-ons running + // in safe mode. assuming for now that they are. + if (aAddon._installLocation.name == KEY_APP_TEMPORARY) + return true; + return aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS; } @@ -666,8 +673,10 @@ function isUsableAddon(aAddon) { aAddon.signedState != AddonManager.SIGNEDSTATE_SYSTEM) { return false; } - - if (aAddon._installLocation.name != KEY_APP_SYSTEM_DEFAULTS && mustSign(aAddon.type)) { + // temporary and system add-ons do not require signing + if ((aAddon._installLocation.name != KEY_APP_SYSTEM_DEFAULTS && + aAddon._installLocation.name != KEY_APP_TEMPORARY) && + mustSign(aAddon.type)) { if (aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING) return false; if (aAddon.foreignInstall && aAddon.signedState < AddonManager.SIGNEDSTATE_SIGNED) @@ -2544,6 +2553,11 @@ this.XPIProvider = { // These must be in order of priority, highest to lowest, // for processFileChanges etc. to work + + XPIProvider.installLocations.push(TemporaryInstallLocation); + XPIProvider.installLocationsByName[TemporaryInstallLocation.name] = + TemporaryInstallLocation; + // The profile location is always enabled addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR, [DIR_EXTENSIONS], @@ -3784,6 +3798,95 @@ this.XPIProvider = { }, aFile); }, + /** + * Temporarily installs add-on from local directory. + * As this is intended for development, the signature is not checked and + * the add-on does not persist on application restart. + * + * @param aDirectory + * The directory containing the unpacked add-on directory or XPI file + * + * @return a Promise that rejects if the add-on is not restartless + * or an add-on with the same ID is already temporarily installed + */ + installTemporaryAddon: Task.async(function* + XPI_installTemporaryAddon(aDirectory) { + let addon = yield loadManifestFromFile(aDirectory, TemporaryInstallLocation); + + if (!addon.bootstrap) { + throw new Error("Only restartless (bootstrap) add-ons" + + " can be temporarily installed:", addon.id); + } + let oldAddon = yield new Promise( + resolve => XPIDatabase.getVisibleAddonForID(addon.id, resolve)); + if (oldAddon) { + if (oldAddon.location == KEY_APP_TEMPORARY) { + logger.warn("temporary add-on already installed:", addon.id); + throw new Error("Add-on with ID " + oldAddon.id + " is already" + + " temporarily installed"); + } + else if (!oldAddon.bootstrap) { + logger.warn("Non-restartless Add-on is already installed", addon.id); + throw new Error("Non-restartless add-on with ID " + + oldAddon.id + " is already installed"); + } + else { + logger.warn("Addon with ID " + oldAddon.id + " already installed," + + " older version will be disabled"); + + let existingAddonID = oldAddon.id; + let existingAddon = oldAddon._sourceBundle; + + // We'll be replacing a currently active bootstrapped add-on so + // call its uninstall method + let newVersion = addon.version; + let oldVersion = oldAddon.version; + let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ? + BOOTSTRAP_REASONS.ADDON_UPGRADE : + BOOTSTRAP_REASONS.ADDON_DOWNGRADE; + + if (oldAddon.active) { + XPIProvider.callBootstrapMethod(oldAddon, existingAddon, + "shutdown", uninstallReason, + { newVersion }); + } + this.callBootstrapMethod(oldAddon, existingAddon, + "uninstall", uninstallReason, { newVersion }); + this.unloadBootstrapScope(existingAddonID); + flushStartupCache(); + } + } + + let file = addon._sourceBundle; + + let wrapper = createWrapper(addon); + let oldWrapper = createWrapper(oldAddon); + XPIProvider.callBootstrapMethod(addon, file, "install", + BOOTSTRAP_REASONS.ADDON_INSTALL); + addon.state = AddonManager.STATE_INSTALLED; + logger.debug("Install of temporary addon in " + aDirectory.path + " completed."); + addon.visible = true; + addon.enabled = true; + addon.active = true; + + addon = XPIDatabase.addAddonMetadata(addon, file.persistentDescriptor); + + XPIStates.addAddon(addon); + XPIDatabase.saveChanges(); + + // addon was modified above, create new wrapper + wrapper = createWrapper(addon); + + AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, + false); + XPIProvider.callBootstrapMethod(addon, file, "startup", + BOOTSTRAP_REASONS.ADDON_ENABLE); + AddonManagerPrivate.callInstallListeners("onExternalInstall", + null, wrapper, oldWrapper, + false); + AddonManagerPrivate.callAddonListeners("onInstalled", wrapper); + }), + /** * Removes an AddonInstall from the list of active installs. * @@ -6988,6 +7091,9 @@ function AddonWrapper(aAddon) { }); this.__defineGetter__("hidden", function AddonWrapper_hidden() { + if (aAddon._installLocation.name == KEY_APP_TEMPORARY) + return false; + return (aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS); }); @@ -7818,6 +7924,21 @@ Object.assign(SystemAddonInstallLocation.prototype, { }), }); +/** + * An object which identifies a directory install location for temporary + * add-ons. + */ +const TemporaryInstallLocation = { + locked: false, + name: KEY_APP_TEMPORARY, + scope: AddonManager.SCOPE_TEMPORARY, + getAddonLocations: () => [], + isLinkedAddon: () => false, + installAddon: () => {}, + uninstallAddon: (aAddon) => {}, + getStagingDir: () => {}, +} + #ifdef XP_WIN /** * An object that identifies a registry install location for add-ons. The location diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 1ba7de2526d..4d06bf890d2 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -323,7 +323,7 @@ function DBAddonInternal(aLoaded) { if (aLoaded._installLocation) { this._installLocation = aLoaded._installLocation; - this.location = aLoaded._installLocation._name; + this.location = aLoaded._installLocation.name; } else if (aLoaded.location) { this._installLocation = XPIProvider.installLocationsByName[this.location]; diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_temporary/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/test_temporary/bootstrap.js new file mode 100644 index 00000000000..1666f2972c3 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_temporary/bootstrap.js @@ -0,0 +1 @@ +Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js new file mode 100644 index 00000000000..065289d23ec --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js @@ -0,0 +1,445 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const ID = "bootstrap1@tests.mozilla.org"; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42"); +startupManager(); + +BootstrapMonitor.init(); + +// Install a temporary add-on with no existing add-on present. +// Restart and make sure it has gone away. +add_task(function*() { + let extInstallCalled = false; + AddonManager.addInstallListener({ + onExternalInstall: (aInstall) => { + do_check_eq(aInstall.id, ID); + do_check_eq(aInstall.version, "1.0"); + extInstallCalled = true; + }, + }); + + let installingCalled = false; + let installedCalled = false; + AddonManager.addAddonListener({ + onInstalling: (aInstall) => { + do_check_eq(aInstall.id, ID); + do_check_eq(aInstall.version, "1.0"); + installingCalled = true; + }, + onInstalled: (aInstall) => { + do_check_eq(aInstall.id, ID); + do_check_eq(aInstall.version, "1.0"); + installedCalled = true; + }, + onInstallStarted: (aInstall) => { + do_throw("onInstallStarted called unexpectedly"); + } + }); + + yield AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1")); + + do_check_true(extInstallCalled); + do_check_true(installingCalled); + do_check_true(installedCalled); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + let addon = yield promiseAddonByID(ID); + + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + yield promiseRestartManager(); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + + addon = yield promiseAddonByID(ID); + do_check_eq(addon, null); + + yield 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(function*() { + yield promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + let addon = yield promiseAddonByID(ID); + + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + // test that an unpacked add-on works too + let tempdir = gTmpD.clone(); + + writeInstallRDFToDir({ + id: ID, + version: "2.0", + bootstrap: true, + unpack: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Bootstrap 1 (temporary)", + }, tempdir, "bootstrap1@tests.mozilla.org", "bootstrap.js"); + + let unpacked_addon = tempdir.clone(); + unpacked_addon.append(ID); + do_get_file("data/test_temporary/bootstrap.js") + .copyTo(unpacked_addon, "bootstrap.js"); + + yield AddonManager.installTemporaryAddon(unpacked_addon); + + BootstrapMonitor.checkAddonInstalled(ID, "2.0"); + BootstrapMonitor.checkAddonStarted(ID, "2.0"); + + addon = yield promiseAddonByID(ID); + + // temporary add-on is installed and started + do_check_neq(addon, null); + do_check_eq(addon.version, "2.0"); + do_check_eq(addon.name, "Test Bootstrap 1 (temporary)"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + restartManager(); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + addon = yield promiseAddonByID(ID); + + // existing add-on is back + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + unpacked_addon.remove(true); + addon.uninstall(); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + + yield promiseRestartManager(); +}); + +// 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(function*() { + yield promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + let tempdir = gTmpD.clone(); + writeInstallRDFToDir({ + id: ID, + version: "2.0", + bootstrap: true, + unpack: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Bootstrap 1 (temporary)", + }, tempdir); + + let unpacked_addon = tempdir.clone(); + unpacked_addon.append(ID); + + let extInstallCalled = false; + AddonManager.addInstallListener({ + onExternalInstall: (aInstall) => { + do_check_eq(aInstall.id, ID); + do_check_eq(aInstall.version, "2.0"); + extInstallCalled = true; + }, + }); + + let installingCalled = false; + let installedCalled = false; + AddonManager.addAddonListener({ + onInstalling: (aInstall) => { + do_check_eq(aInstall.id, ID); + if (!installingCalled) + do_check_eq(aInstall.version, "2.0"); + installingCalled = true; + }, + onInstalled: (aInstall) => { + do_check_eq(aInstall.id, ID); + if (!installedCalled) + do_check_eq(aInstall.version, "2.0"); + installedCalled = true; + }, + onInstallStarted: (aInstall) => { + do_throw("onInstallStarted called unexpectedly"); + } + }); + + yield AddonManager.installTemporaryAddon(unpacked_addon); + + do_check_true(extInstallCalled); + do_check_true(installingCalled); + do_check_true(installedCalled); + + let addon = yield promiseAddonByID(ID); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + + // temporary add-on is installed and started + do_check_neq(addon, null); + do_check_eq(addon.version, "2.0"); + do_check_eq(addon.name, "Test Bootstrap 1 (temporary)"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + addon.uninstall(); + + addon = yield promiseAddonByID(ID); + + BootstrapMonitor.checkAddonInstalled(ID); + BootstrapMonitor.checkAddonStarted(ID); + + // existing add-on is back + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + unpacked_addon.remove(true); + addon.uninstall(); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + + yield 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(function*() { + yield promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + let addon = yield promiseAddonByID(ID); + + addon.userDisabled = true; + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonNotStarted(ID); + + let tempdir = gTmpD.clone(); + writeInstallRDFToDir({ + id: ID, + version: "2.0", + bootstrap: true, + unpack: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Bootstrap 1 (temporary)", + }, tempdir, "bootstrap1@tests.mozilla.org", "bootstrap.js"); + + let unpacked_addon = tempdir.clone(); + unpacked_addon.append(ID); + do_get_file("data/test_temporary/bootstrap.js") + .copyTo(unpacked_addon, "bootstrap.js"); + + let extInstallCalled = false; + AddonManager.addInstallListener({ + onExternalInstall: (aInstall) => { + do_check_eq(aInstall.id, ID); + do_check_eq(aInstall.version, "2.0"); + extInstallCalled = true; + }, + }); + + yield AddonManager.installTemporaryAddon(unpacked_addon); + + do_check_true(extInstallCalled); + + let tempAddon = yield promiseAddonByID(ID); + + BootstrapMonitor.checkAddonInstalled(ID, "2.0"); + BootstrapMonitor.checkAddonStarted(ID); + + // temporary add-on is installed and started + do_check_neq(tempAddon, null); + do_check_eq(tempAddon.version, "2.0"); + do_check_eq(tempAddon.name, "Test Bootstrap 1 (temporary)"); + do_check_true(tempAddon.isCompatible); + do_check_false(tempAddon.appDisabled); + do_check_true(tempAddon.isActive); + do_check_eq(tempAddon.type, "extension"); + do_check_eq(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + tempAddon.uninstall(); + unpacked_addon.remove(true); + + addon.userDisabled = false; + addon = yield promiseAddonByID(ID); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID); + + // existing add-on is back + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + addon.uninstall(); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + + yield promiseRestartManager(); +}); + +// Installing a temporary add-on over a non-restartless add-on should fail. +add_task(function*(){ + yield promiseInstallAllFiles([do_get_addon("test_install1")], true); + + let non_restartless_ID = "addon1@tests.mozilla.org"; + + BootstrapMonitor.checkAddonNotInstalled(non_restartless_ID); + BootstrapMonitor.checkAddonNotStarted(non_restartless_ID); + + restartManager(); + + BootstrapMonitor.checkAddonNotInstalled(non_restartless_ID); + BootstrapMonitor.checkAddonNotStarted(non_restartless_ID); + + let addon = yield promiseAddonByID(non_restartless_ID); + + // non-restartless add-on is installed and started + do_check_neq(addon, null); + do_check_eq(addon.id, non_restartless_ID); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + let tempdir = gTmpD.clone(); + writeInstallRDFToDir({ + id: non_restartless_ID, + version: "2.0", + bootstrap: true, + unpack: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test 1 (temporary)", + }, tempdir); + + let unpacked_addon = tempdir.clone(); + unpacked_addon.append(non_restartless_ID); + + try { + yield AddonManager.installTemporaryAddon(unpacked_addon); + do_throw("Installing over a non-restartless add-on should return" + + " a rejected promise"); + } catch (err) { + do_check_eq(err.message, + "Non-restartless add-on with ID addon1@tests.mozilla.org is" + + " already installed"); + } + + unpacked_addon.remove(true); + addon.uninstall(); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + + yield promiseRestartManager(); +}); + +// Installing a temporary add-on when there is already a temporary +// add-on should fail. +add_task(function*() { + yield AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1")); + + let addon = yield promiseAddonByID(ID); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + try { + yield AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1")); + do_throw("Installing a temporary second temporary add-on should return" + + " a rejected promise"); + } catch (err) { + do_check_eq(err.message, + "Add-on with ID bootstrap1@tests.mozilla.org is already temporarily" + + " installed"); + } + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + yield promiseRestartManager(); + + BootstrapMonitor.checkAddonNotInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 25e8d9336f8..e1b1bdcf92b 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -29,6 +29,7 @@ skip-if = appname != "firefox" [test_system_reset.js] [test_XPIcancel.js] [test_XPIStates.js] +[test_temporary.js]