Bug 853388: Save and load XPIProvider state to/from a JSON file; r=unfocused

This commit is contained in:
Irving Reid 2013-08-08 15:56:22 -04:00
parent 9cd41247c5
commit c8f557cdc0
20 changed files with 686 additions and 1287 deletions

View File

@ -78,7 +78,7 @@ const DIR_STAGE = "staged";
const DIR_XPI_STAGE = "staged-xpis";
const DIR_TRASH = "trash";
const FILE_DATABASE = "extensions.sqlite";
const FILE_DATABASE = "extensions.json";
const FILE_OLD_CACHE = "extensions.cache";
const FILE_INSTALL_MANIFEST = "install.rdf";
const FILE_XPI_ADDONS_LIST = "extensions.ini";
@ -120,7 +120,12 @@ const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
// or calculated
const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
"sourceURI", "applyBackgroundUpdates",
"releaseNotesURI", "isForeignInstall", "syncGUID"];
"releaseNotesURI", "foreignInstall", "syncGUID"];
// Properties to cache and reload when an addon installation is pending
const PENDING_INSTALL_METADATA =
["syncGUID", "targetApplications", "userDisabled", "softDisabled",
"existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
"updateDate", "applyBackgroundUpdates", "compatibilityOverrides"];
// Note: When adding/changing/removing items here, remember to change the
// DB schema version to ensure changes are picked up ASAP.
@ -169,12 +174,15 @@ var gGlobalScope = this;
var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
["LOG", "WARN", "ERROR"].forEach(function(aName) {
this.__defineGetter__(aName, function logFuncGetter() {
Components.utils.import("resource://gre/modules/AddonLogging.jsm");
Object.defineProperty(this, aName, {
get: function logFuncGetter() {
Components.utils.import("resource://gre/modules/AddonLogging.jsm");
LogManager.getLogger("addons.xpi", this);
return this[aName];
})
LogManager.getLogger("addons.xpi", this);
return this[aName];
},
configurable: true
});
}, this);
@ -197,9 +205,12 @@ function loadLazyObjects() {
}
for (let name of LAZY_OBJECTS) {
gGlobalScope.__defineGetter__(name, function lazyObjectGetter() {
let objs = loadLazyObjects();
return objs[name];
Object.defineProperty(gGlobalScope, name, {
get: function lazyObjectGetter() {
let objs = loadLazyObjects();
return objs[name];
},
configurable: true
});
}
@ -584,10 +595,13 @@ function isAddonDisabled(aAddon) {
return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled;
}
this.__defineGetter__("gRDF", function gRDFGetter() {
delete this.gRDF;
return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
getService(Ci.nsIRDFService);
Object.defineProperty(this, "gRDF", {
get: function gRDFGetter() {
delete this.gRDF;
return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
getService(Ci.nsIRDFService);
},
configurable: true
});
function EM_R(aProperty) {
@ -694,8 +708,11 @@ function loadManifestFromRDF(aUri, aStream) {
});
PROP_LOCALE_MULTI.forEach(function(aProp) {
locale[aProp] = getPropertyArray(aDs, aSource,
aProp.substring(0, aProp.length - 1));
// Don't store empty arrays
let props = getPropertyArray(aDs, aSource,
aProp.substring(0, aProp.length - 1));
if (props.length > 0)
locale[aProp] = props;
});
return locale;
@ -2518,31 +2535,33 @@ var XPIProvider = {
newAddon.visible = !(newAddon.id in visibleAddons);
// Update the database
XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor);
if (newAddon.visible) {
visibleAddons[newAddon.id] = newAddon;
let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
aAddonState.descriptor);
if (newDBAddon.visible) {
visibleAddons[newDBAddon.id] = newDBAddon;
// Remember add-ons that were changed during startup
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
newAddon.id);
newDBAddon.id);
// If this was the active theme and it is now disabled then enable the
// default theme
if (aOldAddon.active && isAddonDisabled(newAddon))
if (aOldAddon.active && isAddonDisabled(newDBAddon))
XPIProvider.enableDefaultTheme();
// If the new add-on is bootstrapped and active then call its install method
if (newAddon.active && newAddon.bootstrap) {
if (newDBAddon.active && newDBAddon.bootstrap) {
// Startup cache must be flushed before calling the bootstrap script
flushStartupCache();
let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ?
let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.persistentDescriptor = aAddonState.descriptor;
XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file,
"install", installReason, { oldVersion: aOldAddon.version });
XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version,
newDBAddon.type, file, "install",
installReason, { oldVersion: aOldAddon.version });
return false;
}
@ -2569,7 +2588,7 @@ var XPIProvider = {
function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
aOldAddon._descriptor = aAddonState.descriptor;
aOldAddon.descriptor = aAddonState.descriptor;
aOldAddon.visible = !(aOldAddon.id in visibleAddons);
// Update the database
@ -2630,8 +2649,7 @@ var XPIProvider = {
// If it should be active then mark it as active otherwise unload
// its scope
if (!isAddonDisabled(aOldAddon)) {
aOldAddon.active = true;
XPIDatabase.updateAddonActive(aOldAddon);
XPIDatabase.updateAddonActive(aOldAddon, true);
}
else {
XPIProvider.unloadBootstrapScope(newAddon.id);
@ -2690,8 +2708,7 @@ var XPIProvider = {
AddonManagerPrivate.addStartupChange(change, aOldAddon.id);
if (aOldAddon.bootstrap) {
// Update the add-ons active state
aOldAddon.active = !isDisabled;
XPIDatabase.updateAddonActive(aOldAddon);
XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
}
else {
changed = true;
@ -2713,17 +2730,15 @@ var XPIProvider = {
/**
* Called when an add-on has been removed.
*
* @param aInstallLocation
* The install location containing the add-on
* @param aOldAddon
* The AddonInternal as it appeared the last time the application
* ran
* @return a boolean indicating if flushing caches is required to complete
* changing this add-on
*/
function removeMetadata(aInstallLocation, aOldAddon) {
function removeMetadata(aOldAddon) {
// This add-on has disappeared
LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation);
LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
XPIDatabase.removeAddonMetadata(aOldAddon);
// Remember add-ons that were uninstalled during startup
@ -2886,9 +2901,10 @@ var XPIProvider = {
newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon))
}
let newDBAddon = null;
try {
// Update the database.
XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
}
catch (e) {
// Failing to write the add-on into the database is non-fatal, the
@ -2899,36 +2915,36 @@ var XPIProvider = {
return false;
}
if (newAddon.visible) {
if (newDBAddon.visible) {
// Remember add-ons that were first detected during startup.
if (isDetectedInstall) {
// If a copy from a higher priority location was removed then this
// add-on has changed
if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED)
.indexOf(newAddon.id) != -1) {
.indexOf(newDBAddon.id) != -1) {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
newAddon.id);
newDBAddon.id);
}
else {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
newAddon.id);
newDBAddon.id);
}
}
// Note if any visible add-on is not in the application install location
if (newAddon._installLocation.name != KEY_APP_GLOBAL)
if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
XPIProvider.allAppGlobal = false;
visibleAddons[newAddon.id] = newAddon;
visibleAddons[newDBAddon.id] = newDBAddon;
let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
let extraParams = {};
// If we're hiding a bootstrapped add-on then call its uninstall method
if (newAddon.id in oldBootstrappedAddons) {
let oldBootstrap = oldBootstrappedAddons[newAddon.id];
if (newDBAddon.id in oldBootstrappedAddons) {
let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
extraParams.oldVersion = oldBootstrap.version;
XPIProvider.bootstrappedAddons[newAddon.id] = oldBootstrap;
XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
// If the old version is the same as the new version, or we're
// recovering from a corrupt DB, don't call uninstall and install
@ -2936,7 +2952,7 @@ var XPIProvider = {
if (sameVersion || !isNewInstall)
return false;
installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ?
installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
@ -2944,27 +2960,27 @@ var XPIProvider = {
createInstance(Ci.nsIFile);
oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version,
XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version,
oldBootstrap.type, oldAddonFile, "uninstall",
installReason, { newVersion: newAddon.version });
XPIProvider.unloadBootstrapScope(newAddon.id);
installReason, { newVersion: newDBAddon.version });
XPIProvider.unloadBootstrapScope(newDBAddon.id);
// If the new add-on is bootstrapped then we must flush the caches
// before calling the new bootstrap script
if (newAddon.bootstrap)
if (newDBAddon.bootstrap)
flushStartupCache();
}
if (!newAddon.bootstrap)
if (!newDBAddon.bootstrap)
return true;
// Visible bootstrapped add-ons need to have their install method called
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.persistentDescriptor = aAddonState.descriptor;
XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file,
XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file,
"install", installReason, extraParams);
if (!newAddon.active)
XPIProvider.unloadBootstrapScope(newAddon.id);
if (!newDBAddon.active)
XPIProvider.unloadBootstrapScope(newDBAddon.id);
}
return false;
@ -3034,7 +3050,7 @@ var XPIProvider = {
changed = updateMetadata(installLocation, aOldAddon, addonState) ||
changed;
}
else if (aOldAddon._descriptor != addonState.descriptor) {
else if (aOldAddon.descriptor != addonState.descriptor) {
changed = updateDescriptor(installLocation, aOldAddon, addonState) ||
changed;
}
@ -3047,7 +3063,7 @@ var XPIProvider = {
XPIProvider.allAppGlobal = false;
}
else {
changed = removeMetadata(installLocation.name, aOldAddon) || changed;
changed = removeMetadata(aOldAddon) || changed;
}
}, this);
}
@ -3071,7 +3087,7 @@ var XPIProvider = {
knownLocations.forEach(function(aLocation) {
let addons = XPIDatabase.getAddonsInLocation(aLocation);
addons.forEach(function(aOldAddon) {
changed = removeMetadata(aLocation, aOldAddon) || changed;
changed = removeMetadata(aOldAddon) || changed;
}, this);
}, this);
@ -3465,7 +3481,7 @@ var XPIProvider = {
let results = [createWrapper(a) for each (a in aAddons)];
XPIProvider.installs.forEach(function(aInstall) {
if (aInstall.state == AddonManager.STATE_INSTALLED &&
!(aInstall.addon instanceof DBAddonInternal))
!(aInstall.addon.inDatabase))
results.push(createWrapper(aInstall.addon));
});
aCallback(results);
@ -3783,7 +3799,7 @@ var XPIProvider = {
// This wouldn't normally be called for an already installed add-on (except
// for forming the operationsRequiringRestart flags) so is really here as
// a safety measure.
if (aAddon instanceof DBAddonInternal)
if (aAddon.inDatabase)
return false;
// If we have an AddonInstall for this add-on then we can see if there is
@ -4032,7 +4048,7 @@ var XPIProvider = {
updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon,
aUserDisabled,
aSoftDisabled) {
if (!(aAddon instanceof DBAddonInternal))
if (!(aAddon.inDatabase))
throw new Error("Can only update addon states for installed addons.");
if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
throw new Error("Cannot change userDisabled and softDisabled at the " +
@ -4105,8 +4121,7 @@ var XPIProvider = {
}
if (!needsRestart) {
aAddon.active = !isDisabled;
XPIDatabase.updateAddonActive(aAddon);
XPIDatabase.updateAddonActive(aAddon, !isDisabled);
if (isDisabled) {
if (aAddon.bootstrap) {
let file = aAddon._installLocation.getLocationForID(aAddon.id);
@ -4142,7 +4157,7 @@ var XPIProvider = {
* location that does not allow it
*/
uninstallAddon: function XPI_uninstallAddon(aAddon) {
if (!(aAddon instanceof DBAddonInternal))
if (!(aAddon.inDatabase))
throw new Error("Can only uninstall installed addons.");
if (aAddon._installLocation.locked)
@ -4184,8 +4199,7 @@ var XPIProvider = {
AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) {
aAddon.active = true;
XPIDatabase.updateAddonActive(aAddon);
XPIDatabase.updateAddonActive(aAddon, true);
}
if (aAddon.bootstrap) {
@ -4255,7 +4269,7 @@ var XPIProvider = {
* The DBAddonInternal to cancel uninstall for
*/
cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) {
if (!(aAddon instanceof DBAddonInternal))
if (!(aAddon.inDatabase))
throw new Error("Can only cancel uninstall for installed addons.");
cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]);
@ -5244,7 +5258,7 @@ AddonInstall.prototype = {
// Point the add-on to its extracted files as the xpi may get deleted
this.addon._sourceBundle = stagedAddon;
// Cache the AddonInternal as it may have updated compatibiltiy info
// Cache the AddonInternal as it may have updated compatibility info
let stagedJSON = stagedAddon.clone();
stagedJSON.leafName = this.addon.id + ".json";
if (stagedJSON.exists())
@ -5313,8 +5327,7 @@ AddonInstall.prototype = {
}
if (!isUpgrade && this.existingAddon.active) {
this.existingAddon.active = false;
XPIDatabase.updateAddonActive(this.existingAddon);
XPIDatabase.updateAddonActive(this.existingAddon, false);
}
}
@ -5330,51 +5343,45 @@ AddonInstall.prototype = {
this.addon.updateDate = recursiveLastModifiedTime(file);
this.addon.visible = true;
if (isUpgrade) {
XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
file.persistentDescriptor);
this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
file.persistentDescriptor);
}
else {
this.addon.installDate = this.addon.updateDate;
this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon))
XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
}
// Retrieve the new DBAddonInternal for the add-on we just added
let self = this;
XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name,
function startInstall_getAddonInLocation(a) {
self.addon = a;
let extraParams = {};
if (self.existingAddon) {
extraParams.oldVersion = self.existingAddon.version;
}
let extraParams = {};
if (this.existingAddon) {
extraParams.oldVersion = this.existingAddon.version;
}
if (self.addon.bootstrap) {
XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
self.addon.type, file, "install",
if (this.addon.bootstrap) {
XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
this.addon.type, file, "install",
reason, extraParams);
}
AddonManagerPrivate.callAddonListeners("onInstalled",
createWrapper(this.addon));
LOG("Install of " + this.sourceURI.spec + " completed.");
this.state = AddonManager.STATE_INSTALLED;
AddonManagerPrivate.callInstallListeners("onInstallEnded",
this.listeners, this.wrapper,
createWrapper(this.addon));
if (this.addon.bootstrap) {
if (this.addon.active) {
XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
this.addon.type, file, "startup",
reason, extraParams);
}
AddonManagerPrivate.callAddonListeners("onInstalled",
createWrapper(self.addon));
LOG("Install of " + self.sourceURI.spec + " completed.");
self.state = AddonManager.STATE_INSTALLED;
AddonManagerPrivate.callInstallListeners("onInstallEnded",
self.listeners, self.wrapper,
createWrapper(self.addon));
if (self.addon.bootstrap) {
if (self.addon.active) {
XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
self.addon.type, file, "startup",
reason, extraParams);
}
else {
XPIProvider.unloadBootstrapScope(self.addon.id);
}
else {
XPIProvider.unloadBootstrapScope(this.addon.id);
}
});
}
}
}
catch (e) {
@ -5766,13 +5773,6 @@ AddonInternal.prototype = {
releaseNotesURI: null,
foreignInstall: false,
get isForeignInstall() {
return this.foreignInstall;
},
set isForeignInstall(aVal) {
this.foreignInstall = aVal;
},
get selectedLocale() {
if (this._selectedLocale)
return this._selectedLocale;
@ -5967,10 +5967,7 @@ AddonInternal.prototype = {
* A JS object containing the cached metadata
*/
importMetadata: function AddonInternal_importMetaData(aObj) {
["syncGUID", "targetApplications", "userDisabled", "softDisabled",
"existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
"updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]
.forEach(function(aProp) {
PENDING_INSTALL_METADATA.forEach(function(aProp) {
if (!(aProp in aObj))
return;
@ -5982,77 +5979,6 @@ AddonInternal.prototype = {
}
};
/**
* The DBAddonInternal is a special AddonInternal that has been retrieved from
* the database. Add-ons retrieved synchronously only have the basic metadata
* the rest is filled out synchronously when needed. Asynchronously read add-ons
* have all data available.
*/
function DBAddonInternal() {
this.__defineGetter__("targetApplications", function DBA_targetApplicationsGetter() {
delete this.targetApplications;
return this.targetApplications = XPIDatabase._getTargetApplications(this);
});
this.__defineGetter__("targetPlatforms", function DBA_targetPlatformsGetter() {
delete this.targetPlatforms;
return this.targetPlatforms = XPIDatabase._getTargetPlatforms(this);
});
this.__defineGetter__("locales", function DBA_localesGetter() {
delete this.locales;
return this.locales = XPIDatabase._getLocales(this);
});
this.__defineGetter__("defaultLocale", function DBA_defaultLocaleGetter() {
delete this.defaultLocale;
return this.defaultLocale = XPIDatabase._getDefaultLocale(this);
});
this.__defineGetter__("pendingUpgrade", function DBA_pendingUpgradeGetter() {
delete this.pendingUpgrade;
for (let install of XPIProvider.installs) {
if (install.state == AddonManager.STATE_INSTALLED &&
!(install.addon instanceof DBAddonInternal) &&
install.addon.id == this.id &&
install.installLocation == this._installLocation) {
return this.pendingUpgrade = install.addon;
}
};
});
}
DBAddonInternal.prototype = {
applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
let changes = [];
this.targetApplications.forEach(function(aTargetApp) {
aUpdate.targetApplications.forEach(function(aUpdateTarget) {
if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
aTargetApp.minVersion = aUpdateTarget.minVersion;
aTargetApp.maxVersion = aUpdateTarget.maxVersion;
changes.push(aUpdateTarget);
}
});
});
try {
XPIDatabase.updateTargetApplications(this, changes);
}
catch (e) {
// A failure just means that we discard the compatibility update
ERROR("Failed to update target application info in the database for " +
"add-on " + this.id, e);
return;
}
XPIProvider.updateAddonDisabledState(this);
}
}
DBAddonInternal.prototype.__proto__ = AddonInternal.prototype;
// Make it accessible to XPIDatabase.
XPIProvider.DBAddonInternal = DBAddonInternal;
/**
* Creates an AddonWrapper for an AddonInternal.
*
@ -6304,7 +6230,7 @@ function AddonWrapper(aAddon) {
if (aAddon.syncGUID == val)
return val;
if (aAddon instanceof DBAddonInternal)
if (aAddon.inDatabase)
XPIDatabase.setAddonSyncGUID(aAddon, val);
aAddon.syncGUID = val;
@ -6331,7 +6257,7 @@ function AddonWrapper(aAddon) {
this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() {
let pending = 0;
if (!(aAddon instanceof DBAddonInternal)) {
if (!(aAddon.inDatabase)) {
// Add-on is pending install if there is no associated install (shouldn't
// happen here) or if the install is in the process of or has successfully
// completed the install. If an add-on is pending install then we ignore
@ -6375,7 +6301,7 @@ function AddonWrapper(aAddon) {
let permissions = 0;
// Add-ons that aren't installed cannot be modified in any way
if (!(aAddon instanceof DBAddonInternal))
if (!(aAddon.inDatabase))
return permissions;
if (!aAddon.appDisabled) {
@ -6410,7 +6336,7 @@ function AddonWrapper(aAddon) {
if (val == this.userDisabled)
return val;
if (aAddon instanceof DBAddonInternal) {
if (aAddon.inDatabase) {
if (aAddon.type == "theme" && val) {
if (aAddon.internalName == XPIProvider.defaultSkin)
throw new Error("Cannot disable the default theme");
@ -6434,7 +6360,7 @@ function AddonWrapper(aAddon) {
if (val == aAddon.softDisabled)
return val;
if (aAddon instanceof DBAddonInternal) {
if (aAddon.inDatabase) {
// When softDisabling a theme just enable the active theme
if (aAddon.type == "theme" && val && !aAddon.userDisabled) {
if (aAddon.internalName == XPIProvider.defaultSkin)
@ -6459,7 +6385,7 @@ function AddonWrapper(aAddon) {
};
this.uninstall = function AddonWrapper_uninstall() {
if (!(aAddon instanceof DBAddonInternal))
if (!(aAddon.inDatabase))
throw new Error("Cannot uninstall an add-on that isn't installed");
if (aAddon.pendingUninstall)
throw new Error("Add-on is already marked to be uninstalled");
@ -6467,7 +6393,7 @@ function AddonWrapper(aAddon) {
};
this.cancelUninstall = function AddonWrapper_cancelUninstall() {
if (!(aAddon instanceof DBAddonInternal))
if (!(aAddon.inDatabase))
throw new Error("Cannot cancel uninstall for an add-on that isn't installed");
if (!aAddon.pendingUninstall)
throw new Error("Add-on is not marked to be uninstalled");

File diff suppressed because it is too large Load Diff

View File

@ -1394,6 +1394,60 @@ function do_exception_wrap(func) {
};
}
const EXTENSIONS_DB = "extensions.json";
/**
* Change the schema version of the JSON extensions database
*/
function changeXPIDBVersion(aNewVersion) {
let dbfile = gProfD.clone();
dbfile.append(EXTENSIONS_DB);
let jData = loadJSON(dbfile);
jData.schemaVersion = aNewVersion;
saveJSON(jData, dbfile);
}
/**
* Raw load of a JSON file
*/
function loadJSON(aFile) {
let data = "";
let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Components.interfaces.nsIConverterInputStream);
fstream.init(aFile, -1, 0, 0);
cstream.init(fstream, "UTF-8", 0, 0);
let (str = {}) {
let read = 0;
do {
read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
data += str.value;
} while (read != 0);
}
cstream.close();
do_print("Loaded JSON file " + aFile.spec);
return(JSON.parse(data));
}
/**
* Raw save of a JSON blob to file
*/
function saveJSON(aData, aFile) {
do_print("Starting to save JSON file " + aFile.path);
let stream = FileUtils.openSafeFileOutputStream(aFile);
let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"].
createInstance(AM_Ci.nsIConverterOutputStream);
converter.init(stream, "UTF-8", 0, 0x0000);
// XXX pretty print the JSON while debugging
converter.writeString(JSON.stringify(aData, null, 2));
converter.flush();
// nsConverterOutputStream doesn't finish() safe output streams on close()
FileUtils.closeSafeFileOutputStream(stream);
converter.close();
do_print("Done saving JSON file " + aFile.path);
}
/**
* Create a callback function that calls do_execute_soon on an actual callback and arguments
*/

View File

@ -254,14 +254,11 @@ function run_test_1() {
function run_test_1_modified_db() {
// After restarting the database won't be open and so can be replaced with
// a bad file
restartManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
var db = Services.storage.openDatabase(dbfile);
db.schemaVersion = 100;
db.close();
// After restarting the database won't be open so we can alter
// the schema
shutdownManager();
changeXPIDBVersion(100);
startupManager();
// Accessing the add-ons should open and recover the database. Since
// migration occurs everything should be recovered correctly

View File

@ -531,10 +531,10 @@ function manual_update(aVersion, aCallback) {
// Checks that an add-ons properties match expected values
function check_addon(aAddon, aExpectedVersion, aExpectedUserDisabled,
aExpectedSoftDisabled, aExpectedState) {
do_check_neq(aAddon, null);
dump("Testing " + aAddon.id + " version " + aAddon.version + "\n");
dump(aAddon.userDisabled + " " + aAddon.softDisabled + "\n");
do_check_neq(aAddon, null);
do_check_eq(aAddon.version, aExpectedVersion);
do_check_eq(aAddon.blocklistState, aExpectedState);
do_check_eq(aAddon.userDisabled, aExpectedUserDisabled);
@ -706,11 +706,7 @@ add_test(function run_app_update_schema_test() {
function update_schema_2() {
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
var db = Services.storage.openDatabase(dbfile);
db.schemaVersion = 100;
db.close();
changeXPIDBVersion(100);
gAppInfo.version = "2";
startupManager(true);
@ -738,11 +734,7 @@ add_test(function run_app_update_schema_test() {
restartManager();
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
var db = Services.storage.openDatabase(dbfile);
db.schemaVersion = 100;
db.close();
changeXPIDBVersion(100);
gAppInfo.version = "2.5";
startupManager(true);
@ -764,11 +756,7 @@ add_test(function run_app_update_schema_test() {
function update_schema_4() {
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
var db = Services.storage.openDatabase(dbfile);
db.schemaVersion = 100;
db.close();
changeXPIDBVersion(100);
startupManager(false);
AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
@ -789,11 +777,7 @@ add_test(function run_app_update_schema_test() {
function update_schema_5() {
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
var db = Services.storage.openDatabase(dbfile);
db.schemaVersion = 100;
db.close();
changeXPIDBVersion(100);
gAppInfo.version = "1";
startupManager(true);

View File

@ -11,8 +11,6 @@ const ADDON_UNINSTALL = 6;
const ADDON_UPGRADE = 7;
const ADDON_DOWNGRADE = 8;
const EXTENSIONS_DB = "extensions.sqlite";
// This verifies that bootstrappable add-ons can be used without restarts.
Components.utils.import("resource://gre/modules/Services.jsm");

View File

@ -5,8 +5,6 @@
// This verifies that deleting the database from the profile doesn't break
// anything
const EXTENSIONS_DB = "extensions.sqlite";
const profileDir = gProfD.clone();
profileDir.append("extensions");

View File

@ -108,14 +108,8 @@ function run_test_1() {
shutdownManager();
// Make it look like the next time the app is started it has a new DB schema
let dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let db = AM_Cc["@mozilla.org/storage/service;1"].
getService(AM_Ci.mozIStorageService).
openDatabase(dbfile);
db.schemaVersion = 1;
changeXPIDBVersion(1);
Services.prefs.setIntPref("extensions.databaseSchema", 1);
db.close();
let jsonfile = gProfD.clone();
jsonfile.append("extensions");
@ -255,14 +249,8 @@ function run_test_2() {
shutdownManager();
// Make it look like the next time the app is started it has a new DB schema
let dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let db = AM_Cc["@mozilla.org/storage/service;1"].
getService(AM_Ci.mozIStorageService).
openDatabase(dbfile);
db.schemaVersion = 1;
changeXPIDBVersion(1);
Services.prefs.setIntPref("extensions.databaseSchema", 1);
db.close();
let jsonfile = gProfD.clone();
jsonfile.append("extensions");

View File

@ -252,7 +252,7 @@ function run_test_1() {
// because there is a file there still.
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
dbfile.append("extensions.json");
dbfile.remove(true);
dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
startupManager(false);

View File

@ -253,7 +253,7 @@ function run_test_1() {
// because there is a file there still.
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
dbfile.append("extensions.json");
dbfile.remove(true);
dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
startupManager(false);

View File

@ -1,181 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// This tests the data in extensions.sqlite for general sanity, making sure
// rows in one table only reference rows in another table that actually exist.
function check_db() {
do_print("Checking DB sanity...");
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
var db = Services.storage.openDatabase(dbfile);
do_print("Checking locale_strings references rows in locale correctly...");
let localeStringsStmt = db.createStatement("SELECT * FROM locale_strings");
let localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id");
let i = 0;
while (localeStringsStmt.executeStep()) {
i++;
localeStmt.params.locale_id = localeStringsStmt.row.locale_id;
do_check_true(localeStmt.executeStep());
do_check_eq(localeStmt.row.count, 1);
localeStmt.reset();
}
localeStmt.finalize();
localeStringsStmt.finalize();
do_print("Done. " + i + " rows in locale_strings checked.");
do_print("Checking locale references rows in addon_locale and addon correctly...");
localeStmt = db.createStatement("SELECT * FROM locale");
let addonLocaleStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon_locale WHERE locale_id=:locale_id");
let addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE defaultLocale=:locale_id");
i = 0;
while (localeStmt.executeStep()) {
i++;
addonLocaleStmt.params.locale_id = localeStmt.row.id;
do_check_true(addonLocaleStmt.executeStep());
if (addonLocaleStmt.row.count == 0) {
addonStmt.params.locale_id = localeStmt.row.id;
do_check_true(addonStmt.executeStep());
do_check_eq(addonStmt.row.count, 1);
} else {
do_check_eq(addonLocaleStmt.row.count, 1);
}
addonLocaleStmt.reset();
addonStmt.reset();
}
addonLocaleStmt.finalize();
localeStmt.finalize();
addonStmt.finalize();
do_print("Done. " + i + " rows in locale checked.");
do_print("Checking addon_locale references rows in locale correctly...");
addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale");
localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id");
i = 0;
while (addonLocaleStmt.executeStep()) {
i++;
localeStmt.params.locale_id = addonLocaleStmt.row.locale_id;
do_check_true(localeStmt.executeStep());
do_check_eq(localeStmt.row.count, 1);
localeStmt.reset();
}
addonLocaleStmt.finalize();
localeStmt.finalize();
do_print("Done. " + i + " rows in addon_locale checked.");
do_print("Checking addon_locale references rows in addon correctly...");
addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale");
addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id");
i = 0;
while (addonLocaleStmt.executeStep()) {
i++;
addonStmt.params.addon_internal_id = addonLocaleStmt.row.addon_internal_id;
do_check_true(addonStmt.executeStep());
do_check_eq(addonStmt.row.count, 1);
addonStmt.reset();
}
addonLocaleStmt.finalize();
addonStmt.finalize();
do_print("Done. " + i + " rows in addon_locale checked.");
do_print("Checking addon references rows in locale correctly...");
addonStmt = db.createStatement("SELECT * FROM addon");
localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:defaultLocale");
i = 0;
while (addonStmt.executeStep()) {
i++;
localeStmt.params.defaultLocale = addonStmt.row.defaultLocale;
do_check_true(localeStmt.executeStep());
do_check_eq(localeStmt.row.count, 1);
localeStmt.reset();
}
addonStmt.finalize();
localeStmt.finalize();
do_print("Done. " + i + " rows in addon checked.");
do_print("Checking targetApplication references rows in addon correctly...");
let targetAppStmt = db.createStatement("SELECT * FROM targetApplication");
addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id");
i = 0;
while (targetAppStmt.executeStep()) {
i++;
addonStmt.params.addon_internal_id = targetAppStmt.row.addon_internal_id;
do_check_true(addonStmt.executeStep());
do_check_eq(addonStmt.row.count, 1);
addonStmt.reset();
}
targetAppStmt.finalize();
addonStmt.finalize();
do_print("Done. " + i + " rows in targetApplication checked.");
do_print("Checking targetPlatform references rows in addon correctly...");
let targetPlatformStmt = db.createStatement("SELECT * FROM targetPlatform");
addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id");
i = 0;
while (targetPlatformStmt.executeStep()) {
i++;
addonStmt.params.addon_internal_id = targetPlatformStmt.row.addon_internal_id;
do_check_true(addonStmt.executeStep());
do_check_eq(addonStmt.row.count, 1);
addonStmt.reset();
}
targetPlatformStmt.finalize();
addonStmt.finalize();
do_print("Done. " + i + " rows in targetPlatform checked.");
db.close();
do_print("Done checking DB sanity.");
}
function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
startupManager();
installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_1);
}
function run_test_1() {
shutdownManager();
check_db();
startupManager();
AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) {
aAddon.uninstall();
shutdownManager();
check_db();
startupManager();
installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_2);
});
}
function run_test_2() {
installAllFiles([do_get_addon("test_db_sanity_1_2")], function() {
shutdownManager();
check_db();
startupManager();
run_test_3();
});
}
function run_test_3() {
AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) {
aAddon.uninstall();
shutdownManager();
check_db();
do_test_finished();
});
}

View File

@ -254,16 +254,13 @@ function run_test_1() {
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// After shutting down the database won't be open so we can lock it
// After shutting down the database won't be open so we can
// mess with permissions
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let connection = Services.storage.openUnsharedDatabase(dbfile);
connection.executeSimpleSQL("PRAGMA synchronous = FULL");
connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
// Force the DB to become locked
connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE);
connection.commitTransaction();
dbfile.append(EXTENSIONS_DB);
var savedPermissions = dbfile.permissions;
dbfile.permissions = 0;
startupManager(false);
@ -431,7 +428,7 @@ function run_test_1() {
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
connection.close();
dbfile.permissions = savedPermissions;
// After allowing access to the original DB things should go back to as
// they were previously

View File

@ -145,13 +145,9 @@ function run_test() {
// After shutting down the database won't be open so we can lock it
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let connection = Services.storage.openUnsharedDatabase(dbfile);
connection.executeSimpleSQL("PRAGMA synchronous = FULL");
connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
// Force the DB to become locked
connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE);
connection.commitTransaction();
dbfile.append(EXTENSIONS_DB);
var savedPermissions = dbfile.permissions;
dbfile.permissions = 0;
startupManager(false);
@ -203,7 +199,7 @@ function run_test() {
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
connection.close();
dbfile.permissions = savedPermissions;
// After allowing access to the original DB things should still be
// applied correctly

View File

@ -257,13 +257,9 @@ function run_test_1() {
// After shutting down the database won't be open so we can lock it
shutdownManager();
var dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let connection = Services.storage.openUnsharedDatabase(dbfile);
connection.executeSimpleSQL("PRAGMA synchronous = FULL");
connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
// Force the DB to become locked
connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE);
connection.commitTransaction();
dbfile.append(EXTENSIONS_DB);
var savedPermissions = dbfile.permissions;
dbfile.permissions = 0;
startupManager(false);
@ -429,7 +425,7 @@ function run_test_1() {
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
connection.close();
dbfile.permissions = savedPermissions;
// After allowing access to the original DB things should go back to as
// they were previously

View File

@ -224,7 +224,7 @@ function run_test() {
do_check_true(a4.isActive);
do_check_true(a4.strictCompatibility);
do_check_false(a4.foreignInstall);
// addon5 was enabled in the database but needed a compatibiltiy update
// addon5 was enabled in the database but needed a compatibility update
do_check_neq(a5, null);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);

View File

@ -2,7 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Checks that we migrate data from a previous version of the sqlite database
// Checks that we migrate data from a previous version of the JSON database
// The test extension uses an insecure update url.
Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
@ -172,14 +172,8 @@ function perform_migration() {
// Turn on disabling for all scopes
Services.prefs.setIntPref("extensions.autoDisableScopes", 15);
let dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let db = AM_Cc["@mozilla.org/storage/service;1"].
getService(AM_Ci.mozIStorageService).
openDatabase(dbfile);
db.schemaVersion = 1;
changeXPIDBVersion(1);
Services.prefs.setIntPref("extensions.databaseSchema", 1);
db.close();
gAppInfo.version = "2"
startupManager(true);
@ -247,7 +241,7 @@ function test_results() {
do_check_false(a4.hasBinaryComponents);
do_check_true(a4.strictCompatibility);
// addon5 was enabled in the database but needed a compatibiltiy update
// addon5 was enabled in the database but needed a compatibility update
do_check_neq(a5, null);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);

View File

@ -2,7 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Checks that we fail to migrate but still start up ok when there is a database
// Checks that we fail to migrate but still start up ok when there is a SQLITE database
// with no useful data in it.
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";

View File

@ -133,7 +133,7 @@ function run_test() {
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
let file = gProfD.clone();
file.append("extensions.sqlite");
file.append("extensions.json");
do_check_false(file.exists());
file.leafName = "extensions.ini";
@ -191,7 +191,7 @@ function run_test_1() {
do_check_true(gCachePurged);
let file = gProfD.clone();
file.append("extensions.sqlite");
file.append("extensions.json");
do_check_true(file.exists());
file.leafName = "extensions.ini";

View File

@ -94,8 +94,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() {
do_throw("Should not get here.");
}
catch (e) {
do_check_eq(e.result,
Components.results.NS_ERROR_STORAGE_CONSTRAINT);
do_check_true(e.message.startsWith("Addon sync GUID conflict"));
restartManager();
AddonManager.getAddonByID(installIDs[1], function(addon) {

View File

@ -17,7 +17,11 @@ skip-if = os == "android"
[test_LightweightThemeManager.js]
[test_backgroundupdate.js]
[test_badschema.js]
# Needs rewrite for JSON XPIDB
fail-if = true
[test_blocklistchange.js]
# Needs rewrite for JSON XPIDB
fail-if = true
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
[test_blocklist_regexp.js]
@ -138,6 +142,8 @@ fail-if = os == "android"
[test_bug620837.js]
[test_bug655254.js]
[test_bug659772.js]
# needs to be converted from sqlite to JSON
fail-if = true
[test_bug675371.js]
[test_bug740612.js]
[test_bug753900.js]
@ -147,8 +153,11 @@ fail-if = os == "android"
[test_ChromeManifestParser.js]
[test_compatoverrides.js]
[test_corrupt.js]
# needs to be converted from sqlite to JSON
fail-if = true
[test_corrupt_strictcompat.js]
[test_db_sanity.js]
# needs to be converted from sqlite to JSON
fail-if = true
[test_dictionary.js]
[test_langpack.js]
[test_disable.js]
@ -193,17 +202,33 @@ skip-if = os == "android"
run-sequentially = Uses hardcoded ports in xpi files.
[test_locale.js]
[test_locked.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_locked2.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_locked_strictcompat.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_manifest.js]
[test_mapURIToAddonID.js]
# Same as test_bootstrap.js
skip-if = os == "android"
[test_migrate1.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_migrate2.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_migrate3.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_migrate4.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_migrate5.js]
# Needs sqlite->JSON conversion
fail-if = true
[test_migrateAddonRepository.js]
[test_onPropertyChanged_appDisabled.js]
[test_permissions.js]