зеркало из https://github.com/mozilla/gecko-dev.git
Bug 474289: Automatically install add-ons distributed with the application into the user's profile. r=bsmedberg, r=robstrong, a=beltner
This commit is contained in:
Родитель
9d589f4402
Коммит
47601232c0
|
@ -109,7 +109,7 @@ function callProvider(aProvider, aMethod, aDefault) {
|
|||
return aProvider[aMethod].apply(aProvider, args);
|
||||
}
|
||||
catch (e) {
|
||||
ERROR("Exception calling provider" + aMethod, e);
|
||||
ERROR("Exception calling provider " + aMethod, e);
|
||||
return aDefault;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,8 @@ const PREF_XPI_WHITELIST_PERMISSIONS = "xpinstall.whitelist.add";
|
|||
const PREF_XPI_BLACKLIST_PERMISSIONS = "xpinstall.blacklist.add";
|
||||
const PREF_XPI_UNPACK = "extensions.alwaysUnpack";
|
||||
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
|
||||
const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
|
||||
const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
|
||||
|
||||
const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul";
|
||||
|
||||
|
@ -91,6 +93,7 @@ const FILE_XPI_ADDONS_LIST = "extensions.ini";
|
|||
const KEY_PROFILEDIR = "ProfD";
|
||||
const KEY_APPDIR = "XCurProcD";
|
||||
const KEY_TEMPDIR = "TmpD";
|
||||
const KEY_APP_DISTRIBUTION = "XREAppDist";
|
||||
|
||||
const KEY_APP_PROFILE = "app-profile";
|
||||
const KEY_APP_GLOBAL = "app-global";
|
||||
|
@ -163,38 +166,42 @@ var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\
|
|||
}, this);
|
||||
|
||||
/**
|
||||
* A safe way to move a file or the contents of a directory to a new directory.
|
||||
* The move is performed recursively and if anything fails an attempt is made to
|
||||
* rollback the entire operation. The operation may also be rolled back to its
|
||||
* original state after it has completed by calling the rollback method.
|
||||
* A safe way to install a file or the contents of a directory to a new
|
||||
* directory. The file or directory is moved or copied recursively and if
|
||||
* anything fails an attempt is made to rollback the entire operation. The
|
||||
* operation may also be rolled back to its original state after it has
|
||||
* completed by calling the rollback method.
|
||||
*
|
||||
* Moves can be chained. Calling move multiple times will remember the whole set
|
||||
* and if one fails all of the move operations will be rolled back.
|
||||
* Operations can be chained. Calling move or copy multiple times will remember
|
||||
* the whole set and if one fails all of the operations will be rolled back.
|
||||
*/
|
||||
function SafeMoveOperation() {
|
||||
this._movedFiles = [];
|
||||
function SafeInstallOperation() {
|
||||
this._installedFiles = [];
|
||||
this._createdDirs = [];
|
||||
}
|
||||
|
||||
SafeMoveOperation.prototype = {
|
||||
_movedFiles: null,
|
||||
SafeInstallOperation.prototype = {
|
||||
_installedFiles: null,
|
||||
_createdDirs: null,
|
||||
|
||||
_moveFile: function(aFile, aTargetDirectory) {
|
||||
let oldFile = aFile.clone();
|
||||
_installFile: function(aFile, aTargetDirectory, aCopy) {
|
||||
let oldFile = aCopy ? null : aFile.clone();
|
||||
let newFile = aFile.clone();
|
||||
try {
|
||||
newFile.moveTo(aTargetDirectory, null);
|
||||
if (aCopy)
|
||||
newFile.copyTo(aTargetDirectory, null);
|
||||
else
|
||||
newFile.moveTo(aTargetDirectory, null);
|
||||
}
|
||||
catch (e) {
|
||||
ERROR("Failed to move file " + aFile.path + " to " +
|
||||
aTargetDirectory.path, e);
|
||||
ERROR("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path +
|
||||
" to " + aTargetDirectory.path, e);
|
||||
throw e;
|
||||
}
|
||||
this._movedFiles.push({ oldFile: oldFile, newFile: newFile });
|
||||
this._installedFiles.push({ oldFile: null, newFile: newFile });
|
||||
},
|
||||
|
||||
_moveDirectory: function(aDirectory, aTargetDirectory) {
|
||||
_installDirectory: function(aDirectory, aTargetDirectory, aCopy) {
|
||||
let newDir = aTargetDirectory.clone();
|
||||
newDir.append(aDirectory.leafName);
|
||||
try {
|
||||
|
@ -220,14 +227,19 @@ SafeMoveOperation.prototype = {
|
|||
|
||||
cacheEntries.forEach(function(aEntry) {
|
||||
try {
|
||||
this._moveDirEntry(aEntry, newDir);
|
||||
this._installDirEntry(aEntry, newDir, aCopy);
|
||||
}
|
||||
catch (e) {
|
||||
ERROR("Failed to move entry " + aEntry.path, e);
|
||||
ERROR("Failed to " + (aCopy ? "copy" : "move") + " entry " +
|
||||
aEntry.path, e);
|
||||
throw e;
|
||||
}
|
||||
}, this);
|
||||
|
||||
// If this is only a copy operation then there is nothing else to do
|
||||
if (aCopy)
|
||||
return;
|
||||
|
||||
// The directory should be empty by this point. If it isn't this will throw
|
||||
// and all of the operations will be rolled back
|
||||
try {
|
||||
|
@ -241,18 +253,19 @@ SafeMoveOperation.prototype = {
|
|||
|
||||
// Note we put the directory move in after all the file moves so the
|
||||
// directory is recreated before all the files are moved back
|
||||
this._movedFiles.push({ oldFile: aDirectory, newFile: newDir });
|
||||
this._installedFiles.push({ oldFile: aDirectory, newFile: newDir });
|
||||
},
|
||||
|
||||
_moveDirEntry: function(aDirEntry, aTargetDirectory) {
|
||||
_installDirEntry: function(aDirEntry, aTargetDirectory, aCopy) {
|
||||
try {
|
||||
if (aDirEntry.isDirectory())
|
||||
this._moveDirectory(aDirEntry, aTargetDirectory);
|
||||
this._installDirectory(aDirEntry, aTargetDirectory, aCopy);
|
||||
else
|
||||
this._moveFile(aDirEntry, aTargetDirectory);
|
||||
this._installFile(aDirEntry, aTargetDirectory, aCopy);
|
||||
}
|
||||
catch (e) {
|
||||
ERROR("Failure moving " + aDirEntry.path + " to " + aTargetDirectory.path);
|
||||
ERROR("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
|
||||
" to " + aTargetDirectory.path);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
@ -269,7 +282,27 @@ SafeMoveOperation.prototype = {
|
|||
*/
|
||||
move: function(aFile, aTargetDirectory) {
|
||||
try {
|
||||
this._moveDirEntry(aFile, aTargetDirectory);
|
||||
this._installDirEntry(aFile, aTargetDirectory, false);
|
||||
}
|
||||
catch (e) {
|
||||
this.rollback();
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copies a file or directory into a new directory. If an error occurs then
|
||||
* all new files that have been created will be removed.
|
||||
*
|
||||
* @param aFile
|
||||
* The file or directory to be copied.
|
||||
* @param aTargetDirectory
|
||||
* The directory to copy into, this is expected to be an empty
|
||||
* directory.
|
||||
*/
|
||||
copy: function(aFile, aTargetDirectory) {
|
||||
try {
|
||||
this._installDirEntry(aFile, aTargetDirectory, true);
|
||||
}
|
||||
catch (e) {
|
||||
this.rollback();
|
||||
|
@ -283,13 +316,17 @@ SafeMoveOperation.prototype = {
|
|||
* state
|
||||
*/
|
||||
rollback: function() {
|
||||
while (this._movedFiles.length > 0) {
|
||||
let move = this._movedFiles.pop();
|
||||
while (this._installedFiles.length > 0) {
|
||||
let move = this._installedFiles.pop();
|
||||
if (move.newFile.isDirectory()) {
|
||||
let oldDir = move.oldFile.parent.clone();
|
||||
oldDir.append(move.oldFile.leafName);
|
||||
oldDir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
}
|
||||
else if (!move.oldFile) {
|
||||
// No old file means this was a copied file
|
||||
move.newFile.remove(true);
|
||||
}
|
||||
else {
|
||||
move.newFile.moveTo(move.oldFile.parent, null);
|
||||
}
|
||||
|
@ -777,6 +814,13 @@ function loadManifestFromZipFile(aXPIFile) {
|
|||
}
|
||||
}
|
||||
|
||||
function loadManifestFromFile(aFile) {
|
||||
if (aFile.isFile())
|
||||
return loadManifestFromZipFile(aFile);
|
||||
else
|
||||
return loadManifestFromDir(aFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a jar: URI for a file inside a ZIP file.
|
||||
*
|
||||
|
@ -1801,6 +1845,131 @@ var XPIProvider = {
|
|||
return changed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Installs any add-ons located in the extensions directory of the
|
||||
* application's distribution specific directory into the profile unless a
|
||||
* newer version already exists or the user has previously uninstalled the
|
||||
* distributed add-on.
|
||||
*
|
||||
* @param aManifests
|
||||
* A dictionary to add new install manifests to to save having to
|
||||
* reload them later
|
||||
* @return true if any new add-ons were installed
|
||||
*/
|
||||
installDistributionAddons: function XPI_installDistributionAddons(aManifests) {
|
||||
let distroDir;
|
||||
try {
|
||||
distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!distroDir.exists())
|
||||
return false;
|
||||
|
||||
if (!distroDir.isDirectory())
|
||||
return false;
|
||||
|
||||
let changed = false;
|
||||
let profileLocation = this.installLocationsByName[KEY_APP_PROFILE];
|
||||
|
||||
let entries = distroDir.directoryEntries
|
||||
.QueryInterface(Ci.nsIDirectoryEnumerator);
|
||||
let entry;
|
||||
while (entry = entries.nextFile) {
|
||||
// Should never happen really
|
||||
if (!(entry instanceof Ci.nsILocalFile))
|
||||
continue;
|
||||
|
||||
let id = entry.leafName;
|
||||
|
||||
if (entry.isFile()) {
|
||||
if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
|
||||
id = id.substring(0, id.length - 4);
|
||||
}
|
||||
else {
|
||||
LOG("Ignoring distribution add-on that isn't an XPI: " + entry.path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!entry.isDirectory()) {
|
||||
LOG("Ignoring distribution add-on that isn't a file or directory: " +
|
||||
entry.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!gIDTest.test(id)) {
|
||||
LOG("Ignoring distribution add-on whose name is not a valid add-on ID: " +
|
||||
entry.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
let addon;
|
||||
try {
|
||||
addon = loadManifestFromFile(entry);
|
||||
}
|
||||
catch (e) {
|
||||
WARN("File entry " + entry.path + " contains an invalid add-on", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addon.id != id) {
|
||||
WARN("File entry " + entry.path + " contains an add-on with an " +
|
||||
"incorrect ID")
|
||||
continue;
|
||||
}
|
||||
|
||||
let existingEntry = null;
|
||||
try {
|
||||
existingEntry = profileLocation.getLocationForID(id);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
if (existingEntry) {
|
||||
let existingAddon;
|
||||
try {
|
||||
existingAddon = loadManifestFromFile(existingEntry);
|
||||
|
||||
if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
|
||||
continue;
|
||||
}
|
||||
catch (e) {
|
||||
// Bad add-on in the profile so just proceed and install over the top
|
||||
WARN("Profile contains an add-on with a bad or missing install " +
|
||||
"manifest at " + existingEntry.path + ", overwriting", e);
|
||||
}
|
||||
}
|
||||
else if (Prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Install the add-on
|
||||
try {
|
||||
profileLocation.installAddon(id, entry, null, true);
|
||||
LOG("Installed distribution add-on " + id);
|
||||
|
||||
Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)
|
||||
|
||||
// aManifests may contain a copy of a newly installed add-on's manifest
|
||||
// and we'll have overwritten that so instead cache our install manifest
|
||||
// which will later be put into the database in processFileChanges
|
||||
if (!(KEY_APP_PROFILE in aManifests))
|
||||
aManifests[KEY_APP_PROFILE] = {};
|
||||
aManifests[KEY_APP_PROFILE][id] = addon;
|
||||
changed = true;
|
||||
}
|
||||
catch (e) {
|
||||
ERROR("Failed to install distribution add-on " + entry.path, e);
|
||||
}
|
||||
}
|
||||
|
||||
entries.close();
|
||||
|
||||
return changed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Compares the add-ons that are currently installed to those that were
|
||||
* known to be installed when the application last ran and applies any
|
||||
|
@ -1858,10 +2027,7 @@ var XPIProvider = {
|
|||
// If not load it
|
||||
if (!newAddon) {
|
||||
let file = aInstallLocation.getLocationForID(aOldAddon.id);
|
||||
if (file.isFile())
|
||||
newAddon = loadManifestFromZipFile(file);
|
||||
else
|
||||
newAddon = loadManifestFromDir(file);
|
||||
newAddon = loadManifestFromFile(file);
|
||||
// Carry over the userDisabled setting for add-ons that just appeared
|
||||
newAddon.userDisabled = aOldAddon.userDisabled;
|
||||
}
|
||||
|
@ -2074,10 +2240,7 @@ var XPIProvider = {
|
|||
// Otherwise load the manifest from the add-on
|
||||
if (!newAddon) {
|
||||
let file = aInstallLocation.getLocationForID(aId);
|
||||
if (file.isFile())
|
||||
newAddon = loadManifestFromZipFile(file);
|
||||
else
|
||||
newAddon = loadManifestFromDir(file);
|
||||
newAddon = loadManifestFromFile(file);
|
||||
}
|
||||
// The add-on in the manifest should match the add-on ID.
|
||||
if (newAddon.id != aId)
|
||||
|
@ -2342,6 +2505,11 @@ var XPIProvider = {
|
|||
// If the schema appears to have changed then we should update the database
|
||||
updateDatabase |= DB_SCHEMA != Prefs.getIntPref(PREF_DB_SCHEMA, 0);
|
||||
|
||||
// If the application has changed then check for new distribution add-ons
|
||||
if (aAppChanged !== false &&
|
||||
Prefs.getBoolPref(PREF_INSTALL_DISTRO_ADDONS, true))
|
||||
updateDatabase = this.installDistributionAddons(manifests) | updateDatabase;
|
||||
|
||||
// Load the list of bootstrapped add-ons first so processFileChanges can
|
||||
// modify it
|
||||
this.bootstrappedAddons = JSON.parse(Prefs.getCharPref(PREF_BOOTSTRAP_ADDONS,
|
||||
|
@ -6851,12 +7019,17 @@ DirectoryInstallLocation.prototype = {
|
|||
* The source nsIFile to install from
|
||||
* @param aExistingAddonID
|
||||
* The ID of an existing add-on to uninstall at the same time
|
||||
* @param aCopy
|
||||
* If false the source files will be moved to the new location,
|
||||
* otherwise they will only be copied
|
||||
* @return an nsIFile indicating where the add-on was installed to
|
||||
*/
|
||||
installAddon: function DirInstallLocation_installAddon(aId, aSource, aExistingAddonID) {
|
||||
installAddon: function DirInstallLocation_installAddon(aId, aSource,
|
||||
aExistingAddonID,
|
||||
aCopy) {
|
||||
let trashDir = this.getTrashDir();
|
||||
|
||||
let transaction = new SafeMoveOperation();
|
||||
let transaction = new SafeInstallOperation();
|
||||
|
||||
let self = this;
|
||||
function moveOldAddon(aId) {
|
||||
|
@ -6881,10 +7054,15 @@ DirectoryInstallLocation.prototype = {
|
|||
if (aExistingAddonID && aExistingAddonID != aId)
|
||||
moveOldAddon(aExistingAddonID);
|
||||
|
||||
if (aSource.isFile())
|
||||
Services.obs.notifyObservers(aSource, "flush-cache-entry", null);
|
||||
if (aCopy) {
|
||||
transaction.copy(aSource, this._directory);
|
||||
}
|
||||
else {
|
||||
if (aSource.isFile())
|
||||
Services.obs.notifyObservers(aSource, "flush-cache-entry", null);
|
||||
|
||||
transaction.move(aSource, this._directory);
|
||||
transaction.move(aSource, this._directory);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// It isn't ideal if this cleanup fails but it isn't worth rolling back
|
||||
|
@ -6946,7 +7124,7 @@ DirectoryInstallLocation.prototype = {
|
|||
if (file.leafName != aId)
|
||||
Services.obs.notifyObservers(file, "flush-cache-entry", null);
|
||||
|
||||
let transaction = new SafeMoveOperation();
|
||||
let transaction = new SafeInstallOperation();
|
||||
|
||||
try {
|
||||
transaction.move(file, trashDir);
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?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>addon1@tests.mozilla.org</em:id>
|
||||
<em:version>2.0</em:version>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Distributed add-ons test</em:name>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<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>
|
21
toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/bootstrap.js
поставляемый
Normal file
21
toolkit/mozapps/extensions/test/xpcshell/data/test_distribution2_2/bootstrap.js
поставляемый
Normal file
|
@ -0,0 +1,21 @@
|
|||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function install(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", 2);
|
||||
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
|
||||
}
|
||||
|
||||
function startup(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", 2);
|
||||
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.active_version", 0);
|
||||
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
|
||||
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?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>addon2@tests.mozilla.org</em:id>
|
||||
<em:version>2.0</em:version>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Distributed add-ons test</em:name>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<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>
|
|
@ -0,0 +1 @@
|
|||
Test of a file in a sub directory
|
|
@ -0,0 +1 @@
|
|||
Nested dummy file
|
|
@ -1056,6 +1056,9 @@ Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false);
|
|||
Services.prefs.setCharPref("extensions.update.url", "http://127.0.0.1/updateURL");
|
||||
Services.prefs.setCharPref("extensions.blocklist.url", "http://127.0.0.1/blocklistURL");
|
||||
|
||||
// By default ignore bundled add-ons
|
||||
Services.prefs.setBoolPref("extensions.installDistroAddons", false);
|
||||
|
||||
// Register a temporary directory for the tests.
|
||||
const gTmpD = gProfD.clone();
|
||||
gTmpD.append("temp");
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// This verifies that add-ons distributed with the application get installed
|
||||
// correctly
|
||||
|
||||
// Allow distributed add-ons to install
|
||||
Services.prefs.setBoolPref("extensions.installDistroAddons", true);
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
|
||||
const profileDir = gProfD.clone();
|
||||
profileDir.append("extensions");
|
||||
const distroDir = gProfD.clone();
|
||||
distroDir.append("distribution");
|
||||
distroDir.append("extensions");
|
||||
registerDirectory("XREAppDist", distroDir.parent);
|
||||
|
||||
var addon1_1 = {
|
||||
id: "addon1@tests.mozilla.org",
|
||||
version: "1.0",
|
||||
name: "Test version 1",
|
||||
targetApplications: [{
|
||||
id: "xpcshell@tests.mozilla.org",
|
||||
minVersion: "1",
|
||||
maxVersion: "5"
|
||||
}]
|
||||
};
|
||||
|
||||
var addon1_2 = {
|
||||
id: "addon1@tests.mozilla.org",
|
||||
version: "2.0",
|
||||
name: "Test version 2",
|
||||
targetApplications: [{
|
||||
id: "xpcshell@tests.mozilla.org",
|
||||
minVersion: "1",
|
||||
maxVersion: "5"
|
||||
}]
|
||||
};
|
||||
|
||||
var addon1_3 = {
|
||||
id: "addon1@tests.mozilla.org",
|
||||
version: "3.0",
|
||||
name: "Test version 3",
|
||||
targetApplications: [{
|
||||
id: "xpcshell@tests.mozilla.org",
|
||||
minVersion: "1",
|
||||
maxVersion: "5"
|
||||
}]
|
||||
};
|
||||
|
||||
function getActiveVersion() {
|
||||
return Services.prefs.getIntPref("bootstraptest.active_version");
|
||||
}
|
||||
|
||||
function getInstalledVersion() {
|
||||
return Services.prefs.getIntPref("bootstraptest.installed_version");
|
||||
}
|
||||
|
||||
function setOldModificationTime() {
|
||||
// Make sure the installed extension has an old modification time so any
|
||||
// changes will be detected
|
||||
shutdownManager()
|
||||
let extension = gProfD.clone();
|
||||
extension.append("extensions");
|
||||
if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
|
||||
extension.append("addon1@tests.mozilla.org");
|
||||
else
|
||||
extension.append("addon1@tests.mozilla.org.xpi");
|
||||
setExtensionModifiedTime(extension, Date.now - 10000);
|
||||
startupManager(false);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
do_test_pending();
|
||||
|
||||
run_test_1();
|
||||
}
|
||||
|
||||
// Tests that on the first startup the add-on gets installed
|
||||
function run_test_1() {
|
||||
writeInstallRDFForExtension(addon1_1, distroDir);
|
||||
|
||||
startupManager();
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_neq(a1, null);
|
||||
do_check_eq(a1.version, "1.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
|
||||
run_test_2();
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that starting with a newer version in the distribution dir doesn't
|
||||
// install it yet
|
||||
function run_test_2() {
|
||||
setOldModificationTime();
|
||||
|
||||
writeInstallRDFForExtension(addon1_2, distroDir);
|
||||
|
||||
restartManager();
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_neq(a1, null);
|
||||
do_check_eq(a1.version, "1.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
|
||||
run_test_3();
|
||||
});
|
||||
}
|
||||
|
||||
// Test that an app upgrade installs the newer version
|
||||
function run_test_3() {
|
||||
restartManager("2");
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_neq(a1, null);
|
||||
do_check_eq(a1.version, "2.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
|
||||
run_test_4();
|
||||
});
|
||||
}
|
||||
|
||||
// Test that an app upgrade doesn't downgrade the extension
|
||||
function run_test_4() {
|
||||
setOldModificationTime();
|
||||
|
||||
writeInstallRDFForExtension(addon1_1, distroDir);
|
||||
|
||||
restartManager("3");
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_neq(a1, null);
|
||||
do_check_eq(a1.version, "2.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
|
||||
run_test_5();
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that after uninstalling a restart doesn't re-install the extension
|
||||
function run_test_5() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
a1.uninstall();
|
||||
|
||||
restartManager();
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_eq(a1, null);
|
||||
|
||||
run_test_6();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that upgrading the application still doesn't re-install the uninstalled
|
||||
// extension
|
||||
function run_test_6() {
|
||||
restartManager("4");
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_eq(a1, null);
|
||||
|
||||
run_test_7();
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that a pending install of a newer version of a distributed add-on
|
||||
// at app change still gets applied
|
||||
function run_test_7() {
|
||||
Services.prefs.clearUserPref("extensions.installedDistroAddon.addon1@tests.mozilla.org");
|
||||
|
||||
installAllFiles([do_get_addon("test_distribution1_2")], function() {
|
||||
restartManager(2);
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_neq(a1, null);
|
||||
do_check_eq(a1.version, "2.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
|
||||
a1.uninstall();
|
||||
restartManager();
|
||||
|
||||
run_test_8();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that a pending install of a older version of a distributed add-on
|
||||
// at app change gets replaced by the distributed version
|
||||
function run_test_8() {
|
||||
writeInstallRDFForExtension(addon1_3, distroDir);
|
||||
|
||||
installAllFiles([do_get_addon("test_distribution1_2")], function() {
|
||||
restartManager(3);
|
||||
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
|
||||
do_check_neq(a1, null);
|
||||
do_check_eq(a1.version, "3.0");
|
||||
do_check_true(a1.isActive);
|
||||
do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
|
||||
|
||||
a1.uninstall();
|
||||
restartManager();
|
||||
|
||||
run_test_9();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Tests that bootstrapped add-ons distributed start up correctly, also that
|
||||
// add-ons with multiple directories get copied fully
|
||||
function run_test_9() {
|
||||
// Copy the test add-on to the distro dir
|
||||
let addon = do_get_file("data/test_distribution2_2");
|
||||
addon.copyTo(distroDir, "addon2@tests.mozilla.org");
|
||||
|
||||
restartManager("5");
|
||||
|
||||
AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
|
||||
do_check_neq(a2, null);
|
||||
do_check_true(a2.isActive);
|
||||
|
||||
do_check_eq(getInstalledVersion(), 2);
|
||||
do_check_eq(getActiveVersion(), 2);
|
||||
|
||||
do_check_true(a2.hasResource("bootstrap.js"));
|
||||
do_check_true(a2.hasResource("subdir/dummy.txt"));
|
||||
do_check_true(a2.hasResource("subdir/subdir2/dummy2.txt"));
|
||||
|
||||
// Currently installs are unpacked if the source is a directory regardless
|
||||
// of the install.rdf property or the global preference
|
||||
|
||||
let addonDir = profileDir.clone();
|
||||
addonDir.append("addon2@tests.mozilla.org");
|
||||
do_check_true(addonDir.exists());
|
||||
do_check_true(addonDir.isDirectory());
|
||||
addonDir.append("subdir");
|
||||
do_check_true(addonDir.exists());
|
||||
do_check_true(addonDir.isDirectory());
|
||||
addonDir.append("subdir2");
|
||||
do_check_true(addonDir.exists());
|
||||
do_check_true(addonDir.isDirectory());
|
||||
addonDir.append("dummy2.txt");
|
||||
do_check_true(addonDir.exists());
|
||||
do_check_true(addonDir.isFile());
|
||||
|
||||
a2.uninstall();
|
||||
|
||||
do_test_finished();
|
||||
});
|
||||
}
|
|
@ -338,6 +338,11 @@ nsXREDirProvider::GetFile(const char* aProperty, PRBool* aPersistent,
|
|||
else if (!strcmp(aProperty, XRE_USER_SYS_EXTENSION_DIR)) {
|
||||
return GetSysUserExtensionsDirectory((nsILocalFile**)(nsIFile**) aFile);
|
||||
}
|
||||
else if (!strcmp(aProperty, XRE_APP_DISTRIBUTION_DIR)) {
|
||||
rv = GetAppDir()->Clone(getter_AddRefs(file));
|
||||
if (NS_SUCCEEDED(rv))
|
||||
rv = file->AppendNative(NS_LITERAL_CSTRING("distribution"));
|
||||
}
|
||||
else if (NS_SUCCEEDED(GetProfileStartupDir(getter_AddRefs(file)))) {
|
||||
// We need to allow component, xpt, and chrome registration to
|
||||
// occur prior to the profile-after-change notification.
|
||||
|
|
|
@ -262,6 +262,12 @@ struct nsXREAppData
|
|||
*/
|
||||
#define XRE_USER_SYS_EXTENSION_DIR "XREUSysExt"
|
||||
|
||||
/**
|
||||
* A directory service key which specifies the distribution specific files for
|
||||
* the application.
|
||||
*/
|
||||
#define XRE_APP_DISTRIBUTION_DIR "XREAppDist"
|
||||
|
||||
/**
|
||||
* Begin an XUL application. Does not return until the user exits the
|
||||
* application.
|
||||
|
|
Загрузка…
Ссылка в новой задаче