Bug 552742: Support multi-package XPIs in the new add-ons manager. r=robstrong

This commit is contained in:
Dave Townsend 2010-08-01 10:52:24 -07:00
parent e8f6daf69e
commit e65cba989c
17 changed files with 674 additions and 113 deletions

2
nsprpub/configure vendored
View File

@ -6396,7 +6396,7 @@ s%\[%\\&%g
s%\]%\\&%g
s%\$%$$%g
EOF
DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' ' | tr '\015' ' '` # Manually modified for MKS support.
DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' '`
rm -f conftest.defs

View File

@ -79,6 +79,7 @@ const FILE_XPI_ADDONS_LIST = "extensions.ini";
const KEY_PROFILEDIR = "ProfD";
const KEY_APPDIR = "XCurProcD";
const KEY_TEMPDIR = "TmpD";
const KEY_APP_PROFILE = "app-profile";
const KEY_APP_GLOBAL = "app-global";
@ -132,7 +133,8 @@ const BOOTSTRAP_REASONS = {
const TYPES = {
extension: 2,
theme: 4,
locale: 8
locale: 8,
multipackage: 32
};
/**
@ -405,10 +407,6 @@ function loadManifestFromRDF(aUri, aStream) {
PROP_METADATA.forEach(function(aProp) {
addon[aProp] = getRDFProperty(ds, root, aProp);
});
if (!addon.id || !addon.version)
throw new Error("No ID or version in install manifest");
if (!gIDTest.test(addon.id))
throw new Error("Illegal add-on ID " + addon.id);
if (!addon.type) {
addon.type = addon.internalName ? "theme" : "extension";
@ -425,6 +423,15 @@ function loadManifestFromRDF(aUri, aStream) {
if (!(addon.type in TYPES))
throw new Error("Install manifest specifies unknown type: " + addon.type);
if (addon.type != "multipackage") {
if (!addon.id)
throw new Error("No ID in install manifest");
if (!gIDTest.test(addon.id))
throw new Error("Illegal add-on ID " + addon.id);
if (!addon.version)
throw new Error("No version in install manifest");
}
// Only read the bootstrapped property for extensions
if (addon.type == "extension") {
addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
@ -563,12 +570,12 @@ function loadManifestFromDir(aDir) {
}
/**
* Loads an AddonInternal object from an add-on in a zip file.
* Loads an AddonInternal object from an nsIZipReader for an add-on.
*
* @param aZipReader
* An nsIZipReader open reading from the add-on XPI file
* An open nsIZipReader for the add-on's files
* @return an AddonInternal object
* @throws if the directory does not contain a valid install manifest
* @throws if the XPI file does not contain a valid install manifest
*/
function loadManifestFromZipReader(aZipReader) {
let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
@ -594,6 +601,27 @@ function loadManifestFromZipReader(aZipReader) {
}
}
/**
* Loads an AddonInternal object from an add-on in an XPI file.
*
* @param aXPIFile
* An nsIFile pointing to the add-on's XPI file
* @return an AddonInternal object
* @throws if the XPI file does not contain a valid install manifest
*/
function loadManifestFromZipFile(aXPIFile) {
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
try {
zipReader.open(aXPIFile);
return loadManifestFromZipReader(zipReader);
}
finally {
zipReader.close();
}
}
/**
* Creates a jar: URI for a file inside a ZIP file.
*
@ -609,6 +637,21 @@ function buildJarURI(aJarfile, aPath) {
return NetUtil.newURI(uri);
}
/**
* Creates and returns a new unique temporary file. The caller should delete
* the file when it is no longer needed.
* @return an nsIFile that points to a randomly named, initially empty file in
* the OS temporary files directory
*/
function getTemporaryFile() {
let file = FileUtils.getDir(KEY_TEMPDIR, []);
let random = Math.random().toString(36).replace(/0./, '').substr(-3);
file.append("tmp-" + random + ".xpi");
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
return file;
}
/**
* Extracts files from a ZIP file into a directory.
*
@ -3869,7 +3912,36 @@ function AddonInstall(aCallback, aInstallLocation, aUrl, aHash, aName, aType,
}
try {
this.loadManifest();
let self = this;
this.loadManifest(function() {
XPIDatabase.getVisibleAddonForID(self.addon.id, function(aAddon) {
self.existingAddon = aAddon;
if (!self.addon.isCompatible) {
// TODO Should we send some event here?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function(aAddon) {
self.state = AddonManager.STATE_DOWNLOADED;
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
aCallback(self);
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
else {
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
aCallback(self);
}
});
});
}
catch (e) {
WARN("Invalid XPI: " + e);
@ -3878,34 +3950,6 @@ function AddonInstall(aCallback, aInstallLocation, aUrl, aHash, aName, aType,
aCallback(this);
return;
}
let self = this;
XPIDatabase.getVisibleAddonForID(this.addon.id, function(aAddon) {
self.existingAddon = aAddon;
if (!self.addon.isCompatible) {
// TODO Should we send some event here?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function(aAddon) {
self.state = AddonManager.STATE_DOWNLOADED;
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
aCallback(self);
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
else {
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall", self.listeners,
self.wrapper);
aCallback(self);
}
});
}
else {
this.state = AddonManager.STATE_AVAILABLE;
@ -3940,9 +3984,11 @@ AddonInstall.prototype = {
releaseNotesURI: null,
sourceURI: null,
file: null,
ownsTempFile: false,
certificate: null,
certName: null,
linkedInstalls: null,
existingAddon: null,
addon: null,
@ -4044,60 +4090,212 @@ AddonInstall.prototype = {
});
},
/**
* Removes the temporary file owned by this AddonInstall if there is one.
*/
removeTemporaryFile: function AI_removeTemporaryFile() {
// Only proceed if this AddonInstall owns its XPI file
if (!this.ownsTempFile)
return;
try {
this.file.remove(true);
this.ownsTempFile = false;
}
catch (e) {
WARN("Failed to remove temporary file " + this.file.path + ": " + e);
}
},
/**
* Updates the sourceURI and releaseNotesURI values on the Addon being
* installed by this AddonInstall instance.
*/
updateAddonURIs: function AI_updateAddonURIs() {
this.addon.sourceURI = this.sourceURI.spec;
if (this.releaseNotesURI)
this.addon.releaseNotesURI = this.releaseNotesURI.spec;
},
/**
* Loads add-on manifests from a multi-package XPI file. Each of the
* XPI and JAR files contained in the XPI will be extracted. Any that
* do not contain valid add-ons will be ignored. The first valid add-on will
* be installed by this AddonInstall instance, the rest will have new
* AddonInstall instances created for them.
*
* @param aZipReader
* An open nsIZipReader for the multi-package XPI's files. This will
* be closed before this method returns.
* @param aCallback
* A function to call when all of the add-on manifests have been
* loaded.
*/
loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader,
aCallback) {
let files = [];
let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])");
while (entries.hasMore()) {
let entryName = entries.getNext();
var target = getTemporaryFile();
try {
aZipReader.extract(entryName, target);
files.push(target);
}
catch (e) {
WARN("Failed to extract " + entryName + " from multi-package " +
"XPI: " + e);
target.remove(false);
}
}
aZipReader.close();
if (files.length == 0) {
throw new Error("Multi-package XPI does not contain any packages " +
"to install");
}
let addon = null;
// Find the first file that has a valid install manifest and use it for
// the add-on that this AddonInstall instance will install.
while (files.length > 0) {
this.removeTemporaryFile();
this.file = files.shift();
this.ownsTempFile = true;
try {
addon = loadManifestFromZipFile(this.file);
break;
}
catch (e) {
WARN(this.file.leafName + " cannot be installed from multi-package " +
"XPI: " + e);
}
}
if (!addon) {
// No valid add-on was found
aCallback();
return;
}
this.addon = addon;
this.updateAddonURIs();
this.addon._install = this;
this.name = this.addon.selectedLocale.name;
this.type = this.addon.type;
this.version = this.addon.version;
// Setting the iconURL to something inside the XPI locks the XPI and
// makes it impossible to delete on Windows.
//let newIcon = createWrapper(this.addon).iconURL;
//if (newIcon)
// this.iconURL = newIcon;
// Create new AddonInstall instances for every remaining file
if (files.length > 0) {
this.linkedInstalls = [];
let count = 0;
let self = this;
files.forEach(function(file) {
AddonInstall.createInstall(function(aInstall) {
// Ignore bad add-ons (createInstall will have logged the error)
if (aInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
// Manually remove the temporary file
file.remove(true);
}
else {
// Make the new install own its temporary file
aInstall.ownsTempFile = true;
self.linkedInstalls.push(aInstall)
aInstall.sourceURI = self.sourceURI;
aInstall.releaseNotesURI = self.releaseNotesURI;
aInstall.updateAddonURIs();
}
count++;
if (count == files.length)
aCallback();
}, file);
}, this);
}
else {
aCallback();
}
},
/**
* Called after the add-on is a local file and the signature and install
* manifest can be read.
*
* @param aCallback
* A function to call when the manifest has been loaded
* @throws if the add-on does not contain a valid install manifest or the
* XPI is incorrectly signed
*/
loadManifest: function AI_loadManifest() {
loadManifest: function AI_loadManifest(aCallback) {
let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
zipreader.open(this.file);
try {
zipreader.open(this.file);
}
catch (e) {
zipreader.close();
throw e;
}
let principal = zipreader.getCertificatePrincipal(null);
if (principal && principal.hasCertificate) {
LOG("Verifying XPI signature");
if (verifyZipSigning(zipreader, principal)) {
let x509 = principal.certificate;
if (x509 instanceof Ci.nsIX509Cert)
this.certificate = x509;
if (this.certificate && this.certificate.commonName.length > 0)
this.certName = this.certificate.commonName;
else
this.certName = principal.prettyName;
}
else {
zipreader.close();
throw new Error("XPI is incorrectly signed");
}
}
try {
let principal = zipreader.getCertificatePrincipal(null);
if (principal && principal.hasCertificate) {
LOG("Verifying XPI signature");
if (verifyZipSigning(zipreader, principal)) {
let x509 = principal.certificate;
if (x509 instanceof Ci.nsIX509Cert)
this.certificate = x509;
if (this.certificate && this.certificate.commonName.length > 0)
this.certName = this.certificate.commonName;
else
this.certName = principal.prettyName;
}
else {
throw new Error("XPI is incorrectly signed");
}
}
if (!zipreader.hasEntry(FILE_INSTALL_MANIFEST)) {
zipreader.close();
throw new Error("Missing install.rdf");
}
this.addon = loadManifestFromZipReader(zipreader);
this.addon.sourceURI = this.sourceURI.spec;
if (this.releaseNotesURI)
this.addon.releaseNotesURI = this.releaseNotesURI.spec;
this.addon._install = this;
this.name = this.addon.selectedLocale.name;
this.type = this.addon.type;
this.version = this.addon.version;
// Setting the iconURL to something inside the XPI locks the XPI and
// makes it impossible to delete on Windows.
//let newIcon = createWrapper(this.addon).iconURL;
//if (newIcon)
// this.iconURL = newIcon;
}
finally {
catch (e) {
zipreader.close();
throw e;
}
if (this.addon.type == "multipackage") {
this.loadMultipackageManifests(zipreader, aCallback);
return;
}
zipreader.close();
this.updateAddonURIs();
this.addon._install = this;
this.name = this.addon.selectedLocale.name;
this.type = this.addon.type;
this.version = this.addon.version;
// Setting the iconURL to something inside the XPI locks the XPI and
// makes it impossible to delete on Windows.
//let newIcon = createWrapper(this.addon).iconURL;
//if (newIcon)
// this.iconURL = newIcon;
aCallback();
},
observe: function AI_observe(aSubject, aTopic, aData) {
@ -4146,10 +4344,8 @@ AddonInstall.prototype = {
}
try {
this.file = FileUtils.getDir("TmpD", []);
let random = Math.random().toString(36).replace(/0./, '').substr(-3);
this.file.append("tmp-" + random + ".xpi");
this.file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
this.file = getTemporaryFile();
this.ownsTempFile = true;
this.stream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
this.stream.init(this.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
@ -4272,21 +4468,21 @@ AddonInstall.prototype = {
return;
}
try {
this.loadManifest();
if (this.addon.isCompatible) {
this.downloadCompleted();
}
else {
// TODO Should we send some event here (bug 557716)?
this.state = AddonManager.STATE_CHECKING;
let self = this;
new UpdateChecker(this.addon, {
onUpdateFinished: function(aAddon) {
self.downloadCompleted();
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
let self = this;
this.loadManifest(function() {
if (self.addon.isCompatible) {
self.downloadCompleted();
}
else {
// TODO Should we send some event here (bug 557716)?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function(aAddon) {
self.downloadCompleted();
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
});
}
catch (e) {
this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e);
@ -4321,12 +4517,7 @@ AddonInstall.prototype = {
XPIProvider.removeActiveInstall(this);
AddonManagerPrivate.callInstallListeners("onDownloadFailed", this.listeners,
this.wrapper);
try {
this.file.remove(true);
}
catch (e) {
WARN("Failed to remove temporary file " + this.file.path + ": " + e);
}
this.removeTemporaryFile();
},
/**
@ -4339,8 +4530,15 @@ AddonInstall.prototype = {
self.state = AddonManager.STATE_DOWNLOADED;
if (AddonManagerPrivate.callInstallListeners("onDownloadEnded",
self.listeners,
self.wrapper))
self.wrapper)) {
self.install();
if (self.linkedInstalls) {
self.linkedInstalls.forEach(function(aInstall) {
aInstall.install();
});
}
}
});
},
@ -4515,15 +4713,7 @@ AddonInstall.prototype = {
this.wrapper);
}
finally {
// If the file was downloaded then delete it
if (!(this.sourceURI instanceof Ci.nsIFileURL)) {
try {
this.file.remove(true);
}
catch (e) {
WARN("Failed to remove temporary file " + this.file.path + ": " + e);
}
}
this.removeTemporaryFile();
}
}
}
@ -4619,6 +4809,12 @@ function AddonInstallWrapper(aInstall) {
this.__defineGetter__("addon", function() createWrapper(aInstall.addon));
this.__defineGetter__("sourceURI", function() aInstall.sourceURI);
this.__defineGetter__("linkedInstalls", function() {
if (!aInstall.linkedInstalls)
return null;
return [i.wrapper for each (i in aInstall.linkedInstalls)];
});
this.install = function() {
aInstall.install();
}

View File

@ -137,6 +137,17 @@ Installer.prototype = {
failed.push(install);
else
installs.push(install);
if (install.linkedInstalls) {
install.linkedInstalls.forEach(function(aInstall) {
aInstall.addListener(this);
// App disabled items are not compatible and so fail to install
if (aInstall.addon.appDisabled)
failed.push(aInstall);
else
installs.push(aInstall);
}, this);
}
break;
default:
WARN("Download of " + install.sourceURI + " in unexpected state " +

View File

@ -0,0 +1 @@
This is corrupt

View File

@ -0,0 +1 @@
This is corrupt

View File

@ -0,0 +1 @@
This is ignored

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- A multi-package XPI -->
<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:type>32</em:type>
</Description>
</RDF>

View File

@ -22,4 +22,42 @@
</em:updates>
</Description>
<Description about="urn:mozilla:extension:addon4@tests.mozilla.org">
<em:updates>
<Seq>
<li>
<Description>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>1</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</li>
</Seq>
</em:updates>
</Description>
<Description about="urn:mozilla:extension:addon7@tests.mozilla.org">
<em:updates>
<Seq>
<li>
<Description>
<em:version>5.0</em:version>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>1</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</li>
</Seq>
</em:updates>
</Description>
</RDF>

View File

@ -795,4 +795,20 @@ Services.prefs.setBoolPref("extensions.logging.enabled", true);
// By default only load extensions from the profile install location
Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_PROFILE);
do_register_cleanup(shutdownManager);
// Register a temporary directory for the tests.
const gTmpD = gProfD.clone();
gTmpD.append("temp");
gTmpD.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
registerDirectory("TmpD", gTmpD);
do_register_cleanup(function() {
// Check that the temporary directory is empty
var dirEntries = gTmpD.directoryEntries
.QueryInterface(AM_Ci.nsIDirectoryEnumerator);
var entry;
while (entry = dirEntries.nextFile) {
do_throw("Found unexpected file in temporary directory: " + entry.leafName);
}
shutdownManager();
});

View File

@ -17,6 +17,7 @@ const ADDON1_SIZE = 705 + 16;
do_load_httpd_js();
var testserver;
var gInstallDate;
var gInstall = null;
// The test extension uses an insecure update url.
Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
@ -58,6 +59,7 @@ function run_test_1() {
ensure_test_completed();
do_check_neq(install, null);
do_check_eq(install.linkedInstalls, null);
do_check_eq(install.type, "extension");
do_check_eq(install.version, "1.0");
do_check_eq(install.name, "Test 1");
@ -165,6 +167,7 @@ function run_test_2() {
let url = "http://localhost:4444/addons/test_install2_1.xpi";
AddonManager.getInstallForURL(url, function(install) {
do_check_neq(install, null);
do_check_eq(install.linkedInstalls, null);
do_check_eq(install.version, "1.0");
do_check_eq(install.name, "Test 2");
do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
@ -564,6 +567,238 @@ function check_test_10(install) {
AddonManager.getAllInstalls(function(activeInstalls) {
do_check_eq(activeInstalls.length, 0);
run_test_11();
});
}
// Tests that a multi-package install shows up as multiple installs with the
// correct sourceURI.
function run_test_11() {
prepare_test({ }, [
"onNewInstall",
"onNewInstall",
"onNewInstall",
"onNewInstall"
]);
AddonManager.getInstallForFile(do_get_addon("test_install4"), function(install) {
ensure_test_completed();
do_check_neq(install, null);
do_check_neq(install.linkedInstalls, null);
do_check_eq(install.linkedInstalls.length, 3);
// Might be in any order so sort them based on ID
let installs = [install].concat(install.linkedInstalls);
installs.sort(function(a, b) {
if (a.addon.id < b.addon.id)
return -1;
if (a.addon.id > b.addon.id)
return 1;
return 0;
});
// Comes from addon4.xpi and is made compatible by an update check
do_check_eq(installs[0].sourceURI, install.sourceURI);
do_check_eq(installs[0].addon.id, "addon4@tests.mozilla.org");
do_check_false(installs[0].addon.appDisabled);
do_check_eq(installs[0].version, "1.0");
do_check_eq(installs[0].name, "Multi Test 1");
do_check_eq(installs[0].state, AddonManager.STATE_DOWNLOADED);
// Comes from addon5.jar and is compatible by default
do_check_eq(installs[1].sourceURI, install.sourceURI);
do_check_eq(installs[1].addon.id, "addon5@tests.mozilla.org");
do_check_false(installs[1].addon.appDisabled);
do_check_eq(installs[1].version, "3.0");
do_check_eq(installs[1].name, "Multi Test 2");
do_check_eq(installs[1].state, AddonManager.STATE_DOWNLOADED);
// Comes from addon6.xpi and is incompatible
do_check_eq(installs[2].sourceURI, install.sourceURI);
do_check_eq(installs[2].addon.id, "addon6@tests.mozilla.org");
do_check_true(installs[2].addon.appDisabled);
do_check_eq(installs[2].version, "2.0");
do_check_eq(installs[2].name, "Multi Test 3");
do_check_eq(installs[2].state, AddonManager.STATE_DOWNLOADED);
// Comes from addon7.jar and is made compatible by an update check
do_check_eq(installs[3].sourceURI, install.sourceURI);
do_check_eq(installs[3].addon.id, "addon7@tests.mozilla.org");
do_check_false(installs[3].addon.appDisabled);
do_check_eq(installs[3].version, "5.0");
do_check_eq(installs[3].name, "Multi Test 4");
do_check_eq(installs[3].state, AddonManager.STATE_DOWNLOADED);
AddonManager.getAllInstalls(function(aInstalls) {
do_check_eq(aInstalls.length, 4);
prepare_test({
"addon4@tests.mozilla.org": [
"onInstalling"
],
"addon5@tests.mozilla.org": [
"onInstalling"
],
"addon6@tests.mozilla.org": [
"onInstalling"
],
"addon7@tests.mozilla.org": [
"onInstalling"
]
}, [
"onInstallStarted",
"onInstallEnded",
"onInstallStarted",
"onInstallEnded",
"onInstallStarted",
"onInstallEnded",
"onInstallStarted",
"onInstallEnded"
], check_test_11);
installs[0].install();
installs[1].install();
installs[2].install();
installs[3].install();
});
});
}
function check_test_11() {
restartManager();
AddonManager.getAddonsByIDs(["addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org"],
function([a4, a5, a6, a7]) {
do_check_neq(a4, null);
do_check_neq(a5, null);
do_check_neq(a6, null);
do_check_neq(a7, null);
a4.uninstall();
a5.uninstall();
a6.uninstall();
a7.uninstall();
restartManager();
run_test_12();
});
}
// Same as test 11 but for a remote XPI
function run_test_12() {
prepare_test({ }, [
"onNewInstall",
]);
let url = "http://localhost:4444/addons/test_install4.xpi";
AddonManager.getInstallForURL(url, function(install) {
gInstall = install;
ensure_test_completed();
do_check_neq(install, null);
do_check_eq(install.linkedInstalls, null);
do_check_eq(install.state, AddonManager.STATE_AVAILABLE);
prepare_test({
"addon4@tests.mozilla.org": [
"onInstalling"
],
"addon5@tests.mozilla.org": [
"onInstalling"
],
"addon6@tests.mozilla.org": [
"onInstalling"
],
"addon7@tests.mozilla.org": [
"onInstalling"
]
}, [
"onDownloadStarted",
"onNewInstall",
"onNewInstall",
"onNewInstall",
"onDownloadEnded",
"onInstallStarted",
"onInstallEnded",
"onInstallStarted",
"onInstallEnded",
"onInstallStarted",
"onInstallEnded",
"onInstallStarted",
"onInstallEnded"
], check_test_12);
install.install();
}, "application/x-xpinstall", null, "Multi Test 4");
}
function check_test_12() {
do_check_eq(gInstall.linkedInstalls.length, 3);
// Might be in any order so sort them based on ID
let installs = [gInstall].concat(gInstall.linkedInstalls);
installs.sort(function(a, b) {
if (a.addon.id < b.addon.id)
return -1;
if (a.addon.id > b.addon.id)
return 1;
return 0;
});
// Comes from addon4.xpi and is made compatible by an update check
do_check_eq(installs[0].sourceURI, gInstall.sourceURI);
do_check_eq(installs[0].addon.id, "addon4@tests.mozilla.org");
do_check_false(installs[0].addon.appDisabled);
do_check_eq(installs[0].version, "1.0");
do_check_eq(installs[0].name, "Multi Test 1");
do_check_eq(installs[0].state, AddonManager.STATE_INSTALLED);
// Comes from addon5.jar and is compatible by default
do_check_eq(installs[1].sourceURI, gInstall.sourceURI);
do_check_eq(installs[1].addon.id, "addon5@tests.mozilla.org");
do_check_false(installs[1].addon.appDisabled);
do_check_eq(installs[1].version, "3.0");
do_check_eq(installs[1].name, "Multi Test 2");
do_check_eq(installs[1].state, AddonManager.STATE_INSTALLED);
// Comes from addon6.xpi and is incompatible
do_check_eq(installs[2].sourceURI, gInstall.sourceURI);
do_check_eq(installs[2].addon.id, "addon6@tests.mozilla.org");
do_check_true(installs[2].addon.appDisabled);
do_check_eq(installs[2].version, "2.0");
do_check_eq(installs[2].name, "Multi Test 3");
do_check_eq(installs[2].state, AddonManager.STATE_INSTALLED);
// Comes from addon7.jar and is made compatible by an update check
do_check_eq(installs[3].sourceURI, gInstall.sourceURI);
do_check_eq(installs[3].addon.id, "addon7@tests.mozilla.org");
do_check_false(installs[3].addon.appDisabled);
do_check_eq(installs[3].version, "5.0");
do_check_eq(installs[3].name, "Multi Test 4");
do_check_eq(installs[3].state, AddonManager.STATE_INSTALLED);
restartManager();
AddonManager.getAddonsByIDs(["addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org"],
function([a4, a5, a6, a7]) {
do_check_neq(a4, null);
do_check_neq(a5, null);
do_check_neq(a6, null);
do_check_neq(a7, null);
a4.uninstall();
a5.uninstall();
a6.uninstall();
a7.uninstall();
restartManager();
end_test();
});
}

View File

@ -83,6 +83,7 @@ _BROWSER_FILES = head.js \
browser_bug540558.js \
browser_relative.js \
browser_cancel.js \
browser_multipackage.js \
unsigned.xpi \
signed.xpi \
signed2.xpi \
@ -94,6 +95,7 @@ _BROWSER_FILES = head.js \
incompatible.xpi \
empty.xpi \
corrupt.xpi \
multipackage.xpi \
enabled.html \
installtrigger.html \
startsoftwareupdate.html \

View File

@ -0,0 +1,50 @@
// ----------------------------------------------------------------------------
// Tests installing an signed add-on by navigating directly to the url
function test() {
Harness.installConfirmCallback = confirm_install;
Harness.installEndedCallback = install_ended;
Harness.installsCompletedCallback = finish_test;
Harness.setup();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "multipackage.xpi");
}
function get_item(items, name) {
for (let i = 0; i < items.length; i++) {
if (items[i].name == name)
return items[i];
}
ok(false, "Item for " + name + " was not listed");
}
function confirm_install(window) {
items = window.document.getElementById("itemList").childNodes;
is(items.length, 2, "Should be 2 items listed in the confirmation dialog");
let item = get_item(items, "XPI Test");
if (item) {
is(item.signed, "false", "Should not have listed the item as signed");
is(item.icon, "", "Should have listed no icon for the item");
}
item = get_item(items, "Signed XPI Test");
if (item) {
is(item.cert, "(Object Signer)", "Should have seen the signer");
is(item.signed, "true", "Should have listed the item as signed");
is(item.icon, "", "Should have listed no icon for the item");
}
return true;
}
function install_ended(install, addon) {
install.cancel();
}
function finish_test(count) {
is(count, 2, "2 Add-ons should have been successfully installed");
gBrowser.removeCurrentTab();
Harness.finish();
}
// ----------------------------------------------------------------------------