Bug 555349: Finalize the restartless add-on format. r=robstrong

This commit is contained in:
Dave Townsend 2010-04-28 09:23:36 -07:00
Родитель 7746c1c7e5
Коммит ab93423703
6 изменённых файлов: 400 добавлений и 158 удалений

Просмотреть файл

@ -107,12 +107,22 @@ const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
const BOOTSTRAP_REASONS = {
APP_STARTUP : 1,
APP_SHUTDOWN : 2,
ADDON_ENABLE : 3,
ADDON_DISABLE : 4,
ADDON_INSTALL : 5,
ADDON_UNINSTALL : 6,
ADDON_UPGRADE : 7,
ADDON_DOWNGRADE : 8
};
// Map new string type identifiers to old style nsIUpdateItem types // Map new string type identifiers to old style nsIUpdateItem types
const TYPES = { const TYPES = {
extension: 2, extension: 2,
theme: 4, theme: 4,
locale: 8, locale: 8
bootstrapped: 64
}; };
/** /**
@ -393,6 +403,10 @@ function loadManifestFromRDF(aUri, aStream) {
addon.aboutURL = null; addon.aboutURL = null;
} }
// Only read the bootstrapped property for extensions
if (addon.type == "extension")
addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
addon.defaultLocale = readLocale(ds, root, true); addon.defaultLocale = readLocale(ds, root, true);
addon.locales = []; addon.locales = [];
@ -1191,7 +1205,7 @@ var XPIProvider = {
WARN("Could not uninstall invalid item from locked install location"); WARN("Could not uninstall invalid item from locked install location");
// If this was an active add-on then we must force a restart // If this was an active add-on then we must force a restart
if (aOldAddon.active) { if (aOldAddon.active) {
if (aOldAddon.type == "bootstrapped") if (aOldAddon.bootstrap)
delete XPIProvider.bootstrappedAddons[aOldAddon.id]; delete XPIProvider.bootstrappedAddons[aOldAddon.id];
else else
return true; return true;
@ -1212,8 +1226,8 @@ var XPIProvider = {
// If the old version was active and wasn't bootstrapped or the new // If the old version was active and wasn't bootstrapped or the new
// version will be active and isn't bootstrapped then we must force a // version will be active and isn't bootstrapped then we must force a
// restart // restart
if ((aOldAddon.active && aOldAddon.type != "bootstrapped") || if ((aOldAddon.active && !aOldAddon.bootstrap) ||
(newAddon.active && newAddon.type != "bootstrapped")) { (newAddon.active && !newAddon.bootstrap)) {
return true; return true;
} }
} }
@ -1250,7 +1264,7 @@ var XPIProvider = {
// If the add-on is bootstrappable and it should be active then // If the add-on is bootstrappable and it should be active then
// mark it as active and add it to the list to be activated. // mark it as active and add it to the list to be activated.
if (aOldAddon.type == "bootstrapped" && !aOldAddon.appDisabled && if (aOldAddon.bootstrap && !aOldAddon.appDisabled &&
!aOldAddon.userDisabled) { !aOldAddon.userDisabled) {
aOldAddon.active = true; aOldAddon.active = true;
XPIDatabase.updateAddonActive(aOldAddon); XPIDatabase.updateAddonActive(aOldAddon);
@ -1281,7 +1295,7 @@ var XPIProvider = {
// If this is a visible add-on and it isn't userDisabled then we // If this is a visible add-on and it isn't userDisabled then we
// may need a restart or to update the bootstrap list. // may need a restart or to update the bootstrap list.
if (aOldAddon.visible && !aOldAddon.userDisabled) { if (aOldAddon.visible && !aOldAddon.userDisabled) {
if (aOldAddon.type == "bootstrapped") { if (aOldAddon.bootstrap) {
// When visible and not userDisabled, active is the opposite of // When visible and not userDisabled, active is the opposite of
// appDisabled. // appDisabled.
aOldAddon.active = !aOldAddon.appDisabled; aOldAddon.active = !aOldAddon.appDisabled;
@ -1330,10 +1344,10 @@ var XPIProvider = {
// If this was an active add-on and bootstrapped we must remove it from // If this was an active add-on and bootstrapped we must remove it from
// the bootstrapped list, otherwise we need to force a restart. // the bootstrapped list, otherwise we need to force a restart.
if (aOldAddon.type != "bootstrapped") if (!aOldAddon.bootstrap)
return true; return true;
delete XPIProvider.bootstrappedAddons[aOldAddon.id]; XPIProvider.unloadBootstrapScope(aOldAddon.id);
} }
return false; return false;
@ -1398,16 +1412,17 @@ var XPIProvider = {
// Update the database. // Update the database.
XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
// Visible bootstrapped add-ons need to be added to the bootstrap list. // Visible bootstrapped add-ons need to have their install method called
if (newAddon.visible) { if (newAddon.visible) {
visibleAddons[newAddon.id] = newAddon; visibleAddons[newAddon.id] = newAddon;
if (newAddon.type != "bootstrapped") if (!newAddon.bootstrap)
return true; return true;
XPIProvider.bootstrappedAddons[newAddon.id] = { let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
version: newAddon.version, dir.persistentDescriptor = aAddonState.descriptor;
descriptor: aAddonState.descriptor XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, dir,
}; "install",
BOOTSTRAP_REASONS.ADDON_INSTALL);
} }
return false; return false;
@ -1566,12 +1581,11 @@ var XPIProvider = {
return true; return true;
} }
let bootstrappedAddons = this.bootstrappedAddons; for (let id in this.bootstrappedAddons) {
this.bootstrappedAddons = {};
for (let id in bootstrappedAddons) {
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
dir.persistentDescriptor = bootstrappedAddons[id].descriptor; dir.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
this.activateAddon(id, bootstrappedAddons[id].version, dir, true, false); this.callBootstrapMethod(id, this.bootstrappedAddons[id].version, dir,
"startup", BOOTSTRAP_REASONS.APP_STARTUP);
} }
// Let these shutdown a little earlier when they still have access to most // Let these shutdown a little earlier when they still have access to most
@ -1580,8 +1594,13 @@ var XPIProvider = {
observe: function(aSubject, aTopic, aData) { observe: function(aSubject, aTopic, aData) {
Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
JSON.stringify(XPIProvider.bootstrappedAddons)); JSON.stringify(XPIProvider.bootstrappedAddons));
for (let id in XPIProvider.bootstrappedAddons) for (let id in XPIProvider.bootstrappedAddons) {
XPIProvider.deactivateAddon(id, true, false); let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
dir.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor;
XPIProvider.callBootstrapMethod(id, XPIProvider.bootstrappedAddons[id].version,
dir, "shutdown",
BOOTSTRAP_REASONS.APP_SHUTDOWN);
}
Services.obs.removeObserver(this, "quit-application-granted"); Services.obs.removeObserver(this, "quit-application-granted");
} }
}, "quit-application-granted", false); }, "quit-application-granted", false);
@ -1872,7 +1891,7 @@ var XPIProvider = {
return aAddon.internalName != return aAddon.internalName !=
Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
return aAddon.type != "bootstrapped"; return !aAddon.bootstrap;
}, },
/** /**
@ -1891,7 +1910,7 @@ var XPIProvider = {
return this.selectedSkin != return this.selectedSkin !=
Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
return aAddon.type != "bootstrapped"; return !aAddon.bootstrap;
}, },
/** /**
@ -1907,7 +1926,7 @@ var XPIProvider = {
return aAddon.internalName == return aAddon.internalName ==
Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
return aAddon.type != "bootstrapped"; return !aAddon.bootstrap;
}, },
/** /**
@ -1923,85 +1942,53 @@ var XPIProvider = {
return aAddon.internalName == return aAddon.internalName ==
Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
return aAddon.type != "bootstrapped"; return !aAddon.bootstrap;
}, },
/** /**
* Calls a method in a bootstrap.js loaded scope logging any exceptions thrown. * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
* values as constants in the scope. This will also add information about the
* add-on to the bootstrappedAddons dictionary and notify the crash reporter
* that new add-ons have been loaded.
* *
* @param aId * @param aId
* The ID of the add-on being bootstrapped * The add-on's ID
* @param aScope
* The loaded JS scope to call into
* @param aMethods
* An array of methods. The first method in the array that exists in
* the scope will be called
*/
callBootstrapMethod: function XPI_callBootstrapMethod(aId, aScope, aMethods) {
for (let i = 0; i < aMethods.length; i++) {
if (aMethods[i] in aScope) {
LOG("Calling bootstrap method " + aMethods[i] + " on " + aId);
try {
aScope[aMethods[i]](aId);
}
catch (e) {
WARN("Exception running bootstrap method " + aMethods[i] + " on " +
aId + ": " + e);
}
return;
}
}
},
/**
* Activates a bootstrapped add-on by loading its JS scope and calling the
* appropriate method on it. Adds the add-on and its scope to the
* bootstrapScopes and bootstrappedAddons dictionaries.
*
* @param aId
* The ID of the add-on being activated
* @param aVersion
* The version of the add-on being activated
* @param aDir * @param aDir
* The directory containing the add-on * The nsILocalFile for the directory containing the add-on
* @param aStartup * @param aVersion
* true if the add-on is being activated during startup * The add-on's version
* @param aInstall * @return a JavaScript scope
* true if the add-on is being activated during installation
*/ */
activateAddon: function XPI_activateAddon(aId, aVersion, aDir, aStartup, aInstall) { loadBootstrapScope: function XPI_loadBootstrapScope(aId, aDir, aVersion) {
let methods = ["enable"]; LOG("Loading bootstrap scope from " + aDir.path);
if (aStartup) // Mark the add-on as active for the crash reporter before loading
methods.unshift("startup"); this.bootstrappedAddons[aId] = {
if (aInstall) version: aVersion,
methods.unshift("install"); descriptor: aDir.persistentDescriptor
};
this.addAddonsToCrashReporter();
let principal = Cc["@mozilla.org/systemprincipal;1"].
createInstance(Ci.nsIPrincipal);
this.bootstrapScopes[aId] = new Components.utils.Sandbox(principal);
let bootstrap = aDir.clone(); let bootstrap = aDir.clone();
bootstrap.append("bootstrap.js"); bootstrap.append("bootstrap.js");
if (bootstrap.exists()) { if (bootstrap.exists()) {
let uri = Services.io.newFileURI(bootstrap); let uri = Services.io.newFileURI(bootstrap);
let principal = Cc["@mozilla.org/systemprincipal;1"].
createInstance(Ci.nsIPrincipal);
let scope = new Components.utils.Sandbox(principal);
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
createInstance(Ci.mozIJSSubScriptLoader); createInstance(Ci.mozIJSSubScriptLoader);
// Mark the add-on as active for the crash reporter before loading
this.bootstrappedAddons[aId] = {
version: aVersion,
descriptor: aDir.persistentDescriptor
};
this.bootstrapScopes[aId] = scope;
this.addAddonsToCrashReporter();
try { try {
loader.loadSubScript(uri.spec, scope); loader.loadSubScript(uri.spec, this.bootstrapScopes[aId]);
} }
catch (e) { catch (e) {
WARN("Error loading bootstrap.js for " + aId + ": " + e); WARN("Error loading bootstrap.js for " + aId + ": " + e);
} }
this.callBootstrapMethod(aId, scope, methods); // Copy the reason values from the global object into the bootstrap scope.
for (let name in BOOTSTRAP_REASONS)
this.bootstrapScopes[aId][name] = BOOTSTRAP_REASONS[name];
} }
else { else {
WARN("Bootstrap missing for " + aId); WARN("Bootstrap missing for " + aId);
@ -2009,34 +1996,58 @@ var XPIProvider = {
}, },
/** /**
* Dectivates a bootstrapped add-on by by calling the appropriate method on * Unloads a bootstrap scope by dropping all references to it and then
* the cached JS scope. Removes the add-on and its scope from the * updating the list of active add-ons with the crash reporter.
* bootstrapScopes and bootstrappedAddons dictionaries.
* *
* @param aId * @param aId
* The ID of the add-on being deactivated * The add-on's ID
* @param aShutdown
* true if the add-on is being deactivated during shutdown
* @param aUninstall
* true if the add-on is being deactivated during uninstallation
*/ */
deactivateAddon: function XPI_deactivateAddon(aId, aShutdown, aUninstall) { unloadBootstrapScope: function XPI_unloadBootstrapScope(aId) {
if (!(aId in this.bootstrappedAddons)) { delete this.bootstrapScopes[aId];
ERROR("Attempted to deactivate an add-on that was never activated"); delete this.bootstrappedAddons[aId];
this.addAddonsToCrashReporter();
},
/**
* Calls a bootstrap method for an add-on.
*
* @param aId
* The ID of the add-on
* @param aVersion
* The version of the add-on
* @param aDir
* The nsILocalFile for the directory containing the add-on
* @param aMethod
* The name of the bootstrap method to call
* @param aReason
* The reason flag to pass to the bootstrap's startup method
*/
callBootstrapMethod: function XPI_callBootstrapMethod(aId, aVersion, aDir,
aMethod, aReason) {
// Load the scope if it hasn't already been loaded
if (!(aId in this.bootstrapScopes))
this.loadBootstrapScope(aId, aDir, aVersion);
if (!(aMethod in this.bootstrapScopes[aId])) {
WARN("Add-on " + aId + " is missing bootstrap method " + aMethod);
return; return;
} }
let scope = this.bootstrapScopes[aId];
delete this.bootstrappedAddons[aId];
delete this.bootstrapScopes[aId];
let methods = ["disable"]; let params = {
if (aShutdown) id: aId,
methods.unshift("shutdown"); version: aVersion,
if (aUninstall) installPath: aDir.clone()
methods.unshift("uninstall"); };
this.callBootstrapMethod(aId, scope, methods);
this.addAddonsToCrashReporter(); LOG("Calling bootstrap method " + aMethod + " on " + aId + " version " +
aVersion);
try {
this.bootstrapScopes[aId][aMethod](params, aReason);
}
catch (e) {
WARN("Exception running bootstrap method " + aMethods + " on " +
aId + ": " + e);
}
}, },
/** /**
@ -2113,14 +2124,19 @@ var XPIProvider = {
aAddon.active = !isDisabled; aAddon.active = !isDisabled;
XPIDatabase.updateAddonActive(aAddon); XPIDatabase.updateAddonActive(aAddon);
if (isDisabled) { if (isDisabled) {
if (aAddon.type == "bootstrapped") if (aAddon.bootstrap) {
this.deactivateAddon(aAddon.id, false, false); let dir = aAddon._installLocation.getLocationForID(aAddon.id);
this.callBootstrapMethod(aAddon.id, aAddon.version, dir, "shutdown",
BOOTSTRAP_REASONS.ADDON_DISABLE);
this.unloadBootstrapScope(aAddon.id);
}
AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
} }
else { else {
if (aAddon.type == "bootstrapped") { if (aAddon.bootstrap) {
let dir = aAddon._installLocation.getLocationForID(aAddon.id); let dir = aAddon._installLocation.getLocationForID(aAddon.id);
this.activateAddon(aAddon.id, aAddon.version, dir, false, false); this.callBootstrapMethod(aAddon.id, aAddon.version, dir, "startup",
BOOTSTRAP_REASONS.ADDON_ENABLE);
} }
AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
} }
@ -2174,8 +2190,16 @@ var XPIProvider = {
requiresRestart); requiresRestart);
if (!requiresRestart) { if (!requiresRestart) {
if (aAddon.type == "bootstrapped") if (aAddon.bootstrap) {
this.deactivateAddon(aAddon.id, false, true); let dir = aAddon._installLocation.getLocationForID(aAddon.id);
if (aAddon.active) {
this.callBootstrapMethod(aAddon.id, aAddon.version, dir, "shutdown",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
}
this.callBootstrapMethod(aAddon.id, aAddon.version, dir, "uninstall",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
this.unloadBootstrapScope(aAddon.id);
}
aAddon._installLocation.uninstallAddon(aAddon.id); aAddon._installLocation.uninstallAddon(aAddon.id);
XPIDatabase.removeAddonMetadata(aAddon); XPIDatabase.removeAddonMetadata(aAddon);
AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
@ -2225,7 +2249,7 @@ const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, "
"updateURL, updateKey, optionsURL, aboutURL, iconURL, " + "updateURL, updateKey, optionsURL, aboutURL, iconURL, " +
"defaultLocale, visible, active, userDisabled, appDisabled, " + "defaultLocale, visible, active, userDisabled, appDisabled, " +
"pendingUninstall, descriptor, installDate, updateDate, " + "pendingUninstall, descriptor, installDate, updateDate, " +
"applyBackgroundUpdates"; "applyBackgroundUpdates, bootstrap";
// A helper function to simply log any errors that occur during async statements. // A helper function to simply log any errors that occur during async statements.
function asyncErrorLogger(aError) { function asyncErrorLogger(aError) {
@ -2261,7 +2285,7 @@ var XPIDatabase = {
":updateKey, :optionsURL, :aboutURL, :iconURL, " + ":updateKey, :optionsURL, :aboutURL, :iconURL, " +
":locale, :visible, :active, :userDisabled," + ":locale, :visible, :active, :userDisabled," +
" :appDisabled, 0, :descriptor, :installDate, " + " :appDisabled, 0, :descriptor, :installDate, " +
":updateDate, :applyBackgroundUpdates)", ":updateDate, :applyBackgroundUpdates, :bootstrap)",
addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " + addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
"(:internal_id, :name, :locale)", "(:internal_id, :name, :locale)",
addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " + addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
@ -2278,7 +2302,7 @@ var XPIDatabase = {
"internal_id=:internal_id", "internal_id=:internal_id",
getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " + getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
"type<>'theme' AND type<>'bootstrapped'", "type<>'theme' AND bootstrap=0",
getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
"internalName=:internalName AND type='theme'", "internalName=:internalName AND type='theme'",
@ -2299,7 +2323,8 @@ var XPIDatabase = {
makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id", makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id", removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
// Equates to active = visible && !userDisabled && !appDisabled && !pendingUninstall // Equates to active = visible && !userDisabled && !appDisabled &&
// !pendingUninstall
setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " + setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
"1 - appDisabled, 1 - pendingUninstall)", "1 - appDisabled, 1 - pendingUninstall)",
setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " + setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
@ -2369,9 +2394,11 @@ var XPIDatabase = {
if (disabled == "true" || disabled == "needs-disable") if (disabled == "true" || disabled == "needs-disable")
migrateData[location][id].userDisabled = true; migrateData[location][id].userDisabled = true;
let targetApps = ds.GetTargets(source, EM_R("targetApplication"), true); let targetApps = ds.GetTargets(source, EM_R("targetApplication"),
true);
while (targetApps.hasMoreElements()) { while (targetApps.hasMoreElements()) {
let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource); let targetApp = targetApps.getNext()
.QueryInterface(Ci.nsIRDFResource);
let appInfo = { let appInfo = {
id: getRDFProperty(ds, targetApp, "id"), id: getRDFProperty(ds, targetApp, "id"),
minVersion: getRDFProperty(ds, targetApp, "minVersion"), minVersion: getRDFProperty(ds, targetApp, "minVersion"),
@ -2482,7 +2509,7 @@ var XPIDatabase = {
"pendingUninstall INTEGER, descriptor TEXT, " + "pendingUninstall INTEGER, descriptor TEXT, " +
"installDate INTEGER, updateDate INTEGER, " + "installDate INTEGER, updateDate INTEGER, " +
"applyBackgroundUpdates INTEGER, " + "applyBackgroundUpdates INTEGER, " +
"UNIQUE (id, location)"); "bootstrap INTEGER, UNIQUE (id, location)");
this.connection.createTable("targetApplication", this.connection.createTable("targetApplication",
"addon_internal_id INTEGER, " + "addon_internal_id INTEGER, " +
"id TEXT, minVersion TEXT, maxVersion TEXT, " + "id TEXT, minVersion TEXT, maxVersion TEXT, " +
@ -2618,7 +2645,8 @@ var XPIDatabase = {
addon.installDate = aRow.installDate; addon.installDate = aRow.installDate;
addon.updateDate = aRow.updateDate; addon.updateDate = aRow.updateDate;
["visible", "active", "userDisabled", "appDisabled", ["visible", "active", "userDisabled", "appDisabled",
"pendingUninstall", "applyBackgroundUpdates"].forEach(function(aProp) { "pendingUninstall", "applyBackgroundUpdates",
"bootstrap"].forEach(function(aProp) {
addon[aProp] = aRow[aProp] != 0; addon[aProp] = aRow[aProp] != 0;
}); });
this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon); this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon);
@ -2768,7 +2796,8 @@ var XPIDatabase = {
addon.installDate = aRow.getResultByName("installDate"); addon.installDate = aRow.getResultByName("installDate");
addon.updateDate = aRow.getResultByName("updateDate"); addon.updateDate = aRow.getResultByName("updateDate");
["visible", "active", "userDisabled", "appDisabled", ["visible", "active", "userDisabled", "appDisabled",
"pendingUninstall", "applyBackgroundUpdates"].forEach(function(aProp) { "pendingUninstall", "applyBackgroundUpdates",
"bootstrap"].forEach(function(aProp) {
addon[aProp] = aRow.getResultByName(aProp) != 0; addon[aProp] = aRow.getResultByName(aProp) != 0;
}); });
this.addonCache[internal_id] = Components.utils.getWeakReference(addon); this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
@ -3042,8 +3071,8 @@ var XPIDatabase = {
stmt.params.installDate = aAddon.installDate; stmt.params.installDate = aAddon.installDate;
stmt.params.updateDate = aAddon.updateDate; stmt.params.updateDate = aAddon.updateDate;
copyProperties(aAddon, PROP_METADATA, stmt.params); copyProperties(aAddon, PROP_METADATA, stmt.params);
["visible", "userDisabled", "appDisabled", ["visible", "userDisabled", "appDisabled", "applyBackgroundUpdates",
"applyBackgroundUpdates"].forEach(function(aProp) { "bootstrap"].forEach(function(aProp) {
stmt.params[aProp] = aAddon[aProp] ? 1 : 0; stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
}); });
stmt.params.active = (aAddon.visible && !aAddon.userDisabled && stmt.params.active = (aAddon.visible && !aAddon.userDisabled &&
@ -3842,19 +3871,34 @@ AddonInstall.prototype = {
// See bug 553015. // See bug 553015.
// Deactivate and remove the old add-on as necessary // Deactivate and remove the old add-on as necessary
let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
if (this.existingAddon) { if (this.existingAddon) {
if (this.existingAddon.active) { if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
if (this.existingAddon.type == "bootstrapped") reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
XPIProvider.deactivateAddon(this.existingAddon.id, false, else
isUpgrade); reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
// If this is an upgrade its metadata will be removed below
if (!isUpgrade) { if (this.existingAddon.bootstrap) {
this.existingAddon.active = false; let dir = this.existingAddon._installLocation
XPIDatabase.updateAddonActive(this.existingAddon); .getLocationForID(this.existingAddon.id);
if (this.existingAddon.active) {
XPIProvider.callBootstrapMethod(this.existingAddon.id,
this.existingAddon.version,
dir, "shutdown", reason);
} }
XPIProvider.callBootstrapMethod(this.existingAddon.id,
this.existingAddon.version,
dir, "uninstall", reason);
XPIProvider.unloadBootstrapScope(this.existingAddon.id);
} }
if (isUpgrade)
if (isUpgrade) {
this.installLocation.uninstallAddon(this.existingAddon.id); this.installLocation.uninstallAddon(this.existingAddon.id);
}
else if (this.existingAddon.active) {
this.existingAddon.active = false;
XPIDatabase.updateAddonActive(this.existingAddon);
}
} }
// Install the new add-on into its final directory // Install the new add-on into its final directory
@ -3878,8 +3922,17 @@ AddonInstall.prototype = {
XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name, XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name,
function(a) { function(a) {
self.addon = a; self.addon = a;
if (self.addon.active && self.addon.type == "bootstrapped") if (self.addon.bootstrap) {
XPIProvider.activateAddon(self.addon.id, self.addon.version, dir, false, true); XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
dir, "install", reason);
if (self.addon.active) {
XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
dir, "startup", reason);
}
else {
this.unloadBootstrapScope(self.addon.id);
}
}
AddonManagerPrivate.callAddonListeners("onInstalled", AddonManagerPrivate.callAddonListeners("onInstalled",
createWrapper(self.addon)); createWrapper(self.addon));

Просмотреть файл

@ -1,9 +1,19 @@
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
function enable() { function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.version", 1); Services.prefs.setIntPref("bootstraptest.installed_version", 1);
} }
function disable() { function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.version", 0); Services.prefs.setIntPref("bootstraptest.active_version", 1);
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);
} }

Просмотреть файл

@ -6,7 +6,7 @@
<Description about="urn:mozilla:install-manifest"> <Description about="urn:mozilla:install-manifest">
<em:id>bootstrap1@tests.mozilla.org</em:id> <em:id>bootstrap1@tests.mozilla.org</em:id>
<em:version>1.0</em:version> <em:version>1.0</em:version>
<em:type>64</em:type> <em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData --> <!-- Front End MetaData -->
<em:name>Test Bootstrap 1</em:name> <em:name>Test Bootstrap 1</em:name>

Просмотреть файл

@ -1,9 +1,19 @@
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
function enable() { function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.version", 2); Services.prefs.setIntPref("bootstraptest.installed_version", 2);
} }
function disable() { function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.version", 0); 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);
} }

Просмотреть файл

@ -6,7 +6,7 @@
<Description about="urn:mozilla:install-manifest"> <Description about="urn:mozilla:install-manifest">
<em:id>bootstrap1@tests.mozilla.org</em:id> <em:id>bootstrap1@tests.mozilla.org</em:id>
<em:version>2.0</em:version> <em:version>2.0</em:version>
<em:type>64</em:type> <em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData --> <!-- Front End MetaData -->
<em:name>Test Bootstrap 1</em:name> <em:name>Test Bootstrap 1</em:name>

Просмотреть файл

@ -2,6 +2,15 @@
* http://creativecommons.org/publicdomain/zero/1.0/ * http://creativecommons.org/publicdomain/zero/1.0/
*/ */
const APP_STARTUP = 1;
const APP_SHUTDOWN = 2;
const ADDON_ENABLE = 3;
const ADDON_DISABLE = 4;
const ADDON_INSTALL = 5;
const ADDON_UNINSTALL = 6;
const ADDON_UPGRADE = 7;
const ADDON_DOWNGRADE = 8;
// This verifies that bootstrappable add-ons can be used with restarts. // This verifies that bootstrappable add-ons can be used with restarts.
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
@ -10,8 +19,20 @@ Services.prefs.setIntPref("bootstraptest.version", 0);
const profileDir = gProfD.clone(); const profileDir = gProfD.clone();
profileDir.append("extensions"); profileDir.append("extensions");
function getActivatedVersion() { function getActiveVersion() {
return Services.prefs.getIntPref("bootstraptest.version"); return Services.prefs.getIntPref("bootstraptest.active_version");
}
function getInstalledVersion() {
return Services.prefs.getIntPref("bootstraptest.installed_version");
}
function getStartupReason() {
return Services.prefs.getIntPref("bootstraptest.startup_reason");
}
function getShutdownReason() {
return Services.prefs.getIntPref("bootstraptest.shutdown_reason");
} }
function run_test() { function run_test() {
@ -33,7 +54,7 @@ function run_test_1() {
ensure_test_completed(); ensure_test_completed();
do_check_neq(install, null); do_check_neq(install, null);
do_check_eq(install.type, "bootstrapped"); do_check_eq(install.type, "extension");
do_check_eq(install.version, "1.0"); do_check_eq(install.version, "1.0");
do_check_eq(install.name, "Test Bootstrap 1"); do_check_eq(install.name, "Test Bootstrap 1");
do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
@ -62,7 +83,9 @@ function check_test_1() {
do_check_false(b1.appDisabled); do_check_false(b1.appDisabled);
do_check_false(b1.userDisabled); do_check_false(b1.userDisabled);
do_check_true(b1.isActive); do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 1); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 1);
do_check_eq(getStartupReason(), ADDON_INSTALL);
do_check_true(b1.hasResource("install.rdf")); do_check_true(b1.hasResource("install.rdf"));
do_check_true(b1.hasResource("bootstrap.js")); do_check_true(b1.hasResource("bootstrap.js"));
do_check_false(b1.hasResource("foo.bar")); do_check_false(b1.hasResource("foo.bar"));
@ -100,7 +123,9 @@ function run_test_2() {
do_check_false(b1.appDisabled); do_check_false(b1.appDisabled);
do_check_true(b1.userDisabled); do_check_true(b1.userDisabled);
do_check_false(b1.isActive); do_check_false(b1.isActive);
do_check_eq(getActivatedVersion(), 0); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 0);
do_check_eq(getShutdownReason(), ADDON_DISABLE);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(newb1) { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(newb1) {
@ -118,9 +143,13 @@ function run_test_2() {
// Test that restarting doesn't accidentally re-enable // Test that restarting doesn't accidentally re-enable
function run_test_3() { function run_test_3() {
shutdownManager(); shutdownManager();
do_check_eq(getActivatedVersion(), 0); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 0);
do_check_eq(getShutdownReason(), ADDON_DISABLE);
startupManager(0, false); startupManager(0, false);
do_check_eq(getActivatedVersion(), 0); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 0);
do_check_eq(getShutdownReason(), ADDON_DISABLE);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
@ -152,7 +181,9 @@ function run_test_4() {
do_check_false(b1.appDisabled); do_check_false(b1.appDisabled);
do_check_false(b1.userDisabled); do_check_false(b1.userDisabled);
do_check_true(b1.isActive); do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 1); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 1);
do_check_eq(getStartupReason(), ADDON_ENABLE);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(newb1) { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(newb1) {
@ -170,10 +201,14 @@ function run_test_4() {
// Tests that a restart shuts down and restarts the add-on // Tests that a restart shuts down and restarts the add-on
function run_test_5() { function run_test_5() {
shutdownManager(); shutdownManager();
do_check_eq(getActivatedVersion(), 0); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 0);
do_check_eq(getShutdownReason(), APP_SHUTDOWN);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
startupManager(0, false); startupManager(0, false);
do_check_eq(getActivatedVersion(), 1); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 1);
do_check_eq(getStartupReason(), APP_STARTUP);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
@ -198,7 +233,7 @@ function run_test_6() {
ensure_test_completed(); ensure_test_completed();
do_check_neq(install, null); do_check_neq(install, null);
do_check_eq(install.type, "bootstrapped"); do_check_eq(install.type, "extension");
do_check_eq(install.version, "2.0"); do_check_eq(install.version, "2.0");
do_check_eq(install.name, "Test Bootstrap 1"); do_check_eq(install.name, "Test Bootstrap 1");
do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
@ -223,7 +258,10 @@ function check_test_6() {
do_check_false(b1.appDisabled); do_check_false(b1.appDisabled);
do_check_false(b1.userDisabled); do_check_false(b1.userDisabled);
do_check_true(b1.isActive); do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 2); do_check_eq(getInstalledVersion(), 2);
do_check_eq(getActiveVersion(), 2);
do_check_eq(getStartupReason(), ADDON_UPGRADE);
do_check_eq(getShutdownReason(), ADDON_UPGRADE);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0"); do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
@ -249,7 +287,9 @@ function run_test_7() {
function check_test_7() { function check_test_7() {
ensure_test_completed(); ensure_test_completed();
do_check_eq(getActivatedVersion(), 0); do_check_eq(getInstalledVersion(), 0);
do_check_eq(getActiveVersion(), 0);
do_check_eq(getShutdownReason(), ADDON_UNINSTALL);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0"); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
@ -291,7 +331,9 @@ function run_test_8() {
do_check_false(b1.appDisabled); do_check_false(b1.appDisabled);
do_check_false(b1.userDisabled); do_check_false(b1.userDisabled);
do_check_true(b1.isActive); do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 1); do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 1);
do_check_eq(getStartupReason(), APP_STARTUP);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
run_test_9(); run_test_9();
@ -311,6 +353,133 @@ function run_test_9() {
do_check_eq(b1, null); do_check_eq(b1, null);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
do_test_finished(); run_test_10();
}); });
} }
// Tests that installing a downgrade sends the right reason
function run_test_10() {
prepare_test({ }, [
"onNewInstall"
]);
AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_2"), function(install) {
ensure_test_completed();
do_check_neq(install, null);
do_check_eq(install.type, "extension");
do_check_eq(install.version, "2.0");
do_check_eq(install.name, "Test Bootstrap 1");
do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
do_check_true(install.addon.hasResource("install.rdf"));
do_check_true(install.addon.hasResource("bootstrap.js"));
do_check_false(install.addon.hasResource("foo.bar"));
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
prepare_test({
"bootstrap1@tests.mozilla.org": [
["onInstalling", false],
"onInstalled"
]
}, [
"onInstallStarted",
"onInstallEnded",
], check_test_10_pt1);
install.install();
});
}
function check_test_10_pt1() {
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
do_check_neq(b1, null);
do_check_eq(b1.version, "2.0");
do_check_false(b1.appDisabled);
do_check_false(b1.userDisabled);
do_check_true(b1.isActive);
do_check_eq(getInstalledVersion(), 2);
do_check_eq(getActiveVersion(), 2);
do_check_eq(getStartupReason(), ADDON_INSTALL);
do_check_true(b1.hasResource("install.rdf"));
do_check_true(b1.hasResource("bootstrap.js"));
do_check_false(b1.hasResource("foo.bar"));
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
prepare_test({ }, [
"onNewInstall"
]);
AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), function(install) {
ensure_test_completed();
do_check_neq(install, null);
do_check_eq(install.type, "extension");
do_check_eq(install.version, "1.0");
do_check_eq(install.name, "Test Bootstrap 1");
do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
prepare_test({
"bootstrap1@tests.mozilla.org": [
["onInstalling", false],
"onInstalled"
]
}, [
"onInstallStarted",
"onInstallEnded",
], check_test_10_pt2);
install.install();
});
});
}
function check_test_10_pt2() {
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
do_check_neq(b1, null);
do_check_eq(b1.version, "1.0");
do_check_false(b1.appDisabled);
do_check_false(b1.userDisabled);
do_check_true(b1.isActive);
do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 1);
do_check_eq(getStartupReason(), ADDON_DOWNGRADE);
do_check_eq(getShutdownReason(), ADDON_DOWNGRADE);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
run_test_11();
});
}
// Tests that uninstalling a disabled add-on still calls the uninstall method
function run_test_11() {
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
prepare_test({
"bootstrap1@tests.mozilla.org": [
["onDisabling", false],
"onDisabled",
["onUninstalling", false],
"onUninstalled"
]
});
b1.userDisabled = true;
do_check_eq(getInstalledVersion(), 1);
do_check_eq(getActiveVersion(), 0);
do_check_eq(getShutdownReason(), ADDON_DISABLE);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
b1.uninstall();
check_test_11();
});
}
function check_test_11() {
ensure_test_completed();
do_check_eq(getInstalledVersion(), 0);
do_check_eq(getActiveVersion(), 0);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
do_test_finished();
}