--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -73,17 +73,17 @@ const URI_EXTENSION_STRINGS =
const STRING_TYPE_NAME = "type.%ID%.name";
const DIR_EXTENSIONS = "extensions";
const DIR_STAGE = "staged";
const DIR_XPI_STAGE = "staged-xpis";
const DIR_TRASH = "trash";
-const FILE_DATABASE = "extensions.json";
+const FILE_DATABASE = "extensions.sqlite";
const FILE_OLD_CACHE = "extensions.cache";
const FILE_INSTALL_MANIFEST = "install.rdf";
const FILE_XPI_ADDONS_LIST = "extensions.ini";
const KEY_PROFILEDIR = "ProfD";
const KEY_APPDIR = "XCurProcD";
const KEY_TEMPDIR = "TmpD";
const KEY_APP_DISTRIBUTION = "XREAppDist";
@@ -115,22 +115,17 @@ const PROP_LOCALE_SINGLE = ["name", "des
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
// Properties that should be migrated where possible from an old database. These
// shouldn't include properties that can be read directly from install.rdf files
// or calculated
const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
"sourceURI", "applyBackgroundUpdates",
- "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"];
+ "releaseNotesURI", "isForeignInstall", "syncGUID"];
// Note: When adding/changing/removing items here, remember to change the
// DB schema version to ensure changes are picked up ASAP.
const STATIC_BLOCKLIST_PATTERNS = [
{ creator: "Mozilla Corp.",
level: Ci.nsIBlocklistService.STATE_BLOCKED,
blockID: "i162" },
{ creator: "Mozilla.org",
@@ -169,25 +164,22 @@ const MSG_JAR_FLUSH = "AddonJarFlush";
var gGlobalScope = this;
/**
* Valid IDs fit this pattern.
*/
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) {
- Object.defineProperty(this, aName, {
- get: function logFuncGetter() {
- Components.utils.import("resource://gre/modules/AddonLogging.jsm");
-
- LogManager.getLogger("addons.xpi", this);
- return this[aName];
- },
- configurable: true
- });
+ this.__defineGetter__(aName, function logFuncGetter() {
+ Components.utils.import("resource://gre/modules/AddonLogging.jsm");
+
+ LogManager.getLogger("addons.xpi", this);
+ return this[aName];
+ })
}, this);
const LAZY_OBJECTS = ["XPIDatabase"];
var gLazyObjectsLoaded = false;
function loadLazyObjects() {
@@ -200,22 +192,19 @@ function loadLazyObjects() {
delete gGlobalScope[name];
gGlobalScope[name] = scope[name];
}
gLazyObjectsLoaded = true;
return scope;
}
for (let name of LAZY_OBJECTS) {
- Object.defineProperty(gGlobalScope, name, {
- get: function lazyObjectGetter() {
- let objs = loadLazyObjects();
- return objs[name];
- },
- configurable: true
+ gGlobalScope.__defineGetter__(name, function lazyObjectGetter() {
+ let objs = loadLazyObjects();
+ return objs[name];
});
}
function findMatchingStaticBlocklistItem(aAddon) {
for (let item of STATIC_BLOCKLIST_PATTERNS) {
if ("creator" in item && typeof item.creator == "string") {
if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) ||
@@ -590,23 +579,20 @@ function isUsableAddon(aAddon) {
return true;
}
function isAddonDisabled(aAddon) {
return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled;
}
-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
+this.__defineGetter__("gRDF", function gRDFGetter() {
+ delete this.gRDF;
+ return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+ getService(Ci.nsIRDFService);
});
function EM_R(aProperty) {
return gRDF.GetResource(PREFIX_NS_EM + aProperty);
}
/**
* Converts an RDF literal, resource or integer into a string.
@@ -703,21 +689,18 @@ function loadManifestFromRDF(aUri, aStre
}
}
PROP_LOCALE_SINGLE.forEach(function(aProp) {
locale[aProp] = getRDFProperty(aDs, aSource, aProp);
});
PROP_LOCALE_MULTI.forEach(function(aProp) {
- // Don't store empty arrays
- let props = getPropertyArray(aDs, aSource,
- aProp.substring(0, aProp.length - 1));
- if (props.length > 0)
- locale[aProp] = props;
+ locale[aProp] = getPropertyArray(aDs, aSource,
+ aProp.substring(0, aProp.length - 1));
});
return locale;
}
let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
createInstance(Ci.nsIRDFXMLParser)
let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
@@ -2530,43 +2513,41 @@ var XPIProvider = {
}
// Set the additional properties on the new AddonInternal
newAddon._installLocation = aInstallLocation;
newAddon.updateDate = aAddonState.mtime;
newAddon.visible = !(newAddon.id in visibleAddons);
// Update the database
- let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
- aAddonState.descriptor);
- if (newDBAddon.visible) {
- visibleAddons[newDBAddon.id] = newDBAddon;
+ XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor);
+ if (newAddon.visible) {
+ visibleAddons[newAddon.id] = newAddon;
// Remember add-ons that were changed during startup
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- newDBAddon.id);
+ newAddon.id);
// If this was the active theme and it is now disabled then enable the
// default theme
- if (aOldAddon.active && isAddonDisabled(newDBAddon))
+ if (aOldAddon.active && isAddonDisabled(newAddon))
XPIProvider.enableDefaultTheme();
// If the new add-on is bootstrapped and active then call its install method
- if (newDBAddon.active && newDBAddon.bootstrap) {
+ if (newAddon.active && newAddon.bootstrap) {
// Startup cache must be flushed before calling the bootstrap script
flushStartupCache();
- let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
+ let installReason = Services.vc.compare(aOldAddon.version, newAddon.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(newDBAddon.id, newDBAddon.version,
- newDBAddon.type, file, "install",
- installReason, { oldVersion: aOldAddon.version });
+ XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file,
+ "install", installReason, { oldVersion: aOldAddon.version });
return false;
}
return true;
}
return false;
}
@@ -2583,17 +2564,17 @@ var XPIProvider = {
* @param aAddonState
* The new state of the add-on
* @return a boolean indicating if flushing caches is required to complete
* changing this add-on
*/
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
XPIDatabase.setAddonDescriptor(aOldAddon, aAddonState.descriptor);
if (aOldAddon.visible) {
visibleAddons[aOldAddon.id] = aOldAddon;
if (aOldAddon.bootstrap && aOldAddon.active) {
@@ -2644,17 +2625,18 @@ var XPIProvider = {
file.persistentDescriptor = aAddonState.descriptor;
XPIProvider.callBootstrapMethod(aOldAddon.id, aOldAddon.version, aOldAddon.type, file,
"install",
BOOTSTRAP_REASONS.ADDON_INSTALL);
// If it should be active then mark it as active otherwise unload
// its scope
if (!isAddonDisabled(aOldAddon)) {
- XPIDatabase.updateAddonActive(aOldAddon, true);
+ aOldAddon.active = true;
+ XPIDatabase.updateAddonActive(aOldAddon);
}
else {
XPIProvider.unloadBootstrapScope(newAddon.id);
}
}
else {
// Otherwise a restart is necessary
changed = true;
@@ -2703,17 +2685,18 @@ var XPIProvider = {
if (aOldAddon.visible && wasDisabled != isDisabled) {
// Remember add-ons that became disabled or enabled by the application
// change
let change = isDisabled ? AddonManager.STARTUP_CHANGE_DISABLED
: AddonManager.STARTUP_CHANGE_ENABLED;
AddonManagerPrivate.addStartupChange(change, aOldAddon.id);
if (aOldAddon.bootstrap) {
// Update the add-ons active state
- XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
+ aOldAddon.active = !isDisabled;
+ XPIDatabase.updateAddonActive(aOldAddon);
}
else {
changed = true;
}
}
}
if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) {
@@ -2725,25 +2708,27 @@ var XPIProvider = {
}
return changed;
}
/**
* 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(aOldAddon) {
+ function removeMetadata(aInstallLocation, aOldAddon) {
// This add-on has disappeared
- LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
+ LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation);
XPIDatabase.removeAddonMetadata(aOldAddon);
// Remember add-ons that were uninstalled during startup
if (aOldAddon.visible) {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
aOldAddon.id);
}
else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
@@ -2896,96 +2881,95 @@ var XPIProvider = {
else
newAddon.userDisabled = true;
}
}
else {
newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon))
}
- let newDBAddon = null;
try {
// Update the database.
- newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
+ XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
}
catch (e) {
// Failing to write the add-on into the database is non-fatal, the
// add-on will just be unavailable until we try again in a subsequent
// startup
ERROR("Failed to add add-on " + aId + " in " + aInstallLocation.name +
" to database", e);
return false;
}
- if (newDBAddon.visible) {
+ if (newAddon.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(newDBAddon.id) != -1) {
+ .indexOf(newAddon.id) != -1) {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- newDBAddon.id);
+ newAddon.id);
}
else {
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
- newDBAddon.id);
+ newAddon.id);
}
}
// Note if any visible add-on is not in the application install location
- if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
+ if (newAddon._installLocation.name != KEY_APP_GLOBAL)
XPIProvider.allAppGlobal = false;
- visibleAddons[newDBAddon.id] = newDBAddon;
+ visibleAddons[newAddon.id] = newAddon;
let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
let extraParams = {};
// If we're hiding a bootstrapped add-on then call its uninstall method
- if (newDBAddon.id in oldBootstrappedAddons) {
- let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
+ if (newAddon.id in oldBootstrappedAddons) {
+ let oldBootstrap = oldBootstrappedAddons[newAddon.id];
extraParams.oldVersion = oldBootstrap.version;
- XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
+ XPIProvider.bootstrappedAddons[newAddon.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
// methods.
if (sameVersion || !isNewInstall)
return false;
- installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
+ installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
let oldAddonFile = Cc["@mozilla.org/file/local;1"].
createInstance(Ci.nsIFile);
oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
- XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version,
+ XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version,
oldBootstrap.type, oldAddonFile, "uninstall",
- installReason, { newVersion: newDBAddon.version });
- XPIProvider.unloadBootstrapScope(newDBAddon.id);
+ installReason, { newVersion: newAddon.version });
+ XPIProvider.unloadBootstrapScope(newAddon.id);
// If the new add-on is bootstrapped then we must flush the caches
// before calling the new bootstrap script
- if (newDBAddon.bootstrap)
+ if (newAddon.bootstrap)
flushStartupCache();
}
- if (!newDBAddon.bootstrap)
+ if (!newAddon.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(newDBAddon.id, newDBAddon.version, newDBAddon.type, file,
+ XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file,
"install", installReason, extraParams);
- if (!newDBAddon.active)
- XPIProvider.unloadBootstrapScope(newDBAddon.id);
+ if (!newAddon.active)
+ XPIProvider.unloadBootstrapScope(newAddon.id);
}
return false;
}
let changed = false;
let knownLocations = XPIDatabase.getInstallLocations();
@@ -3045,30 +3029,30 @@ var XPIProvider = {
// add-ons in the application directory when the application version
// has changed
if (aOldAddon.id in aManifests[installLocation.name] ||
aOldAddon.updateDate != addonState.mtime ||
(aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) {
changed = updateMetadata(installLocation, aOldAddon, addonState) ||
changed;
}
- else if (aOldAddon.descriptor != addonState.descriptor) {
+ else if (aOldAddon._descriptor != addonState.descriptor) {
changed = updateDescriptor(installLocation, aOldAddon, addonState) ||
changed;
}
else {
changed = updateVisibilityAndCompatibility(installLocation,
aOldAddon, addonState) ||
changed;
}
if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL)
XPIProvider.allAppGlobal = false;
}
else {
- changed = removeMetadata(aOldAddon) || changed;
+ changed = removeMetadata(installLocation.name, aOldAddon) || changed;
}
}, this);
}
// All the remaining add-ons in this install location must be new.
// Get the migration data for this install location.
let locMigrateData = {};
@@ -3082,17 +3066,17 @@ var XPIProvider = {
// The remaining locations that had add-ons installed in them no longer
// have any add-ons installed in them, or the locations no longer exist.
// The metadata for the add-ons that were in them must be removed from the
// database.
knownLocations.forEach(function(aLocation) {
let addons = XPIDatabase.getAddonsInLocation(aLocation);
addons.forEach(function(aOldAddon) {
- changed = removeMetadata(aOldAddon) || changed;
+ changed = removeMetadata(aLocation, aOldAddon) || changed;
}, this);
}, this);
// Tell Telemetry what we found
AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked);
if (modifiedUnpacked > 0)
AddonManagerPrivate.recordSimpleMeasure("modifiedExceptInstallRDF", modifiedExManifest);
AddonManagerPrivate.recordSimpleMeasure("modifiedXPI", modifiedXPI);
@@ -3476,17 +3460,17 @@ var XPIProvider = {
*/
getAddonsWithOperationsByTypes:
function XPI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
XPIDatabase.getVisibleAddonsWithPendingOperations(aTypes,
function getAddonsWithOpsByTypes_getVisibleAddonsWithPendingOps(aAddons) {
let results = [createWrapper(a) for each (a in aAddons)];
XPIProvider.installs.forEach(function(aInstall) {
if (aInstall.state == AddonManager.STATE_INSTALLED &&
- !(aInstall.addon.inDatabase))
+ !(aInstall.addon instanceof DBAddonInternal))
results.push(createWrapper(aInstall.addon));
});
aCallback(results);
});
},
/**
* Called to get the current AddonInstalls, optionally limiting to a list of
@@ -3794,17 +3778,17 @@ var XPIProvider = {
// restarting
if (Services.appinfo.inSafeMode)
return false;
// Add-ons that are already installed don't require a restart to install.
// 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.inDatabase)
+ if (aAddon instanceof DBAddonInternal)
return false;
// If we have an AddonInstall for this add-on then we can see if there is
// an existing installed add-on with the same ID
if ("_install" in aAddon && aAddon._install) {
// If there is an existing installed add-on and uninstalling it would
// require a restart then installing the update will also require a
// restart
@@ -4043,17 +4027,17 @@ var XPIProvider = {
* @param aSoftDisabled
* Value for the softDisabled property. If undefined the value will
* not change. If true this will force userDisabled to be true
* @throws if addon is not a DBAddonInternal
*/
updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon,
aUserDisabled,
aSoftDisabled) {
- if (!(aAddon.inDatabase))
+ if (!(aAddon instanceof DBAddonInternal))
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 " +
"same time");
}
if (aUserDisabled === undefined) {
aUserDisabled = aAddon.userDisabled;
@@ -4116,17 +4100,18 @@ var XPIProvider = {
}
else {
needsRestart = this.enableRequiresRestart(aAddon);
AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
needsRestart);
}
if (!needsRestart) {
- XPIDatabase.updateAddonActive(aAddon, !isDisabled);
+ aAddon.active = !isDisabled;
+ XPIDatabase.updateAddonActive(aAddon);
if (isDisabled) {
if (aAddon.bootstrap) {
let file = aAddon._installLocation.getLocationForID(aAddon.id);
this.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file, "shutdown",
BOOTSTRAP_REASONS.ADDON_DISABLE);
this.unloadBootstrapScope(aAddon.id);
}
AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
@@ -4152,17 +4137,17 @@ var XPIProvider = {
* uninstall if not.
*
* @param aAddon
* The DBAddonInternal to uninstall
* @throws if the addon cannot be uninstalled because it is in an install
* location that does not allow it
*/
uninstallAddon: function XPI_uninstallAddon(aAddon) {
- if (!(aAddon.inDatabase))
+ if (!(aAddon instanceof DBAddonInternal))
throw new Error("Can only uninstall installed addons.");
if (aAddon._installLocation.locked)
throw new Error("Cannot uninstall addons from locked install locations");
if ("_hasResourceCache" in aAddon)
aAddon._hasResourceCache = new Map();
@@ -4194,17 +4179,18 @@ var XPIProvider = {
// Reveal the highest priority add-on with the same ID
function revealAddon(aAddon) {
XPIDatabase.makeAddonVisible(aAddon);
let wrappedAddon = createWrapper(aAddon);
AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) {
- XPIDatabase.updateAddonActive(aAddon, true);
+ aAddon.active = true;
+ XPIDatabase.updateAddonActive(aAddon);
}
if (aAddon.bootstrap) {
let file = aAddon._installLocation.getLocationForID(aAddon.id);
XPIProvider.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file,
"install", BOOTSTRAP_REASONS.ADDON_INSTALL);
if (aAddon.active) {
@@ -4264,17 +4250,17 @@ var XPIProvider = {
/**
* Cancels the pending uninstall of an add-on.
*
* @param aAddon
* The DBAddonInternal to cancel uninstall for
*/
cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) {
- if (!(aAddon.inDatabase))
+ if (!(aAddon instanceof DBAddonInternal))
throw new Error("Can only cancel uninstall for installed addons.");
cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]);
XPIDatabase.setAddonProperties(aAddon, {
pendingUninstall: false
});
@@ -5253,17 +5239,17 @@ AddonInstall.prototype = {
this.file.copyTo(this.installLocation.getStagingDir(),
this.addon.id + ".xpi");
}
if (requiresRestart) {
// 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 compatibility info
+ // Cache the AddonInternal as it may have updated compatibiltiy info
let stagedJSON = stagedAddon.clone();
stagedJSON.leafName = this.addon.id + ".json";
if (stagedJSON.exists())
stagedJSON.remove(true);
let stream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Ci.nsIConverterOutputStream);
@@ -5322,71 +5308,78 @@ AddonInstall.prototype = {
this.existingAddon.type, file,
"uninstall", reason,
{ newVersion: this.addon.version });
XPIProvider.unloadBootstrapScope(this.existingAddon.id);
flushStartupCache();
}
if (!isUpgrade && this.existingAddon.active) {
- XPIDatabase.updateAddonActive(this.existingAddon, false);
+ this.existingAddon.active = false;
+ XPIDatabase.updateAddonActive(this.existingAddon);
}
}
// Install the new add-on into its final location
let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
let file = this.installLocation.installAddon(this.addon.id, stagedAddon,
existingAddonID);
cleanStagingDir(stagedAddon.parent, []);
// Update the metadata in the database
this.addon._sourceBundle = file;
this.addon._installLocation = this.installLocation;
this.addon.updateDate = recursiveLastModifiedTime(file);
this.addon.visible = true;
if (isUpgrade) {
- this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
- file.persistentDescriptor);
+ XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
+ file.persistentDescriptor);
}
else {
this.addon.installDate = this.addon.updateDate;
this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon))
- this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
- }
-
- let extraParams = {};
- if (this.existingAddon) {
- extraParams.oldVersion = this.existingAddon.version;
- }
-
- if (this.addon.bootstrap) {
- XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
- this.addon.type, file, "install",
- reason, extraParams);
+ XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
}
- 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",
+ // 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;
+ }
+
+ if (self.addon.bootstrap) {
+ XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
+ self.addon.type, file, "install",
reason, extraParams);
}
- else {
- XPIProvider.unloadBootstrapScope(this.addon.id);
+
+ 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);
+ }
}
- }
+ });
}
}
catch (e) {
WARN("Failed to install", e);
if (stagedAddon.exists())
recursiveRemove(stagedAddon);
this.state = AddonManager.STATE_INSTALL_FAILED;
this.error = AddonManager.ERROR_FILE_ACCESS;
@@ -5768,16 +5761,23 @@ AddonInternal.prototype = {
visible: false,
userDisabled: false,
appDisabled: false,
softDisabled: false,
sourceURI: null,
releaseNotesURI: null,
foreignInstall: false,
+ get isForeignInstall() {
+ return this.foreignInstall;
+ },
+ set isForeignInstall(aVal) {
+ this.foreignInstall = aVal;
+ },
+
get selectedLocale() {
if (this._selectedLocale)
return this._selectedLocale;
let locale = findClosestLocale(this.locales);
this._selectedLocale = locale ? locale : this.defaultLocale;
return this._selectedLocale;
},
@@ -5962,29 +5962,103 @@ AddonInternal.prototype = {
* When an add-on install is pending its metadata will be cached in a file.
* This method reads particular properties of that metadata that may be newer
* than that in the install manifest, like compatibility information.
*
* @param aObj
* A JS object containing the cached metadata
*/
importMetadata: function AddonInternal_importMetaData(aObj) {
- PENDING_INSTALL_METADATA.forEach(function(aProp) {
+ ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
+ "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
+ "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]
+ .forEach(function(aProp) {
if (!(aProp in aObj))
return;
this[aProp] = aObj[aProp];
}, this);
// Compatibility info may have changed so update appDisabled
this.appDisabled = !isUsableAddon(this);
}
};
/**
+ * 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.
*
* @param addon
* The AddonInternal to wrap
* @return an AddonWrapper or null if addon was null
*/
function createWrapper(aAddon) {
if (!aAddon)
@@ -6225,17 +6299,17 @@ function AddonWrapper(aAddon) {
return val;
});
this.__defineSetter__("syncGUID", function AddonWrapper_syncGUIDGetter(val) {
if (aAddon.syncGUID == val)
return val;
- if (aAddon.inDatabase)
+ if (aAddon instanceof DBAddonInternal)
XPIDatabase.setAddonSyncGUID(aAddon, val);
aAddon.syncGUID = val;
return val;
});
this.__defineGetter__("install", function AddonWrapper_installGetter() {
@@ -6252,17 +6326,17 @@ function AddonWrapper(aAddon) {
if (aAddon._installLocation)
return aAddon._installLocation.scope;
return AddonManager.SCOPE_PROFILE;
});
this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() {
let pending = 0;
- if (!(aAddon.inDatabase)) {
+ if (!(aAddon instanceof DBAddonInternal)) {
// 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
// any other pending operations.
if (!aAddon._install || aAddon._install.state == AddonManager.STATE_INSTALLING ||
aAddon._install.state == AddonManager.STATE_INSTALLED)
return AddonManager.PENDING_INSTALL;
}
@@ -6296,17 +6370,17 @@ function AddonWrapper(aAddon) {
return ops;
});
this.__defineGetter__("permissions", function AddonWrapper_permisionsGetter() {
let permissions = 0;
// Add-ons that aren't installed cannot be modified in any way
- if (!(aAddon.inDatabase))
+ if (!(aAddon instanceof DBAddonInternal))
return permissions;
if (!aAddon.appDisabled) {
if (this.userDisabled)
permissions |= AddonManager.PERM_CAN_ENABLE;
else if (aAddon.type != "theme")
permissions |= AddonManager.PERM_CAN_DISABLE;
}
@@ -6331,17 +6405,17 @@ function AddonWrapper(aAddon) {
this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
return aAddon.softDisabled || aAddon.userDisabled;
});
this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) {
if (val == this.userDisabled)
return val;
- if (aAddon.inDatabase) {
+ if (aAddon instanceof DBAddonInternal) {
if (aAddon.type == "theme" && val) {
if (aAddon.internalName == XPIProvider.defaultSkin)
throw new Error("Cannot disable the default theme");
XPIProvider.enableDefaultTheme();
}
else {
XPIProvider.updateAddonDisabledState(aAddon, val);
}
@@ -6355,17 +6429,17 @@ function AddonWrapper(aAddon) {
return val;
});
this.__defineSetter__("softDisabled", function AddonWrapper_softDisabledSetter(val) {
if (val == aAddon.softDisabled)
return val;
- if (aAddon.inDatabase) {
+ if (aAddon instanceof DBAddonInternal) {
// When softDisabling a theme just enable the active theme
if (aAddon.type == "theme" && val && !aAddon.userDisabled) {
if (aAddon.internalName == XPIProvider.defaultSkin)
throw new Error("Cannot disable the default theme");
XPIProvider.enableDefaultTheme();
}
else {
XPIProvider.updateAddonDisabledState(aAddon, undefined, val);
@@ -6380,25 +6454,25 @@ function AddonWrapper(aAddon) {
return val;
});
this.isCompatibleWith = function AddonWrapper_isCompatiblewith(aAppVersion, aPlatformVersion) {
return aAddon.isCompatibleWith(aAppVersion, aPlatformVersion);
};
this.uninstall = function AddonWrapper_uninstall() {
- if (!(aAddon.inDatabase))
+ if (!(aAddon instanceof DBAddonInternal))
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");
XPIProvider.uninstallAddon(aAddon);
};
this.cancelUninstall = function AddonWrapper_cancelUninstall() {
- if (!(aAddon.inDatabase))
+ if (!(aAddon instanceof DBAddonInternal))
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");
XPIProvider.cancelUninstallAddon(aAddon);
};
this.findUpdates = function AddonWrapper_findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
new UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion);
--- a/toolkit/mozapps/extensions/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/XPIProviderUtils.js
@@ -13,31 +13,27 @@ Components.utils.import("resource://gre/
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
["LOG", "WARN", "ERROR"].forEach(function(aName) {
- Object.defineProperty(this, aName, {
- get: function logFuncGetter () {
- Components.utils.import("resource://gre/modules/AddonLogging.jsm");
+ this.__defineGetter__(aName, function logFuncGetter () {
+ Components.utils.import("resource://gre/modules/AddonLogging.jsm");
- LogManager.getLogger("addons.xpi-utils", this);
- return this[aName];
- },
- configurable: true
- });
+ LogManager.getLogger("addons.xpi-utils", this);
+ return this[aName];
+ })
}, this);
const KEY_PROFILEDIR = "ProfD";
const FILE_DATABASE = "extensions.sqlite";
-const FILE_JSON_DB = "extensions.json";
const FILE_OLD_DATABASE = "extensions.rdf";
const FILE_XPI_ADDONS_LIST = "extensions.ini";
// The value for this is in Makefile.in
#expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__;
const PREF_DB_SCHEMA = "extensions.databaseSchema";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
@@ -71,40 +67,26 @@ const FIELDS_ADDON = "internal_id, id, s
// Properties that exist in the install manifest
const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
"updateKey", "optionsURL", "optionsType", "aboutURL",
"iconURL", "icon64URL"];
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
-// Properties to save in JSON file
-const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
- "internalName", "updateURL", "updateKey", "optionsURL",
- "optionsType", "aboutURL", "iconURL", "icon64URL",
- "defaultLocale", "visible", "active", "userDisabled",
- "appDisabled", "pendingUninstall", "descriptor", "installDate",
- "updateDate", "applyBackgroundUpdates", "bootstrap",
- "skinnable", "size", "sourceURI", "releaseNotesURI",
- "softDisabled", "foreignInstall", "hasBinaryComponents",
- "strictCompatibility", "locales", "targetApplications",
- "targetPlatforms"];
const PREFIX_ITEM_URI = "urn:mozilla:item:";
const RDFURI_ITEM_ROOT = "urn:mozilla:item:root"
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
-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
+this.__defineGetter__("gRDF", function gRDFGetter() {
+ delete this.gRDF;
+ return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+ getService(Ci.nsIRDFService);
});
function EM_R(aProperty) {
return gRDF.GetResource(PREFIX_NS_EM + aProperty);
}
/**
* Converts an RDF literal, resource or integer into a string.
@@ -187,77 +169,16 @@ AsyncAddonListCallback.prototype = {
handleCompletion: function AsyncAddonListCallback_handleCompletion(aReason) {
this.complete = true;
if (this.addons.length == this.count)
this.callback(this.addons);
}
};
-/**
- * Asynchronously fill in the _repositoryAddon field for one addon
- */
-function getRepositoryAddon(aAddon, aCallback) {
- if (!aAddon) {
- aCallback(aAddon);
- return;
- }
- function completeAddon(aRepositoryAddon) {
- aAddon._repositoryAddon = aRepositoryAddon;
- aAddon.compatibilityOverrides = aRepositoryAddon ?
- aRepositoryAddon.compatibilityOverrides :
- null;
- aCallback(aAddon);
- }
- AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
-}
-
-/**
- * A helper method to asynchronously call a function on an array
- * of objects, calling a callback when function(x) has been gathered
- * for every element of the array.
- * WARNING: not currently error-safe; if the async function does not call
- * our internal callback for any of the array elements, asyncMap will not
- * call the callback parameter.
- *
- * @param aObjects
- * The array of objects to process asynchronously
- * @param aMethod
- * Function with signature function(object, function aCallback(f_of_object))
- * @param aCallback
- * Function with signature f([aMethod(object)]), called when all values
- * are available
- */
-function asyncMap(aObjects, aMethod, aCallback) {
- var resultsPending = aObjects.length;
- var results = []
- if (resultsPending == 0) {
- aCallback(results);
- return;
- }
-
- function asyncMap_gotValue(aIndex, aValue) {
- results[aIndex] = aValue;
- if (--resultsPending == 0) {
- aCallback(results);
- }
- }
-
- aObjects.map(function asyncMap_each(aObject, aIndex, aArray) {
- try {
- aMethod(aObject, function asyncMap_callback(aResult) {
- asyncMap_gotValue(aIndex, aResult);
- });
- }
- catch (e) {
- WARN("Async map function failed", e);
- asyncMap_gotValue(aIndex, undefined);
- }
- });
-}
/**
* A generator to synchronously return result rows from an mozIStorageStatement.
*
* @param aStatement
* The statement to execute
*/
function resultRows(aStatement) {
@@ -367,101 +288,28 @@ function copyRowProperties(aRow, aProper
if (!aTarget)
aTarget = {};
aProperties.forEach(function(aProp) {
aTarget[aProp] = aRow.getResultByName(aProp);
});
return aTarget;
}
-/**
- * Create a DBAddonInternal from the fields saved in the JSON database
- * or loaded into an AddonInternal from an XPI manifest.
- * @return a DBAddonInternal populated with the loaded data
- */
-
-/**
- * The DBAddonInternal is a special AddonInternal that has been retrieved from
- * the database. The constructor will initialize the DBAddonInternal with a set
- * of fields, which could come from either the JSON store or as an
- * XPIProvider.AddonInternal created from an addon's manifest
- * @constructor
- * @param aLoaded
- * Addon data fields loaded from JSON or the addon manifest.
- */
-function DBAddonInternal(aLoaded) {
- copyProperties(aLoaded, PROP_JSON_FIELDS, this);
- if (aLoaded._installLocation) {
- this._installLocation = aLoaded._installLocation;
- this.location = aLoaded._installLocation._name;
- }
- else if (aLoaded.location) {
- this._installLocation = XPIProvider.installLocationsByName[this.location];
- }
- this._key = this.location + ":" + this.id;
- try {
- this._sourceBundle = this._installLocation.getLocationForID(this.id);
- }
- catch (e) {
- // An exception will be thrown if the add-on appears in the database but
- // not on disk. In general this should only happen during startup as
- // this change is being detected.
- }
-
- Object.defineProperty(this, "pendingUpgrade", {
- get: function DBA_pendingUpgradeGetter() {
- delete this.pendingUpgrade;
- for (let install of XPIProvider.installs) {
- if (install.state == AddonManager.STATE_INSTALLED &&
- !(install.addon.inDatabase) &&
- install.addon.id == this.id &&
- install.installLocation == this._installLocation) {
- return this.pendingUpgrade = install.addon;
- }
- };
- },
- configurable: true
- });
-}
-
-DBAddonInternal.prototype = {
- applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
- XPIDatabase.beginTransaction();
- 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;
- }
- });
- });
- XPIProvider.updateAddonDisabledState(this);
- XPIDatabase.commitTransaction();
- },
- get inDatabase() {
- return true;
- }
-}
-
-DBAddonInternal.prototype.__proto__ = AddonInternal.prototype;
-
this.XPIDatabase = {
// true if the database connection has been opened
initialized: false,
// A cache of statements that are used and need to be finalized on shutdown
statementCache: {},
// A cache of weak referenced DBAddonInternals so we can reuse objects where
// possible
addonCache: [],
// The nested transaction count
transactionCount: 0,
// The database file
dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true),
- jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true),
// Migration data loaded from an old version of the database.
migrateData: null,
// Active add-on directories loaded from extensions.ini and prefs at startup.
activeBundles: null,
// The statements used by the database
statements: {
_getDefaultLocale: "SELECT id, name, description, creator, homepageURL " +
@@ -474,16 +322,40 @@ this.XPIDatabase = {
_getTargetApplications: "SELECT addon_internal_id, id, minVersion, " +
"maxVersion FROM targetApplication WHERE " +
"addon_internal_id=:internal_id",
_getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " +
"addon_internal_id=:internal_id",
_readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
"WHERE locale_id=:id",
+ addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " +
+ ":location, :version, :type, :internalName, " +
+ ":updateURL, :updateKey, :optionsURL, " +
+ ":optionsType, :aboutURL, " +
+ ":iconURL, :icon64URL, :locale, :visible, :active, " +
+ ":userDisabled, :appDisabled, :pendingUninstall, " +
+ ":descriptor, :installDate, :updateDate, " +
+ ":applyBackgroundUpdates, :bootstrap, :skinnable, " +
+ ":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
+ ":isForeignInstall, :hasBinaryComponents, " +
+ ":strictCompatibility)",
+ addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
+ "(:internal_id, :name, :locale)",
+ addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
+ "homepageURL) VALUES (:name, :description, " +
+ ":creator, :homepageURL)",
+ addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
+ ":type, :value)",
+ addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
+ "(:internal_id, :id, :minVersion, " +
+ ":maxVersion)",
+ addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " +
+ "(:internal_id, :os, :abi)",
+
clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id",
updateAddonActive: "UPDATE addon SET active=:active WHERE " +
"internal_id=:internal_id",
getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
"type<>'theme' AND bootstrap=0",
getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
"internalName=:internalName AND type='theme'",
@@ -537,152 +409,60 @@ this.XPIDatabase = {
return this.dbfileExists = this.dbfile.exists();
},
set dbfileExists(aValue) {
delete this.dbfileExists;
return this.dbfileExists = aValue;
},
/**
- * Converts the current internal state of the XPI addon database to JSON
- * and writes it to the user's profile. Synchronous for now, eventually must
- * be async, reliable, etc.
- */
- writeJSON: function XPIDB_writeJSON() {
- // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet
- let addons = [];
- for (let aKey in this.addonDB) {
- addons.push(copyProperties(this.addonDB[aKey], PROP_JSON_FIELDS));
- }
- let toSave = {
- schemaVersion: DB_SCHEMA,
- addons: addons
- };
-
- let stream = FileUtils.openSafeFileOutputStream(this.jsonFile);
- let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
- createInstance(Ci.nsIConverterOutputStream);
- try {
- converter.init(stream, "UTF-8", 0, 0x0000);
- // XXX pretty print the JSON while debugging
- converter.writeString(JSON.stringify(toSave, null, 2));
- converter.flush();
- // nsConverterOutputStream doesn't finish() safe output streams on close()
- FileUtils.closeSafeFileOutputStream(stream);
- converter.close();
- }
- catch(e) {
- ERROR("Failed to save database to JSON", e);
- stream.close();
- }
- },
-
- /**
- * Open and parse the JSON XPI extensions database.
- * @return true: the DB was successfully loaded
- * false: The DB either needs upgrade or did not exist at all.
- * XXX upgrade and errors handled in a following patch
- */
- openJSONDatabase: function XPIDB_openJSONDatabase() {
- dump("XPIDB_openJSONDatabase\n");
- try {
- 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(this.jsonFile, -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();
- let inputAddons = JSON.parse(data);
- // Now do some sanity checks on our JSON db
- if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
- // XXX Content of JSON file is bad, need to rebuild from scratch
- ERROR("bad JSON file contents");
- delete this.addonDB;
- this.addonDB = {};
- return false;
- }
- if (inputAddons.schemaVersion != DB_SCHEMA) {
- // XXX UPGRADE FROM PREVIOUS VERSION OF JSON DB
- ERROR("JSON schema upgrade needed");
- return false;
- }
- // If we got here, we probably have good data
- // Make AddonInternal instances from the loaded data and save them
- delete this.addonDB;
- let addonDB = {}
- inputAddons.addons.forEach(function(loadedAddon) {
- let newAddon = new DBAddonInternal(loadedAddon);
- addonDB[newAddon._key] = newAddon;
- });
- this.addonDB = addonDB;
- // dump("Finished reading DB: " + this.addonDB.toSource() + "\n");
- return true;
- }
- catch(e) {
- // XXX handle missing JSON database
- ERROR("Failed to load XPI JSON data from profile", e);
- // XXX for now, start from scratch
- delete this.addonDB;
- this.addonDB = {};
- return false;
- }
- },
-
- /**
* Begins a new transaction in the database. Transactions may be nested. Data
* written by an inner transaction may be rolled back on its own. Rolling back
* an outer transaction will rollback all the changes made by inner
* transactions even if they were committed. No data is written to the disk
* until the outermost transaction is committed. Transactions can be started
* even when the database is not yet open in which case they will be started
* when the database is first opened.
*/
beginTransaction: function XPIDB_beginTransaction() {
+ if (this.initialized)
+ this.getStatement("createSavepoint").execute();
this.transactionCount++;
},
/**
* Commits the most recent transaction. The data may still be rolled back if
* an outer transaction is rolled back.
*/
commitTransaction: function XPIDB_commitTransaction() {
if (this.transactionCount == 0) {
ERROR("Attempt to commit one transaction too many.");
return;
}
+ if (this.initialized)
+ this.getStatement("releaseSavepoint").execute();
this.transactionCount--;
-
- if (this.transactionCount == 0) {
- // All our nested transactions are done, write the JSON file
- this.writeJSON();
- }
},
/**
* Rolls back the most recent transaction. The database will return to its
* state when the transaction was started.
*/
rollbackTransaction: function XPIDB_rollbackTransaction() {
if (this.transactionCount == 0) {
ERROR("Attempt to rollback one transaction too many.");
return;
}
+ if (this.initialized) {
+ this.getStatement("rollbackSavepoint").execute();
+ this.getStatement("releaseSavepoint").execute();
+ }
this.transactionCount--;
- // XXX IRVING we don't handle rollback in the JSON store
},
/**
* Attempts to open the database file. If it fails it will try to delete the
* existing file and create an empty database. If that fails then it will
* open an in-memory database that can be used during this session.
*
* @param aDBFile
@@ -709,17 +489,17 @@ this.XPIDatabase = {
catch (e) {
ERROR("Failed to remove database that could not be opened", e);
}
try {
connection = Services.storage.openUnsharedDatabase(aDBFile);
}
catch (e) {
ERROR("Failed to open database (2nd attempt)", e);
-
+
// If we have got here there seems to be no way to open the real
// database, instead open a temporary memory database so things will
// work for this session.
return Services.storage.openSpecialDatabase("memory");
}
}
else {
return Services.storage.openSpecialDatabase("memory");
@@ -733,30 +513,28 @@ this.XPIDatabase = {
},
/**
* Opens a new connection to the database file.
*
* @param aRebuildOnError
* A boolean indicating whether add-on information should be loaded
* from the install locations if the database needs to be rebuilt.
+ * @return the migration data from the database if it was an old schema or
+ * null otherwise.
*/
openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
- this.openJSONDatabase();
- this.initialized = true;
- return;
- // XXX IRVING deal with the migration logic below and in openDatabaseFile...
-
delete this.connection;
if (!aForceOpen && !this.dbfileExists) {
this.connection = null;
- return;
+ return {};
}
+ this.initialized = true;
this.migrateData = null;
this.connection = this.openDatabaseFile(this.dbfile);
// If the database was corrupt or missing then the new blank database will
// have a schema version of 0.
let schemaVersion = this.connection.schemaVersion;
if (schemaVersion != DB_SCHEMA) {
@@ -835,22 +613,21 @@ this.XPIDatabase = {
}
// Begin any pending transactions
for (let i = 0; i < this.transactionCount; i++)
this.connection.executeSimpleSQL("SAVEPOINT 'default'");
},
/**
- * Lazy getter for the addons database
+ * A lazy getter for the database connection.
*/
- get addonDB() {
- delete this.addonDB;
- this.openJSONDatabase();
- return this.addonDB;
+ get connection() {
+ this.openConnection(true);
+ return this.connection;
},
/**
* Gets the list of file descriptors of active extension directories or XPI
* files from the add-ons list. This must be loaded from disk since the
* directory service gives no easy way to get both directly. This list doesn't
* include themes as preferences already say which theme is currently active
*
@@ -1001,18 +778,16 @@ this.XPIDatabase = {
if (!(row.location in migrateData))
migrateData[row.location] = {};
let addonData = {
targetApplications: []
}
migrateData[row.location][row.id] = addonData;
props.forEach(function(aProp) {
- if (aProp == "isForeignInstall")
- addonData.foreignInstall = (row[aProp] == 1);
if (DB_BOOL_METADATA.indexOf(aProp) != -1)
addonData[aProp] = row[aProp] == 1;
else
addonData[aProp] = row[aProp];
})
}
var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
@@ -1050,481 +825,1078 @@ this.XPIDatabase = {
},
/**
* Shuts down the database connection and releases all cached objects.
*/
shutdown: function XPIDB_shutdown(aCallback) {
LOG("shutdown");
if (this.initialized) {
+ for each (let stmt in this.statementCache)
+ stmt.finalize();
+ this.statementCache = {};
+ this.addonCache = [];
+
if (this.transactionCount > 0) {
ERROR(this.transactionCount + " outstanding transactions, rolling back.");
while (this.transactionCount > 0)
this.rollbackTransaction();
}
// If we are running with an in-memory database then force a new
// extensions.ini to be written to disk on the next startup
- // XXX IRVING special case for if we fail to save extensions.json?
- // XXX maybe doesn't need to be at shutdown?
- // if (!this.connection.databaseFile)
- // Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+ if (!this.connection.databaseFile)
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
this.initialized = false;
+ let connection = this.connection;
+ delete this.connection;
- // Clear out the cached addons data loaded from JSON and recreate
- // the getter to allow database re-loads during testing.
- delete this.addonDB;
- Object.defineProperty(this, "addonDB", {
- get: function addonsGetter() {
- this.openJSONDatabase();
- return this.addonDB;
- },
- configurable: true
+ // Re-create the connection smart getter to allow the database to be
+ // re-loaded during testing.
+ this.__defineGetter__("connection", function connectionGetter() {
+ this.openConnection(true);
+ return this.connection;
});
- // XXX IRVING removed an async callback when the database was closed
- // XXX do we want to keep the ability to async flush extensions.json
- // XXX and then call back?
- if (aCallback)
+
+ connection.asyncClose(function shutdown_asyncClose() {
+ LOG("Database closed");
aCallback();
+ });
}
else {
if (aCallback)
aCallback();
}
},
/**
- * Return a list of all install locations known about by the database. This
+ * Gets a cached statement or creates a new statement if it doesn't already
+ * exist.
+ *
+ * @param key
+ * A unique key to reference the statement
+ * @param aSql
+ * An optional SQL string to use for the query, otherwise a
+ * predefined sql string for the key will be used.
+ * @return a mozIStorageStatement for the passed SQL
+ */
+ getStatement: function XPIDB_getStatement(aKey, aSql) {
+ if (aKey in this.statementCache)
+ return this.statementCache[aKey];
+ if (!aSql)
+ aSql = this.statements[aKey];
+
+ try {
+ return this.statementCache[aKey] = this.connection.createStatement(aSql);
+ }
+ catch (e) {
+ ERROR("Error creating statement " + aKey + " (" + aSql + ")");
+ throw e;
+ }
+ },
+
+ /**
+ * Creates the schema in the database.
+ */
+ createSchema: function XPIDB_createSchema() {
+ LOG("Creating database schema");
+ this.beginTransaction();
+
+ // Any errors in here should rollback the transaction
+ try {
+ this.connection.createTable("addon",
+ "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ "id TEXT, syncGUID TEXT, " +
+ "location TEXT, version TEXT, " +
+ "type TEXT, internalName TEXT, updateURL TEXT, " +
+ "updateKey TEXT, optionsURL TEXT, " +
+ "optionsType TEXT, aboutURL TEXT, iconURL TEXT, " +
+ "icon64URL TEXT, defaultLocale INTEGER, " +
+ "visible INTEGER, active INTEGER, " +
+ "userDisabled INTEGER, appDisabled INTEGER, " +
+ "pendingUninstall INTEGER, descriptor TEXT, " +
+ "installDate INTEGER, updateDate INTEGER, " +
+ "applyBackgroundUpdates INTEGER, " +
+ "bootstrap INTEGER, skinnable INTEGER, " +
+ "size INTEGER, sourceURI TEXT, " +
+ "releaseNotesURI TEXT, softDisabled INTEGER, " +
+ "isForeignInstall INTEGER, " +
+ "hasBinaryComponents INTEGER, " +
+ "strictCompatibility INTEGER, " +
+ "UNIQUE (id, location), " +
+ "UNIQUE (syncGUID)");
+ this.connection.createTable("targetApplication",
+ "addon_internal_id INTEGER, " +
+ "id TEXT, minVersion TEXT, maxVersion TEXT, " +
+ "UNIQUE (addon_internal_id, id)");
+ this.connection.createTable("targetPlatform",
+ "addon_internal_id INTEGER, " +
+ "os, abi TEXT, " +
+ "UNIQUE (addon_internal_id, os, abi)");
+ this.connection.createTable("addon_locale",
+ "addon_internal_id INTEGER, "+
+ "locale TEXT, locale_id INTEGER, " +
+ "UNIQUE (addon_internal_id, locale)");
+ this.connection.createTable("locale",
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ "name TEXT, description TEXT, creator TEXT, " +
+ "homepageURL TEXT");
+ this.connection.createTable("locale_strings",
+ "locale_id INTEGER, type TEXT, value TEXT");
+ this.connection.executeSimpleSQL("CREATE INDEX locale_strings_idx ON " +
+ "locale_strings (locale_id)");
+ this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
+ "ON addon BEGIN " +
+ "DELETE FROM targetApplication WHERE addon_internal_id=old.internal_id; " +
+ "DELETE FROM targetPlatform WHERE addon_internal_id=old.internal_id; " +
+ "DELETE FROM addon_locale WHERE addon_internal_id=old.internal_id; " +
+ "DELETE FROM locale WHERE id=old.defaultLocale; " +
+ "END");
+ this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon_locale AFTER " +
+ "DELETE ON addon_locale WHEN NOT EXISTS " +
+ "(SELECT * FROM addon_locale WHERE locale_id=old.locale_id) BEGIN " +
+ "DELETE FROM locale WHERE id=old.locale_id; " +
+ "END");
+ this.connection.executeSimpleSQL("CREATE TRIGGER delete_locale AFTER " +
+ "DELETE ON locale BEGIN " +
+ "DELETE FROM locale_strings WHERE locale_id=old.id; " +
+ "END");
+ this.connection.schemaVersion = DB_SCHEMA;
+ this.commitTransaction();
+ }
+ catch (e) {
+ ERROR("Failed to create database schema", e);
+ logSQLError(this.connection.lastError, this.connection.lastErrorString);
+ this.rollbackTransaction();
+ this.connection.close();
+ this.connection = null;
+ throw e;
+ }
+ },
+
+ /**
+ * Synchronously reads the multi-value locale strings for a locale
+ *
+ * @param aLocale
+ * The locale object to read into
+ */
+ _readLocaleStrings: function XPIDB__readLocaleStrings(aLocale) {
+ let stmt = this.getStatement("_readLocaleStrings");
+
+ stmt.params.id = aLocale.id;
+ for (let row in resultRows(stmt)) {
+ if (!(row.type in aLocale))
+ aLocale[row.type] = [];
+ aLocale[row.type].push(row.value);
+ }
+ },
+
+ /**
+ * Synchronously reads the locales for an add-on
+ *
+ * @param aAddon
+ * The DBAddonInternal to read the locales for
+ * @return the array of locales
+ */
+ _getLocales: function XPIDB__getLocales(aAddon) {
+ let stmt = this.getStatement("_getLocales");
+
+ let locales = [];
+ stmt.params.internal_id = aAddon._internal_id;
+ for (let row in resultRows(stmt)) {
+ let locale = {
+ id: row.id,
+ locales: [row.locale]
+ };
+ copyProperties(row, PROP_LOCALE_SINGLE, locale);
+ locales.push(locale);
+ }
+ locales.forEach(function(aLocale) {
+ this._readLocaleStrings(aLocale);
+ }, this);
+ return locales;
+ },
+
+ /**
+ * Synchronously reads the default locale for an add-on
+ *
+ * @param aAddon
+ * The DBAddonInternal to read the default locale for
+ * @return the default locale for the add-on
+ * @throws if the database does not contain the default locale information
+ */
+ _getDefaultLocale: function XPIDB__getDefaultLocale(aAddon) {
+ let stmt = this.getStatement("_getDefaultLocale");
+
+ stmt.params.id = aAddon._defaultLocale;
+ if (!stepStatement(stmt))
+ throw new Error("Missing default locale for " + aAddon.id);
+ let locale = copyProperties(stmt.row, PROP_LOCALE_SINGLE);
+ locale.id = aAddon._defaultLocale;
+ stmt.reset();
+ this._readLocaleStrings(locale);
+ return locale;
+ },
+
+ /**
+ * Synchronously reads the target application entries for an add-on
+ *
+ * @param aAddon
+ * The DBAddonInternal to read the target applications for
+ * @return an array of target applications
+ */
+ _getTargetApplications: function XPIDB__getTargetApplications(aAddon) {
+ let stmt = this.getStatement("_getTargetApplications");
+
+ stmt.params.internal_id = aAddon._internal_id;
+ return [copyProperties(row, PROP_TARGETAPP) for each (row in resultRows(stmt))];
+ },
+
+ /**
+ * Synchronously reads the target platform entries for an add-on
+ *
+ * @param aAddon
+ * The DBAddonInternal to read the target platforms for
+ * @return an array of target platforms
+ */
+ _getTargetPlatforms: function XPIDB__getTargetPlatforms(aAddon) {
+ let stmt = this.getStatement("_getTargetPlatforms");
+
+ stmt.params.internal_id = aAddon._internal_id;
+ return [copyProperties(row, ["os", "abi"]) for each (row in resultRows(stmt))];
+ },
+
+ /**
+ * Synchronously makes a DBAddonInternal from a storage row or returns one
+ * from the cache.
+ *
+ * @param aRow
+ * The storage row to make the DBAddonInternal from
+ * @return a DBAddonInternal
+ */
+ makeAddonFromRow: function XPIDB_makeAddonFromRow(aRow) {
+ if (this.addonCache[aRow.internal_id]) {
+ let addon = this.addonCache[aRow.internal_id].get();
+ if (addon)
+ return addon;
+ }
+
+ let addon = new XPIProvider.DBAddonInternal();
+ addon._internal_id = aRow.internal_id;
+ addon._installLocation = XPIProvider.installLocationsByName[aRow.location];
+ addon._descriptor = aRow.descriptor;
+ addon._defaultLocale = aRow.defaultLocale;
+ copyProperties(aRow, PROP_METADATA, addon);
+ copyProperties(aRow, DB_METADATA, addon);
+ DB_BOOL_METADATA.forEach(function(aProp) {
+ addon[aProp] = aRow[aProp] != 0;
+ });
+ try {
+ addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
+ }
+ catch (e) {
+ // An exception will be thrown if the add-on appears in the database but
+ // not on disk. In general this should only happen during startup as
+ // this change is being detected.
+ }
+
+ this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon);
+ return addon;
+ },
+
+ /**
+ * Asynchronously fetches additional metadata for a DBAddonInternal.
+ *
+ * @param aAddon
+ * The DBAddonInternal
+ * @param aCallback
+ * The callback to call when the metadata is completely retrieved
+ */
+ fetchAddonMetadata: function XPIDB_fetchAddonMetadata(aAddon) {
+ function readLocaleStrings(aLocale, aCallback) {
+ let stmt = XPIDatabase.getStatement("_readLocaleStrings");
+
+ stmt.params.id = aLocale.id;
+ stmt.executeAsync({
+ handleResult: function readLocaleStrings_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let type = row.getResultByName("type");
+ if (!(type in aLocale))
+ aLocale[type] = [];
+ aLocale[type].push(row.getResultByName("value"));
+ }
+ },
+
+ handleError: asyncErrorLogger,
+
+ handleCompletion: function readLocaleStrings_handleCompletion(aReason) {
+ aCallback();
+ }
+ });
+ }
+
+ function readDefaultLocale() {
+ delete aAddon.defaultLocale;
+ let stmt = XPIDatabase.getStatement("_getDefaultLocale");
+
+ stmt.params.id = aAddon._defaultLocale;
+ stmt.executeAsync({
+ handleResult: function readDefaultLocale_handleResult(aResults) {
+ aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(),
+ PROP_LOCALE_SINGLE);
+ aAddon.defaultLocale.id = aAddon._defaultLocale;
+ },
+
+ handleError: asyncErrorLogger,
+
+ handleCompletion: function readDefaultLocale_handleCompletion(aReason) {
+ if (aAddon.defaultLocale) {
+ readLocaleStrings(aAddon.defaultLocale, readLocales);
+ }
+ else {
+ ERROR("Missing default locale for " + aAddon.id);
+ readLocales();
+ }
+ }
+ });
+ }
+
+ function readLocales() {
+ delete aAddon.locales;
+ aAddon.locales = [];
+ let stmt = XPIDatabase.getStatement("_getLocales");
+
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.executeAsync({
+ handleResult: function readLocales_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let locale = {
+ id: row.getResultByName("id"),
+ locales: [row.getResultByName("locale")]
+ };
+ copyRowProperties(row, PROP_LOCALE_SINGLE, locale);
+ aAddon.locales.push(locale);
+ }
+ },
+
+ handleError: asyncErrorLogger,
+
+ handleCompletion: function readLocales_handleCompletion(aReason) {
+ let pos = 0;
+ function readNextLocale() {
+ if (pos < aAddon.locales.length)
+ readLocaleStrings(aAddon.locales[pos++], readNextLocale);
+ else
+ readTargetApplications();
+ }
+
+ readNextLocale();
+ }
+ });
+ }
+
+ function readTargetApplications() {
+ delete aAddon.targetApplications;
+ aAddon.targetApplications = [];
+ let stmt = XPIDatabase.getStatement("_getTargetApplications");
+
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.executeAsync({
+ handleResult: function readTargetApplications_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow()))
+ aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP));
+ },
+
+ handleError: asyncErrorLogger,
+
+ handleCompletion: function readTargetApplications_handleCompletion(aReason) {
+ readTargetPlatforms();
+ }
+ });
+ }
+
+ function readTargetPlatforms() {
+ delete aAddon.targetPlatforms;
+ aAddon.targetPlatforms = [];
+ let stmt = XPIDatabase.getStatement("_getTargetPlatforms");
+
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.executeAsync({
+ handleResult: function readTargetPlatforms_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow()))
+ aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"]));
+ },
+
+ handleError: asyncErrorLogger,
+
+ handleCompletion: function readTargetPlatforms_handleCompletion(aReason) {
+ let callbacks = aAddon._pendingCallbacks;
+ delete aAddon._pendingCallbacks;
+ callbacks.forEach(function(aCallback) {
+ aCallback(aAddon);
+ });
+ }
+ });
+ }
+
+ readDefaultLocale();
+ },
+
+ /**
+ * Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one
+ * from the cache.
+ *
+ * @param aRow
+ * The mozIStorageRow to make the DBAddonInternal from
+ * @return a DBAddonInternal
+ */
+ makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) {
+ let internal_id = aRow.getResultByName("internal_id");
+ if (this.addonCache[internal_id]) {
+ let addon = this.addonCache[internal_id].get();
+ if (addon) {
+ // If metadata is still pending for this instance add our callback to
+ // the list to be called when complete, otherwise pass the addon to
+ // our callback
+ if ("_pendingCallbacks" in addon)
+ addon._pendingCallbacks.push(aCallback);
+ else
+ aCallback(addon);
+ return;
+ }
+ }
+
+ let addon = new XPIProvider.DBAddonInternal();
+ addon._internal_id = internal_id;
+ let location = aRow.getResultByName("location");
+ addon._installLocation = XPIProvider.installLocationsByName[location];
+ addon._descriptor = aRow.getResultByName("descriptor");
+ copyRowProperties(aRow, PROP_METADATA, addon);
+ addon._defaultLocale = aRow.getResultByName("defaultLocale");
+ copyRowProperties(aRow, DB_METADATA, addon);
+ DB_BOOL_METADATA.forEach(function(aProp) {
+ addon[aProp] = aRow.getResultByName(aProp) != 0;
+ });
+ try {
+ addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
+ }
+ catch (e) {
+ // An exception will be thrown if the add-on appears in the database but
+ // not on disk. In general this should only happen during startup as
+ // this change is being detected.
+ }
+
+ this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
+ addon._pendingCallbacks = [aCallback];
+ this.fetchAddonMetadata(addon);
+ },
+
+ /**
+ * Synchronously reads all install locations known about by the database. This
* is often a a subset of the total install locations when not all have
* installed add-ons, occasionally a superset when an install location no
* longer exists.
*
* @return an array of names of install locations
*/
getInstallLocations: function XPIDB_getInstallLocations() {
- if (!this.addonDB)
- return [];
-
- let locations = {};
- for each (let addon in this.addonDB) {
- locations[addon.location] = 1;
- }
- return Object.keys(locations);
- },
-
- /**
- * List all addons that match the filter function
- * @param aFilter
- * Function that takes an addon instance and returns
- * true if that addon should be included in the selected array
- * @return an array of DBAddonInternals
- */
- _listAddons: function XPIDB_listAddons(aFilter) {
- if (!this.addonDB)
+ if (!this.connection)
return [];
- let addonList = [];
- for (let key in this.addonDB) {
- let addon = this.addonDB[key];
- if (aFilter(addon)) {
- addonList.push(addon);
- }
- }
-
- return addonList;
- },
+ let stmt = this.getStatement("getInstallLocations");
- /**
- * Find the first addon that matches the filter function
- * @param aFilter
- * Function that takes an addon instance and returns
- * true if that addon should be selected
- * @return The first DBAddonInternal for which the filter returns true
- */
- _findAddon: function XPIDB_findAddon(aFilter) {
- if (!this.addonDB)
- return null;
-
- for (let key in this.addonDB) {
- let addon = this.addonDB[key];
- if (aFilter(addon)) {
- return addon;
- }
- }
-
- return null;
+ return [row.location for each (row in resultRows(stmt))];
},
/**
* Synchronously reads all the add-ons in a particular install location.
*
- * @param aLocation
+ * @param location
* The name of the install location
* @return an array of DBAddonInternals
*/
getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) {
- return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);});
+ if (!this.connection)
+ return [];
+
+ let stmt = this.getStatement("getAddonsInLocation");
+
+ stmt.params.location = aLocation;
+ return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
},
/**
* Asynchronously gets an add-on with a particular ID in a particular
* install location.
- * XXX IRVING sync for now
*
* @param aId
* The ID of the add-on to retrieve
* @param aLocation
* The name of the install location
* @param aCallback
* A callback to pass the DBAddonInternal to
*/
getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
- getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback);
+ if (!this.connection) {
+ aCallback(null);
+ return;
+ }
+
+ let stmt = this.getStatement("getAddonInLocation");
+
+ stmt.params.id = aId;
+ stmt.params.location = aLocation;
+ stmt.executeAsync(new AsyncAddonListCallback(function getAddonInLocation_executeAsync(aAddons) {
+ if (aAddons.length == 0) {
+ aCallback(null);
+ return;
+ }
+ // This should never happen but indicates invalid data in the database if
+ // it does
+ if (aAddons.length > 1)
+ ERROR("Multiple addons with ID " + aId + " found in location " + aLocation);
+ aCallback(aAddons[0]);
+ }));
},
/**
* Asynchronously gets the add-on with an ID that is visible.
- * XXX IRVING sync
*
* @param aId
* The ID of the add-on to retrieve
* @param aCallback
* A callback to pass the DBAddonInternal to
*/
getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
- let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)});
- getRepositoryAddon(addon, aCallback);
+ if (!this.connection) {
+ aCallback(null);
+ return;
+ }
+
+ let stmt = this.getStatement("getVisibleAddonForID");
+
+ stmt.params.id = aId;
+ stmt.executeAsync(new AsyncAddonListCallback(function getVisibleAddonForID_executeAsync(aAddons) {
+ if (aAddons.length == 0) {
+ aCallback(null);
+ return;
+ }
+ // This should never happen but indicates invalid data in the database if
+ // it does
+ if (aAddons.length > 1)
+ ERROR("Multiple visible addons with ID " + aId + " found");
+ aCallback(aAddons[0]);
+ }));
},
/**
* Asynchronously gets the visible add-ons, optionally restricting by type.
- * XXX IRVING sync
*
* @param aTypes
* An array of types to include or null to include all types
* @param aCallback
* A callback to pass the array of DBAddonInternals to
*/
getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
- let addons = this._listAddons(function visibleType(aAddon) {
- return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1)))
- });
- asyncMap(addons, getRepositoryAddon, aCallback);
+ if (!this.connection) {
+ aCallback([]);
+ return;
+ }
+
+ let stmt = null;
+ if (!aTypes || aTypes.length == 0) {
+ stmt = this.getStatement("getVisibleAddons");
+ }
+ else {
+ let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " +
+ "type IN (";
+ for (let i = 1; i <= aTypes.length; i++) {
+ sql += "?" + i;
+ if (i < aTypes.length)
+ sql += ",";
+ }
+ sql += ")";
+
+ // Note that binding to index 0 sets the value for the ?1 parameter
+ stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql);
+ for (let i = 0; i < aTypes.length; i++)
+ stmt.bindByIndex(i, aTypes[i]);
+ }
+
+ stmt.executeAsync(new AsyncAddonListCallback(aCallback));
},
/**
* Synchronously gets all add-ons of a particular type.
*
* @param aType
* The type of add-on to retrieve
* @return an array of DBAddonInternals
*/
getAddonsByType: function XPIDB_getAddonsByType(aType) {
- return this._listAddons(function byType(aAddon) { return aAddon.type == aType; });
+ if (!this.connection)
+ return [];
+
+ let stmt = this.getStatement("getAddonsByType");
+
+ stmt.params.type = aType;
+ return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
},
/**
* Synchronously gets an add-on with a particular internalName.
*
* @param aInternalName
* The internalName of the add-on to retrieve
* @return a DBAddonInternal
*/
getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
- return this._findAddon(function visibleInternalName(aAddon) {
- return (aAddon.visible && (aAddon.internalName == aInternalName));
- });
+ if (!this.connection)
+ return null;
+
+ let stmt = this.getStatement("getVisibleAddonForInternalName");
+
+ let addon = null;
+ stmt.params.internalName = aInternalName;
+
+ if (stepStatement(stmt))
+ addon = this.makeAddonFromRow(stmt.row);
+
+ stmt.reset();
+ return addon;
},
/**
* Asynchronously gets all add-ons with pending operations.
- * XXX IRVING sync
*
* @param aTypes
* The types of add-ons to retrieve or null to get all types
* @param aCallback
* A callback to pass the array of DBAddonInternal to
*/
getVisibleAddonsWithPendingOperations:
function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
+ if (!this.connection) {
+ aCallback([]);
+ return;
+ }
- let addons = this._listAddons(function visibleType(aAddon) {
- return (aAddon.visible &&
- (aAddon.pendingUninstall ||
- // Logic here is tricky. If we're active but either
- // disabled flag is set, we're pending disable; if we're not
- // active and neither disabled flag is set, we're pending enable
- (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) &&
- (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1)))
- });
- asyncMap(addons, getRepositoryAddon, aCallback);
+ let stmt = null;
+ if (!aTypes || aTypes.length == 0) {
+ stmt = this.getStatement("getVisibleAddonsWithPendingOperations");
+ }
+ else {
+ let sql = "SELECT * FROM addon WHERE visible=1 AND " +
+ "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " +
+ "AND type IN (";
+ for (let i = 1; i <= aTypes.length; i++) {
+ sql += "?" + i;
+ if (i < aTypes.length)
+ sql += ",";
+ }
+ sql += ")";
+
+ // Note that binding to index 0 sets the value for the ?1 parameter
+ stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" +
+ aTypes.length, sql);
+ for (let i = 0; i < aTypes.length; i++)
+ stmt.bindByIndex(i, aTypes[i]);
+ }
+
+ stmt.executeAsync(new AsyncAddonListCallback(aCallback));
},
/**
* Asynchronously get an add-on by its Sync GUID.
- * XXX IRVING sync
*
* @param aGUID
* Sync GUID of add-on to fetch
* @param aCallback
* A callback to pass the DBAddonInternal record to. Receives null
* if no add-on with that GUID is found.
*
*/
getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
- let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; });
- getRepositoryAddon(addon, aCallback);
+ let stmt = this.getStatement("getAddonBySyncGUID");
+ stmt.params.syncGUID = aGUID;
+
+ stmt.executeAsync(new AsyncAddonListCallback(function getAddonBySyncGUID_executeAsync(aAddons) {
+ if (aAddons.length == 0) {
+ aCallback(null);
+ return;
+ }
+ aCallback(aAddons[0]);
+ }));
},
/**
* Synchronously gets all add-ons in the database.
*
* @return an array of DBAddonInternals
*/
getAddons: function XPIDB_getAddons() {
- return this._listAddons(function(aAddon) {return true;});
+ if (!this.connection)
+ return [];
+
+ let stmt = this.getStatement("getAddons");
+
+ return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
},
/**
* Synchronously adds an AddonInternal's metadata to the database.
*
* @param aAddon
* AddonInternal to add
* @param aDescriptor
* The file descriptor of the add-on
- * @return The DBAddonInternal that was added to the database
*/
addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) {
// If there is no DB yet then forcibly create one
- // XXX IRVING I don't think this will work as expected because the addonDB
- // getter will kick in. Might not matter because of the way the new DB
- // creates itself.
- if (!this.addonDB)
+ if (!this.connection)
this.openConnection(false, true);
this.beginTransaction();
- let newAddon = new DBAddonInternal(aAddon);
- newAddon.descriptor = aDescriptor;
- this.addonDB[newAddon._key] = newAddon;
- if (newAddon.visible) {
- this.makeAddonVisible(newAddon);
+ var self = this;
+ function insertLocale(aLocale) {
+ let localestmt = self.getStatement("addAddonMetadata_locale");
+ let stringstmt = self.getStatement("addAddonMetadata_strings");
+
+ copyProperties(aLocale, PROP_LOCALE_SINGLE, localestmt.params);
+ executeStatement(localestmt);
+ let row = XPIDatabase.connection.lastInsertRowID;
+
+ PROP_LOCALE_MULTI.forEach(function(aProp) {
+ aLocale[aProp].forEach(function(aStr) {
+ stringstmt.params.locale = row;
+ stringstmt.params.type = aProp;
+ stringstmt.params.value = aStr;
+ executeStatement(stringstmt);
+ });
+ });
+ return row;
}
- this.commitTransaction();
- return newAddon;
+ // Any errors in here should rollback the transaction
+ try {
+
+ if (aAddon.visible) {
+ let stmt = this.getStatement("clearVisibleAddons");
+ stmt.params.id = aAddon.id;
+ executeStatement(stmt);
+ }
+
+ let stmt = this.getStatement("addAddonMetadata_addon");
+
+ stmt.params.locale = insertLocale(aAddon.defaultLocale);
+ stmt.params.location = aAddon._installLocation.name;
+ stmt.params.descriptor = aDescriptor;
+ copyProperties(aAddon, PROP_METADATA, stmt.params);
+ copyProperties(aAddon, DB_METADATA, stmt.params);
+ DB_BOOL_METADATA.forEach(function(aProp) {
+ stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
+ });
+ executeStatement(stmt);
+ let internal_id = this.connection.lastInsertRowID;
+
+ stmt = this.getStatement("addAddonMetadata_addon_locale");
+ aAddon.locales.forEach(function(aLocale) {
+ let id = insertLocale(aLocale);
+ aLocale.locales.forEach(function(aName) {
+ stmt.params.internal_id = internal_id;
+ stmt.params.name = aName;
+ stmt.params.locale = id;
+ executeStatement(stmt);
+ });
+ });
+
+ stmt = this.getStatement("addAddonMetadata_targetApplication");
+
+ aAddon.targetApplications.forEach(function(aApp) {
+ stmt.params.internal_id = internal_id;
+ stmt.params.id = aApp.id;
+ stmt.params.minVersion = aApp.minVersion;
+ stmt.params.maxVersion = aApp.maxVersion;
+ executeStatement(stmt);
+ });
+
+ stmt = this.getStatement("addAddonMetadata_targetPlatform");
+
+ aAddon.targetPlatforms.forEach(function(aPlatform) {
+ stmt.params.internal_id = internal_id;
+ stmt.params.os = aPlatform.os;
+ stmt.params.abi = aPlatform.abi;
+ executeStatement(stmt);
+ });
+
+ this.commitTransaction();
+ }
+ catch (e) {
+ this.rollbackTransaction();
+ throw e;
+ }
},
/**
* Synchronously updates an add-ons metadata in the database. Currently just
* removes and recreates.
*
* @param aOldAddon
* The DBAddonInternal to be replaced
* @param aNewAddon
* The new AddonInternal to add
* @param aDescriptor
* The file descriptor of the add-on
- * @return The DBAddonInternal that was added to the database
*/
updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
aDescriptor) {
this.beginTransaction();
// Any errors in here should rollback the transaction
try {
this.removeAddonMetadata(aOldAddon);
aNewAddon.syncGUID = aOldAddon.syncGUID;
aNewAddon.installDate = aOldAddon.installDate;
aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
aNewAddon.foreignInstall = aOldAddon.foreignInstall;
aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
!aNewAddon.appDisabled && !aNewAddon.pendingUninstall)
- let newDBAddon = this.addAddonMetadata(aNewAddon, aDescriptor);
+ this.addAddonMetadata(aNewAddon, aDescriptor);
this.commitTransaction();
- return newDBAddon;
+ }
+ catch (e) {
+ this.rollbackTransaction();
+ throw e;
+ }
+ },
+
+ /**
+ * Synchronously updates the target application entries for an add-on.
+ *
+ * @param aAddon
+ * The DBAddonInternal being updated
+ * @param aTargets
+ * The array of target applications to update
+ */
+ updateTargetApplications: function XPIDB_updateTargetApplications(aAddon,
+ aTargets) {
+ this.beginTransaction();
+
+ // Any errors in here should rollback the transaction
+ try {
+ let stmt = this.getStatement("updateTargetApplications");
+ aTargets.forEach(function(aTarget) {
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.params.id = aTarget.id;
+ stmt.params.minVersion = aTarget.minVersion;
+ stmt.params.maxVersion = aTarget.maxVersion;
+ executeStatement(stmt);
+ });
+ this.commitTransaction();
}
catch (e) {
this.rollbackTransaction();
throw e;
}
},
/**
* Synchronously removes an add-on from the database.
*
* @param aAddon
* The DBAddonInternal being removed
*/
removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
- this.beginTransaction();
- delete this.addonDB[aAddon._key];
- this.commitTransaction();
+ let stmt = this.getStatement("removeAddonMetadata");
+ stmt.params.internal_id = aAddon._internal_id;
+ executeStatement(stmt);
},
/**
* Synchronously marks a DBAddonInternal as visible marking all other
* instances with the same ID as not visible.
*
* @param aAddon
* The DBAddonInternal to make visible
* @param callback
* A callback to pass the DBAddonInternal to
*/
makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
- this.beginTransaction();
- LOG("Make addon " + aAddon._key + " visible");
- for (let key in this.addonDB) {
- let otherAddon = this.addonDB[key];
- if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
- LOG("Hide addon " + otherAddon._key);
- otherAddon.visible = false;
- }
- }
+ let stmt = this.getStatement("clearVisibleAddons");
+ stmt.params.id = aAddon.id;
+ executeStatement(stmt);
+
+ stmt = this.getStatement("makeAddonVisible");
+ stmt.params.internal_id = aAddon._internal_id;
+ executeStatement(stmt);
+
aAddon.visible = true;
- this.commitTransaction();
},
/**
* Synchronously sets properties for an add-on.
*
* @param aAddon
* The DBAddonInternal being updated
* @param aProperties
* A dictionary of properties to set
*/
setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
- this.beginTransaction();
- for (let key in aProperties) {
- aAddon[key] = aProperties[key];
+ function convertBoolean(value) {
+ return value ? 1 : 0;
}
- this.commitTransaction();
+
+ let stmt = this.getStatement("setAddonProperties");
+ stmt.params.internal_id = aAddon._internal_id;
+
+ ["userDisabled", "appDisabled", "softDisabled",
+ "pendingUninstall"].forEach(function(aProp) {
+ if (aProp in aProperties) {
+ stmt.params[aProp] = convertBoolean(aProperties[aProp]);
+ aAddon[aProp] = aProperties[aProp];
+ }
+ else {
+ stmt.params[aProp] = convertBoolean(aAddon[aProp]);
+ }
+ });
+
+ if ("applyBackgroundUpdates" in aProperties) {
+ stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
+ aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
+ }
+ else {
+ stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
+ }
+
+ executeStatement(stmt);
},
/**
* Synchronously sets the Sync GUID for an add-on.
*
* @param aAddon
* The DBAddonInternal being updated
* @param aGUID
* GUID string to set the value to
- * @throws if another addon already has the specified GUID
*/
setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) {
- // Need to make sure no other addon has this GUID
- function excludeSyncGUID(otherAddon) {
- return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID);
- }
- let otherAddon = this._findAddon(excludeSyncGUID);
- if (otherAddon) {
- throw new Error("Addon sync GUID conflict for addon " + aAddon._key +
- ": " + otherAddon._key + " already has GUID " + aGUID);
- }
- this.beginTransaction();
- aAddon.syncGUID = aGUID;
- this.commitTransaction();
+ let stmt = this.getStatement("setAddonSyncGUID");
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.params.syncGUID = aGUID;
+
+ executeStatement(stmt);
},
/**
* Synchronously sets the file descriptor for an add-on.
- * XXX IRVING could replace this with setAddonProperties
*
* @param aAddon
* The DBAddonInternal being updated
- * @param aDescriptor
- * File path of the installed addon
+ * @param aProperties
+ * A dictionary of properties to set
*/
setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) {
- this.beginTransaction();
- aAddon.descriptor = aDescriptor;
- this.commitTransaction();
+ let stmt = this.getStatement("setAddonDescriptor");
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.params.descriptor = aDescriptor;
+
+ executeStatement(stmt);
},
/**
* Synchronously updates an add-on's active flag in the database.
*
* @param aAddon
* The DBAddonInternal to update
*/
- updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) {
- LOG("Updating active state for add-on " + aAddon.id + " to " + aActive);
+ updateAddonActive: function XPIDB_updateAddonActive(aAddon) {
+ LOG("Updating add-on state");
- this.beginTransaction();
- aAddon.active = aActive;
- this.commitTransaction();
+ let stmt = this.getStatement("updateAddonActive");
+ stmt.params.internal_id = aAddon._internal_id;
+ stmt.params.active = aAddon.active ? 1 : 0;
+ executeStatement(stmt);
},
/**
* Synchronously calculates and updates all the active flags in the database.
*/
updateActiveAddons: function XPIDB_updateActiveAddons() {
- // XXX IRVING this may get called during XPI-utils shutdown
- // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean
LOG("Updating add-on states");
- this.beginTransaction();
- for (let key in this.addonDB) {
- let addon = this.addonDB[key];
- addon.active = (addon.visible && !addon.userDisabled &&
- !addon.softDisabled && !addon.appDisabled &&
- !addon.pendingUninstall);
- }
- this.commitTransaction();
+ let stmt = this.getStatement("setActiveAddons");
+ executeStatement(stmt);
+
+ // Note that this does not update the active property on cached
+ // DBAddonInternal instances so we throw away the cache. This should only
+ // happen during shutdown when everything is going away anyway or during
+ // startup when the only references are internal.
+ this.addonCache = [];
},
/**
* Writes out the XPI add-ons list for the platform to read.
*/
writeAddonsList: function XPIDB_writeAddonsList() {
Services.appinfo.invalidateCachesOnRestart();
let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
true);
+ if (!this.connection) {
+ try {
+ addonsList.remove(false);
+ LOG("Deleted add-ons list");
+ }
+ catch (e) {
+ }
+
+ Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
+ return;
+ }
+
let enabledAddons = [];
let text = "[ExtensionDirs]\r\n";
let count = 0;
let fullCount = 0;
- let activeAddons = this._listAddons(function active(aAddon) {
- return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme");
- });
+ let stmt = this.getStatement("getActiveAddons");
- for (let row of activeAddons) {
+ for (let row in resultRows(stmt)) {
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
enabledAddons.push(encodeURIComponent(row.id) + ":" +
encodeURIComponent(row.version));
}
fullCount += count;
// The selected skin may come from an inactive theme (the default theme
// when a lightweight theme is applied for example)
text += "\r\n[ThemeDirs]\r\n";
let dssEnabled = false;
try {
dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED);
} catch (e) {}
- let themes = [];
if (dssEnabled) {
- themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; });
+ stmt = this.getStatement("getThemes");
}
else {
- let activeTheme = this._findAddon(function isSelected(aAddon) {
- return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin));
- });
- if (activeTheme) {
- themes.push(activeTheme);
- }
+ stmt = this.getStatement("getActiveTheme");
+ stmt.params.internalName = XPIProvider.selectedSkin;
}
- if (themes.length > 0) {
+ if (stmt) {
count = 0;
- for (let row of themes) {
+ for (let row in resultRows(stmt)) {
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
enabledAddons.push(encodeURIComponent(row.id) + ":" +
encodeURIComponent(row.version));
}
fullCount += count;
}
if (fullCount > 0) {
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -1389,70 +1389,16 @@ function do_exception_wrap(func) {
func.apply(null, arguments);
}
catch(e) {
do_report_unexpected_exception(e);
}
};
}
-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
*/
function callback_soon(aFunction) {
return function(...args) {
do_execute_soon(function() {
aFunction.apply(null, args);
}, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js
@@ -249,21 +249,24 @@ function run_test_1() {
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_execute_soon(run_test_1_modified_db);
});
}
function run_test_1_modified_db() {
- // After restarting the database won't be open so we can alter
- // the schema
- shutdownManager();
- changeXPIDBVersion(100);
- startupManager();
+ // 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();
// Accessing the add-ons should open and recover the database. Since
// migration occurs everything should be recovered correctly
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
@@ -526,20 +526,20 @@ function manual_update(aVersion, aCallba
}, "application/x-xpinstall");
}, "application/x-xpinstall");
}, "application/x-xpinstall");
}
// 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);
do_check_eq(aAddon.softDisabled, aExpectedSoftDisabled);
if (aAddon.softDisabled)
do_check_true(aAddon.userDisabled);
if (aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED) {
@@ -701,17 +701,21 @@ add_test(function run_app_update_schema_
do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "test/1.0");
do_execute_soon(update_schema_2);
});
function update_schema_2() {
shutdownManager();
- changeXPIDBVersion(100);
+ var dbfile = gProfD.clone();
+ dbfile.append("extensions.sqlite");
+ var db = Services.storage.openDatabase(dbfile);
+ db.schemaVersion = 100;
+ db.close();
gAppInfo.version = "2";
startupManager(true);
AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
@@ -729,17 +733,21 @@ add_test(function run_app_update_schema_
do_execute_soon(update_schema_3);
});
}
function update_schema_3() {
restartManager();
shutdownManager();
- changeXPIDBVersion(100);
+ var dbfile = gProfD.clone();
+ dbfile.append("extensions.sqlite");
+ var db = Services.storage.openDatabase(dbfile);
+ db.schemaVersion = 100;
+ db.close();
gAppInfo.version = "2.5";
startupManager(true);
AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
@@ -751,17 +759,21 @@ add_test(function run_app_update_schema_
do_execute_soon(update_schema_4);
});
}
function update_schema_4() {
shutdownManager();
- changeXPIDBVersion(100);
+ var dbfile = gProfD.clone();
+ dbfile.append("extensions.sqlite");
+ var db = Services.storage.openDatabase(dbfile);
+ db.schemaVersion = 100;
+ db.close();
startupManager(false);
AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
@@ -772,17 +784,21 @@ add_test(function run_app_update_schema_
do_execute_soon(update_schema_5);
});
}
function update_schema_5() {
shutdownManager();
- changeXPIDBVersion(100);
+ var dbfile = gProfD.clone();
+ dbfile.append("extensions.sqlite");
+ var db = Services.storage.openDatabase(dbfile);
+ db.schemaVersion = 100;
+ db.close();
gAppInfo.version = "1";
startupManager(true);
AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -6,16 +6,18 @@ const APP_STARTUP =
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;
+const EXTENSIONS_DB = "extensions.sqlite";
+
// This verifies that bootstrappable add-ons can be used without restarts.
Components.utils.import("resource://gre/modules/Services.jsm");
// Enable loading extensions from the user scopes
Services.prefs.setIntPref("extensions.enabledScopes",
AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
@@ -1,15 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// 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");
// getting an unused port
Components.utils.import("resource://testing-common/httpd.js");
let gServer = new HttpServer();
gServer.start(-1);
gPort = gServer.identity.primaryPort;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
@@ -103,18 +103,24 @@ function run_test_1() {
// Prepare the add-on update, and a bootstrapped addon (bug 693714)
installAllFiles([
do_get_addon("test_bug659772"),
do_get_addon("test_bootstrap1_1")
], function() {
shutdownManager();
// Make it look like the next time the app is started it has a new DB schema
- changeXPIDBVersion(1);
+ 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;
Services.prefs.setIntPref("extensions.databaseSchema", 1);
+ db.close();
let jsonfile = gProfD.clone();
jsonfile.append("extensions");
jsonfile.append("staged");
jsonfile.append("addon3@tests.mozilla.org.json");
do_check_true(jsonfile.exists());
// Remove an unnecessary property from the cached manifest
@@ -244,18 +250,24 @@ function run_test_2() {
do_get_addon("test_bug659772"),
do_get_addon("test_bootstrap1_1")
], function() { do_execute_soon(prepare_schema_migrate); });
function prepare_schema_migrate() {
shutdownManager();
// Make it look like the next time the app is started it has a new DB schema
- changeXPIDBVersion(1);
+ 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;
Services.prefs.setIntPref("extensions.databaseSchema", 1);
+ db.close();
let jsonfile = gProfD.clone();
jsonfile.append("extensions");
jsonfile.append("staged");
jsonfile.append("addon3@tests.mozilla.org.json");
do_check_true(jsonfile.exists());
// Remove an unnecessary property from the cached manifest
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
@@ -247,17 +247,17 @@ function run_test_1() {
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
// Shutdown and replace the database with a corrupt file (a directory
// serves this purpose). On startup the add-ons manager won't rebuild
// because there is a file there still.
shutdownManager();
var dbfile = gProfD.clone();
- dbfile.append("extensions.json");
+ dbfile.append("extensions.sqlite");
dbfile.remove(true);
dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
startupManager(false);
// Accessing the add-ons should open and recover the database
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js
@@ -248,17 +248,17 @@ function run_test_1() {
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
// Shutdown and replace the database with a corrupt file (a directory
// serves this purpose). On startup the add-ons manager won't rebuild
// because there is a file there still.
shutdownManager();
var dbfile = gProfD.clone();
- dbfile.append("extensions.json");
+ dbfile.append("extensions.sqlite");
dbfile.remove(true);
dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
startupManager(false);
// Accessing the add-ons should open and recover the database
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js
@@ -0,0 +1,181 @@
+/* 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();
+ });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
@@ -249,23 +249,26 @@ function run_test_1() {
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
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
- // mess with permissions
+ // After shutting down the database won't be open so we can lock it
shutdownManager();
var dbfile = gProfD.clone();
- dbfile.append(EXTENSIONS_DB);
- var savedPermissions = dbfile.permissions;
- dbfile.permissions = 0;
+ 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();
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Accessing the add-ons should open and recover the database
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -423,17 +426,17 @@ function run_test_1() {
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
- dbfile.permissions = savedPermissions;
+ connection.close();
// After allowing access to the original DB things should go back to as
// they were previously
restartManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js
@@ -140,19 +140,23 @@ function run_test() {
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_UPGRADE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
// After shutting down the database won't be open so we can lock it
shutdownManager();
var dbfile = gProfD.clone();
- dbfile.append(EXTENSIONS_DB);
- var savedPermissions = dbfile.permissions;
- dbfile.permissions = 0;
+ 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();
startupManager(false);
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
@@ -194,17 +198,17 @@ function run_test() {
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
- dbfile.permissions = savedPermissions;
+ connection.close();
// After allowing access to the original DB things should still be
// applied correctly
restartManager();
// These things happened when we had no access to the database so
// they are seen as external changes when we get the database back :(
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, ["addon6@tests.mozilla.org"]);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js
@@ -252,19 +252,23 @@ function run_test_1() {
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
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
shutdownManager();
var dbfile = gProfD.clone();
- dbfile.append(EXTENSIONS_DB);
- var savedPermissions = dbfile.permissions;
- dbfile.permissions = 0;
+ 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();
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Accessing the add-ons should open and recover the database
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -420,17 +424,17 @@ function run_test_1() {
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
- dbfile.permissions = savedPermissions;
+ connection.close();
// After allowing access to the original DB things should go back to as
// they were previously
restartManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
@@ -219,17 +219,17 @@ function run_test() {
do_check_false(a3.foreignInstall);
// addon4 was pending-enable in the database
do_check_neq(a4, null);
do_check_false(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_true(a4.isActive);
do_check_true(a4.strictCompatibility);
do_check_false(a4.foreignInstall);
- // addon5 was enabled in the database but needed a compatibility update
+ // addon5 was enabled in the database but needed a compatibiltiy update
do_check_neq(a5, null);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_true(a5.isActive);
do_check_false(a5.strictCompatibility);
do_check_false(a5.foreignInstall);
// addon6 was disabled and compatible but a new version has been installed
// since, it should still be disabled but should be incompatible
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js
@@ -1,13 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
-// Checks that we migrate data from a previous version of the JSON database
+// Checks that we migrate data from a previous version of the sqlite database
// The test extension uses an insecure update url.
Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
Components.utils.import("resource://testing-common/httpd.js");
var testserver = new HttpServer();
testserver.start(-1);
gPort = testserver.identity.primaryPort;
@@ -167,18 +167,24 @@ function prepare_profile() {
}
function perform_migration() {
shutdownManager();
// Turn on disabling for all scopes
Services.prefs.setIntPref("extensions.autoDisableScopes", 15);
- changeXPIDBVersion(1);
+ 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;
Services.prefs.setIntPref("extensions.databaseSchema", 1);
+ db.close();
gAppInfo.version = "2"
startupManager(true);
test_results();
}
function test_results() {
check_startup_changes("installed", []);
@@ -236,17 +242,17 @@ function test_results() {
do_check_false(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_true(a4.isActive);
do_check_eq(a4.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT);
do_check_true(a4.foreignInstall);
do_check_false(a4.hasBinaryComponents);
do_check_true(a4.strictCompatibility);
- // addon5 was enabled in the database but needed a compatibility update
+ // addon5 was enabled in the database but needed a compatibiltiy update
do_check_neq(a5, null);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_true(a5.isActive);
do_check_eq(a4.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT);
do_check_true(a5.foreignInstall);
do_check_false(a5.hasBinaryComponents);
do_check_false(a5.strictCompatibility);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js
@@ -1,13 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
-// Checks that we fail to migrate but still start up ok when there is a SQLITE database
+// Checks that we fail to migrate but still start up ok when there is a database
// with no useful data in it.
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
var addon1 = {
id: "addon1@tests.mozilla.org",
version: "1.0",
name: "Test 1",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js
@@ -128,17 +128,17 @@ function run_test() {
startupManager();
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
let file = gProfD.clone();
- file.append("extensions.json");
+ file.append("extensions.sqlite");
do_check_false(file.exists());
file.leafName = "extensions.ini";
do_check_false(file.exists());
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
@@ -186,17 +186,17 @@ function run_test_1() {
"addon3@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_CHANGED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_DISABLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []);
do_check_true(gCachePurged);
let file = gProfD.clone();
- file.append("extensions.json");
+ file.append("extensions.sqlite");
do_check_true(file.exists());
file.leafName = "extensions.ini";
do_check_true(file.exists());
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js
@@ -89,17 +89,18 @@ add_test(function test_error_on_duplicat
AddonManager.getAddonsByIDs(installIDs, function(addons) {
let initialGUID = addons[1].syncGUID;
try {
addons[1].syncGUID = addons[0].syncGUID;
do_throw("Should not get here.");
}
catch (e) {
- do_check_true(e.message.startsWith("Addon sync GUID conflict"));
+ do_check_eq(e.result,
+ Components.results.NS_ERROR_STORAGE_CONSTRAINT);
restartManager();
AddonManager.getAddonByID(installIDs[1], function(addon) {
do_check_eq(initialGUID, addon.syncGUID);
run_next_test();
});
}
});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -12,21 +12,17 @@ skip-if = os == "android"
run-sequentially = Uses hardcoded ports in xpi files.
[test_AddonRepository_compatmode.js]
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
[test_DeferredSave.js]
[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]
skip-if = os == "android"
[test_bootstrap.js]
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
[test_bug299716.js]
@@ -137,32 +133,27 @@ fail-if = os == "android"
[test_bug596607.js]
[test_bug616841.js]
# Bug 676992: test consistently fails on Android
fail-if = os == "android"
[test_bug619730.js]
[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]
[test_bug757663.js]
[test_cacheflush.js]
[test_checkcompatibility.js]
[test_ChromeManifestParser.js]
[test_compatoverrides.js]
[test_corrupt.js]
-# needs to be converted from sqlite to JSON
-fail-if = true
[test_corrupt_strictcompat.js]
-# needs to be converted from sqlite to JSON
-fail-if = true
+[test_db_sanity.js]
[test_dictionary.js]
[test_langpack.js]
[test_disable.js]
[test_distribution.js]
[test_dss.js]
# Bug 676992: test consistently fails on Android
fail-if = os == "android"
[test_duplicateplugins.js]
@@ -197,43 +188,27 @@ skip-if = os == "android"
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
[test_install_strictcompat.js]
# Bug 676992: test consistently hangs on Android
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]
[test_plugins.js]
[test_pluginchange.js]
[test_pluginBlocklistCtp.js]
# Bug 676992: test consistently fails on Android
fail-if = os == "android"