--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2285,34 +2285,30 @@ var AddonManagerInternal = {
logger.debug(`Registering upgrade listener for ${addonId}`);
this.upgradeListeners.set(addonId, aCallback);
});
},
/**
* Removes an UpgradeListener if the listener is registered.
*
- * @param aInstanceID
- * The instance ID of the addon to remove
+ * @param aID
+ * The addon ID of the addon to remove
*/
- removeUpgradeListener: function(aInstanceID) {
- if (!aInstanceID || typeof aInstanceID != "symbol")
- throw Components.Exception("aInstanceID must be a symbol",
+ removeUpgradeListener: function(aID) {
+ if (!aID || typeof aID != "string") {
+ throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
-
- this.getAddonByInstanceID(aInstanceID).then(addon => {
- if (!addon) {
- throw Error("No addon for instanceID:", aInstanceID.toString());
- }
- if (this.upgradeListeners.has(addon.id)) {
- this.upgradeListeners.delete(addon.id);
- } else {
- throw Error("No upgrade listener registered for addon ID:", addon.id);
- }
- });
+ }
+
+ if (this.upgradeListeners.has(aID)) {
+ this.upgradeListeners.delete(aID);
+ } else {
+ throw Error("No upgrade listener registered for addon ID:", aID);
+ }
},
/**
* Installs a temporary add-on from a local file or directory.
* @param aFile
* An nsIFile for the file or directory of the add-on to be
* temporarily installed.
* @return a Promise that rejects if the add-on is not a valid restartless
@@ -3171,16 +3167,20 @@ this.AddonManagerPrivate = {
hasUpgradeListener: function(aId) {
return AddonManagerInternal.upgradeListeners.has(aId);
},
getUpgradeListener: function(aId) {
return AddonManagerInternal.upgradeListeners.get(aId);
},
+
+ removeUpgradeListener: function(aId) {
+ AddonManagerInternal.removeUpgradeListener(aId);
+ },
};
/**
* This is the public API that UI and developers should be calling. All methods
* just forward to AddonManagerInternal.
*/
this.AddonManager = {
// Constants for the AddonInstall.state property
@@ -3547,20 +3547,16 @@ this.AddonManager = {
getUpgradeListener: function(aId) {
return AddonManagerInternal.upgradeListeners.get(aId);
},
addUpgradeListener: function(aInstanceID, aCallback) {
AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback);
},
- removeUpgradeListener: function(aInstanceID) {
- AddonManagerInternal.removeUpgradeListener(aInstanceID);
- },
-
addAddonListener: function(aListener) {
AddonManagerInternal.addAddonListener(aListener);
},
removeAddonListener: function(aListener) {
AddonManagerInternal.removeAddonListener(aListener);
},
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -3237,46 +3237,25 @@ this.XPIProvider = {
}
if (!systemAddonLocation.isValidAddon(item.addon))
return false;
return true;
}
- try {
- if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
- throw new Error("Rejecting updated system add-on set that either could not " +
- "be downloaded or contained unusable add-ons.");
- }
-
- // Install into the install location
- logger.info("Installing new system add-on set");
- yield systemAddonLocation.installAddonSet(Array.from(addonList.values())
- .map(a => a.addon));
-
- // Bug 1204156: Switch to the new system add-ons without requiring a restart
- }
- finally {
- // Delete the temporary files
- logger.info("Deleting temporary files");
- for (let item of addonList.values()) {
- // If this item downloaded delete the temporary file.
- if (item.path) {
- try {
- yield OS.File.remove(item.path);
- }
- catch (e) {
- logger.warn(`Failed to remove temporary file ${item.path}.`, e);
- }
- }
- }
-
- yield systemAddonLocation.cleanDirectories();
- }
+ if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
+ throw new Error("Rejecting updated system add-on set that either could not " +
+ "be downloaded or contained unusable add-ons.");
+ }
+
+ // Install into the install location
+ logger.info("Installing new system add-on set");
+ yield systemAddonLocation.installAddonSet(Array.from(addonList.values())
+ .map(a => a.addon));
}),
/**
* Verifies that all installed add-ons are still correctly signed.
*/
verifySignatures: function() {
XPIDatabase.getAddonList(a => true, (addons) => {
Task.spawn(function*() {
@@ -3370,18 +3349,19 @@ this.XPIProvider = {
* of passing through updated compatibility information
* @return true if an add-on was installed or uninstalled
*/
processPendingFileChanges: function(aManifests) {
let changed = false;
for (let location of this.installLocations) {
aManifests[location.name] = {};
// We can't install or uninstall anything in locked locations
- if (location.locked)
+ if (location.locked) {
continue;
+ }
let stagingDir = location.getStagingDir();
try {
if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
continue;
}
catch (e) {
@@ -3511,17 +3491,17 @@ this.XPIProvider = {
// Pass this through to addMetadata so it knows this add-on was
// likely installed through the UI
aManifests[location.name][id] = addon;
}
catch (e) {
// If some data can't be recovered from the cached metadata then it
// is unlikely to be a problem big enough to justify throwing away
- // the install, just log and error and continue
+ // the install, just log an error and continue
logger.error("Unable to read metadata from " + jsonfile.path, e);
}
finally {
fis.close();
}
}
seenFiles.push(jsonfile.leafName);
@@ -5459,34 +5439,41 @@ AddonInstall.prototype = {
progress: null,
maxProgress: null,
/**
* Initialises this install to be a staged install waiting to be applied
*
* @param aManifest
* The cached manifest for the staged install
- */
- initStagedInstall: function(aManifest) {
+ * @param aCallback
+ * A callback to call with the resulting install.
+ */
+ initStagedInstall: function(aManifest, aCallback) {
this.name = aManifest.name;
this.type = aManifest.type;
this.version = aManifest.version;
this.icons = aManifest.icons;
this.releaseNotesURI = aManifest.releaseNotesURI ?
NetUtil.newURI(aManifest.releaseNotesURI) :
- null
+ null;
this.sourceURI = aManifest.sourceURI ?
NetUtil.newURI(aManifest.sourceURI) :
null;
- this.file = null;
+ this.file = this.sourceURI;
+ this._sourceBundle = this.sourceURI;
this.addon = aManifest;
-
+ this.addon.sourceURI = this.sourceURI;
+
+ this.alreadyStaged = true;
this.state = AddonManager.STATE_INSTALLED;
XPIProvider.installs.push(this);
+
+ aCallback(this);
},
/**
* Initialises this install to be an install from a local file.
*
* @param aCallback
* The callback to pass the initialised AddonInstall to
*/
@@ -5778,17 +5765,19 @@ AddonInstall.prototype = {
return;
// Create new AddonInstall instances for every remaining file
if (!this.linkedInstalls)
this.linkedInstalls = [];
for (let { entryName, file } of aFiles) {
logger.debug("Creating linked install from " + entryName);
- let install = yield new Promise(resolve => AddonInstall.createInstall(resolve, file));
+ let install = yield new Promise(
+ resolve => AddonInstall.createInstall(resolve, file)
+ );
// Make the new install own its temporary file
install.ownsTempFile = true;
this.linkedInstalls.push(install);
// If one of the internal XPIs was multipackage then move its linked
// installs to the outer install
@@ -6338,23 +6327,20 @@ AddonInstall.prototype = {
// upgrade has been staged for restart, notify the add-on and give
// it a way to resume.
let callback = AddonManagerPrivate.getUpgradeListener(this.addon.id);
callback({
version: this.version,
install: () => {
switch (this.state) {
- case AddonManager.STATE_INSTALLED:
- // this addon has already been installed, nothing to do
- logger.warn(`${this.addon.id} tried to resume postponed upgrade, but it's already installed`);
- break;
case AddonManager.STATE_POSTPONED:
logger.info(`${this.addon.id} has resumed a previously postponed upgrade`);
this.state = AddonManager.STATE_DOWNLOADED;
+ AddonManagerPrivate.removeUpgradeListener(this.addon.id);
this.installLocation.releaseStagingDir();
this.install();
break;
default:
logger.warn(`${this.addon.id} cannot resume postponed upgrade from state (${this.state})`);
break;
}
},
@@ -6406,31 +6392,34 @@ AddonInstall.prototype = {
this.existingAddon._installLocation == this.installLocation;
let requiresRestart = XPIProvider.installRequiresRestart(this.addon);
logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec);
AddonManagerPrivate.callAddonListeners("onInstalling",
this.addon.wrapper,
requiresRestart);
- let stagingDir = this.installLocation.getStagingDir();
- let stagedAddon = stagingDir.clone();
+ let stagedAddon = this.installLocation.getStagingDir();
Task.spawn((function*() {
let installedUnpacked = 0;
yield this.installLocation.requestStagingDir();
// remove any previously staged files
- yield this.unstageInstall(stagedAddon);
+ if (!this.alreadyStaged) {
+ yield this.unstageInstall(stagedAddon);
+ }
stagedAddon.append(this.addon.id);
stagedAddon.leafName = this.addon.id + ".xpi";
- installedUnpacked = yield this.stageInstall(requiresRestart, stagedAddon, isUpgrade);
+ if (!this.alreadyStaged) {
+ installedUnpacked = yield this.stageInstall(requiresRestart, stagedAddon, isUpgrade);
+ }
if (requiresRestart) {
this.state = AddonManager.STATE_INSTALLED;
AddonManagerPrivate.callInstallListeners("onInstallEnded",
this.listeners, this.wrapper,
this.addon.wrapper);
}
else {
@@ -6529,17 +6518,18 @@ AddonInstall.prototype = {
// listeners because important cleanup hasn't been done yet
XPIProvider.unloadBootstrapScope(this.addon.id);
}
}
XPIProvider.setTelemetry(this.addon.id, "unpacked", installedUnpacked);
recordAddonTelemetry(this.addon);
}
}).bind(this)).then(null, (e) => {
- logger.warn("Failed to install " + this.file.path + " from " + this.sourceURI.spec + " to " + stagedAddon.path, e);
+ logger.warn(`Failed to install ${this.file.path} from ${this.sourceURI.spec} to ${stagedAddon.path}`, e);
+
if (stagedAddon.exists())
recursiveRemove(stagedAddon);
this.state = AddonManager.STATE_INSTALL_FAILED;
this.error = AddonManager.ERROR_FILE_ACCESS;
XPIProvider.removeActiveInstall(this);
AddonManagerPrivate.callAddonListeners("onOperationCancelled",
this.addon.wrapper);
AddonManagerPrivate.callInstallListeners("onInstallFailed",
@@ -6565,19 +6555,19 @@ AddonInstall.prototype = {
logger.debug("Addon " + this.addon.id + " will be installed as " +
"an unpacked directory");
stagedAddon.leafName = this.addon.id;
yield OS.File.makeDir(stagedAddon.path);
yield ZipUtils.extractFilesAsync(this.file, stagedAddon);
installedUnpacked = 1;
}
else {
- logger.debug("Addon " + this.addon.id + " will be installed as " +
- "a packed xpi");
+ logger.debug(`Addon ${this.addon.id} will be installed as a packed xpi`);
stagedAddon.leafName = this.addon.id + ".xpi";
+
yield OS.File.copy(this.file.path, stagedAddon.path);
}
if (restartRequired) {
// Point the add-on to its extracted files as the xpi may get deleted
this.addon._sourceBundle = stagedAddon;
// Cache the AddonInternal as it may have updated compatibility info
@@ -6631,46 +6621,52 @@ AddonInstall.prototype = {
return this;
}
return this.badCertHandler.getInterface(iid);
}
}
/**
- * Creates a new AddonInstall for an already staged install. Used when
- * installing the staged install failed for some reason.
+ * Creates a new AddonInstall for an already staged install.
*
+ * @param aInstallLocation
+ * The install location holding the staged install.
* @param aDir
* The directory holding the staged install
* @param aManifest
* The cached manifest for the install
+ * @param aCallback
+ * A callback to call with the resulting install.
*/
-AddonInstall.createStagedInstall = function(aInstallLocation, aDir, aManifest) {
+AddonInstall.createStagedInstall = function(aInstallLocation, aDir, aManifest, aCallback = undefined) {
let url = Services.io.newFileURI(aDir);
let install = new AddonInstall(aInstallLocation, aDir);
- install.initStagedInstall(aManifest);
+ install.initStagedInstall(aManifest, aCallback);
};
/**
- * Creates a new AddonInstall to install an add-on from a local file. Installs
- * always go into the profile install location.
+ * Creates a new AddonInstall to install an add-on from a local file.
*
* @param aCallback
* The callback to pass the new AddonInstall to
* @param aFile
* The file to install
+ * @param aLocation
+ * The location to install to
*/
-AddonInstall.createInstall = function(aCallback, aFile) {
- let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
+AddonInstall.createInstall = function(aCallback, aFile, aLocation = undefined) {
+ if (!aLocation) {
+ aLocation = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
+ }
let url = Services.io.newFileURI(aFile);
try {
- let install = new AddonInstall(location, url);
+ let install = new AddonInstall(aLocation, url);
install.initLocalInstall(aCallback);
}
catch (e) {
logger.error("Error creating install", e);
makeSafe(aCallback)(null);
}
};
@@ -8543,55 +8539,151 @@ Object.assign(MutableDirectoryInstallLoc
}
}
delete this._IDToFileMap[aId];
},
});
/**
- * An object which identifies a directory install location for system add-ons.
- * The location consists of a directory which contains the add-ons installed in
- * the location.
+ * An object which identifies a directory install location for system add-ons
+ * upgrades.
+ *
+ * The location consists of a directory which contains the add-ons installed.
*
* @param aName
* The string identifier for the install location
* @param aDirectory
* The nsIFile directory for the install location
* @param aScope
* The scope of add-ons installed in this location
* @param aResetSet
* True to throw away the current add-on set
*/
function SystemAddonInstallLocation(aName, aDirectory, aScope, aResetSet) {
this._baseDir = aDirectory;
this._nextDir = null;
+ this._stagingDirLock = 0;
+
if (aResetSet)
this.resetAddonSet();
this._addonSet = this._loadAddonSet();
this._directory = null;
if (this._addonSet.directory) {
this._directory = aDirectory.clone();
this._directory.append(this._addonSet.directory);
logger.info("SystemAddonInstallLocation scanning directory " + this._directory.path);
}
else {
logger.info("SystemAddonInstallLocation directory is missing");
}
DirectoryInstallLocation.call(this, aName, this._directory, aScope);
- this.locked = true;
+ this.locked = false;
}
SystemAddonInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
Object.assign(SystemAddonInstallLocation.prototype, {
/**
+ * Removes the specified files or directories in the staging directory and
+ * then if the staging directory is empty attempts to remove it.
+ *
+ * @param aLeafNames
+ * An array of file or directory to remove from the directory, the
+ * array may be empty
+ */
+ cleanStagingDir: function(aLeafNames = []) {
+ let dir = this.getStagingDir();
+
+ for (let name of aLeafNames) {
+ let file = dir.clone();
+ file.append(name);
+ recursiveRemove(file);
+ }
+
+ if (this._stagingDirLock > 0)
+ return;
+
+ let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+ try {
+ if (dirEntries.nextFile)
+ return;
+ }
+ finally {
+ dirEntries.close();
+ }
+
+ try {
+ setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
+ dir.remove(false);
+ }
+ catch (e) {
+ logger.warn("Failed to remove staging dir", e);
+ // Failing to remove the staging directory is ignorable
+ }
+ },
+
+ /**
+ * Gets the staging directory to put add-ons that are pending install and
+ * uninstall into.
+ *
+ * @return {nsIFile} - staging directory for system add-on upgrades.
+ */
+ getStagingDir: function() {
+ this._addonSet = this._loadAddonSet();
+ let dir = null;
+ if (this._addonSet.directory) {
+ this._directory = this._baseDir.clone();
+ this._directory.append(this._addonSet.directory);
+ dir = this._directory.clone();
+ dir.append(DIR_STAGE);
+ }
+ else {
+ logger.info("SystemAddonInstallLocation directory is missing");
+ }
+
+ return dir;
+ },
+
+ requestStagingDir: function() {
+ this._stagingDirLock++;
+ if (this._stagingDirPromise)
+ return this._stagingDirPromise;
+
+ this._addonSet = this._loadAddonSet();
+ if (this._addonSet.directory) {
+ this._directory = this._baseDir.clone();
+ this._directory.append(this._addonSet.directory);
+ }
+
+ OS.File.makeDir(this._directory.path);
+ let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
+ return this._stagingDirPromise = OS.File.makeDir(stagepath).then(null, (e) => {
+ if (e instanceof OS.File.Error && e.becauseExists)
+ return;
+ logger.error("Failed to create staging directory", e);
+ throw e;
+ });
+ },
+
+ releaseStagingDir: function() {
+ this._stagingDirLock--;
+
+ if (this._stagingDirLock == 0) {
+ this._stagingDirPromise = null;
+ this.cleanStagingDir();
+ }
+
+ return Promise.resolve();
+ },
+
+ /**
* Reads the current set of system add-ons
*/
_loadAddonSet: function() {
try {
let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null);
if (setStr) {
let addonSet = JSON.parse(setStr);
if ((typeof addonSet == "object") && addonSet.schema == 1)
@@ -8602,16 +8694,19 @@ Object.assign(SystemAddonInstallLocation
logger.error("Malformed system add-on set, resetting.");
}
return { schema: 1, addons: {} };
},
/**
* Saves the current set of system add-ons
+ *
+ * @param {Object} aAddonSet - object containing schema, directory and set
+ * of system add-on IDs and versions.
*/
_saveAddonSet: function(aAddonSet) {
Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet));
},
getAddonLocations: function() {
// Updated system add-ons are ignored in safe mode
if (Services.appinfo.inSafeMode)
@@ -8681,17 +8776,33 @@ Object.assign(SystemAddonInstallLocation
return true;
},
/**
* Resets the add-on set so on the next startup the default set will be used.
*/
resetAddonSet: function() {
- this._saveAddonSet({ schema: 1, addons: {} });
+
+ if (this._addonSet) {
+ logger.info("Removing all system add-on upgrades.");
+
+ // remove everything from the pref first, if uninstall
+ // fails then at least they will not be re-activated on
+ // next restart.
+ this._saveAddonSet({ schema: 1, addons: {} });
+
+ for (let id of Object.keys(this._addonSet.addons)) {
+ AddonManager.getAddonByID(id, addon => {
+ if (addon) {
+ addon.uninstall();
+ }
+ });
+ }
+ }
},
/**
* Removes any directories not currently in use or pending use after a
* restart. Any errors that happen here don't really matter as we'll attempt
* to cleanup again next time.
*/
cleanDirectories: Task.async(function*() {
@@ -8744,77 +8855,303 @@ Object.assign(SystemAddonInstallLocation
}
finally {
iterator.close();
}
}),
/**
* Installs a new set of system add-ons into the location and updates the
- * add-on set in prefs. We wait to switch state until a restart.
+ * add-on set in prefs.
+ *
+ * @param {Array} aAddons - An array of addons to install.
*/
installAddonSet: Task.async(function*(aAddons) {
// Make sure the base dir exists
yield OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });
+ let addonSet = this._loadAddonSet();
+
+ // Remove any add-ons that are no longer part of the set.
+ let oldAddons = new Set(Object.keys(addonSet.addons));
+ let newAddons = new Set(aAddons);
+ var difference = new Set([...oldAddons].filter(x => !newAddons.has(x)));
+
+ if (difference.size > 0) {
+ for (let addonID of difference.values()) {
+ let addon = yield new Promise(resolve => AddonManager.getAddonByID(addonID, resolve));
+ if (addon) {
+ addon.uninstall();
+ }
+ }
+ }
+
let newDir = this._baseDir.clone();
let uuidGen = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator);
newDir.append("blank");
while (true) {
newDir.leafName = uuidGen.generateUUID().toString();
try {
yield OS.File.makeDir(newDir.path, { ignoreExisting: false });
break;
}
catch (e) {
- // Directory already exists, pick another
- }
- }
-
- let copyAddon = Task.async(function*(addon) {
- let target = OS.Path.join(newDir.path, addon.id + ".xpi");
- logger.info(`Copying ${addon.id} from ${addon._sourceBundle.path} to ${target}.`);
- try {
- yield OS.File.copy(addon._sourceBundle.path, target);
- }
- catch (e) {
- logger.error(`Failed to copy ${addon.id} from ${addon._sourceBundle.path} to ${target}.`, e);
- throw e;
- }
- addon._sourceBundle = new nsIFile(target);
+ logger.debug("Could not create new system add-on updates dir, retrying", e);
+ }
+ }
+
+ // Record the new upgrade directory.
+ let state = { schema: 1, directory: newDir.leafName, addons: {} };
+ this._saveAddonSet(state);
+
+ this._nextDir = newDir;
+
+ let checkPostponed = (addon) => {
+ let result = false;
+ if (AddonManagerPrivate.hasUpgradeListener(addon.id)) {
+ result = true;
+ }
+ return result;
+ }
+
+ let installAddon = Task.async(function*(addon) {
+ let install = yield new Promise(resolve => {
+ AddonInstall.createInstall(resolve, addon._sourceBundle, this);
+ });
+ // Make the new install own its temporary file.
+ install.ownsTempFile = true;
+ install.install();
+ });
+
+ let removeAddon = Task.async(function*(addon) {
+ if (addon) {
+ addon.uninstall();
+ }
});
try {
- yield waitForAllPromises(aAddons.map(copyAddon));
+ // All add-ons in position, create the new state and store it in prefs
+ state = { schema: 1, directory: newDir.leafName, addons: {} };
+ for (let addon of aAddons) {
+ state.addons[addon.id] = {
+ version: addon.version
+ }
+ }
+
+ this._saveAddonSet(state);
+
+ let blockers = aAddons.filter(
+ addon => AddonManagerPrivate.hasUpgradeListener(addon.id)
+ );
+
+ if (blockers) {
+ yield this.postponeAddonSet(aAddons);
+ } else {
+ yield waitForAllPromises(aAddons.map(installAddon));
+ }
}
catch (e) {
+ // roll back to built-in set.
+ let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_DEFAULTS];
+ let states = XPIStates.getLocation(systemAddonLocation.name);
+ this.resetAddonSet();
+
try {
yield OS.File.removeDir(newDir.path, { ignorePermissions: true });
}
catch (e) {
- logger.warn(`Failed to remove new system add-on directory ${newDir.path}.`, e);
+ logger.warn(`Failed to remove failed system add-on directory ${newDir.path}.`, e);
}
throw e;
}
-
- // All add-ons in position, create the new state and store it in prefs
- let state = { schema: 1, directory: newDir.leafName, addons: {} };
- for (let addon of aAddons) {
- state.addons[addon.id] = {
- version: addon.version
- }
- }
-
- this._saveAddonSet(state);
- this._nextDir = newDir;
+ }),
+
+ /**
+ * Postpone all add-ons in a set. If any have upgrade listeners, provide
+ * them a callback with which to resume the install of the set.
+ *
+ * @param {Array} aAddons - An array of add-ons to check for upgrade listeners.
+ */
+ postponeAddonSet: Task.async(function*(aAddons) {
+ let postponeAddon = Task.async(function*(addon) {
+ // If an upgrade listener is registered for this add-on, pass control
+ // over the upgrade to the add-on.
+ let install = yield new Promise(resolve => {
+ let location = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
+ AddonInstall.createInstall(resolve, addon._sourceBundle, location);
+ });
+
+ logger.info(`system add-on ${addon.id} has an upgrade listener, postponing upgrade set until restart`);
+
+ install.state = AddonManager.STATE_POSTPONED;
+
+ let stagingDir = install.installLocation.getStagingDir();
+ let stagedAddon = stagingDir.clone();
+
+ yield install.installLocation.requestStagingDir();
+ yield install.unstageInstall(stagedAddon);
+
+ stagedAddon.append(install.addon.id);
+ stagedAddon.leafName = install.addon.id + ".xpi";
+
+ yield install.stageInstall(true, stagedAddon, true);
+
+ AddonManagerPrivate.callInstallListeners("onInstallPostponed",
+ install.listeners, install.wrapper)
+
+ if (AddonManagerPrivate.hasUpgradeListener(addon.id)) {
+ // upgrade has been staged for restart, if it has an upgrade listener
+ // then provide it a way to resume.
+ let callback = AddonManagerPrivate.getUpgradeListener(install.addon.id);
+ callback({
+ version: install.version,
+ install: () => {
+ switch (install.state) {
+ case AddonManager.STATE_POSTPONED:
+ logger.info(`${install.addon.id} has resumed a previously postponed upgrade set`);
+ AddonManagerPrivate.removeUpgradeListener(install.addon.id);
+ install.installLocation.releaseStagingDir();
+ install.installLocation.resumeAddonSet();
+ break;
+ default:
+ logger.warn(`${install.addon.id} cannot resume postponed upgrade from state (${install.state})`);
+ break;
+ }
+ },
+ });
+ }
+ });
+
+ let blockers = aAddons.filter(
+ addon => AddonManagerPrivate.hasUpgradeListener(addon.id)
+ );
+
+ return blockers;
}),
+
+ /**
+ * Resumes upgrade of a previously-delayed add-on set.
+ */
+ resumeAddonSet: Task.async(function*() {
+ let checkPostponed = (addon) => {
+ let result = false;
+ if (AddonManagerPrivate.hasUpgradeListener(addon.id)) {
+ result = true;
+ }
+ return result;
+ }
+
+ let resumeAddon = Task.async(function*(id) {
+ let existingAddonID = id;
+
+ let location = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
+ let stagingDir = location.getStagingDir()
+ let stagedAddon = stagingDir.clone();
+ stagedAddon.append(`${id}.xpi`);
+
+ let addon = syncLoadManifestFromFile(stagedAddon, location);
+ addon.sourceURI = stagedAddon;
+
+ let install = yield new Promise(resolve => {
+ AddonInstall.createStagedInstall(location, stagingDir, addon, resolve);
+ });
+
+ install.state = AddonManager.STATE_DOWNLOADED;
+ install.install();
+ });
+
+ let addonSet = this._loadAddonSet();
+ let addonIDs = Object.keys(addonSet.addons);
+
+ let blockers = yield waitForAllPromises(addonIDs.map(checkPostponed));
+ if (blockers.some(a => a)) {
+ logger.warn("Attempted to resume system add-on install but upgrade blockers are still present");
+ } else {
+ yield waitForAllPromises(addonIDs.map(resumeAddon));
+ }
+ }),
+
+ /**
+ * Returns a directory that is normally on the same filesystem as the rest of
+ * the install location and can be used for temporarily storing files during
+ * safe move operations. Calling this method will delete the existing trash
+ * directory and its contents.
+ *
+ * @return an nsIFile
+ */
+ getTrashDir: function() {
+ let trashDir = this._directory.clone();
+ trashDir.append(DIR_TRASH);
+ let trashDirExists = trashDir.exists();
+ try {
+ if (trashDirExists)
+ recursiveRemove(trashDir);
+ trashDirExists = false;
+ } catch (e) {
+ logger.warn("Failed to remove trash directory", e);
+ }
+ if (!trashDirExists)
+ trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ return trashDir;
+ },
+
+ /**
+ * Installs an add-on into the install location.
+ *
+ * @param id
+ * The ID of the add-on to install
+ * @param source
+ * The source nsIFile to install from
+ * @return an nsIFile indicating where the add-on was installed to
+ */
+ installAddon: function({id, source}) {
+ let trashDir = this.getTrashDir();
+ let transaction = new SafeInstallOperation();
+
+ // If any of these operations fails the finally block will clean up the
+ // temporary directory
+ try {
+ if (source.isFile()) {
+ flushJarCache(source);
+ }
+
+ transaction.moveUnder(source, this._directory);
+ }
+ finally {
+ // It isn't ideal if this cleanup fails but it isn't worth rolling back
+ // the install because of it.
+ try {
+ recursiveRemove(trashDir);
+ }
+ catch (e) {
+ logger.warn("Failed to remove trash directory when installing " + id, e);
+ }
+ }
+
+ let newFile = this._directory.clone();
+ newFile.append(source.leafName);
+
+ try {
+ newFile.lastModifiedTime = Date.now();
+ } catch (e) {
+ logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
+ }
+ this._IDToFileMap[id] = newFile;
+ XPIProvider._addURIMapping(id, newFile);
+
+ return newFile;
+ },
+
+ // old system add-on upgrade dirs get automatically removed
+ uninstallAddon: (aAddon) => {},
});
/**
* An object which identifies an install location for temporary add-ons.
*/
const TemporaryInstallLocation = {
locked: false,
name: KEY_APP_TEMPORARY,
new file mode 100644
index 0000000000000000000000000000000000000000..06a81e260e7319fd6dedc09e18884b398e2988ee
GIT binary patch
literal 1097
zc$^FHW@Zs#U|`^2Xitpr?2Y&syOxoGL57)ufrmkcAt^t<q`0Igu|O}YI5dQlftfd!
zFTxCnODnh;7+Jmom4bCoo#>x`+kofj`+p)W{H@F^-)ddkmwK|Xc+5(8+i*ALXp#``
zri_3P=KK@o^K+h`56fwvs}tOBAN1aLW!{N}!t>b;uRT}$w>d>~@$v(rbsAl^$$^vk
z#A-kG{7KXL^0PdC`QqOxzvllHUVC!wl;G+o+{!CYR5qT>*;c0|Zd}n2a>0A$#Z}vQ
zdc&GhMcE@o{#=Ruxo4UDv8buS?=Sy0PWbrck5Q!F>6cYoBhSS@d;0A3pJMmhLY?Aa
zzSn2ZUJ-gNt~2!xhqB5|*6>b6_H<V<_SJ?4TK;(~_WqthA`d!VSKK@#_|W5E7Khx!
zk48zZ=Qs>yCKU4AR+xKEF)Lu=mhRASMUGMpONAfX?oE2@Ce{1lSMKH;278Yd=bz86
zyRG%^h|sL>(W(rBOJ}LAGpS%*wk~jX#iranrq(a2KQ(`Py}0`aTjVXBxAP^pN1nMP
zc;~L8{w`5=vjg%IHP`Mps}7TzevI4d$(z{Q5}OP(+&+Bxc!t5^y575V!|&^AUhP+R
zkoJ|?Qj}nIXwFOvZ`p&qyuF|7tTb7!d+2Xl!mMHWciLimmWw4N^_-tpalV_M`JX8O
z9_2HDQGW370i_6FJYHpDVBiKud1hX5Nn%cpUQtRKIK~Ztv2B4F<E@iU=N&Q-aQ*(1
z%Y02UbFR3H;ufXU>%Q^PDU%p=eyUanEr^d#_6TxWUG;osb?x(>Ip@CxP4M5)mu=eX
ze%NH5XyBCnQ@9^lpYnaIyXd6G-M3|j@4gMV%`5AxCOoU}WyPEuRdaVVJ8koyB7H>A
za$dq2hq#KR{~kGQ_;A3&_r1o6#nl48Eu20H#iZ_Zv}a4yzWcx=Z_TN5>w2`?eL7r>
zj$VIoVRxUW<%yo_t=1MSJJ&dFIK1i7B@TxQhxNtw#l=;V%C8xhKiufNes6ix)N3y;
z7zHoLRmk`iw0u&pL*ANo2X^lCPd^dPRCQYZ?~5-7d?!t4y~=fR@~V@@3fhuJlT>OO
zl7zahGkD&!5!LKp^5pInp2joh4=8GTEI!wHCXYeBS8z|#jg7H-d)AizzBBt&u46}i
z*o}|HUoYl;V!0!(*P3(x+I#;)`!1YwUC+jSD&Ap{cB$Xr{kn_t1H2iT<d|{gNC^fI
z0CE|YG=f;D`I8lrKha_e*+k3;M>g>@kV1)mR!H<?7|6;7(!>OWi-Gh6W)Kemj&i(U
new file mode 100644
index 0000000000000000000000000000000000000000..9f38cb3f81409db54a132af2a62ff24811a3cf0c
GIT binary patch
literal 730
zc$^FHW@Zs#U|`^2*pe9GdEiykMH?Wm8;E%rWEhh2^Gk|LiV_R-vWi1PI2o8zV)-Ic
zfw;7Sn}Lz#D<cB~Shu(KS#Q0QXU+$oIHRrSdFJFr4^JJ>D}fqbzB=d5Uk+KZ?x|Pv
zN`_P7Q>TbM2%Z=iHEB}(ymoibOgX=9h5)z=)&pI@9T{j51vK_569WS`&;^-!#U+V3
zIeJAYX<!FL0xgfkbU^E*(|Lys1YE!W<T76)+`c7Np!Gu6lvwlH+=-rS8=g#m<JnkO
z`&mhIQRv>!XLjx@cJ~u^*F2#faeV8n-<J(OiO+n#RR3q!71^iH{>fcbnIgy6{-HoR
zYxm;IH*?$;&p1|?a60#aAJ<L8fV@Z5ODxwKFjg*{`m3A!;4St?8oL%NNPj)D{w;@R
zaY5@-w%3Lnw_k8QuS*TRv|4DLjFBSKNs;?ahoi1qEL9MWOMCZ3;K?qQgMP1_yp<Uq
zGG?Fq`^7i2o&S<uxmbJPZR`8&35!jwwwy`H(9W2-(IjA#(23rwtQOnX+?D*)+YtE4
z{r+pY9h^*?Hbp5`2B}uErC2(<H6@q?_&9yL)uxhPsx`^n^W<(#Nv6nejnV7HzOtzr
z?Pjhqar@BpaKhW7htF?qIKNo)j$F%+^j7D(b#}kb`tClE{7@+6c=6wT%*PK^>Q$LW
zc>fbRvU^c*)c@OOezOI5Gcw6B<4PhD3?KlE1%@S!AQozZVTB|Zw75bx5i`n>O>6>^
aC<%ZSk^nFaWMu<sVgka&Kw6Or!~+1WC<V9x
new file mode 100644
index 0000000000000000000000000000000000000000..9c968cab7fdf679c30c3049f190882de33234b7b
GIT binary patch
literal 1113
zc$^FHW@Zs#U|`^2aE*)dygbe6_&!Dkh8AW91|9|(hNS%blH!u0!~(sn;?NLI2Ihyq
z1R@>-acKoN10%~<pi;2zsX^Y^cMJsf{SN1`W04m7J#T{3Y&8Loj0GZ9JvnD@d8PD9
z_LfDS`uDx>ZLLXW)r*sxOg^9eK0{i{jY~o$%yUlv)XVkLl+t^q>^ORQ*E@;VjX9qr
zUcWyfeLZPS+@lH8!*8Cg|5m?k+M&gq%be|!Qc8K}E<aY05Tl%&A=t7&Jzud`R=V5a
zEcdoal_D?dbjok-^jj$E&KT2?v`2r9;N~C+Va+KYvhADMA34gGZ*aX4G%G;)khoIk
zp|qMM4_YLuWcN0yoeU8d(4Ar4qNedu@KIjI34i6eLe>9%mi#}m<>KcxFLqjO4)W8<
ztU0g69H8Ot<#V8Cv61)UYYICO9bSKVQoh=C|DqM5*-M{)owfDq+C;WBRl@soo6pHj
zObvgsnceH_zwEd}M_JFfK0lpma9}n^&sKJG`4|zN6GwM%6BTLa@!IjP<Um6(M?r1R
zi>$hv(=V6vzqtBnX++gB-8)9Y;*|;($%iwNLgt4i7W+KTh}y@H+8uc+`0@w#K7mX>
zCj+i6YG*7i1PNSAkh;7tt^W0#|NdIySt9SwXy3~#Y1Ci%BOswPX6+}1f~3&2-}ck=
zzj^B99%jE?zRL6AeC=P-0q`_%IX24k{@cBu!+{Zdo{52h8<++%^NLFnb8_^GQqsUl
zfDah`e3(g~b<*j)W&;7&?=`L7*N(Jo>sD}znwoaqH~#jiM$e>^`Zp)HeR-X&;IMd$
z7Pt4l;`Qn0I&vbt9qkTUt-Go;$M<o;Om@d_F>5;3Yi#Ez?@(L%rNF|6|9Wul_7b!C
z=QHPAQ0dIs+Ic5XakcVOwM2(m>v@+coZl2wU#J-I+d=5Kxk$?7c`f^76(bqfZMm~R
zzInsedykdwL{FU;q#4dPQ($6BSN?&8{-$${CTOlNDlg&qw3z8&-@iKzd!6%(>}RKK
z`OLRdxa8f;+fnuMJlSh6E=*N(-`#%A*sIz59cxi?Dd#!cj7*77TN?~Nx!b;w+tH=8
zX;YM9Wr%1cQwpoI+oFt|1v6UyELBxmKd(!KSFO@m)cJ$B|829Z+JgelY1<pVALEEK
zk~wfL{BHJPwd~+svK$}J|6{9Ickj1p*_Ooxc`2pq@0T45eDQpdc=OhC@eWPg<!*oF
zXS3fA@MdI^W5$(3B^W>e$Yog42x6h;S5`=VMT;n86EWi(*+dqg2ukd;LSi4oKvp)8
OCMF<U45Sw@gLnYK@W74$
new file mode 100644
index 0000000000000000000000000000000000000000..e5358ffa709048c06262f0540e3ce5c8e3616754
GIT binary patch
literal 729
zc$^FHW@Zs#U|`^2NR5l~Jn*XNq79JO4a7VQG7L%i`6b0AMTrG^S;e6toD9rzzXc*p
z{s}~sR&X;gvV3J^U;yj();{a4ck;~n;1g%G^*qm<yy)Sn<9Q`e!^>Ca-1*BPE7m>r
zYF^23N_^@Ru?N8u1EVHQil5i+?wKj)*Ub<BcR>Qs1-m=F{Ud?Ko@ZiU;0C%NGq1QL
zF(*f_C?yT-0AZj5j4>V1I_Y#?vw?u?_nKDkYe!nPbt^bUO-;M*8-M#0=j0=k;y1NB
z{rYM;!C~<hEpG39#p}}Fb>!UiaI`yUweG6Y9N)(UGua)##jNRAud$uudxzT6E$d!q
z*4kbzb5H;LbIvhN-$^H?-98~JvPmr{Pl#>FU#n!UPiM5hii<au@~afqI6H28=lSBc
z;3UxpX49m<Bnxc2CaQPn)T%Epyml6yXz5T1uHoj}yZY^vMxV-=yPKUS$g6UceRwYR
zMz-q2-<(+&=gHO_zfft^yZhElJBIAF7Z;|gxko!5@44h~(o4ZSEdRhxc3ocQePRYt
z|5mX7WeyK?2@BI!>0hPd->@d(goj8kugK&H`%5@auJ(8BGE=KGR(1X$K7U*GmBNDw
z&S~Wh-;Z(ZOSE9Ij{oW@ZFX};@9k!R|8Md+&R_jqn7i5Tt;U?@HI^^8+gflf-}&Nb
zg6vQ42@jgit@xk(`GItRHzSiAGp-aO!2kllNMKmf2x6h87gk7mL5nJ66ER~P*~BIw
ai4y;;kod<ikd+Oji3tc718D^&5Dx$t-Ugun
new file mode 100644
index 0000000000000000000000000000000000000000..1ee3e74072f5f53fe899b8156e5bd2c091b1b448
GIT binary patch
literal 1129
zc$^FHW@Zs#U|`^2m>d-0IjOhO@eCsaLk%+n0}q1?LsEWzNpVS0Vu4;(acBr91M^cS
zzK8%<zKGHaZU#n{uRx_>-BW`O=G`{n+520YL!OJB<IV3bzSpyII5wC$-e%mrv&&Uo
zD&^wj6}xUusF%-GKP#5gJ}<}fy{(<yr?y$sWUTlqQ<9zYp1iU2wq@(ucc|QT<Hrb-
zC5w*+*es|w6K(rbopI@8YSCx6e_O9T$(tH1{l&d^iITX)y7bLYV|UItD7vJl(YrV7
z?Wr4CM<ZtSD)-C(YM)^K<#NR8qEEBGd~&FtV{bF9`+WK7-<3Oe&98rz%GNq>Qhy1f
zdTEKMbWuXf(luvY;(QM-=6a&`A$z`xD`QjMlR|b`C-;k49gGi^1=+u?+N+vA!zSnA
zW07kUtLHdhS>UpAT6DOg!PlTYt{l75e7EUmnjE^k{c6(Fmy!S41?GL4to`wc<+kVR
zgc`2Dh+Zl1sX?dgjThg|%J;!5rt;oM=T;E=uypG(Bb~oJjt6GE60tfdc#Xqe$*nNn
z+n+B!f{Wj+B`-_j%#&!*X>*0|>sH-MeK}$NmYp+}3h&s_z*w?N#+UhzY2nhZ*UKJH
zJ*KZ_VD;suWG+{vjG*w2s`?8R$)DdO->*J@oBzJ;f+K|jkJm2edv<T~`5^a#RlIAt
z9D@>?z7}3O-ne9Oz^4Dn&4<i3Ej#x&xi!x4;@|S~b<6?q6yXyZ;aNK8@ApVxG+$(5
zVBiL(h|Ij=lEj=Gy`q#faDw>n&KCi~XbGZq(&@ZI1_G|%e{z|xX?DJ)*Wr|LRb*v%
z-mhCqlX((O>fdy3tg97nS8?64s(jwg>*eQ8-29`!`jh4Lj<C%IMtz4X<ydZdZ&WCC
z-x;~X)kJgY@4t0>_RD4OUO4$>iPz&6HP4jQrBC{}ZYl=liLfoPyf>rC=Cjw|qpb=5
z8ddu8HBKC}<$7)5w23JubLYV-o<m_(?VAhIL#v9G##p8}xlGdB$JV}ewdW~^WqVHD
zy{LF1m9<HBdd=c3t=4No-=$}{MHlAV1U@(2^1k$Y+=fe1vQcS8TdY6KT6ksk6F&v>
zu;~x(G*4?&jOUclssGaat#P%668CD+N$#N&(;dPLQdGLy7>=quSuZzn&()A685SpZ
zuasb_{ItXFBzN<@_A_}5?|TLJq}-T1SAX~Fue)t#T*`Iqs296&x%lhFw;PYjay@q{
zd+}=f+5(M|;)~q}_k0qc@W5&A#b4*29W)Q{W@M6M#+73w7(f8XWmwV(Vxi_=R!IIu
liz;LjF=HIrL?NIEO8m1z;vd66RyL3(CLml4q&F~wcmRp&)$#xU
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore/bootstrap.js
@@ -0,0 +1,26 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+const ADDON_ID = "test_delay_update_ignore@tests.mozilla.org";
+const TEST_IGNORE_PREF = "delaytest.ignore";
+
+function install(data, reason) {}
+
+// normally we would use BootstrapMonitor here, but we need a reference to
+// the symbol inside `XPIProvider.jsm`.
+function startup(data, reason) {
+ Services.prefs.setBoolPref(TEST_IGNORE_PREF, false);
+
+ // explicitly ignore update, will be queued for next restart
+ if (data.hasOwnProperty("instanceID") && data.instanceID) {
+ AddonManager.addUpgradeListener(data.instanceID, (upgrade) => {
+ Services.prefs.setBoolPref(TEST_IGNORE_PREF, true);
+ });
+ } else {
+ throw Error("no instanceID passed to bootstrap startup");
+ }
+}
+
+function shutdown(data, reason) {}
+
+function uninstall(data, reason) {}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>system_delay_ignore@tests.mozilla.org</em:id>
+ <em:version>1.0</em:version>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Front End MetaData -->
+ <em:name>System Test Delay Update Ignore</em:name>
+
+ <em:targetApplication>
+ <Description>
+ <em:id>xpcshell@tests.mozilla.org</em:id>
+ <em:minVersion>1</em:minVersion>
+ <em:maxVersion>5</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ </Description>
+</RDF>
new file mode 100644
index 0000000000000000000000000000000000000000..26deb5bc44b8e2253380a410e2308797608faca8
GIT binary patch
literal 731
zc$^FHW@Zs#U|`^2xEvJWdEiykMH?Wm8;E%rWEhh2^Gk|LiV_R-vWi1PI2o8@o%teK
zfw;7Sn}Lz#D<cB~Shu(KS#Q0QXU+$oIHRrSdFJFr4^JJ>D}fqbzB=d5Uk+KZ?x|Pv
zN`_P7Q>TbM2%Z=iHEB}(ymoibOgX=9h5)z=f<q%b1J2Le8x1t}A`=4xH_!!{dBr7(
zIXQYoDQRE_@Okh>fH0Z^S|^>(J7gf>`u!)D`5NK&EwKWv7glKoiQoOQ)wPW$;iUde
zXU@OB)Y?>Bx2!6kXL-H+-HD%TrZv?xmBy^jwCI~$5XR#8FX)`-<-k0tJV9T#mv#I5
zzhB?DP1<UnkD7^R<}9~uQSR>+Ot?C+Xt9C9+mDBMp3Dq;rLV{GnSY6Ak+b8rSAjQf
z2u_;*AZ(g+&M}3YmE{(^<zcDnp~t2=tGTGme3V^qV@1?*nUrIz56Q|j#e^u_NIYon
z6}{tdTakTiaaBw8L(S5O$2#AaewT~L>gCflX1yg_k*4u=${o%p$6kqAd|$9M@x*i{
zwv+OIAN)8FDl&2BRj!kZSDi3c(3Uh3a&2x*66y+<pSZ_#rN_-@D({yiaXdMB{HvcF
zPnE3EZpJ<4ZXZMs+ui)RX?N8A)3?^#5o`I8-dJ0;&d#q|TK+L#Yw?7uv46jD@U`aY
zRT<7uujdqsxnP<5FGsM3Il!BdNsbv;8j)ZC0bn#RENKL>P*V&mB*mab7P5($agJ<a
c6Ocqn0j!V|fMFmj8%Prq5H1GNN=zUg0NVcn<p2Nx
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore_2/bootstrap.js
@@ -0,0 +1,4 @@
+function install(data, reason) {}
+function startup(data, reason) {}
+function shutdown(data, reason) {}
+function uninstall(data, reason) {}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/system_delay_ignore_2/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>system_delay_ignore@tests.mozilla.org</em:id>
+ <em:version>2.0</em:version>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Front End MetaData -->
+ <em:name>System Test Delay Update Ignore</em:name>
+
+ <em:targetApplication>
+ <Description>
+ <em:id>xpcshell@tests.mozilla.org</em:id>
+ <em:minVersion>1</em:minVersion>
+ <em:maxVersion>5</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ </Description>
+</RDF>
new file mode 100644
index 0000000000000000000000000000000000000000..3c673ac2e4fc4f8a0fa1f2688fa3be66b4d999c3
GIT binary patch
literal 735
zc$^FHW@Zs#U|`^2xRw>=x!-wvpf8Y@1jIZHG7L%i`6b0AMTrG^S;e6toD9s91q35@
z0&!^tHv=QfS4IW~u<p}mwDmmCoV<7;WW}?f0F9Fxr}V=MbhS?DUkW(Y;-NY%<=J!1
z6csV;C8}x9mMqiAj4HlbH1%1kQpvR<#+k0FOIZW_+%i4A>MG@0*ck%gPI?P;lIWk=
zOZ|ZMoMd8P;08J=Gq1QLF(*f_C?yT-qP;-Z?8S6Z>!h=OhYbW;-q&$Wo)^2Ek+1iX
zh|9_&jocnF+UAAP-v1`=Rn+wNe`UfM>iFc@&HS?Ru#?kzC;gagt=|*pvN=;mx431~
z_N!9cMc;~FWIV8J%C%D4wfW1By?vQuH~+H5LQkidoyz}KG@82n_H6m^lKa?{)!gfi
z;+?$2q*MxP+;>{-R1dV~dz^E$_F+qF8Sj~G%bHw6Ea%RPU3o!n#uOJr-LgB*udRGf
z?eKD)`Fk<@Ix}lE6@BycZ;Vmr{?D0xkxzcj^E;K&)>nRgXUOSg<nOyF6LXZ=*o%3o
z7Uv<W;DYz_dp7YL3Hx;Mle=swZyv|N>~xa{>9LQ(`ErgnBsv{qoTWK$fBS?AS*J(S
z^-^BQpUPO!xYM@XJ$tE!1W%=2|DEi^ywcG>nE!vzaZX!k|Lb+BqWeRk6yNpt%O7Zb
zslK@SDAP}GffCpBEq}v{zwiclGcw6B<4PwI3?Kjud4?s8AQoz>VTGg`wD>_b5i^>R
eO`HrQQ6ilc66qKQva*3RF#+LXAg#s(;sF4oZ~w3W
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -1340,8 +1340,73 @@ var {promiseWriteProxyFileToDir} = Addon
function writeProxyFileToDir(aDir, aAddon, aId) {
awaitPromise(promiseWriteProxyFileToDir(aDir, aAddon, aId));
let file = aDir.clone();
file.append(aId);
return file
}
+
+function* serve_system_update(xml, perform_update) {
+ testserver.registerPathHandler("/data/update.xml", (request, response) => {
+ response.write(xml);
+ });
+
+ try {
+ yield perform_update();
+ }
+ finally {
+ testserver.registerPathHandler("/data/update.xml", null);
+ }
+}
+
+// Runs an update check making it use the passed in xml string. Uses the direct
+// call to the update function so we get rejections on failure.
+function* install_system_addons(xml) {
+ do_print("Triggering system add-on update check.");
+
+ yield serve_system_update(xml, function*() {
+ let { XPIProvider } = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
+ yield XPIProvider.updateSystemAddons();
+ });
+}
+
+// Runs a full add-on update check which will in some cases do a system add-on
+// update check. Always succeeds.
+function* update_all_system_addons(xml) {
+ do_print("Triggering full add-on update check.");
+
+ yield serve_system_update(xml, function() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function() {
+ Services.obs.removeObserver(arguments.callee, "addons-background-update-complete");
+
+ resolve();
+ }, "addons-background-update-complete", false);
+
+ // Trigger the background update timer handler
+ gInternalManager.notify(null);
+ });
+ });
+}
+
+// Builds an update.xml file for an update check based on the data passed.
+function* build_system_xml(addons) {
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>\n\n<updates>\n`;
+ if (addons) {
+ xml += ` <addons>\n`;
+ for (let addon of addons) {
+ xml += ` <addon id="${addon.id}" URL="${root + addon.path}" version="${addon.version}"`;
+ if (addon.size)
+ xml += ` size="${addon.size}"`;
+ if (addon.hashFunction)
+ xml += ` hashFunction="${addon.hashFunction}"`;
+ if (addon.hashValue)
+ xml += ` hashValue="${addon.hashValue}"`;
+ xml += `/>\n`;
+ }
+ xml += ` </addons>\n`;
+ }
+ xml += `</updates>\n`;
+
+ return xml;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_delay_update.js
@@ -0,0 +1,333 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that delaying a system add-on update works.
+
+// FIXME sign the test addons, or this will fail on beta/release
+Services.prefs.setBoolPref("xpinstall.signatures.required", false);
+
+Components.utils.import("resource://testing-common/httpd.js");
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+const tempdir = gTmpD.clone();
+
+const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
+const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url";
+
+const IGNORE_ID = "system_delay_ignore@tests.mozilla.org";
+const COMPLETE_ID = "system_delay_complete@tests.mozilla.org";
+const DEFER_ID = "system_delay_defer@tests.mozilla.org";
+
+const TEST_IGNORE_PREF = "delaytest.ignore";
+
+const distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
+registerDirectory("XREAppFeat", distroDir);
+
+let testserver = new HttpServer();
+testserver.registerDirectory("/data/", do_get_file("data/system_addons"));
+testserver.start();
+let root = testserver.identity.primaryScheme + "://" +
+ testserver.identity.primaryHost + ":" +
+ testserver.identity.primaryPort + "/data/"
+Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
+
+
+// Note that we would normally use BootstrapMonitor but it currently requires
+// the objects in `data` to be serializable, and we need a real reference to the
+// `instanceID` symbol to test.
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function promiseInstallPostponed() {
+ return new Promise((resolve, reject) => {
+ let listener = {
+ onInstallFailed: () => {
+ AddonManager.removeInstallListener(listener);
+ reject("extension installation should not have failed");
+ },
+ onInstallEnded: () => {
+ AddonManager.removeInstallListener(listener);
+ reject("extension installation should not have ended");
+ },
+ onInstallPostponed: (install) => {
+ AddonManager.removeInstallListener(listener);
+ ok(true, "extension should be postponed")
+ resolve();
+ }
+ };
+
+ AddonManager.addInstallListener(listener);
+ });
+}
+
+function promiseInstallEnded() {
+ return new Promise((resolve, reject) => {
+ let listener = {
+ onInstallFailed: () => {
+ AddonManager.removeInstallListener(listener);
+ reject("extension installation should not have failed");
+ },
+ onInstallEnded: () => {
+ AddonManager.removeInstallListener(listener);
+ ok(true, "extension installation should have ended")
+ resolve();
+ },
+ onInstallPostponed: (install) => {
+ AddonManager.removeInstallListener(listener);
+ reject("extension installation should not be postponed"); }
+ };
+
+ AddonManager.addInstallListener(listener);
+ });
+}
+
+// add-on registers upgrade listener, and ignores update.
+add_task(function*() {
+
+ do_get_file("data/system_addons/system_delay_ignore.xpi").copyTo(distroDir, "system_delay_ignore@tests.mozilla.org.xpi");
+ do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
+
+ startupManager();
+
+ let updateList = [
+ { id: IGNORE_ID, version: "2.0", path: "system_delay_ignore_2.xpi" },
+ { id: "system1@tests.mozilla.org", version: "2.0", path: "system1_2.xpi" },
+ ];
+
+ let installStatus = promiseInstallPostponed();
+ yield install_system_addons(yield build_system_xml(updateList));
+ yield installStatus;
+
+ // addon upgrade has been delayed.
+ let addon_postponed = yield promiseAddonByID(IGNORE_ID);
+ do_check_neq(addon_postponed, null);
+ do_check_eq(addon_postponed.version, "1.0");
+ do_check_eq(addon_postponed.name, "System Test Delay Update Ignore");
+ do_check_true(addon_postponed.isCompatible);
+ do_check_false(addon_postponed.appDisabled);
+ do_check_true(addon_postponed.isActive);
+ do_check_eq(addon_postponed.type, "extension");
+ do_check_true(Services.prefs.getBoolPref(TEST_IGNORE_PREF));
+
+ // other addons in the set are delayed as well.
+ addon_postponed = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_postponed, null);
+ do_check_eq(addon_postponed.version, "1.0");
+ do_check_eq(addon_postponed.name, "System Add-on 1");
+ do_check_true(addon_postponed.isCompatible);
+ do_check_false(addon_postponed.appDisabled);
+ do_check_true(addon_postponed.isActive);
+ do_check_eq(addon_postponed.type, "extension");
+
+ // restarting allows upgrades to proceed
+ yield promiseRestartManager();
+
+ let addon_upgraded = yield promiseAddonByID(IGNORE_ID);
+ do_check_neq(addon_upgraded, null);
+ do_check_eq(addon_upgraded.version, "2.0");
+ do_check_eq(addon_upgraded.name, "System Test Delay Update Ignore");
+ do_check_true(addon_upgraded.isCompatible);
+ do_check_false(addon_upgraded.appDisabled);
+ do_check_true(addon_upgraded.isActive);
+ do_check_eq(addon_upgraded.type, "extension");
+
+ addon_upgraded = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_upgraded, null);
+ do_check_eq(addon_upgraded.version, "2.0");
+ do_check_eq(addon_upgraded.name, "System Add-on 1");
+ do_check_true(addon_upgraded.isCompatible);
+ do_check_false(addon_upgraded.appDisabled);
+ do_check_true(addon_upgraded.isActive);
+ do_check_eq(addon_upgraded.type, "extension");
+
+ yield shutdownManager();
+});
+
+// add-on registers upgrade listener, and allows update.
+add_task(function*() {
+ do_get_file("data/system_addons/system_delay_complete.xpi").copyTo(distroDir, "system_delay_complete@tests.mozilla.org.xpi");
+ do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
+
+ startupManager();
+
+ let updateList = [
+ { id: COMPLETE_ID, version: "2.0", path: "system_delay_complete_2.xpi" },
+ { id: "system1@tests.mozilla.org", version: "2.0", path: "system1_2.xpi" },
+ ];
+
+ let installStatus = promiseInstallPostponed();
+
+ yield install_system_addons(yield build_system_xml(updateList));
+ yield installStatus;
+
+ // upgrade is initially postponed.
+ let addon_postponed = yield promiseAddonByID(COMPLETE_ID);
+ do_check_neq(addon_postponed, null);
+ do_check_eq(addon_postponed.version, "1.0");
+ do_check_eq(addon_postponed.name, "System Test Delay Update Complete");
+ do_check_true(addon_postponed.isCompatible);
+ do_check_false(addon_postponed.appDisabled);
+ do_check_true(addon_postponed.isActive);
+ do_check_eq(addon_postponed.type, "extension");
+
+ // other addons in the set are delayed as well.
+ addon_postponed = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_postponed, null);
+ do_check_eq(addon_postponed.version, "1.0");
+ do_check_eq(addon_postponed.name, "System Add-on 1");
+ do_check_true(addon_postponed.isCompatible);
+ do_check_false(addon_postponed.appDisabled);
+ do_check_true(addon_postponed.isActive);
+ do_check_eq(addon_postponed.type, "extension");
+
+ installStatus = promiseInstallEnded();
+ yield installStatus;
+
+ // addon upgrade has been allowed
+ let addon_allowed = yield promiseAddonByID(COMPLETE_ID);
+ do_check_neq(addon_allowed, null);
+ do_check_eq(addon_allowed.version, "2.0");
+ do_check_eq(addon_allowed.name, "System Test Delay Update Complete");
+ do_check_true(addon_allowed.isCompatible);
+ do_check_false(addon_allowed.appDisabled);
+ do_check_true(addon_allowed.isActive);
+ do_check_eq(addon_allowed.type, "extension");
+
+ installStatus = promiseInstallEnded();
+ yield installStatus;
+
+ // other upgrades in the set are allowed as well
+ addon_allowed = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_allowed, null);
+ do_check_eq(addon_allowed.version, "2.0");
+ do_check_eq(addon_allowed.name, "System Add-on 1");
+ do_check_true(addon_allowed.isCompatible);
+ do_check_false(addon_allowed.appDisabled);
+ do_check_true(addon_allowed.isActive);
+ do_check_eq(addon_allowed.type, "extension");
+
+ // restarting changes nothing
+ yield promiseRestartManager();
+
+ let addon_upgraded = yield promiseAddonByID(COMPLETE_ID);
+ do_check_neq(addon_upgraded, null);
+ do_check_eq(addon_upgraded.version, "2.0");
+ do_check_eq(addon_upgraded.name, "System Test Delay Update Complete");
+ do_check_true(addon_upgraded.isCompatible);
+ do_check_false(addon_upgraded.appDisabled);
+ do_check_true(addon_upgraded.isActive);
+ do_check_eq(addon_upgraded.type, "extension");
+
+ // other addons in the set are allowed as well.
+ addon_upgraded = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_upgraded, null);
+ do_check_eq(addon_upgraded.version, "2.0");
+ do_check_eq(addon_upgraded.name, "System Add-on 1");
+ do_check_true(addon_upgraded.isCompatible);
+ do_check_false(addon_upgraded.appDisabled);
+ do_check_true(addon_upgraded.isActive);
+ do_check_eq(addon_upgraded.type, "extension");
+
+ yield shutdownManager();
+});
+
+// add-on registers upgrade listener, initially defers update then allows upgrade
+add_task(function*() {
+
+ do_get_file("data/system_addons/system_delay_defer.xpi").copyTo(distroDir, "system_delay_defer@tests.mozilla.org.xpi");
+ do_get_file("data/system_addons/system1_1.xpi").copyTo(distroDir, "system1@tests.mozilla.org.xpi");
+
+ startupManager();
+
+ let addon = yield promiseAddonByID(DEFER_ID);
+ do_check_neq(addon, null);
+ do_check_eq(addon.version, "1.0");
+ do_check_eq(addon.name, "System Test Delay Update Defer");
+ do_check_true(addon.isCompatible);
+ do_check_false(addon.appDisabled);
+ do_check_true(addon.isActive);
+ do_check_eq(addon.type, "extension");
+
+ let updateList = [
+ { id: DEFER_ID, version: "2.0", path: "system_delay_defer_2.xpi" },
+ { id: "system1@tests.mozilla.org", version: "2.0", path: "system1_2.xpi" },
+ ];
+
+ let installStatus = promiseInstallPostponed();
+
+ yield install_system_addons(yield build_system_xml(updateList));
+ yield installStatus;
+
+ // upgrade is initially postponed
+ let addon_postponed = yield promiseAddonByID(DEFER_ID);
+ do_check_neq(addon_postponed, null);
+ do_check_eq(addon_postponed.version, "1.0");
+ do_check_eq(addon_postponed.name, "System Test Delay Update Defer");
+ do_check_true(addon_postponed.isCompatible);
+ do_check_false(addon_postponed.appDisabled);
+ do_check_true(addon_postponed.isActive);
+ do_check_eq(addon_postponed.type, "extension");
+
+ // other addons in the set are postponed as well.
+ addon_postponed = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_postponed, null);
+ do_check_eq(addon_postponed.version, "1.0");
+ do_check_eq(addon_postponed.name, "System Add-on 1");
+ do_check_true(addon_postponed.isCompatible);
+ do_check_false(addon_postponed.appDisabled);
+ do_check_true(addon_postponed.isActive);
+ do_check_eq(addon_postponed.type, "extension");
+
+ // add-on will not allow upgrade until fake event fires
+ AddonManagerPrivate.callAddonListeners("onFakeEvent");
+
+ installStatus = promiseInstallEnded();
+ yield installStatus;
+
+ // addon upgrade has been allowed
+ let addon_allowed = yield promiseAddonByID(DEFER_ID);
+ do_check_neq(addon_allowed, null);
+ do_check_eq(addon_allowed.version, "2.0");
+ do_check_eq(addon_allowed.name, "System Test Delay Update Defer");
+ do_check_true(addon_allowed.isCompatible);
+ do_check_false(addon_allowed.appDisabled);
+ do_check_true(addon_allowed.isActive);
+ do_check_eq(addon_allowed.type, "extension");
+
+ installStatus = promiseInstallEnded();
+ yield installStatus;
+
+ // other addons in the set are allowed as well.
+ addon_allowed = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_allowed, null);
+ do_check_eq(addon_allowed.version, "2.0");
+ do_check_eq(addon_allowed.name, "System Add-on 1");
+ do_check_true(addon_allowed.isCompatible);
+ do_check_false(addon_allowed.appDisabled);
+ do_check_true(addon_allowed.isActive);
+ do_check_eq(addon_allowed.type, "extension");
+
+ // restarting changes nothing
+ yield promiseRestartManager();
+
+ addon_upgraded = yield promiseAddonByID(DEFER_ID);
+ do_check_neq(addon_upgraded, null);
+ do_check_eq(addon_upgraded.version, "2.0");
+ do_check_eq(addon_upgraded.name, "System Test Delay Update Defer");
+ do_check_true(addon_upgraded.isCompatible);
+ do_check_false(addon_upgraded.appDisabled);
+ do_check_true(addon_upgraded.isActive);
+ do_check_eq(addon_upgraded.type, "extension");
+
+ addon_upgraded = yield promiseAddonByID("system1@tests.mozilla.org");
+ do_check_neq(addon_upgraded, null);
+ do_check_eq(addon_upgraded.version, "2.0");
+ do_check_eq(addon_upgraded.name, "System Add-on 1");
+ do_check_true(addon_upgraded.isCompatible);
+ do_check_false(addon_upgraded.appDisabled);
+ do_check_true(addon_upgraded.isActive);
+ do_check_eq(addon_upgraded.type, "extension");
+
+ yield shutdownManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -45,18 +45,16 @@ function* check_installed(conditions) {
let expectedDir = isUpgrade ? updatesDir : distroDir;
if (version) {
// Add-on should be installed
do_check_neq(addon, null);
do_check_eq(addon.version, version);
do_check_true(addon.isActive);
do_check_false(addon.foreignInstall);
- do_check_false(hasFlag(addon.permissions, AddonManager.PERM_CAN_UPGRADE));
- do_check_false(hasFlag(addon.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(addon.hidden);
do_check_true(addon.isSystem);
// Verify the add-ons file is in the right place
let file = expectedDir.clone();
file.append(id + ".xpi");
do_check_true(file.exists());
do_check_true(file.isFile());
@@ -368,8 +366,47 @@ add_task(function* test_bad_app_cert() {
{ isUpgrade: false, version: null },
{ isUpgrade: false, version: "1.0" },
];
yield check_installed(conditions);
yield promiseShutdownManager();
});
+
+// A failed upgrade should revert to the default set.
+add_task(function* test_updated() {
+ // Create a random dir to install into
+ let dirname = makeUUID();
+ FileUtils.getDir("ProfD", ["features", dirname], true);
+ updatesDir.append(dirname);
+
+ // Copy in the system add-ons
+ let file = do_get_file("data/system_addons/system2_2.xpi");
+ file.copyTo(updatesDir, "system2@tests.mozilla.org.xpi");
+ file = do_get_file("data/system_addons/system_failed_install.xpi");
+ file.copyTo(updatesDir, "system_failed_install@tests.mozilla.org.xpi");
+
+ // Inject it into the system set
+ let addonSet = {
+ schema: 1,
+ directory: updatesDir.leafName,
+ addons: {
+ "system2@tests.mozilla.org": {
+ version: "2.0"
+ },
+ "system_failed_install@tests.mozilla.org": {
+ version: "1.0"
+ },
+ }
+ };
+ Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
+
+ startupManager(false);
+
+ let conditions = [
+ { isUpgrade: false, version: "1.0" },
+ ];
+
+ yield check_installed(conditions);
+
+ yield promiseShutdownManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
@@ -76,81 +76,16 @@ var root = testserver.identity.primarySc
Services.prefs.setCharPref(PREF_SYSTEM_ADDON_UPDATE_URL, root + "update.xml");
function makeUUID() {
let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"].
getService(AM_Ci.nsIUUIDGenerator);
return uuidGen.generateUUID().toString();
}
-function* serve_update(xml, perform_update) {
- testserver.registerPathHandler("/data/update.xml", (request, response) => {
- response.write(xml);
- });
-
- try {
- yield perform_update();
- }
- finally {
- testserver.registerPathHandler("/data/update.xml", null);
- }
-}
-
-// Runs an update check making it use the passed in xml string. Uses the direct
-// call to the update function so we get rejections on failure.
-function* install_system_addons(xml) {
- do_print("Triggering system add-on update check.");
-
- yield serve_update(xml, function*() {
- let { XPIProvider } = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
- yield XPIProvider.updateSystemAddons();
- });
-}
-
-// Runs a full add-on update check which will in some cases do a system add-on
-// update check. Always succeeds.
-function* update_all_addons(xml) {
- do_print("Triggering full add-on update check.");
-
- yield serve_update(xml, function() {
- return new Promise(resolve => {
- Services.obs.addObserver(function() {
- Services.obs.removeObserver(arguments.callee, "addons-background-update-complete");
-
- resolve();
- }, "addons-background-update-complete", false);
-
- // Trigger the background update timer handler
- gInternalManager.notify(null);
- });
- });
-}
-
-// Builds an update.xml file for an update check based on the data passed.
-function* build_xml(addons) {
- let xml = `<?xml version="1.0" encoding="UTF-8"?>\n\n<updates>\n`;
- if (addons) {
- xml += ` <addons>\n`;
- for (let addon of addons) {
- xml += ` <addon id="${addon.id}" URL="${root + addon.path}" version="${addon.version}"`;
- if (addon.size)
- xml += ` size="${addon.size}"`;
- if (addon.hashFunction)
- xml += ` hashFunction="${addon.hashFunction}"`;
- if (addon.hashValue)
- xml += ` hashValue="${addon.hashValue}"`;
- xml += `/>\n`;
- }
- xml += ` </addons>\n`;
- }
- xml += `</updates>\n`;
-
- return xml;
-}
-
function* check_installed(conditions) {
for (let i = 0; i < conditions.length; i++) {
let condition = conditions[i];
let id = "system" + (i + 1) + "@tests.mozilla.org";
let addon = yield promiseAddonByID(id);
if (!("isUpgrade" in condition) || !("version" in condition)) {
throw Error("condition must contain isUpgrade and version");
@@ -582,17 +517,17 @@ function* setup_conditions(setup) {
startupManager(false);
// Make sure the initial state is correct
do_print("Checking initial state.");
yield check_installed(setup.initialState);
}
-function* verify_state(initialState, finalState = undefined) {
+function* verify_state(initialState, finalState = undefined, alreadyUpgraded = false) {
let expectedDirs = 0;
// If the initial state was using the profile set then that directory will
// still exist.
if (initialState.some(a => a.isUpgrade)) {
expectedDirs++;
}
@@ -600,23 +535,27 @@ function* verify_state(initialState, fin
if (finalState == undefined) {
finalState = initialState;
}
else if (finalState.some(a => a.isUpgrade)) {
// If the new state is using the profile then that directory will exist.
expectedDirs++;
}
+ // Since upgrades are restartless now, the previous update dir hasn't been removed.
+ if (alreadyUpgraded) {
+ expectedDirs++;
+ }
+
do_print("Checking final state.");
let dirs = yield get_directories();
do_check_eq(dirs.length, expectedDirs);
- // Bug 1204156: Currently switching to the new state requires a restart
- // yield check_installed(...finalState);
+ yield check_installed(...finalState);
// Check that the new state is active after a restart
yield promiseRestartManager();
yield check_installed(finalState);
}
function* exec_test(setupName, testName) {
let setup = TEST_CONDITIONS[setupName];
@@ -624,17 +563,17 @@ function* exec_test(setupName, testName)
yield setup_conditions(setup);
try {
if ("test" in test) {
yield test.test();
}
else {
- yield install_system_addons(yield build_xml(test.updateList));
+ yield install_system_addons(yield build_system_xml(test.updateList));
}
if (test.fails) {
do_throw("Expected this test to fail");
}
}
catch (e) {
if (!test.fails) {
@@ -665,17 +604,17 @@ add_task(function*() {
});
// Some custom tests
// Test that the update check is performed as part of the regular add-on update
// check
add_task(function* test_addon_update() {
yield setup_conditions(TEST_CONDITIONS.blank);
- yield update_all_addons(yield build_xml([
+ yield update_all_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
]));
yield verify_state(TEST_CONDITIONS.blank.initialState, [
{isUpgrade: false, version: null},
{isUpgrade: true, version: "2.0"},
{isUpgrade: true, version: "2.0"},
@@ -686,17 +625,17 @@ add_task(function* test_addon_update() {
yield promiseShutdownManager();
});
// Disabling app updates should block system add-on updates
add_task(function* test_app_update_disabled() {
yield setup_conditions(TEST_CONDITIONS.blank);
Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
- yield update_all_addons(yield build_xml([
+ yield update_all_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
]));
Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
yield verify_state(TEST_CONDITIONS.blank.initialState);
yield promiseShutdownManager();
@@ -704,49 +643,49 @@ add_task(function* test_app_update_disab
// Safe mode should block system add-on updates
add_task(function* test_safe_mode() {
gAppInfo.inSafeMode = true;
yield setup_conditions(TEST_CONDITIONS.blank);
Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, false);
- yield update_all_addons(yield build_xml([
+ yield update_all_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
]));
Services.prefs.clearUserPref(PREF_APP_UPDATE_ENABLED);
yield verify_state(TEST_CONDITIONS.blank.initialState);
yield promiseShutdownManager();
gAppInfo.inSafeMode = false;
});
// Tests that a set that matches the default set does nothing
add_task(function* test_match_default() {
yield setup_conditions(TEST_CONDITIONS.withAppSet);
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
]));
// Shouldn't have installed an updated set
yield verify_state(TEST_CONDITIONS.withAppSet.initialState);
yield promiseShutdownManager();
});
// Tests that a set that matches the hidden default set works
add_task(function* test_match_default_revert() {
yield setup_conditions(TEST_CONDITIONS.withBothSets);
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1.xpi" },
{ id: "system2@tests.mozilla.org", version: "1.0", path: "system2_1.xpi" }
]));
// This should revert to the default set instead of installing new versions
// into an updated set.
yield verify_state(TEST_CONDITIONS.withBothSets.initialState, [
{isUpgrade: false, version: "1.0"},
@@ -758,17 +697,17 @@ add_task(function* test_match_default_re
yield promiseShutdownManager();
});
// Tests that a set that matches the current set works
add_task(function* test_match_current() {
yield setup_conditions(TEST_CONDITIONS.withBothSets);
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
]));
// This should remain with the current set instead of creating a new copy
let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
do_check_eq(set.directory, "prefilled");
@@ -777,17 +716,17 @@ add_task(function* test_match_current()
yield promiseShutdownManager();
});
// Tests that a set with a minor change doesn't re-download existing files
add_task(function* test_no_download() {
yield setup_conditions(TEST_CONDITIONS.withBothSets);
// The missing file here is unneeded since there is a local version already
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "missing.xpi" },
{ id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
]));
yield verify_state(TEST_CONDITIONS.withBothSets.initialState, [
{isUpgrade: false, version: "1.0"},
{isUpgrade: true, version: "2.0"},
{isUpgrade: false, version: null},
@@ -797,53 +736,53 @@ add_task(function* test_no_download() {
yield promiseShutdownManager();
});
// Tests that a second update before a restart works
add_task(function* test_double_update() {
yield setup_conditions(TEST_CONDITIONS.withAppSet);
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
]));
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" },
{ id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
]));
yield verify_state(TEST_CONDITIONS.withAppSet.initialState, [
{isUpgrade: false, version: null},
{isUpgrade: false, version: "2.0"},
{isUpgrade: true, version: "2.0"},
{isUpgrade: true, version: "1.0"},
{isUpgrade: false, version: null}
- ]);
+ ], true);
yield promiseShutdownManager();
});
// A second update after a restart will delete the original unused set
add_task(function* test_update_purges() {
yield setup_conditions(TEST_CONDITIONS.withBothSets);
- yield install_system_addons(yield build_xml([
+ yield install_system_addons(yield build_system_xml([
{ id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
{ id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
]));
yield verify_state(TEST_CONDITIONS.withBothSets.initialState, [
{isUpgrade: false, version: "1.0"},
{isUpgrade: true, version: "2.0"},
{isUpgrade: true, version: "1.0"},
{isUpgrade: false, version: null},
{isUpgrade: false, version: null}
]);
- yield install_system_addons(yield build_xml(null));
+ yield install_system_addons(yield build_system_xml(null));
let dirs = yield get_directories();
do_check_eq(dirs.length, 1);
yield promiseShutdownManager();
});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -39,10 +39,11 @@ tags = webextensions
[test_proxy.js]
[test_pass_symbol.js]
[test_delay_update.js]
[test_nodisable_hidden.js]
[test_delay_update_webextension.js]
skip-if = appname == "thunderbird"
tags = webextensions
[test_dependencies.js]
+[test_system_delay_update.js]
[include:xpcshell-shared.ini]