From ec66739453eded37055a70302c55034711ba53ff Mon Sep 17 00:00:00 2001 From: Irving Reid Date: Thu, 8 Aug 2013 15:56:22 -0400 Subject: [PATCH] Bug 853388: Save and load XPIProvider state to/from a JSON file; r=unfocused --- toolkit/mozapps/extensions/XPIProvider.jsm | 310 ++-- .../mozapps/extensions/XPIProviderUtils.js | 1276 ++++++----------- .../extensions/test/xpcshell/head_addons.js | 54 + .../test/xpcshell/test_badschema.js | 13 +- .../test/xpcshell/test_blocklistchange.js | 26 +- .../test/xpcshell/test_bootstrap.js | 2 - .../test/xpcshell/test_bug559800.js | 2 - .../test/xpcshell/test_bug659772.js | 16 +- .../extensions/test/xpcshell/test_corrupt.js | 2 +- .../xpcshell/test_corrupt_strictcompat.js | 2 +- .../test/xpcshell/test_db_sanity.js | 181 --- .../extensions/test/xpcshell/test_locked.js | 15 +- .../extensions/test/xpcshell/test_locked2.js | 12 +- .../test/xpcshell/test_locked_strictcompat.js | 12 +- .../extensions/test/xpcshell/test_migrate2.js | 2 +- .../extensions/test/xpcshell/test_migrate4.js | 12 +- .../extensions/test/xpcshell/test_migrate5.js | 2 +- .../extensions/test/xpcshell/test_startup.js | 4 +- .../extensions/test/xpcshell/test_syncGUID.js | 3 +- .../extensions/test/xpcshell/xpcshell.ini | 27 +- 20 files changed, 686 insertions(+), 1287 deletions(-) delete mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index b29db7b388f0..ca763ec78741 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -78,7 +78,7 @@ const DIR_STAGE = "staged"; const DIR_XPI_STAGE = "staged-xpis"; const DIR_TRASH = "trash"; -const FILE_DATABASE = "extensions.sqlite"; +const FILE_DATABASE = "extensions.json"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_INSTALL_MANIFEST = "install.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; @@ -120,7 +120,12 @@ const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; // or calculated const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled", "sourceURI", "applyBackgroundUpdates", - "releaseNotesURI", "isForeignInstall", "syncGUID"]; + "releaseNotesURI", "foreignInstall", "syncGUID"]; +// Properties to cache and reload when an addon installation is pending +const PENDING_INSTALL_METADATA = + ["syncGUID", "targetApplications", "userDisabled", "softDisabled", + "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", + "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]; // Note: When adding/changing/removing items here, remember to change the // DB schema version to ensure changes are picked up ASAP. @@ -169,12 +174,15 @@ var gGlobalScope = this; var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i; ["LOG", "WARN", "ERROR"].forEach(function(aName) { - this.__defineGetter__(aName, function logFuncGetter() { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + Object.defineProperty(this, aName, { + get: function logFuncGetter() { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi", this); - return this[aName]; - }) + LogManager.getLogger("addons.xpi", this); + return this[aName]; + }, + configurable: true + }); }, this); @@ -197,9 +205,12 @@ function loadLazyObjects() { } for (let name of LAZY_OBJECTS) { - gGlobalScope.__defineGetter__(name, function lazyObjectGetter() { - let objs = loadLazyObjects(); - return objs[name]; + Object.defineProperty(gGlobalScope, name, { + get: function lazyObjectGetter() { + let objs = loadLazyObjects(); + return objs[name]; + }, + configurable: true }); } @@ -584,10 +595,13 @@ function isAddonDisabled(aAddon) { return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled; } -this.__defineGetter__("gRDF", function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); +Object.defineProperty(this, "gRDF", { + get: function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + }, + configurable: true }); function EM_R(aProperty) { @@ -694,8 +708,11 @@ function loadManifestFromRDF(aUri, aStream) { }); PROP_LOCALE_MULTI.forEach(function(aProp) { - locale[aProp] = getPropertyArray(aDs, aSource, - aProp.substring(0, aProp.length - 1)); + // Don't store empty arrays + let props = getPropertyArray(aDs, aSource, + aProp.substring(0, aProp.length - 1)); + if (props.length > 0) + locale[aProp] = props; }); return locale; @@ -2518,31 +2535,33 @@ var XPIProvider = { newAddon.visible = !(newAddon.id in visibleAddons); // Update the database - XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor); - if (newAddon.visible) { - visibleAddons[newAddon.id] = newAddon; + let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, + aAddonState.descriptor); + if (newDBAddon.visible) { + visibleAddons[newDBAddon.id] = newDBAddon; // Remember add-ons that were changed during startup AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newAddon.id); + newDBAddon.id); // If this was the active theme and it is now disabled then enable the // default theme - if (aOldAddon.active && isAddonDisabled(newAddon)) + if (aOldAddon.active && isAddonDisabled(newDBAddon)) XPIProvider.enableDefaultTheme(); // If the new add-on is bootstrapped and active then call its install method - if (newAddon.active && newAddon.bootstrap) { + if (newDBAddon.active && newDBAddon.bootstrap) { // Startup cache must be flushed before calling the bootstrap script flushStartupCache(); - let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ? + let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, - "install", installReason, { oldVersion: aOldAddon.version }); + XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, + newDBAddon.type, file, "install", + installReason, { oldVersion: aOldAddon.version }); return false; } @@ -2569,7 +2588,7 @@ var XPIProvider = { function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) { LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor); - aOldAddon._descriptor = aAddonState.descriptor; + aOldAddon.descriptor = aAddonState.descriptor; aOldAddon.visible = !(aOldAddon.id in visibleAddons); // Update the database @@ -2630,8 +2649,7 @@ var XPIProvider = { // If it should be active then mark it as active otherwise unload // its scope if (!isAddonDisabled(aOldAddon)) { - aOldAddon.active = true; - XPIDatabase.updateAddonActive(aOldAddon); + XPIDatabase.updateAddonActive(aOldAddon, true); } else { XPIProvider.unloadBootstrapScope(newAddon.id); @@ -2690,8 +2708,7 @@ var XPIProvider = { AddonManagerPrivate.addStartupChange(change, aOldAddon.id); if (aOldAddon.bootstrap) { // Update the add-ons active state - aOldAddon.active = !isDisabled; - XPIDatabase.updateAddonActive(aOldAddon); + XPIDatabase.updateAddonActive(aOldAddon, !isDisabled); } else { changed = true; @@ -2713,17 +2730,15 @@ var XPIProvider = { /** * Called when an add-on has been removed. * - * @param aInstallLocation - * The install location containing the add-on * @param aOldAddon * The AddonInternal as it appeared the last time the application * ran * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ - function removeMetadata(aInstallLocation, aOldAddon) { + function removeMetadata(aOldAddon) { // This add-on has disappeared - LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation); + LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location); XPIDatabase.removeAddonMetadata(aOldAddon); // Remember add-ons that were uninstalled during startup @@ -2886,9 +2901,10 @@ var XPIProvider = { newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon)) } + let newDBAddon = null; try { // Update the database. - XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); + newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); } catch (e) { // Failing to write the add-on into the database is non-fatal, the @@ -2899,36 +2915,36 @@ var XPIProvider = { return false; } - if (newAddon.visible) { + if (newDBAddon.visible) { // Remember add-ons that were first detected during startup. if (isDetectedInstall) { // If a copy from a higher priority location was removed then this // add-on has changed if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED) - .indexOf(newAddon.id) != -1) { + .indexOf(newDBAddon.id) != -1) { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newAddon.id); + newDBAddon.id); } else { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, - newAddon.id); + newDBAddon.id); } } // Note if any visible add-on is not in the application install location - if (newAddon._installLocation.name != KEY_APP_GLOBAL) + if (newDBAddon._installLocation.name != KEY_APP_GLOBAL) XPIProvider.allAppGlobal = false; - visibleAddons[newAddon.id] = newAddon; + visibleAddons[newDBAddon.id] = newDBAddon; let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL; let extraParams = {}; // If we're hiding a bootstrapped add-on then call its uninstall method - if (newAddon.id in oldBootstrappedAddons) { - let oldBootstrap = oldBootstrappedAddons[newAddon.id]; + if (newDBAddon.id in oldBootstrappedAddons) { + let oldBootstrap = oldBootstrappedAddons[newDBAddon.id]; extraParams.oldVersion = oldBootstrap.version; - XPIProvider.bootstrappedAddons[newAddon.id] = oldBootstrap; + XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap; // If the old version is the same as the new version, or we're // recovering from a corrupt DB, don't call uninstall and install @@ -2936,7 +2952,7 @@ var XPIProvider = { if (sameVersion || !isNewInstall) return false; - installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ? + installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; @@ -2944,27 +2960,27 @@ var XPIProvider = { createInstance(Ci.nsIFile); oldAddonFile.persistentDescriptor = oldBootstrap.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version, + XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version, oldBootstrap.type, oldAddonFile, "uninstall", - installReason, { newVersion: newAddon.version }); - XPIProvider.unloadBootstrapScope(newAddon.id); + installReason, { newVersion: newDBAddon.version }); + XPIProvider.unloadBootstrapScope(newDBAddon.id); // If the new add-on is bootstrapped then we must flush the caches // before calling the new bootstrap script - if (newAddon.bootstrap) + if (newDBAddon.bootstrap) flushStartupCache(); } - if (!newAddon.bootstrap) + if (!newDBAddon.bootstrap) return true; // Visible bootstrapped add-ons need to have their install method called let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, + XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file, "install", installReason, extraParams); - if (!newAddon.active) - XPIProvider.unloadBootstrapScope(newAddon.id); + if (!newDBAddon.active) + XPIProvider.unloadBootstrapScope(newDBAddon.id); } return false; @@ -3034,7 +3050,7 @@ var XPIProvider = { changed = updateMetadata(installLocation, aOldAddon, addonState) || changed; } - else if (aOldAddon._descriptor != addonState.descriptor) { + else if (aOldAddon.descriptor != addonState.descriptor) { changed = updateDescriptor(installLocation, aOldAddon, addonState) || changed; } @@ -3047,7 +3063,7 @@ var XPIProvider = { XPIProvider.allAppGlobal = false; } else { - changed = removeMetadata(installLocation.name, aOldAddon) || changed; + changed = removeMetadata(aOldAddon) || changed; } }, this); } @@ -3071,7 +3087,7 @@ var XPIProvider = { knownLocations.forEach(function(aLocation) { let addons = XPIDatabase.getAddonsInLocation(aLocation); addons.forEach(function(aOldAddon) { - changed = removeMetadata(aLocation, aOldAddon) || changed; + changed = removeMetadata(aOldAddon) || changed; }, this); }, this); @@ -3465,7 +3481,7 @@ var XPIProvider = { let results = [createWrapper(a) for each (a in aAddons)]; XPIProvider.installs.forEach(function(aInstall) { if (aInstall.state == AddonManager.STATE_INSTALLED && - !(aInstall.addon instanceof DBAddonInternal)) + !(aInstall.addon.inDatabase)) results.push(createWrapper(aInstall.addon)); }); aCallback(results); @@ -3783,7 +3799,7 @@ var XPIProvider = { // This wouldn't normally be called for an already installed add-on (except // for forming the operationsRequiringRestart flags) so is really here as // a safety measure. - if (aAddon instanceof DBAddonInternal) + if (aAddon.inDatabase) return false; // If we have an AddonInstall for this add-on then we can see if there is @@ -4032,7 +4048,7 @@ var XPIProvider = { updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only update addon states for installed addons."); if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { throw new Error("Cannot change userDisabled and softDisabled at the " + @@ -4105,8 +4121,7 @@ var XPIProvider = { } if (!needsRestart) { - aAddon.active = !isDisabled; - XPIDatabase.updateAddonActive(aAddon); + XPIDatabase.updateAddonActive(aAddon, !isDisabled); if (isDisabled) { if (aAddon.bootstrap) { let file = aAddon._installLocation.getLocationForID(aAddon.id); @@ -4142,7 +4157,7 @@ var XPIProvider = { * location that does not allow it */ uninstallAddon: function XPI_uninstallAddon(aAddon) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only uninstall installed addons."); if (aAddon._installLocation.locked) @@ -4184,8 +4199,7 @@ var XPIProvider = { AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false); if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) { - aAddon.active = true; - XPIDatabase.updateAddonActive(aAddon); + XPIDatabase.updateAddonActive(aAddon, true); } if (aAddon.bootstrap) { @@ -4255,7 +4269,7 @@ var XPIProvider = { * The DBAddonInternal to cancel uninstall for */ cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only cancel uninstall for installed addons."); cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]); @@ -5244,7 +5258,7 @@ AddonInstall.prototype = { // Point the add-on to its extracted files as the xpi may get deleted this.addon._sourceBundle = stagedAddon; - // Cache the AddonInternal as it may have updated compatibiltiy info + // Cache the AddonInternal as it may have updated compatibility info let stagedJSON = stagedAddon.clone(); stagedJSON.leafName = this.addon.id + ".json"; if (stagedJSON.exists()) @@ -5313,8 +5327,7 @@ AddonInstall.prototype = { } if (!isUpgrade && this.existingAddon.active) { - this.existingAddon.active = false; - XPIDatabase.updateAddonActive(this.existingAddon); + XPIDatabase.updateAddonActive(this.existingAddon, false); } } @@ -5330,51 +5343,45 @@ AddonInstall.prototype = { this.addon.updateDate = recursiveLastModifiedTime(file); this.addon.visible = true; if (isUpgrade) { - XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, - file.persistentDescriptor); + this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, + file.persistentDescriptor); } else { this.addon.installDate = this.addon.updateDate; this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon)) - XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); + this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); } - // Retrieve the new DBAddonInternal for the add-on we just added - let self = this; - XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name, - function startInstall_getAddonInLocation(a) { - self.addon = a; - let extraParams = {}; - if (self.existingAddon) { - extraParams.oldVersion = self.existingAddon.version; - } + let extraParams = {}; + if (this.existingAddon) { + extraParams.oldVersion = this.existingAddon.version; + } - if (self.addon.bootstrap) { - XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, - self.addon.type, file, "install", + if (this.addon.bootstrap) { + XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, + this.addon.type, file, "install", + reason, extraParams); + } + + AddonManagerPrivate.callAddonListeners("onInstalled", + createWrapper(this.addon)); + + LOG("Install of " + this.sourceURI.spec + " completed."); + this.state = AddonManager.STATE_INSTALLED; + AddonManagerPrivate.callInstallListeners("onInstallEnded", + this.listeners, this.wrapper, + createWrapper(this.addon)); + + if (this.addon.bootstrap) { + if (this.addon.active) { + XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, + this.addon.type, file, "startup", reason, extraParams); } - - AddonManagerPrivate.callAddonListeners("onInstalled", - createWrapper(self.addon)); - - LOG("Install of " + self.sourceURI.spec + " completed."); - self.state = AddonManager.STATE_INSTALLED; - AddonManagerPrivate.callInstallListeners("onInstallEnded", - self.listeners, self.wrapper, - createWrapper(self.addon)); - - if (self.addon.bootstrap) { - if (self.addon.active) { - XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, - self.addon.type, file, "startup", - reason, extraParams); - } - else { - XPIProvider.unloadBootstrapScope(self.addon.id); - } + else { + XPIProvider.unloadBootstrapScope(this.addon.id); } - }); + } } } catch (e) { @@ -5766,13 +5773,6 @@ AddonInternal.prototype = { releaseNotesURI: null, foreignInstall: false, - get isForeignInstall() { - return this.foreignInstall; - }, - set isForeignInstall(aVal) { - this.foreignInstall = aVal; - }, - get selectedLocale() { if (this._selectedLocale) return this._selectedLocale; @@ -5967,10 +5967,7 @@ AddonInternal.prototype = { * A JS object containing the cached metadata */ importMetadata: function AddonInternal_importMetaData(aObj) { - ["syncGUID", "targetApplications", "userDisabled", "softDisabled", - "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", - "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"] - .forEach(function(aProp) { + PENDING_INSTALL_METADATA.forEach(function(aProp) { if (!(aProp in aObj)) return; @@ -5982,77 +5979,6 @@ AddonInternal.prototype = { } }; -/** - * The DBAddonInternal is a special AddonInternal that has been retrieved from - * the database. Add-ons retrieved synchronously only have the basic metadata - * the rest is filled out synchronously when needed. Asynchronously read add-ons - * have all data available. - */ -function DBAddonInternal() { - this.__defineGetter__("targetApplications", function DBA_targetApplicationsGetter() { - delete this.targetApplications; - return this.targetApplications = XPIDatabase._getTargetApplications(this); - }); - - this.__defineGetter__("targetPlatforms", function DBA_targetPlatformsGetter() { - delete this.targetPlatforms; - return this.targetPlatforms = XPIDatabase._getTargetPlatforms(this); - }); - - this.__defineGetter__("locales", function DBA_localesGetter() { - delete this.locales; - return this.locales = XPIDatabase._getLocales(this); - }); - - this.__defineGetter__("defaultLocale", function DBA_defaultLocaleGetter() { - delete this.defaultLocale; - return this.defaultLocale = XPIDatabase._getDefaultLocale(this); - }); - - this.__defineGetter__("pendingUpgrade", function DBA_pendingUpgradeGetter() { - delete this.pendingUpgrade; - for (let install of XPIProvider.installs) { - if (install.state == AddonManager.STATE_INSTALLED && - !(install.addon instanceof DBAddonInternal) && - install.addon.id == this.id && - install.installLocation == this._installLocation) { - return this.pendingUpgrade = install.addon; - } - }; - }); -} - -DBAddonInternal.prototype = { - applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - let changes = []; - this.targetApplications.forEach(function(aTargetApp) { - aUpdate.targetApplications.forEach(function(aUpdateTarget) { - if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || - Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { - aTargetApp.minVersion = aUpdateTarget.minVersion; - aTargetApp.maxVersion = aUpdateTarget.maxVersion; - changes.push(aUpdateTarget); - } - }); - }); - try { - XPIDatabase.updateTargetApplications(this, changes); - } - catch (e) { - // A failure just means that we discard the compatibility update - ERROR("Failed to update target application info in the database for " + - "add-on " + this.id, e); - return; - } - XPIProvider.updateAddonDisabledState(this); - } -} - -DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; -// Make it accessible to XPIDatabase. -XPIProvider.DBAddonInternal = DBAddonInternal; - - /** * Creates an AddonWrapper for an AddonInternal. * @@ -6304,7 +6230,7 @@ function AddonWrapper(aAddon) { if (aAddon.syncGUID == val) return val; - if (aAddon instanceof DBAddonInternal) + if (aAddon.inDatabase) XPIDatabase.setAddonSyncGUID(aAddon, val); aAddon.syncGUID = val; @@ -6331,7 +6257,7 @@ function AddonWrapper(aAddon) { this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() { let pending = 0; - if (!(aAddon instanceof DBAddonInternal)) { + if (!(aAddon.inDatabase)) { // Add-on is pending install if there is no associated install (shouldn't // happen here) or if the install is in the process of or has successfully // completed the install. If an add-on is pending install then we ignore @@ -6375,7 +6301,7 @@ function AddonWrapper(aAddon) { let permissions = 0; // Add-ons that aren't installed cannot be modified in any way - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) return permissions; if (!aAddon.appDisabled) { @@ -6410,7 +6336,7 @@ function AddonWrapper(aAddon) { if (val == this.userDisabled) return val; - if (aAddon instanceof DBAddonInternal) { + if (aAddon.inDatabase) { if (aAddon.type == "theme" && val) { if (aAddon.internalName == XPIProvider.defaultSkin) throw new Error("Cannot disable the default theme"); @@ -6434,7 +6360,7 @@ function AddonWrapper(aAddon) { if (val == aAddon.softDisabled) return val; - if (aAddon instanceof DBAddonInternal) { + if (aAddon.inDatabase) { // When softDisabling a theme just enable the active theme if (aAddon.type == "theme" && val && !aAddon.userDisabled) { if (aAddon.internalName == XPIProvider.defaultSkin) @@ -6459,7 +6385,7 @@ function AddonWrapper(aAddon) { }; this.uninstall = function AddonWrapper_uninstall() { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Cannot uninstall an add-on that isn't installed"); if (aAddon.pendingUninstall) throw new Error("Add-on is already marked to be uninstalled"); @@ -6467,7 +6393,7 @@ function AddonWrapper(aAddon) { }; this.cancelUninstall = function AddonWrapper_cancelUninstall() { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Cannot cancel uninstall for an add-on that isn't installed"); if (!aAddon.pendingUninstall) throw new Error("Add-on is not marked to be uninstalled"); diff --git a/toolkit/mozapps/extensions/XPIProviderUtils.js b/toolkit/mozapps/extensions/XPIProviderUtils.js index 0bdcab9af571..465b663058c5 100644 --- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -18,17 +18,21 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", ["LOG", "WARN", "ERROR"].forEach(function(aName) { - this.__defineGetter__(aName, function logFuncGetter () { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + Object.defineProperty(this, aName, { + get: function logFuncGetter () { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi-utils", this); - return this[aName]; - }) + LogManager.getLogger("addons.xpi-utils", this); + return this[aName]; + }, + configurable: true + }); }, 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"; @@ -72,16 +76,30 @@ 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#"; -this.__defineGetter__("gRDF", function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); +Object.defineProperty(this, "gRDF", { + get: function gRDFGetter() { + delete this.gRDF; + return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + }, + configurable: true }); function EM_R(aProperty) { @@ -174,6 +192,67 @@ AsyncAddonListCallback.prototype = { } }; +/** + * 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. @@ -293,6 +372,78 @@ function copyRowProperties(aRow, aProperties, aTarget) { 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, @@ -305,6 +456,7 @@ this.XPIDatabase = { 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. @@ -327,30 +479,6 @@ this.XPIDatabase = { _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", @@ -413,6 +541,100 @@ this.XPIDatabase = { 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 @@ -423,8 +645,6 @@ this.XPIDatabase = { * when the database is first opened. */ beginTransaction: function XPIDB_beginTransaction() { - if (this.initialized) - this.getStatement("createSavepoint").execute(); this.transactionCount++; }, @@ -438,9 +658,12 @@ this.XPIDatabase = { 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(); + } }, /** @@ -453,11 +676,8 @@ this.XPIDatabase = { 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 }, /** @@ -494,7 +714,7 @@ this.XPIDatabase = { } 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. @@ -518,18 +738,20 @@ this.XPIDatabase = { * @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); @@ -618,11 +840,12 @@ this.XPIDatabase = { }, /** - * A lazy getter for the database connection. + * Lazy getter for the addons database */ - get connection() { - this.openConnection(true); - return this.connection; + get addonDB() { + delete this.addonDB; + this.openJSONDatabase(); + return this.addonDB; }, /** @@ -783,6 +1006,8 @@ this.XPIDatabase = { 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 @@ -830,11 +1055,6 @@ this.XPIDatabase = { 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) @@ -843,24 +1063,28 @@ this.XPIDatabase = { // If we are running with an in-memory database then force a new // extensions.ini to be written to disk on the next startup - if (!this.connection.databaseFile) - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + // 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); this.initialized = false; - let connection = this.connection; - delete this.connection; - // 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; + // 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 }); - - connection.asyncClose(function shutdown_asyncClose() { - LOG("Database closed"); + // 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) aCallback(); - }); } else { if (aCallback) @@ -869,433 +1093,7 @@ this.XPIDatabase = { }, /** - * 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 + * Return a list of 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. @@ -1303,34 +1101,74 @@ this.XPIDatabase = { * @return an array of names of install locations */ getInstallLocations: function XPIDB_getInstallLocations() { - if (!this.connection) + if (!this.addonDB) return []; - let stmt = this.getStatement("getInstallLocations"); + let locations = {}; + for each (let addon in this.addonDB) { + locations[addon.location] = 1; + } + return Object.keys(locations); + }, - return [row.location for each (row in resultRows(stmt))]; + /** + * 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) + return []; + + let addonList = []; + for (let key in this.addonDB) { + let addon = this.addonDB[key]; + if (aFilter(addon)) { + addonList.push(addon); + } + } + + return addonList; + }, + + /** + * 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; }, /** * Synchronously reads all the add-ons in a particular install location. * - * @param location + * @param aLocation * The name of the install location * @return an array of DBAddonInternals */ getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddonsInLocation"); - - stmt.params.location = aLocation; - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return this._listAddons(function inLocation(aAddon) {return (aAddon.location == aLocation);}); }, /** * 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 @@ -1340,30 +1178,12 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, 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]); - })); + getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback); }, /** * Asynchronously gets the add-on with an ID that is visible. + * XXX IRVING sync * * @param aId * The ID of the add-on to retrieve @@ -1371,29 +1191,13 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, 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]); - })); + let addon = this._findAddon(function visibleID(aAddon) {return ((aAddon.id == aId) && aAddon.visible)}); + getRepositoryAddon(addon, aCallback); }, /** * 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 @@ -1401,32 +1205,10 @@ this.XPIDatabase = { * A callback to pass the array of DBAddonInternals to */ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, 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)); + let addons = this._listAddons(function visibleType(aAddon) { + return (aAddon.visible && (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))) + }); + asyncMap(addons, getRepositoryAddon, aCallback); }, /** @@ -1437,13 +1219,7 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddonsByType: function XPIDB_getAddonsByType(aType) { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddonsByType"); - - stmt.params.type = aType; - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return this._listAddons(function byType(aAddon) { return aAddon.type == aType; }); }, /** @@ -1454,23 +1230,14 @@ this.XPIDatabase = { * @return a DBAddonInternal */ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(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; + return this._findAddon(function visibleInternalName(aAddon) { + return (aAddon.visible && (aAddon.internalName == aInternalName)); + }); }, /** * 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 @@ -1479,38 +1246,22 @@ this.XPIDatabase = { */ getVisibleAddonsWithPendingOperations: function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { - if (!this.connection) { - aCallback([]); - return; - } - 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)); + 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); }, /** * Asynchronously get an add-on by its Sync GUID. + * XXX IRVING sync * * @param aGUID * Sync GUID of add-on to fetch @@ -1520,16 +1271,8 @@ this.XPIDatabase = { * */ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, 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]); - })); + let addon = this._findAddon(function bySyncGUID(aAddon) { return aAddon.syncGUID == aGUID; }); + getRepositoryAddon(addon, aCallback); }, /** @@ -1538,12 +1281,7 @@ this.XPIDatabase = { * @return an array of DBAddonInternals */ getAddons: function XPIDB_getAddons() { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddons"); - - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return this._listAddons(function(aAddon) {return true;}); }, /** @@ -1553,92 +1291,27 @@ this.XPIDatabase = { * 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 - if (!this.connection) + // 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) this.openConnection(false, true); this.beginTransaction(); - 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; + let newAddon = new DBAddonInternal(aAddon); + newAddon.descriptor = aDescriptor; + this.addonDB[newAddon._key] = newAddon; + if (newAddon.visible) { + this.makeAddonVisible(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; - } + this.commitTransaction(); + return newAddon; }, /** @@ -1651,6 +1324,7 @@ this.XPIDatabase = { * 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) { @@ -1666,38 +1340,9 @@ this.XPIDatabase = { aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && !aNewAddon.appDisabled && !aNewAddon.pendingUninstall) - this.addAddonMetadata(aNewAddon, aDescriptor); - this.commitTransaction(); - } - 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); - }); + let newDBAddon = this.addAddonMetadata(aNewAddon, aDescriptor); this.commitTransaction(); + return newDBAddon; } catch (e) { this.rollbackTransaction(); @@ -1712,9 +1357,9 @@ this.XPIDatabase = { * The DBAddonInternal being removed */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { - let stmt = this.getStatement("removeAddonMetadata"); - stmt.params.internal_id = aAddon._internal_id; - executeStatement(stmt); + this.beginTransaction(); + delete this.addonDB[aAddon._key]; + this.commitTransaction(); }, /** @@ -1727,15 +1372,17 @@ this.XPIDatabase = { * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { - 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); - + 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; + } + } aAddon.visible = true; + this.commitTransaction(); }, /** @@ -1747,33 +1394,11 @@ this.XPIDatabase = { * A dictionary of properties to set */ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) { - function convertBoolean(value) { - return value ? 1 : 0; + this.beginTransaction(); + for (let key in aProperties) { + aAddon[key] = aProperties[key]; } - - 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); + this.commitTransaction(); }, /** @@ -1783,29 +1408,36 @@ this.XPIDatabase = { * 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) { - let stmt = this.getStatement("setAddonSyncGUID"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.syncGUID = aGUID; - - executeStatement(stmt); + // 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(); }, /** * Synchronously sets the file descriptor for an add-on. + * XXX IRVING could replace this with setAddonProperties * * @param aAddon * The DBAddonInternal being updated - * @param aProperties - * A dictionary of properties to set + * @param aDescriptor + * File path of the installed addon */ setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { - let stmt = this.getStatement("setAddonDescriptor"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.descriptor = aDescriptor; - - executeStatement(stmt); + this.beginTransaction(); + aAddon.descriptor = aDescriptor; + this.commitTransaction(); }, /** @@ -1814,28 +1446,29 @@ this.XPIDatabase = { * @param aAddon * The DBAddonInternal to update */ - updateAddonActive: function XPIDB_updateAddonActive(aAddon) { - LOG("Updating add-on state"); + updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) { + LOG("Updating active state for add-on " + aAddon.id + " to " + aActive); - let stmt = this.getStatement("updateAddonActive"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.active = aAddon.active ? 1 : 0; - executeStatement(stmt); + this.beginTransaction(); + aAddon.active = aActive; + this.commitTransaction(); }, /** * 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"); - 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 = []; + 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(); }, /** @@ -1846,26 +1479,16 @@ this.XPIDatabase = { 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 stmt = this.getStatement("getActiveAddons"); + let activeAddons = this._listAddons(function active(aAddon) { + return aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"); + }); - for (let row in resultRows(stmt)) { + for (let row of activeAddons) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); @@ -1881,17 +1504,22 @@ this.XPIDatabase = { dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED); } catch (e) {} + let themes = []; if (dssEnabled) { - stmt = this.getStatement("getThemes"); + themes = this._listAddons(function isTheme(aAddon){ return aAddon.type == "theme"; }); } else { - stmt = this.getStatement("getActiveTheme"); - stmt.params.internalName = XPIProvider.selectedSkin; + let activeTheme = this._findAddon(function isSelected(aAddon) { + return ((aAddon.type == "theme") && (aAddon.internalName == XPIProvider.selectedSkin)); + }); + if (activeTheme) { + themes.push(activeTheme); + } } - if (stmt) { + if (themes.length > 0) { count = 0; - for (let row in resultRows(stmt)) { + for (let row of themes) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index b7bc65ddc14f..e0c34ee1aa35 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1394,6 +1394,60 @@ function do_exception_wrap(func) { }; } +const EXTENSIONS_DB = "extensions.json"; + +/** + * Change the schema version of the JSON extensions database + */ +function changeXPIDBVersion(aNewVersion) { + let dbfile = gProfD.clone(); + dbfile.append(EXTENSIONS_DB); + let jData = loadJSON(dbfile); + jData.schemaVersion = aNewVersion; + saveJSON(jData, dbfile); +} + +/** + * Raw load of a JSON file + */ +function loadJSON(aFile) { + let data = ""; + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + fstream.init(aFile, -1, 0, 0); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + } + cstream.close(); + do_print("Loaded JSON file " + aFile.spec); + return(JSON.parse(data)); +} + +/** + * Raw save of a JSON blob to file + */ +function saveJSON(aData, aFile) { + do_print("Starting to save JSON file " + aFile.path); + let stream = FileUtils.openSafeFileOutputStream(aFile); + let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(AM_Ci.nsIConverterOutputStream); + converter.init(stream, "UTF-8", 0, 0x0000); + // XXX pretty print the JSON while debugging + converter.writeString(JSON.stringify(aData, null, 2)); + converter.flush(); + // nsConverterOutputStream doesn't finish() safe output streams on close() + FileUtils.closeSafeFileOutputStream(stream); + converter.close(); + do_print("Done saving JSON file " + aFile.path); +} + /** * Create a callback function that calls do_execute_soon on an actual callback and arguments */ diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js index 1ccdbc842c42..6ebf088d6fdd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -254,14 +254,11 @@ function run_test_1() { function run_test_1_modified_db() { - // After restarting the database won't be open and so can be replaced with - // a bad file - restartManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + // After restarting the database won't be open so we can alter + // the schema + shutdownManager(); + changeXPIDBVersion(100); + startupManager(); // Accessing the add-ons should open and recover the database. Since // migration occurs everything should be recovered correctly diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 50dcb427759d..d3b8aceb2ccb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -531,10 +531,10 @@ function manual_update(aVersion, aCallback) { // Checks that an add-ons properties match expected values function check_addon(aAddon, aExpectedVersion, aExpectedUserDisabled, aExpectedSoftDisabled, aExpectedState) { + do_check_neq(aAddon, null); dump("Testing " + aAddon.id + " version " + aAddon.version + "\n"); dump(aAddon.userDisabled + " " + aAddon.softDisabled + "\n"); - do_check_neq(aAddon, null); do_check_eq(aAddon.version, aExpectedVersion); do_check_eq(aAddon.blocklistState, aExpectedState); do_check_eq(aAddon.userDisabled, aExpectedUserDisabled); @@ -706,11 +706,7 @@ add_test(function run_app_update_schema_test() { function update_schema_2() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "2"; startupManager(true); @@ -738,11 +734,7 @@ add_test(function run_app_update_schema_test() { restartManager(); shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "2.5"; startupManager(true); @@ -764,11 +756,7 @@ add_test(function run_app_update_schema_test() { function update_schema_4() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); startupManager(false); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { @@ -789,11 +777,7 @@ add_test(function run_app_update_schema_test() { function update_schema_5() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "1"; startupManager(true); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js index bd51971a0345..6d201640f989 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -11,8 +11,6 @@ const ADDON_UNINSTALL = 6; const ADDON_UPGRADE = 7; const ADDON_DOWNGRADE = 8; -const EXTENSIONS_DB = "extensions.sqlite"; - // This verifies that bootstrappable add-ons can be used without restarts. Components.utils.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js index 4987eb51d4c8..789819cc6b87 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -5,8 +5,6 @@ // This verifies that deleting the database from the profile doesn't break // anything -const EXTENSIONS_DB = "extensions.sqlite"; - const profileDir = gProfD.clone(); profileDir.append("extensions"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js index ea2e18a3698b..92ae8b21dc14 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -108,14 +108,8 @@ function run_test_1() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); @@ -255,14 +249,8 @@ function run_test_2() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js index 5cce5de71412..50dd782da360 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -252,7 +252,7 @@ function run_test_1() { // because there is a file there still. shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); + dbfile.append("extensions.json"); dbfile.remove(true); dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js index e768e2798533..e465b47bbacb 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -253,7 +253,7 @@ function run_test_1() { // because there is a file there still. shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); + dbfile.append("extensions.json"); dbfile.remove(true); dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); startupManager(false); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js b/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js deleted file mode 100644 index 693ca42cda98..000000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js +++ /dev/null @@ -1,181 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -// This tests the data in extensions.sqlite for general sanity, making sure -// rows in one table only reference rows in another table that actually exist. - - -function check_db() { - do_print("Checking DB sanity..."); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - - do_print("Checking locale_strings references rows in locale correctly..."); - let localeStringsStmt = db.createStatement("SELECT * FROM locale_strings"); - let localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); - let i = 0; - while (localeStringsStmt.executeStep()) { - i++; - localeStmt.params.locale_id = localeStringsStmt.row.locale_id; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - localeStmt.finalize(); - localeStringsStmt.finalize(); - do_print("Done. " + i + " rows in locale_strings checked."); - - - do_print("Checking locale references rows in addon_locale and addon correctly..."); - localeStmt = db.createStatement("SELECT * FROM locale"); - let addonLocaleStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon_locale WHERE locale_id=:locale_id"); - let addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE defaultLocale=:locale_id"); - i = 0; - while (localeStmt.executeStep()) { - i++; - addonLocaleStmt.params.locale_id = localeStmt.row.id; - do_check_true(addonLocaleStmt.executeStep()); - if (addonLocaleStmt.row.count == 0) { - addonStmt.params.locale_id = localeStmt.row.id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - } else { - do_check_eq(addonLocaleStmt.row.count, 1); - } - addonLocaleStmt.reset(); - addonStmt.reset(); - } - addonLocaleStmt.finalize(); - localeStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in locale checked."); - - - do_print("Checking addon_locale references rows in locale correctly..."); - addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); - localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); - i = 0; - while (addonLocaleStmt.executeStep()) { - i++; - localeStmt.params.locale_id = addonLocaleStmt.row.locale_id; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - addonLocaleStmt.finalize(); - localeStmt.finalize(); - do_print("Done. " + i + " rows in addon_locale checked."); - - - do_print("Checking addon_locale references rows in addon correctly..."); - addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (addonLocaleStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = addonLocaleStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - addonLocaleStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in addon_locale checked."); - - - do_print("Checking addon references rows in locale correctly..."); - addonStmt = db.createStatement("SELECT * FROM addon"); - localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:defaultLocale"); - i = 0; - while (addonStmt.executeStep()) { - i++; - localeStmt.params.defaultLocale = addonStmt.row.defaultLocale; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - addonStmt.finalize(); - localeStmt.finalize(); - do_print("Done. " + i + " rows in addon checked."); - - - do_print("Checking targetApplication references rows in addon correctly..."); - let targetAppStmt = db.createStatement("SELECT * FROM targetApplication"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (targetAppStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = targetAppStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - targetAppStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in targetApplication checked."); - - - do_print("Checking targetPlatform references rows in addon correctly..."); - let targetPlatformStmt = db.createStatement("SELECT * FROM targetPlatform"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (targetPlatformStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = targetPlatformStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - targetPlatformStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in targetPlatform checked."); - - - db.close(); - do_print("Done checking DB sanity."); -} - -function run_test() { - do_test_pending(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); - startupManager(); - - installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_1); -} - -function run_test_1() { - shutdownManager(); - check_db(); - startupManager(); - - AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { - aAddon.uninstall(); - - shutdownManager(); - check_db(); - startupManager(); - - installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_2); - }); -} - -function run_test_2() { - installAllFiles([do_get_addon("test_db_sanity_1_2")], function() { - shutdownManager(); - check_db(); - startupManager(); - run_test_3(); - }); -} - -function run_test_3() { - AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { - aAddon.uninstall(); - - shutdownManager(); - check_db(); - - do_test_finished(); - }); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js index 7c9d3dc96dbb..883ea08c6d1f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js @@ -254,16 +254,13 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - // After shutting down the database won't be open so we can lock it + // After shutting down the database won't be open so we can + // mess with permissions shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let connection = Services.storage.openUnsharedDatabase(dbfile); - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - // Force the DB to become locked - connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); - connection.commitTransaction(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -431,7 +428,7 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - connection.close(); + dbfile.permissions = savedPermissions; // After allowing access to the original DB things should go back to as // they were previously diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js index 878e088e69a7..6e21df540d1f 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js @@ -145,13 +145,9 @@ function run_test() { // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let connection = Services.storage.openUnsharedDatabase(dbfile); - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - // Force the DB to become locked - connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); - connection.commitTransaction(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -203,7 +199,7 @@ function run_test() { do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isExtensionInAddonsList(profileDir, a6.id)); - connection.close(); + dbfile.permissions = savedPermissions; // After allowing access to the original DB things should still be // applied correctly diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js index 0585295d26d8..2c31171318be 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js @@ -257,13 +257,9 @@ function run_test_1() { // After shutting down the database won't be open so we can lock it shutdownManager(); var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let connection = Services.storage.openUnsharedDatabase(dbfile); - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - // Force the DB to become locked - connection.beginTransactionAs(connection.TRANSACTION_EXCLUSIVE); - connection.commitTransaction(); + dbfile.append(EXTENSIONS_DB); + var savedPermissions = dbfile.permissions; + dbfile.permissions = 0; startupManager(false); @@ -429,7 +425,7 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_check_true(isThemeInAddonsList(profileDir, t2.id)); - connection.close(); + dbfile.permissions = savedPermissions; // After allowing access to the original DB things should go back to as // they were previously diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js index b14e332235f0..20431a9a7d00 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js @@ -224,7 +224,7 @@ function run_test() { do_check_true(a4.isActive); do_check_true(a4.strictCompatibility); do_check_false(a4.foreignInstall); - // addon5 was enabled in the database but needed a compatibiltiy update + // addon5 was enabled in the database but needed a compatibility update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js index c8345d8c9a3d..b2903ead7d54 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we migrate data from a previous version of the sqlite database +// Checks that we migrate data from a previous version of the JSON database // The test extension uses an insecure update url. Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); @@ -172,14 +172,8 @@ function perform_migration() { // Turn on disabling for all scopes Services.prefs.setIntPref("extensions.autoDisableScopes", 15); - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); gAppInfo.version = "2" startupManager(true); @@ -247,7 +241,7 @@ function test_results() { do_check_false(a4.hasBinaryComponents); do_check_true(a4.strictCompatibility); - // addon5 was enabled in the database but needed a compatibiltiy update + // addon5 was enabled in the database but needed a compatibility update do_check_neq(a5, null); do_check_false(a5.userDisabled); do_check_false(a5.appDisabled); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js index 323ede3114f5..91a05fa358e3 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Checks that we fail to migrate but still start up ok when there is a database +// Checks that we fail to migrate but still start up ok when there is a SQLITE database // with no useful data in it. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js index 7e25e836c9c8..841a50224169 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_startup.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_startup.js @@ -133,7 +133,7 @@ function run_test() { check_startup_changes(AddonManager.STARTUP_CHANGE_ENABLED, []); let file = gProfD.clone(); - file.append("extensions.sqlite"); + file.append("extensions.json"); do_check_false(file.exists()); file.leafName = "extensions.ini"; @@ -191,7 +191,7 @@ function run_test_1() { do_check_true(gCachePurged); let file = gProfD.clone(); - file.append("extensions.sqlite"); + file.append("extensions.json"); do_check_true(file.exists()); file.leafName = "extensions.ini"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js index 34cd54039f34..c73a412c4870 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_syncGUID.js @@ -94,8 +94,7 @@ add_test(function test_error_on_duplicate_syncguid_insert() { do_throw("Should not get here."); } catch (e) { - do_check_eq(e.result, - Components.results.NS_ERROR_STORAGE_CONSTRAINT); + do_check_true(e.message.startsWith("Addon sync GUID conflict")); restartManager(); AddonManager.getAddonByID(installIDs[1], function(addon) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index 81f0eddf08a0..49fef590e1c0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -17,7 +17,11 @@ skip-if = os == "android" [test_LightweightThemeManager.js] [test_backgroundupdate.js] [test_badschema.js] +# Needs rewrite for JSON XPIDB +fail-if = true [test_blocklistchange.js] +# Needs rewrite for JSON XPIDB +fail-if = true # Bug 676992: test consistently hangs on Android skip-if = os == "android" [test_blocklist_regexp.js] @@ -138,6 +142,8 @@ fail-if = os == "android" [test_bug620837.js] [test_bug655254.js] [test_bug659772.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_bug675371.js] [test_bug740612.js] [test_bug753900.js] @@ -147,8 +153,11 @@ fail-if = os == "android" [test_ChromeManifestParser.js] [test_compatoverrides.js] [test_corrupt.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_corrupt_strictcompat.js] -[test_db_sanity.js] +# needs to be converted from sqlite to JSON +fail-if = true [test_dictionary.js] [test_langpack.js] [test_disable.js] @@ -193,17 +202,33 @@ skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. [test_locale.js] [test_locked.js] +# Needs sqlite->JSON conversion +fail-if = true [test_locked2.js] +# Needs sqlite->JSON conversion +fail-if = true [test_locked_strictcompat.js] +# Needs sqlite->JSON conversion +fail-if = true [test_manifest.js] [test_mapURIToAddonID.js] # Same as test_bootstrap.js skip-if = os == "android" [test_migrate1.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate2.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate3.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate4.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrate5.js] +# Needs sqlite->JSON conversion +fail-if = true [test_migrateAddonRepository.js] [test_onPropertyChanged_appDisabled.js] [test_permissions.js]