mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-26 11:45:37 +00:00
Bug 1280083: Support dependencies for bootstrapped add-ons. r=aswan
MozReview-Commit-ID: ACmsUcKZ2Jp --HG-- extra : rebase_source : fc7842ff4026ec2b39d1f961253b17cb4f12912e
This commit is contained in:
parent
ec7c520520
commit
83cfaa2af3
@ -752,6 +752,16 @@ function isUsableAddon(aAddon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aAddon.dependencies.length) {
|
||||
let isActive = id => {
|
||||
let active = XPIProvider.activeAddons.get(id);
|
||||
return active && !active.disable;
|
||||
};
|
||||
|
||||
if (aAddon.dependencies.some(id => !isActive(id)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AddonManager.checkCompatibility) {
|
||||
if (!aAddon.isCompatible) {
|
||||
logger.warn(`Add-on ${aAddon.id} is not compatible with application version.`);
|
||||
@ -797,6 +807,7 @@ function createAddonDetails(id, aAddon) {
|
||||
version: aAddon.version,
|
||||
multiprocessCompatible: aAddon.multiprocessCompatible,
|
||||
runInSafeMode: aAddon.runInSafeMode,
|
||||
dependencies: aAddon.dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1178,6 +1189,15 @@ function loadManifestFromRDF(aUri, aStream) {
|
||||
addon.locales.push(locale);
|
||||
}
|
||||
|
||||
let dependencies = new Set();
|
||||
targets = ds.GetTargets(root, EM_R("dependency"), true);
|
||||
while (targets.hasMoreElements()) {
|
||||
let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
|
||||
let id = getRDFProperty(ds, target, "id");
|
||||
dependencies.add(id);
|
||||
}
|
||||
addon.dependencies = Object.freeze(Array.from(dependencies));
|
||||
|
||||
let seenApplications = [];
|
||||
addon.targetApplications = [];
|
||||
targets = ds.GetTargets(root, EM_R("targetApplication"), true);
|
||||
@ -2424,6 +2444,45 @@ this.XPIProvider = {
|
||||
// Have we started shutting down bootstrap add-ons?
|
||||
_closing: false,
|
||||
|
||||
/**
|
||||
* Returns an array of the add-on values in `bootstrappedAddons`,
|
||||
* sorted so that all of an add-on's dependencies appear in the array
|
||||
* before itself.
|
||||
*
|
||||
* @returns {Array<object>}
|
||||
* A sorted array of add-on objects. Each value is a copy of the
|
||||
* corresponding value in the `bootstrappedAddons` object, with an
|
||||
* additional `id` property, which corresponds to the key in that
|
||||
* object, which is the same as the add-ons ID.
|
||||
*/
|
||||
sortBootstrappedAddons: function() {
|
||||
let addons = {};
|
||||
|
||||
// Sort the list of IDs so that the ordering is deterministic.
|
||||
for (let id of Object.keys(this.bootstrappedAddons).sort()) {
|
||||
addons[id] = Object.assign({id}, this.bootstrappedAddons[id]);
|
||||
}
|
||||
|
||||
let res = new Set();
|
||||
let seen = new Set();
|
||||
|
||||
let add = addon => {
|
||||
seen.add(addon.id);
|
||||
|
||||
for (let id of addon.dependencies || []) {
|
||||
if (id in addons && !seen.has(id)) {
|
||||
add(addons[id]);
|
||||
}
|
||||
}
|
||||
|
||||
res.add(addon.id);
|
||||
}
|
||||
|
||||
Object.values(addons).forEach(add);
|
||||
|
||||
return Array.from(res, id => addons[id]);
|
||||
},
|
||||
|
||||
/*
|
||||
* Set a value in the telemetry hash for a given ID
|
||||
*/
|
||||
@ -2762,22 +2821,23 @@ this.XPIProvider = {
|
||||
|
||||
try {
|
||||
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
|
||||
for (let id in this.bootstrappedAddons) {
|
||||
|
||||
for (let addon of this.sortBootstrappedAddons()) {
|
||||
try {
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
|
||||
file.persistentDescriptor = addon.descriptor;
|
||||
let reason = BOOTSTRAP_REASONS.APP_STARTUP;
|
||||
// Eventually set INSTALLED reason when a bootstrap addon
|
||||
// is dropped in profile folder and automatically installed
|
||||
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
|
||||
.indexOf(id) !== -1)
|
||||
.indexOf(addon.id) !== -1)
|
||||
reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
|
||||
this.callBootstrapMethod(createAddonDetails(id, this.bootstrappedAddons[id]),
|
||||
this.callBootstrapMethod(createAddonDetails(addon.id, addon),
|
||||
file, "startup", reason);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to load bootstrap addon " + id + " from " +
|
||||
this.bootstrappedAddons[id].descriptor, e);
|
||||
logger.error("Failed to load bootstrap addon " + addon.id + " from " +
|
||||
addon.descriptor, e);
|
||||
}
|
||||
}
|
||||
AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
|
||||
@ -2792,26 +2852,26 @@ this.XPIProvider = {
|
||||
Services.obs.addObserver({
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
XPIProvider._closing = true;
|
||||
for (let id in XPIProvider.bootstrappedAddons) {
|
||||
for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
|
||||
// If no scope has been loaded for this add-on then there is no need
|
||||
// to shut it down (should only happen when a bootstrapped add-on is
|
||||
// pending enable)
|
||||
if (!XPIProvider.activeAddons.has(id))
|
||||
if (!XPIProvider.activeAddons.has(addon.id))
|
||||
continue;
|
||||
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor;
|
||||
let addon = createAddonDetails(id, XPIProvider.bootstrappedAddons[id]);
|
||||
file.persistentDescriptor = addon.descriptor;
|
||||
let addonDetails = createAddonDetails(addon.id, addon);
|
||||
|
||||
// If the add-on was pending disable then shut it down and remove it
|
||||
// from the persisted data.
|
||||
if (XPIProvider.bootstrappedAddons[id].disable) {
|
||||
XPIProvider.callBootstrapMethod(addon, file, "shutdown",
|
||||
if (addon.disable) {
|
||||
XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown",
|
||||
BOOTSTRAP_REASONS.ADDON_DISABLE);
|
||||
delete XPIProvider.bootstrappedAddons[id];
|
||||
delete XPIProvider.bootstrappedAddons[addon.id];
|
||||
}
|
||||
else {
|
||||
XPIProvider.callBootstrapMethod(addon, file, "shutdown",
|
||||
XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown",
|
||||
BOOTSTRAP_REASONS.APP_SHUTDOWN);
|
||||
}
|
||||
}
|
||||
@ -3603,6 +3663,11 @@ this.XPIProvider = {
|
||||
XPI_PERMISSION);
|
||||
},
|
||||
|
||||
getDependentAddons: function(aAddon) {
|
||||
return Array.from(XPIDatabase.getAddons())
|
||||
.filter(addon => addon.dependencies.includes(aAddon.id));
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks for any changes that have occurred since the last time the
|
||||
* application was launched.
|
||||
@ -4587,10 +4652,13 @@ this.XPIProvider = {
|
||||
* Boolean indicating whether the add-on is compatible with electrolysis.
|
||||
* @param aRunInSafeMode
|
||||
* Boolean indicating whether the add-on can run in safe mode.
|
||||
* @param aDependencies
|
||||
* An array of add-on IDs on which this add-on depends.
|
||||
* @return a JavaScript scope
|
||||
*/
|
||||
loadBootstrapScope: function(aId, aFile, aVersion, aType,
|
||||
aMultiprocessCompatible, aRunInSafeMode) {
|
||||
aMultiprocessCompatible, aRunInSafeMode,
|
||||
aDependencies) {
|
||||
// Mark the add-on as active for the crash reporter before loading
|
||||
this.bootstrappedAddons[aId] = {
|
||||
version: aVersion,
|
||||
@ -4598,6 +4666,7 @@ this.XPIProvider = {
|
||||
descriptor: aFile.persistentDescriptor,
|
||||
multiprocessCompatible: aMultiprocessCompatible,
|
||||
runInSafeMode: aRunInSafeMode,
|
||||
dependencies: aDependencies,
|
||||
};
|
||||
this.persistBootstrappedAddons();
|
||||
this.addAddonsToCrashReporter();
|
||||
@ -4753,7 +4822,7 @@ this.XPIProvider = {
|
||||
if (!activeAddon) {
|
||||
this.loadBootstrapScope(aAddon.id, aFile, aAddon.version, aAddon.type,
|
||||
aAddon.multiprocessCompatible || false,
|
||||
runInSafeMode);
|
||||
runInSafeMode, aAddon.dependencies);
|
||||
activeAddon = this.activeAddons.get(aAddon.id);
|
||||
}
|
||||
|
||||
@ -4783,6 +4852,15 @@ this.XPIProvider = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extensions are automatically deinitialized in the correct order at shutdown.
|
||||
if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
|
||||
activeAddon.disable = true;
|
||||
for (let addon of this.getDependentAddons(aAddon)) {
|
||||
if (addon.active)
|
||||
this.updateAddonDisabledState(addon);
|
||||
}
|
||||
}
|
||||
|
||||
let params = {
|
||||
id: aAddon.id,
|
||||
version: aAddon.version,
|
||||
@ -4806,6 +4884,12 @@ this.XPIProvider = {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Extensions are automatically initialized in the correct order at startup.
|
||||
if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) {
|
||||
for (let addon of this.getDependentAddons(aAddon))
|
||||
this.updateAddonDisabledState(addon);
|
||||
}
|
||||
|
||||
if (CHROME_TYPES.has(aAddon.type) && aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
|
||||
logger.debug("Removing manifest for " + aFile.path);
|
||||
Components.manager.removeBootstrappedManifestLocation(aFile);
|
||||
@ -4916,6 +5000,7 @@ this.XPIProvider = {
|
||||
|
||||
if (!needsRestart) {
|
||||
XPIDatabase.updateAddonActive(aAddon, !isDisabled);
|
||||
|
||||
if (isDisabled) {
|
||||
if (aAddon.bootstrap) {
|
||||
this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
|
||||
@ -4945,6 +5030,7 @@ this.XPIProvider = {
|
||||
descriptor: aAddon._sourceBundle.persistentDescriptor,
|
||||
multiprocessCompatible: aAddon.multiprocessCompatible,
|
||||
runInSafeMode: canRunInSafeMode(aAddon),
|
||||
dependencies: aAddon.dependencies,
|
||||
};
|
||||
this.persistBootstrappedAddons();
|
||||
}
|
||||
@ -6825,6 +6911,14 @@ AddonInternal.prototype = {
|
||||
seen: true,
|
||||
skinnable: false,
|
||||
|
||||
/**
|
||||
* @property {Array<string>} dependencies
|
||||
* An array of bootstrapped add-on IDs on which this add-on depends.
|
||||
* The add-on will remain appDisabled if any of the dependent
|
||||
* add-ons is not installed and enabled.
|
||||
*/
|
||||
dependencies: Object.freeze([]),
|
||||
|
||||
get selectedLocale() {
|
||||
if (this._selectedLocale)
|
||||
return this._selectedLocale;
|
||||
@ -7647,7 +7741,7 @@ function defineAddonWrapperProperty(name, getter) {
|
||||
["id", "syncGUID", "version", "isCompatible", "isPlatformCompatible",
|
||||
"providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
|
||||
"softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
|
||||
"strictCompatibility", "compatibilityOverrides", "updateURL",
|
||||
"strictCompatibility", "compatibilityOverrides", "updateURL", "dependencies",
|
||||
"getDataDirectory", "multiprocessCompatible", "signedState"].forEach(function(aProp) {
|
||||
defineAddonWrapperProperty(aProp, function() {
|
||||
let addon = addonFor(this);
|
||||
|
@ -87,7 +87,7 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
|
||||
"softDisabled", "foreignInstall", "hasBinaryComponents",
|
||||
"strictCompatibility", "locales", "targetApplications",
|
||||
"targetPlatforms", "multiprocessCompatible", "signedState",
|
||||
"seen"];
|
||||
"seen", "dependencies"];
|
||||
|
||||
// Properties that should be migrated where possible from an old database. These
|
||||
// shouldn't include properties that can be read directly from install.rdf files
|
||||
@ -331,6 +331,10 @@ function DBAddonInternal(aLoaded) {
|
||||
|
||||
copyProperties(aLoaded, PROP_JSON_FIELDS, this);
|
||||
|
||||
if (!this.dependencies)
|
||||
this.dependencies = [];
|
||||
Object.freeze(this.dependencies);
|
||||
|
||||
if (aLoaded._installLocation) {
|
||||
this._installLocation = aLoaded._installLocation;
|
||||
this.location = aLoaded._installLocation.name;
|
||||
@ -2155,6 +2159,7 @@ this.XPIDatabaseReconcile = {
|
||||
descriptor: currentAddon._sourceBundle.persistentDescriptor,
|
||||
multiprocessCompatible: currentAddon.multiprocessCompatible,
|
||||
runInSafeMode: canRunInSafeMode(currentAddon),
|
||||
dependencies: currentAddon.dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@ var AM_Cc = Components.classes;
|
||||
var AM_Ci = Components.interfaces;
|
||||
var AM_Cu = Components.utils;
|
||||
|
||||
AM_Cu.importGlobalProperties(["TextEncoder"]);
|
||||
|
||||
const CERTDB_CONTRACTID = "@mozilla.org/security/x509certdb;1";
|
||||
const CERTDB_CID = Components.ID("{fb0bbc5c-452e-4783-b32c-80124693d871}");
|
||||
|
||||
@ -1027,6 +1029,12 @@ function createInstallRDF(aData) {
|
||||
});
|
||||
}
|
||||
|
||||
if ("dependencies" in aData) {
|
||||
aData.dependencies.forEach(function(aDependency) {
|
||||
rdf += `<em:dependency><Description em:id="${escapeXML(aDependency)}"/></em:dependency>\n`;
|
||||
});
|
||||
}
|
||||
|
||||
rdf += "</Description>\n</RDF>\n";
|
||||
return rdf;
|
||||
}
|
||||
@ -1186,6 +1194,39 @@ function writeInstallRDFToXPI(aData, aDir, aId, aExtraFile) {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given data to a file in the given zip file.
|
||||
*
|
||||
* @param aFile
|
||||
* The zip file to write to.
|
||||
* @param aFiles
|
||||
* An object containing filenames and the data to write to the
|
||||
* corresponding paths in the zip file.
|
||||
* @param aFlags
|
||||
* Additional flags to open the file with.
|
||||
*/
|
||||
function writeFilesToZip(aFile, aFiles, aFlags = 0) {
|
||||
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].createInstance(AM_Ci.nsIZipWriter);
|
||||
zipW.open(aFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | aFlags);
|
||||
|
||||
for (let path of Object.keys(aFiles)) {
|
||||
let data = aFiles[path];
|
||||
if (!(data instanceof ArrayBuffer)) {
|
||||
data = new TextEncoder("utf-8").encode(data).buffer;
|
||||
}
|
||||
|
||||
let stream = AM_Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
|
||||
.createInstance(AM_Ci.nsIArrayBufferInputStream);
|
||||
stream.setData(data, 0, data.byteLength);
|
||||
|
||||
// Note these files are being created in the XPI archive with date "0" which is 1970-01-01.
|
||||
zipW.addEntryStream(path, 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
|
||||
stream, false);
|
||||
}
|
||||
|
||||
zipW.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an install.rdf manifest into an XPI file using the properties passed
|
||||
* in a JS object. The objects should contain a property for each property to
|
||||
@ -1201,20 +1242,16 @@ function writeInstallRDFToXPI(aData, aDir, aId, aExtraFile) {
|
||||
* An optional dummy file to create in the extension
|
||||
*/
|
||||
function writeInstallRDFToXPIFile(aData, aFile, aExtraFile) {
|
||||
var rdf = createInstallRDF(aData);
|
||||
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(AM_Ci.nsIStringInputStream);
|
||||
stream.setData(rdf, -1);
|
||||
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
|
||||
createInstance(AM_Ci.nsIZipWriter);
|
||||
zipW.open(aFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
|
||||
// Note these files are being created in the XPI archive with date "0" which is 1970-01-01.
|
||||
zipW.addEntryStream("install.rdf", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
|
||||
stream, false);
|
||||
if (aExtraFile)
|
||||
zipW.addEntryStream(aExtraFile, 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
|
||||
stream, false);
|
||||
zipW.close();
|
||||
let files = {
|
||||
"install.rdf": createInstallRDF(aData),
|
||||
};
|
||||
|
||||
if (typeof aExtraFile == "object")
|
||||
Object.assign(files, aExtraFile);
|
||||
else if (aExtraFile)
|
||||
files[aExtraFile] = "";
|
||||
|
||||
writeFilesToZip(aFile, files, FileUtils.MODE_TRUNCATE);
|
||||
}
|
||||
|
||||
var temp_xpis = [];
|
||||
@ -1226,7 +1263,7 @@ var temp_xpis = [];
|
||||
* The object holding data about the add-on
|
||||
* @return A file pointing to the created XPI file
|
||||
*/
|
||||
function createTempXPIFile(aData) {
|
||||
function createTempXPIFile(aData, aExtraFile) {
|
||||
var file = gTmpD.clone();
|
||||
file.append("foo.xpi");
|
||||
do {
|
||||
@ -1234,7 +1271,7 @@ function createTempXPIFile(aData) {
|
||||
} while (file.exists());
|
||||
|
||||
temp_xpis.push(file);
|
||||
writeInstallRDFToXPIFile(aData, file);
|
||||
writeInstallRDFToXPIFile(aData, file, aExtraFile);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
144
toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js
Normal file
144
toolkit/mozapps/extensions/test/xpcshell/test_dependencies.js
Normal file
@ -0,0 +1,144 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const profileDir = gProfD.clone();
|
||||
profileDir.append("extensions");
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
|
||||
startupManager();
|
||||
|
||||
const BOOTSTRAP = String.raw`
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function startup(data) {
|
||||
Services.obs.notifyObservers(null, "test-addon-bootstrap-startup", data.id);
|
||||
}
|
||||
function shutdown(data) {
|
||||
Services.obs.notifyObservers(null, "test-addon-bootstrap-shutdown", data.id);
|
||||
}
|
||||
function install() {}
|
||||
function uninstall() {}
|
||||
`;
|
||||
|
||||
const ADDONS = [
|
||||
{
|
||||
id: "addon1@dependency-test.mozilla.org",
|
||||
dependencies: ["addon2@dependency-test.mozilla.org"],
|
||||
},
|
||||
{
|
||||
id: "addon2@dependency-test.mozilla.org",
|
||||
dependencies: ["addon3@dependency-test.mozilla.org"],
|
||||
},
|
||||
{
|
||||
id: "addon3@dependency-test.mozilla.org",
|
||||
},
|
||||
{
|
||||
id: "addon4@dependency-test.mozilla.org",
|
||||
},
|
||||
{
|
||||
id: "addon5@dependency-test.mozilla.org",
|
||||
dependencies: ["addon2@dependency-test.mozilla.org"],
|
||||
},
|
||||
];
|
||||
|
||||
let addonFiles = [];
|
||||
|
||||
let events = [];
|
||||
add_task(function* setup() {
|
||||
let startupObserver = (subject, topic, data) => {
|
||||
events.push(["startup", data]);
|
||||
};
|
||||
let shutdownObserver = (subject, topic, data) => {
|
||||
events.push(["shutdown", data]);
|
||||
};
|
||||
|
||||
Services.obs.addObserver(startupObserver, "test-addon-bootstrap-startup", false);
|
||||
Services.obs.addObserver(shutdownObserver, "test-addon-bootstrap-shutdown", false);
|
||||
do_register_cleanup(() => {
|
||||
Services.obs.removeObserver(startupObserver, "test-addon-bootstrap-startup");
|
||||
Services.obs.removeObserver(shutdownObserver, "test-addon-bootstrap-shutdown");
|
||||
});
|
||||
|
||||
for (let addon of ADDONS) {
|
||||
Object.assign(addon, {
|
||||
targetApplications: [{
|
||||
id: "xpcshell@tests.mozilla.org",
|
||||
minVersion: "1",
|
||||
maxVersion: "1",
|
||||
}],
|
||||
version: "1.0",
|
||||
name: addon.id,
|
||||
bootstrap: true,
|
||||
});
|
||||
|
||||
addonFiles.push(createTempXPIFile(addon, {"bootstrap.js": BOOTSTRAP}));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
deepEqual(events, [], "Should have no events");
|
||||
|
||||
yield promiseInstallAllFiles([addonFiles[3]]);
|
||||
|
||||
deepEqual(events, [
|
||||
["startup", ADDONS[3].id],
|
||||
]);
|
||||
|
||||
events.length = 0;
|
||||
|
||||
yield promiseInstallAllFiles([addonFiles[0]]);
|
||||
deepEqual(events, [], "Should have no events");
|
||||
|
||||
yield promiseInstallAllFiles([addonFiles[1]]);
|
||||
deepEqual(events, [], "Should have no events");
|
||||
|
||||
yield promiseInstallAllFiles([addonFiles[2]]);
|
||||
|
||||
deepEqual(events, [
|
||||
["startup", ADDONS[2].id],
|
||||
["startup", ADDONS[1].id],
|
||||
["startup", ADDONS[0].id],
|
||||
]);
|
||||
|
||||
events.length = 0;
|
||||
|
||||
yield promiseInstallAllFiles([addonFiles[2]]);
|
||||
|
||||
deepEqual(events, [
|
||||
["shutdown", ADDONS[0].id],
|
||||
["shutdown", ADDONS[1].id],
|
||||
["shutdown", ADDONS[2].id],
|
||||
|
||||
["startup", ADDONS[2].id],
|
||||
["startup", ADDONS[1].id],
|
||||
["startup", ADDONS[0].id],
|
||||
]);
|
||||
|
||||
events.length = 0;
|
||||
|
||||
yield promiseInstallAllFiles([addonFiles[4]]);
|
||||
|
||||
deepEqual(events, [
|
||||
["startup", ADDONS[4].id],
|
||||
]);
|
||||
|
||||
events.length = 0;
|
||||
|
||||
yield promiseRestartManager();
|
||||
|
||||
deepEqual(events, [
|
||||
["shutdown", ADDONS[4].id],
|
||||
["shutdown", ADDONS[3].id],
|
||||
["shutdown", ADDONS[0].id],
|
||||
["shutdown", ADDONS[1].id],
|
||||
["shutdown", ADDONS[2].id],
|
||||
|
||||
["startup", ADDONS[2].id],
|
||||
["startup", ADDONS[1].id],
|
||||
["startup", ADDONS[0].id],
|
||||
["startup", ADDONS[3].id],
|
||||
["startup", ADDONS[4].id],
|
||||
]);
|
||||
});
|
||||
|
@ -38,6 +38,7 @@ skip-if = appname != "firefox"
|
||||
[test_pass_symbol.js]
|
||||
[test_delay_update.js]
|
||||
[test_nodisable_hidden.js]
|
||||
[test_dependencies.js]
|
||||
|
||||
|
||||
[include:xpcshell-shared.ini]
|
||||
|
Loading…
Reference in New Issue
Block a user