Bug 746916 - Lazy load XPIDatabase. r=dtownsend
authorBlair McBride <bmcbride@mozilla.com>
Wed, 20 Jun 2012 17:52:56 +1200
changeset 101927 83f101d3c3721f6b282a0adc4c86fd0fb3983b3a
parent 101926 6475b0ff65c17b3952b0d6286a82d98ac4344cc4
child 101928 92d3a40382aaee7cc38e0396b45e447335a0310e
push id1316
push userakeybl@mozilla.com
push dateMon, 27 Aug 2012 22:37:00 +0000
treeherdermozilla-beta@db4b09302ee2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdtownsend
bugs746916
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 746916 - Lazy load XPIDatabase. r=dtownsend
toolkit/mozapps/extensions/Makefile.in
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/XPIProviderUtils.js
--- a/toolkit/mozapps/extensions/Makefile.in
+++ b/toolkit/mozapps/extensions/Makefile.in
@@ -8,16 +8,20 @@ srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 ifeq (,$(filter aurora beta release esr,$(MOZ_UPDATE_CHANNEL)))
 DEFINES += -DMOZ_COMPATIBILITY_NIGHTLY=1
 endif
 
+# This is used in multiple places, so is defined here to avoid it getting
+# out of sync.
+DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=13
+
 MODULE = extensions
 
 XPIDLSRCS = \
   amIInstallTrigger.idl \
   amIWebInstallListener.idl \
   amIWebInstaller.idl \
   $(NULL)
 
@@ -31,16 +35,17 @@ EXTRA_PP_COMPONENTS = \
 
 EXTRA_PP_JS_MODULES = \
   AddonLogging.jsm \
   AddonManager.jsm \
   AddonRepository.jsm \
   AddonUpdateChecker.jsm \
   PluginProvider.jsm \
   XPIProvider.jsm \
+  XPIProviderUtils.js \
   $(NULL)
 
 EXTRA_JS_MODULES = \
   ChromeManifestParser.jsm \
   LightweightThemeManager.jsm \
   SpellCheckDictionaryBootstrap.js \
   $(NULL)
 
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -61,19 +61,18 @@ const URI_EXTENSION_STRINGS           = 
 
 const STRING_TYPE_NAME                = "type.%ID%.name";
 
 const DIR_EXTENSIONS                  = "extensions";
 const DIR_STAGE                       = "staged";
 const DIR_XPI_STAGE                   = "staged-xpis";
 const DIR_TRASH                       = "trash";
 
-const FILE_OLD_DATABASE               = "extensions.rdf";
+const FILE_DATABASE                   = "extensions.sqlite";
 const FILE_OLD_CACHE                  = "extensions.cache";
-const FILE_DATABASE                   = "extensions.sqlite";
 const FILE_INSTALL_MANIFEST           = "install.rdf";
 const FILE_XPI_ADDONS_LIST            = "extensions.ini";
 
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_APPDIR                      = "XCurProcD";
 const KEY_TEMPDIR                     = "TmpD";
 const KEY_APP_DISTRIBUTION            = "XREAppDist";
 
@@ -88,39 +87,27 @@ const XPI_PERMISSION                  = 
 
 const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
 const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
 const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
 const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org";
 
-const DB_SCHEMA                       = 13;
+// The value for this is in Makefile.in
+#expand const DB_SCHEMA                       = __MOZ_EXTENSIONS_DB_SCHEMA__;
 
 // Properties that exist in the install manifest
 const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
                             "updateKey", "optionsURL", "optionsType", "aboutURL",
                             "iconURL", "icon64URL"];
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
 const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
 const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
 
-// Properties that only exist in the database
-const DB_METADATA        = ["syncGUID",
-                            "installDate",
-                            "updateDate",
-                            "size",
-                            "sourceURI",
-                            "releaseNotesURI",
-                            "applyBackgroundUpdates"];
-const DB_BOOL_METADATA   = ["visible", "active", "userDisabled", "appDisabled",
-                            "pendingUninstall", "bootstrap", "skinnable",
-                            "softDisabled", "isForeignInstall",
-                            "hasBinaryComponents", "strictCompatibility"];
-
 // Properties that should be migrated where possible from an old database. These
 // shouldn't include properties that can be read directly from install.rdf files
 // or calculated
 const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
                             "sourceURI", "applyBackgroundUpdates",
                             "releaseNotesURI", "isForeignInstall", "syncGUID"];
 
 const BOOTSTRAP_REASONS = {
@@ -156,16 +143,43 @@ var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{
   this.__defineGetter__(aName, function() {
     Components.utils.import("resource://gre/modules/AddonLogging.jsm");
 
     LogManager.getLogger("addons.xpi", this);
     return this[aName];
   })
 }, this);
 
+
+const LAZY_OBJECTS = ["XPIDatabase"];
+
+var gLazyObjectsLoaded = false;
+
+function loadLazyObjects() {
+  let scope = {};
+  Services.scriptloader.loadSubScript("resource://gre/modules/XPIProviderUtils.js",
+                                      scope);
+  scope.XPIProvider = XPIProvider;
+
+  for (let name of LAZY_OBJECTS) {
+    delete gGlobalScope[name];
+    gGlobalScope[name] = scope[name];
+  }
+  gLazyObjectsLoaded = true;
+  return scope;
+}
+
+for (let name of LAZY_OBJECTS) {
+  gGlobalScope.__defineGetter__(name, function() {
+    let objs = loadLazyObjects();
+    return objs[name];
+  });
+}
+
+
 /**
  * Sets permissions on a file
  *
  * @param  aFile
  *         The file or directory to operate on.
  * @param  aPermissions
  *         The permisions to set
  */
@@ -1170,73 +1184,16 @@ function escapeAddonURI(aAddon, aUri, aU
     compatMode = "ignore";
   else if (AddonManager.strictCompatibility)
     compatMode = "strict";
   uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);
 
   return uri;
 }
 
-/**
- * Copies properties from one object to another. If no target object is passed
- * a new object will be created and returned.
- *
- * @param  aObject
- *         An object to copy from
- * @param  aProperties
- *         An array of properties to be copied
- * @param  aTarget
- *         An optional target object to copy the properties to
- * @return the object that the properties were copied onto
- */
-function copyProperties(aObject, aProperties, aTarget) {
-  if (!aTarget)
-    aTarget = {};
-  aProperties.forEach(function(aProp) {
-    aTarget[aProp] = aObject[aProp];
-  });
-  return aTarget;
-}
-
-/**
- * Copies properties from a mozIStorageRow to an object. If no target object is
- * passed a new object will be created and returned.
- *
- * @param  aRow
- *         A mozIStorageRow to copy from
- * @param  aProperties
- *         An array of properties to be copied
- * @param  aTarget
- *         An optional target object to copy the properties to
- * @return the object that the properties were copied onto
- */
-function copyRowProperties(aRow, aProperties, aTarget) {
-  if (!aTarget)
-    aTarget = {};
-  aProperties.forEach(function(aProp) {
-    aTarget[aProp] = aRow.getResultByName(aProp);
-  });
-  return aTarget;
-}
-
-/**
- * A generator to synchronously return result rows from an mozIStorageStatement.
- *
- * @param  aStatement
- *         The statement to execute
- */
-function resultRows(aStatement) {
-  try {
-    while (stepStatement(aStatement))
-      yield aStatement.row;
-  }
-  finally {
-    aStatement.reset();
-  }
-}
 
 /**
  * Removes the specified files or directories in a staging directory and then if
  * the staging directory is empty attempts to remove it.
  *
  * @param  aDir
  *         nsIFile for the staging directory to clean up
  * @param  aLeafNames
@@ -1711,19 +1668,24 @@ var XPIProvider = {
 
     this.installs = null;
     this.installLocations = null;
     this.installLocationsByName = null;
 
     // This is needed to allow xpcshell tests to simulate a restart
     this.extensionsActive = false;
 
-    XPIDatabase.shutdown(function() {
+    if (gLazyObjectsLoaded) {
+      XPIDatabase.shutdown(function shutdownCallback() {
+        Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
+      });
+    }
+    else {
       Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
-    });
+    }
   },
 
   /**
    * Applies any pending theme change to the preferences.
    */
   applyThemeChange: function XPI_applyThemeChange() {
     if (!Prefs.getBoolPref(PREF_DSS_SWITCHPENDING, false))
       return;
@@ -2979,17 +2941,20 @@ var XPIProvider = {
       // If the state has changed then we must update the database
       let cache = Prefs.getCharPref(PREF_INSTALL_CACHE, null);
       updateDatabase |= cache != JSON.stringify(state);
     }
 
     // If the database doesn't exist and there are add-ons installed then we
     // must update the database however if there are no add-ons then there is
     // no need to update the database.
-    if (!XPIDatabase.dbfileExists)
+    // Avoid using XPIDatabase.dbFileExists, as that code is lazy-loaded,
+    // and we want to avoid loading it until absolutely necessary.
+    let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
+    if (!dbFile.exists())
       updateDatabase = state.length > 0;
 
     if (!updateDatabase) {
       let bootstrapDescriptors = [this.bootstrappedAddons[b].descriptor
                                   for (b in this.bootstrappedAddons)];
 
       state.forEach(function(aInstallLocationState) {
         for (let id in aInstallLocationState.addons) {
@@ -3001,22 +2966,24 @@ var XPIProvider = {
 
       if (bootstrapDescriptors.length > 0) {
         WARN("Bootstrap state is invalid (missing add-ons: " + bootstrapDescriptors.toSource() + ")");
         updateDatabase = true;
       }
     }
 
     // Catch any errors during the main startup and rollback the database changes
-    XPIDatabase.beginTransaction();
+    let transationBegun = false;
     try {
       let extensionListChanged = false;
       // If the database needs to be updated then open it and then update it
       // from the filesystem
       if (updateDatabase || hasPendingChanges) {
+        XPIDatabase.beginTransaction();
+        transationBegun = true;
         let migrateData = XPIDatabase.openConnection(false, true);
 
         try {
           extensionListChanged = this.processFileChanges(state, manifests,
                                                          aAppChanged,
                                                          aOldAppVersion,
                                                          aOldPlatformVersion,
                                                          migrateData, null);
@@ -3025,48 +2992,58 @@ var XPIProvider = {
           ERROR("Error processing file changes", e);
         }
       }
 
       if (aAppChanged) {
         // When upgrading the app and using a custom skin make sure it is still
         // compatible otherwise switch back the default
         if (this.currentSkin != this.defaultSkin) {
+          if (!transationBegun) {
+            XPIDatabase.beginTransaction();
+            transationBegun = true;
+          }
           let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
           if (!oldSkin || isAddonDisabled(oldSkin))
             this.enableDefaultTheme();
         }
 
         // When upgrading remove the old extensions cache to force older
         // versions to rescan the entire list of extensions
         let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
         if (oldCache.exists())
           oldCache.remove(true);
       }
 
       // If the application crashed before completing any pending operations then
       // we should perform them now.
       if (extensionListChanged || hasPendingChanges) {
         LOG("Updating database with changes to installed add-ons");
+        if (!transationBegun) {
+          XPIDatabase.beginTransaction();
+          transationBegun = true;
+        }
         XPIDatabase.updateActiveAddons();
         XPIDatabase.commitTransaction();
         XPIDatabase.writeAddonsList();
         Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
         Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
                                    JSON.stringify(this.bootstrappedAddons));
         return true;
       }
 
       LOG("No changes found");
-      XPIDatabase.commitTransaction();
+      if (transationBegun)
+        XPIDatabase.commitTransaction();
     }
     catch (e) {
       ERROR("Error during startup file checks, rolling back any database " +
             "changes", e);
-      XPIDatabase.rollbackTransaction();
+      if (transationBegun)
+        XPIDatabase.rollbackTransaction();
     }
 
     // Check that the add-ons list still exists
     let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
                                        true);
     if (addonsList.exists() == (state.length == 0)) {
       LOG("Add-ons list is invalid, rebuilding");
       XPIDatabase.writeAddonsList();
@@ -3987,1726 +3964,16 @@ var XPIProvider = {
     AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
 
     // Notify any other providers that this theme is now enabled again.
     if (aAddon.type == "theme" && aAddon.active)
       AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
   }
 };
 
-const FIELDS_ADDON = "internal_id, 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, " +
-                     "isForeignInstall, hasBinaryComponents, strictCompatibility";
-
-/**
- * A helper function to log an SQL error.
- *
- * @param  aError
- *         The storage error code associated with the error
- * @param  aErrorString
- *         An error message
- */
-function logSQLError(aError, aErrorString) {
-  ERROR("SQL error " + aError + ": " + aErrorString);
-}
-
-/**
- * A helper function to log any errors that occur during async statements.
- *
- * @param  aError
- *         A mozIStorageError to log
- */
-function asyncErrorLogger(aError) {
-  logSQLError(aError.result, aError.message);
-}
-
-/**
- * A helper function to execute a statement synchronously and log any error
- * that occurs.
- *
- * @param  aStatement
- *         A mozIStorageStatement to execute
- */
-function executeStatement(aStatement) {
-  try {
-    aStatement.execute();
-  }
-  catch (e) {
-    logSQLError(XPIDatabase.connection.lastError,
-                XPIDatabase.connection.lastErrorString);
-    throw e;
-  }
-}
-
-/**
- * A helper function to step a statement synchronously and log any error that
- * occurs.
- *
- * @param  aStatement
- *         A mozIStorageStatement to execute
- */
-function stepStatement(aStatement) {
-  try {
-    return aStatement.executeStep();
-  }
-  catch (e) {
-    logSQLError(XPIDatabase.connection.lastError,
-                XPIDatabase.connection.lastErrorString);
-    throw e;
-  }
-}
-
-/**
- * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal
- * instances from the results it receives. Once the statement has completed
- * executing and all of the metadata for all of the add-ons has been retrieved
- * they will be passed as an array to aCallback.
- *
- * @param  aCallback
- *         A callback function to pass the array of DBAddonInternals to
- */
-function AsyncAddonListCallback(aCallback) {
-  this.callback = aCallback;
-  this.addons = [];
-}
-
-AsyncAddonListCallback.prototype = {
-  callback: null,
-  complete: false,
-  count: 0,
-  addons: null,
-
-  handleResult: function(aResults) {
-    let row = null;
-    while (row = aResults.getNextRow()) {
-      this.count++;
-      let self = this;
-      XPIDatabase.makeAddonFromRowAsync(row, function(aAddon) {
-        function completeAddon(aRepositoryAddon) {
-          aAddon._repositoryAddon = aRepositoryAddon;
-          aAddon.compatibilityOverrides = aRepositoryAddon ?
-                                            aRepositoryAddon.compatibilityOverrides :
-                                            null;
-          self.addons.push(aAddon);
-          if (self.complete && self.addons.length == self.count)
-           self.callback(self.addons);
-        }
-
-        if ("getCachedAddonByID" in AddonRepository)
-          AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
-        else
-          completeAddon(null);
-      });
-    }
-  },
-
-  handleError: asyncErrorLogger,
-
-  handleCompletion: function(aReason) {
-    this.complete = true;
-    if (this.addons.length == this.count)
-      this.callback(this.addons);
-  }
-};
-
-var XPIDatabase = {
-  // true if the database connection has been opened
-  initialized: false,
-  // A cache of statements that are used and need to be finalized on shutdown
-  statementCache: {},
-  // A cache of weak referenced DBAddonInternals so we can reuse objects where
-  // possible
-  addonCache: [],
-  // The nested transaction count
-  transactionCount: 0,
-  // The database file
-  dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true),
-
-  // The statements used by the database
-  statements: {
-    _getDefaultLocale: "SELECT id, name, description, creator, homepageURL " +
-                       "FROM locale WHERE id=:id",
-    _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " +
-                 "locale.description, locale.creator, locale.homepageURL " +
-                 "FROM addon_locale JOIN locale ON " +
-                 "addon_locale.locale_id=locale.id WHERE " +
-                 "addon_internal_id=:internal_id",
-    _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " +
-                            "maxVersion FROM targetApplication WHERE " +
-                            "addon_internal_id=:internal_id",
-    _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " +
-                         "addon_internal_id=:internal_id",
-    _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
-                        "WHERE locale_id=:id",
-
-    addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " +
-                            ":location, :version, :type, :internalName, " +
-                            ":updateURL, :updateKey, :optionsURL, " +
-                            ":optionsType, :aboutURL, " +
-                            ":iconURL, :icon64URL, :locale, :visible, :active, " +
-                            ":userDisabled, :appDisabled, :pendingUninstall, " +
-                            ":descriptor, :installDate, :updateDate, " +
-                            ":applyBackgroundUpdates, :bootstrap, :skinnable, " +
-                            ":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
-                            ":isForeignInstall, :hasBinaryComponents, " +
-                            ":strictCompatibility)",
-    addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
-                                   "(:internal_id, :name, :locale)",
-    addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
-                             "homepageURL) VALUES (:name, :description, " +
-                             ":creator, :homepageURL)",
-    addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
-                              ":type, :value)",
-    addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
-                                        "(:internal_id, :id, :minVersion, " +
-                                        ":maxVersion)",
-    addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " +
-                                     "(:internal_id, :os, :abi)",
-
-    clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id",
-    updateAddonActive: "UPDATE addon SET active=:active WHERE " +
-                       "internal_id=:internal_id",
-
-    getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
-                     "type<>'theme' AND bootstrap=0",
-    getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
-                    "internalName=:internalName AND type='theme'",
-    getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'",
-
-    getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " +
-                        "AND location=:location",
-    getAddons: "SELECT " + FIELDS_ADDON + " FROM addon",
-    getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type",
-    getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
-                         "location=:location",
-    getInstallLocations: "SELECT DISTINCT location FROM addon",
-    getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
-                          "visible=1 AND id=:id",
-    getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " +
-                                    "WHERE visible=1 AND internalName=:internalName",
-    getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1",
-    getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " +
-                                           "addon WHERE visible=1 " +
-                                           "AND (pendingUninstall=1 OR " +
-                                           "MAX(userDisabled,appDisabled)=active)",
-    getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " +
-                        "WHERE syncGUID=:syncGUID",
-    makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
-    removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
-    // Equates to active = visible && !userDisabled && !softDisabled &&
-    //                     !appDisabled && !pendingUninstall
-    setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
-                     "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)",
-    setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
-                        "appDisabled=:appDisabled, " +
-                        "softDisabled=:softDisabled, " +
-                        "pendingUninstall=:pendingUninstall, " +
-                        "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
-                        "internal_id=:internal_id",
-    setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " +
-                        "internal_id=:internal_id",
-    setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " +
-                      "internal_id=:internal_id",
-    updateTargetApplications: "UPDATE targetApplication SET " +
-                              "minVersion=:minVersion, maxVersion=:maxVersion " +
-                              "WHERE addon_internal_id=:internal_id AND id=:id",
-
-    createSavepoint: "SAVEPOINT 'default'",
-    releaseSavepoint: "RELEASE SAVEPOINT 'default'",
-    rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'"
-  },
-
-  get dbfileExists() {
-    delete this.dbfileExists;
-    return this.dbfileExists = this.dbfile.exists();
-  },
-  set dbfileExists(aValue) {
-    delete this.dbfileExists;
-    return this.dbfileExists = aValue;
-  },
-
-  /**
-   * Begins a new transaction in the database. Transactions may be nested. Data
-   * written by an inner transaction may be rolled back on its own. Rolling back
-   * an outer transaction will rollback all the changes made by inner
-   * transactions even if they were committed. No data is written to the disk
-   * until the outermost transaction is committed. Transactions can be started
-   * even when the database is not yet open in which case they will be started
-   * when the database is first opened.
-   */
-  beginTransaction: function XPIDB_beginTransaction() {
-    if (this.initialized)
-      this.getStatement("createSavepoint").execute();
-    this.transactionCount++;
-  },
-
-  /**
-   * Commits the most recent transaction. The data may still be rolled back if
-   * an outer transaction is rolled back.
-   */
-  commitTransaction: function XPIDB_commitTransaction() {
-    if (this.transactionCount == 0) {
-      ERROR("Attempt to commit one transaction too many.");
-      return;
-    }
-
-    if (this.initialized)
-      this.getStatement("releaseSavepoint").execute();
-    this.transactionCount--;
-  },
-
-  /**
-   * Rolls back the most recent transaction. The database will return to its
-   * state when the transaction was started.
-   */
-  rollbackTransaction: function XPIDB_rollbackTransaction() {
-    if (this.transactionCount == 0) {
-      ERROR("Attempt to rollback one transaction too many.");
-      return;
-    }
-
-    if (this.initialized) {
-      this.getStatement("rollbackSavepoint").execute();
-      this.getStatement("releaseSavepoint").execute();
-    }
-    this.transactionCount--;
-  },
-
-  /**
-   * Attempts to open the database file. If it fails it will try to delete the
-   * existing file and create an empty database. If that fails then it will
-   * open an in-memory database that can be used during this session.
-   *
-   * @param  aDBFile
-   *         The nsIFile to open
-   * @return the mozIStorageConnection for the database
-   */
-  openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) {
-    LOG("Opening database");
-    let connection = null;
-
-    // Attempt to open the database
-    try {
-      connection = Services.storage.openUnsharedDatabase(aDBFile);
-      this.dbfileExists = true;
-    }
-    catch (e) {
-      ERROR("Failed to open database (1st attempt)", e);
-      try {
-        aDBFile.remove(true);
-      }
-      catch (e) {
-        ERROR("Failed to remove database that could not be opened", e);
-      }
-      try {
-        connection = Services.storage.openUnsharedDatabase(aDBFile);
-      }
-      catch (e) {
-        ERROR("Failed to open database (2nd attempt)", e);
-
-        // If we have got here there seems to be no way to open the real
-        // database, instead open a temporary memory database so things will
-        // work for this session
-        return Services.storage.openSpecialDatabase("memory");
-      }
-    }
-
-    connection.executeSimpleSQL("PRAGMA synchronous = FULL");
-    connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
-
-    return connection;
-  },
-
-  /**
-   * Opens a new connection to the database file.
-   *
-   * @param  aRebuildOnError
-   *         A boolean indicating whether add-on information should be loaded
-   *         from the install locations if the database needs to be rebuilt.
-   * @return the migration data from the database if it was an old schema or
-   *         null otherwise.
-   */
-  openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
-    delete this.connection;
-
-    if (!aForceOpen && !this.dbfileExists) {
-      this.connection = null;
-      return {};
-    }
-
-    this.initialized = true;
-
-    this.connection = this.openDatabaseFile(this.dbfile);
-
-    let migrateData = null;
-    // If the database was corrupt or missing then the new blank database will
-    // have a schema version of 0.
-    let schemaVersion = this.connection.schemaVersion;
-    if (schemaVersion != DB_SCHEMA) {
-      // A non-zero schema version means that a schema has been successfully
-      // created in the database in the past so we might be able to get useful
-      // information from it
-      if (schemaVersion != 0) {
-        LOG("Migrating data from schema " + schemaVersion);
-        migrateData = this.getMigrateDataFromDatabase();
-
-        // Delete the existing database
-        this.connection.close();
-        try {
-          if (this.dbfileExists)
-            this.dbfile.remove(true);
-
-          // Reopen an empty database
-          this.connection = this.openDatabaseFile(this.dbfile);
-        }
-        catch (e) {
-          ERROR("Failed to remove old database", e);
-          // If the file couldn't be deleted then fall back to an in-memory
-          // database
-          this.connection = Services.storage.openSpecialDatabase("memory");
-        }
-      }
-      else {
-        let dbSchema = 0;
-        try {
-          dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA);
-        } catch (e) {}
-
-        if (dbSchema == 0) {
-          // Only migrate data from the RDF if we haven't done it before
-          migrateData = this.getMigrateDataFromRDF();
-        }
-      }
-
-      // At this point the database should be completely empty
-      this.createSchema();
-
-      if (aRebuildOnError) {
-        let activeBundles = this.getActiveBundles();
-        WARN("Rebuilding add-ons database from installed extensions.");
-        this.beginTransaction();
-        try {
-          let state = XPIProvider.getInstallLocationStates();
-          XPIProvider.processFileChanges(state, {}, false, undefined, undefined,
-                                         migrateData, activeBundles)
-          // Make sure to update the active add-ons and add-ons list on shutdown
-          Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
-          this.commitTransaction();
-        }
-        catch (e) {
-          ERROR("Error processing file changes", e);
-          this.rollbackTransaction();
-        }
-      }
-    }
-
-    // If the database connection has a file open then it has the right schema
-    // by now so make sure the preferences reflect that. If not then there is
-    // an in-memory database open which means a problem opening and deleting the
-    // real database, clear the schema preference to force trying to load the
-    // database on the next startup
-    if (this.connection.databaseFile) {
-      Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
-    }
-    else {
-      try {
-        Services.prefs.clearUserPref(PREF_DB_SCHEMA);
-      }
-      catch (e) {
-        // The preference may not be defined
-      }
-    }
-    Services.prefs.savePrefFile(null);
-
-    // Begin any pending transactions
-    for (let i = 0; i < this.transactionCount; i++)
-      this.connection.executeSimpleSQL("SAVEPOINT 'default'");
-    return migrateData;
-  },
-
-  /**
-   * A lazy getter for the database connection.
-   */
-  get connection() {
-    this.openConnection(true);
-    return this.connection;
-  },
-
-  /**
-   * Gets the list of file descriptors of active extension directories or XPI
-   * files from the add-ons list. This must be loaded from disk since the
-   * directory service gives no easy way to get both directly. This list doesn't
-   * include themes as preferences already say which theme is currently active
-   *
-   * @return an array of persisitent descriptors for the directories
-   */
-  getActiveBundles: function XPIDB_getActiveBundles() {
-    let bundles = [];
-
-    let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
-                                       true);
-
-    let iniFactory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
-                     getService(Ci.nsIINIParserFactory);
-    let parser = iniFactory.createINIParser(addonsList);
-
-    let keys = parser.getKeys("ExtensionDirs");
-
-    while (keys.hasMore())
-      bundles.push(parser.getString("ExtensionDirs", keys.getNext()));
-
-    // Also include the list of active bootstrapped extensions
-    for (let id in XPIProvider.bootstrappedAddons)
-      bundles.push(XPIProvider.bootstrappedAddons[id].descriptor);
-
-    return bundles;
-  },
-
-  /**
-   * Retrieves migration data from the old extensions.rdf database.
-   *
-   * @return an object holding information about what add-ons were previously
-   *         userDisabled and any updated compatibility information
-   */
-  getMigrateDataFromRDF: function XPIDB_getMigrateDataFromRDF(aDbWasMissing) {
-    let migrateData = {};
-
-    // Migrate data from extensions.rdf
-    let rdffile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_DATABASE], true);
-    if (rdffile.exists()) {
-      LOG("Migrating data from extensions.rdf");
-      let ds = gRDF.GetDataSourceBlocking(Services.io.newFileURI(rdffile).spec);
-      let root = Cc["@mozilla.org/rdf/container;1"].
-                 createInstance(Ci.nsIRDFContainer);
-      root.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT));
-      let elements = root.GetElements();
-      while (elements.hasMoreElements()) {
-        let source = elements.getNext().QueryInterface(Ci.nsIRDFResource);
-
-        let location = getRDFProperty(ds, source, "installLocation");
-        if (location) {
-          if (!(location in migrateData))
-            migrateData[location] = {};
-          let id = source.ValueUTF8.substring(PREFIX_ITEM_URI.length);
-          migrateData[location][id] = {
-            version: getRDFProperty(ds, source, "version"),
-            userDisabled: false,
-            targetApplications: []
-          }
-
-          let disabled = getRDFProperty(ds, source, "userDisabled");
-          if (disabled == "true" || disabled == "needs-disable")
-            migrateData[location][id].userDisabled = true;
-
-          let targetApps = ds.GetTargets(source, EM_R("targetApplication"),
-                                         true);
-          while (targetApps.hasMoreElements()) {
-            let targetApp = targetApps.getNext()
-                                      .QueryInterface(Ci.nsIRDFResource);
-            let appInfo = {
-              id: getRDFProperty(ds, targetApp, "id")
-            };
-
-            let minVersion = getRDFProperty(ds, targetApp, "updatedMinVersion");
-            if (minVersion) {
-              appInfo.minVersion = minVersion;
-              appInfo.maxVersion = getRDFProperty(ds, targetApp, "updatedMaxVersion");
-            }
-            else {
-              appInfo.minVersion = getRDFProperty(ds, targetApp, "minVersion");
-              appInfo.maxVersion = getRDFProperty(ds, targetApp, "maxVersion");
-            }
-            migrateData[location][id].targetApplications.push(appInfo);
-          }
-        }
-      }
-    }
-
-    return migrateData;
-  },
-
-  /**
-   * Retrieves migration data from a database that has an older or newer schema.
-   *
-   * @return an object holding information about what add-ons were previously
-   *         userDisabled and any updated compatibility information
-   */
-  getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() {
-    let migrateData = {};
-
-    // Attempt to migrate data from a different (even future!) version of the
-    // database
-    try {
-      var stmt = this.connection.createStatement("PRAGMA table_info(addon)");
-
-      const REQUIRED = ["internal_id", "id", "location", "userDisabled",
-                        "installDate", "version"];
-
-      let reqCount = 0;
-      let props = [];
-      for (let row in resultRows(stmt)) {
-        if (REQUIRED.indexOf(row.name) != -1) {
-          reqCount++;
-          props.push(row.name);
-        }
-        else if (DB_METADATA.indexOf(row.name) != -1) {
-          props.push(row.name);
-        }
-        else if (DB_BOOL_METADATA.indexOf(row.name) != -1) {
-          props.push(row.name);
-        }
-      }
-
-      if (reqCount < REQUIRED.length) {
-        ERROR("Unable to read anything useful from the database");
-        return migrateData;
-      }
-      stmt.finalize();
-
-      stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon");
-      for (let row in resultRows(stmt)) {
-        if (!(row.location in migrateData))
-          migrateData[row.location] = {};
-        let addonData = {
-          targetApplications: []
-        }
-        migrateData[row.location][row.id] = addonData;
-
-        props.forEach(function(aProp) {
-          if (DB_BOOL_METADATA.indexOf(aProp) != -1)
-            addonData[aProp] = row[aProp] == 1;
-          else
-            addonData[aProp] = row[aProp];
-        })
-      }
-
-      var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
-                                                   "maxVersion FROM " +
-                                                   "targetApplication WHERE " +
-                                                   "addon_internal_id=:internal_id");
-
-      for (let location in migrateData) {
-        for (let id in migrateData[location]) {
-          taStmt.params.internal_id = migrateData[location][id].internal_id;
-          delete migrateData[location][id].internal_id;
-          for (let row in resultRows(taStmt)) {
-            migrateData[location][id].targetApplications.push({
-              id: row.id,
-              minVersion: row.minVersion,
-              maxVersion: row.maxVersion
-            });
-          }
-        }
-      }
-    }
-    catch (e) {
-      // An error here means the schema is too different to read
-      ERROR("Error migrating data", e);
-    }
-    finally {
-      if (taStmt)
-        taStmt.finalize();
-      if (stmt)
-        stmt.finalize();
-    }
-
-    return migrateData;
-  },
-
-  /**
-   * Shuts down the database connection and releases all cached objects.
-   */
-  shutdown: function XPIDB_shutdown(aCallback) {
-    if (this.initialized) {
-      for each (let stmt in this.statementCache)
-        stmt.finalize();
-      this.statementCache = {};
-      this.addonCache = [];
-
-      if (this.transactionCount > 0) {
-        ERROR(this.transactionCount + " outstanding transactions, rolling back.");
-        while (this.transactionCount > 0)
-          this.rollbackTransaction();
-      }
-
-      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() {
-        this.openConnection(true);
-        return this.connection;
-      });
-
-      connection.asyncClose(aCallback);
-    }
-    else {
-      if (aCallback)
-        aCallback();
-    }
-  },
-
-  /**
-   * 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(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(aReason) {
-          aCallback();
-        }
-      });
-    }
-
-    function readDefaultLocale() {
-      delete aAddon.defaultLocale;
-      let stmt = XPIDatabase.getStatement("_getDefaultLocale");
-
-      stmt.params.id = aAddon._defaultLocale;
-      stmt.executeAsync({
-        handleResult: function(aResults) {
-          aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(),
-                                                   PROP_LOCALE_SINGLE);
-          aAddon.defaultLocale.id = aAddon._defaultLocale;
-        },
-
-        handleError: asyncErrorLogger,
-
-        handleCompletion: function(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(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(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(aResults) {
-          let row = null;
-          while (row = aResults.getNextRow())
-            aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP));
-        },
-
-        handleError: asyncErrorLogger,
-
-        handleCompletion: function(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(aResults) {
-          let row = null;
-          while (row = aResults.getNextRow())
-            aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"]));
-        },
-
-        handleError: asyncErrorLogger,
-
-        handleCompletion: function(aReason) {
-          let callbacks = aAddon._pendingCallbacks;
-          delete aAddon._pendingCallbacks;
-          callbacks.forEach(function(aCallback) {
-            aCallback(aAddon);
-          });
-        }
-      });
-    }
-
-    readDefaultLocale();
-  },
-
-  /**
-   * Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one
-   * from the cache.
-   *
-   * @param  aRow
-   *         The mozIStorageRow to make the DBAddonInternal from
-   * @return a DBAddonInternal
-   */
-  makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) {
-    let internal_id = aRow.getResultByName("internal_id");
-    if (this.addonCache[internal_id]) {
-      let addon = this.addonCache[internal_id].get();
-      if (addon) {
-        // If metadata is still pending for this instance add our callback to
-        // the list to be called when complete, otherwise pass the addon to
-        // our callback
-        if ("_pendingCallbacks" in addon)
-          addon._pendingCallbacks.push(aCallback);
-        else
-          aCallback(addon);
-        return;
-      }
-    }
-
-    let addon = new XPIProvider.DBAddonInternal();
-    addon._internal_id = internal_id;
-    let location = aRow.getResultByName("location");
-    addon._installLocation = XPIProvider.installLocationsByName[location];
-    addon._descriptor = aRow.getResultByName("descriptor");
-    copyRowProperties(aRow, PROP_METADATA, addon);
-    addon._defaultLocale = aRow.getResultByName("defaultLocale");
-    copyRowProperties(aRow, DB_METADATA, addon);
-    DB_BOOL_METADATA.forEach(function(aProp) {
-      addon[aProp] = aRow.getResultByName(aProp) != 0;
-    });
-    try {
-      addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
-    }
-    catch (e) {
-      // An exception will be thrown if the add-on appears in the database but
-      // not on disk. In general this should only happen during startup as
-      // this change is being detected.
-    }
-
-    this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
-    addon._pendingCallbacks = [aCallback];
-    this.fetchAddonMetadata(addon);
-  },
-
-  /**
-   * Synchronously reads all install locations known about by the database. This
-   * is often a a subset of the total install locations when not all have
-   * installed add-ons, occasionally a superset when an install location no
-   * longer exists.
-   *
-   * @return  an array of names of install locations
-   */
-  getInstallLocations: function XPIDB_getInstallLocations() {
-    if (!this.connection)
-      return [];
-
-    let stmt = this.getStatement("getInstallLocations");
-
-    return [row.location for each (row in resultRows(stmt))];
-  },
-
-  /**
-   * Synchronously reads all the add-ons in a particular install location.
-   *
-   * @param  location
-   *         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))];
-  },
-
-  /**
-   * Asynchronously gets an add-on with a particular ID in a particular
-   * install location.
-   *
-   * @param  aId
-   *         The ID of the add-on to retrieve
-   * @param  aLocation
-   *         The name of the install location
-   * @param  aCallback
-   *         A callback to pass the DBAddonInternal to
-   */
-  getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
-    if (!this.connection) {
-      aCallback(null);
-      return;
-    }
-
-    let stmt = this.getStatement("getAddonInLocation");
-
-    stmt.params.id = aId;
-    stmt.params.location = aLocation;
-    stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
-      if (aAddons.length == 0) {
-        aCallback(null);
-        return;
-      }
-      // This should never happen but indicates invalid data in the database if
-      // it does
-      if (aAddons.length > 1)
-        ERROR("Multiple addons with ID " + aId + " found in location " + aLocation);
-      aCallback(aAddons[0]);
-    }));
-  },
-
-  /**
-   * Asynchronously gets the add-on with an ID that is visible.
-   *
-   * @param  aId
-   *         The ID of the add-on to retrieve
-   * @param  aCallback
-   *         A callback to pass the DBAddonInternal to
-   */
-  getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
-    if (!this.connection) {
-      aCallback(null);
-      return;
-    }
-
-    let stmt = this.getStatement("getVisibleAddonForID");
-
-    stmt.params.id = aId;
-    stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
-      if (aAddons.length == 0) {
-        aCallback(null);
-        return;
-      }
-      // This should never happen but indicates invalid data in the database if
-      // it does
-      if (aAddons.length > 1)
-        ERROR("Multiple visible addons with ID " + aId + " found");
-      aCallback(aAddons[0]);
-    }));
-  },
-
-  /**
-   * Asynchronously gets the visible add-ons, optionally restricting by type.
-   *
-   * @param  aTypes
-   *         An array of types to include or null to include all types
-   * @param  aCallback
-   *         A callback to pass the array of DBAddonInternals to
-   */
-  getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
-    if (!this.connection) {
-      aCallback([]);
-      return;
-    }
-
-    let stmt = null;
-    if (!aTypes || aTypes.length == 0) {
-      stmt = this.getStatement("getVisibleAddons");
-    }
-    else {
-      let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " +
-                "type IN (";
-      for (let i = 1; i <= aTypes.length; i++) {
-        sql += "?" + i;
-        if (i < aTypes.length)
-          sql += ",";
-      }
-      sql += ")";
-
-      // Note that binding to index 0 sets the value for the ?1 parameter
-      stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql);
-      for (let i = 0; i < aTypes.length; i++)
-        stmt.bindByIndex(i, aTypes[i]);
-    }
-
-    stmt.executeAsync(new AsyncAddonListCallback(aCallback));
-  },
-
-  /**
-   * Synchronously gets all add-ons of a particular type.
-   *
-   * @param  aType
-   *         The type of add-on to retrieve
-   * @return an array of DBAddonInternals
-   */
-  getAddonsByType: function XPIDB_getAddonsByType(aType) {
-    if (!this.connection)
-      return [];
-
-    let stmt = this.getStatement("getAddonsByType");
-
-    stmt.params.type = aType;
-    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
-  },
-
-  /**
-   * Synchronously gets an add-on with a particular internalName.
-   *
-   * @param  aInternalName
-   *         The internalName of the add-on to retrieve
-   * @return a DBAddonInternal
-   */
-  getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
-    if (!this.connection)
-      return null;
-
-    let stmt = this.getStatement("getVisibleAddonForInternalName");
-
-    let addon = null;
-    stmt.params.internalName = aInternalName;
-
-    if (stepStatement(stmt))
-      addon = this.makeAddonFromRow(stmt.row);
-
-    stmt.reset();
-    return addon;
-  },
-
-  /**
-   * Asynchronously gets all add-ons with pending operations.
-   *
-   * @param  aTypes
-   *         The types of add-ons to retrieve or null to get all types
-   * @param  aCallback
-   *         A callback to pass the array of DBAddonInternal to
-   */
-  getVisibleAddonsWithPendingOperations:
-    function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
-    if (!this.connection) {
-      aCallback([]);
-      return;
-    }
-
-    let stmt = null;
-    if (!aTypes || aTypes.length == 0) {
-      stmt = this.getStatement("getVisibleAddonsWithPendingOperations");
-    }
-    else {
-      let sql = "SELECT * FROM addon WHERE visible=1 AND " +
-                "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " +
-                "AND type IN (";
-      for (let i = 1; i <= aTypes.length; i++) {
-        sql += "?" + i;
-        if (i < aTypes.length)
-          sql += ",";
-      }
-      sql += ")";
-
-      // Note that binding to index 0 sets the value for the ?1 parameter
-      stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" +
-                               aTypes.length, sql);
-      for (let i = 0; i < aTypes.length; i++)
-        stmt.bindByIndex(i, aTypes[i]);
-    }
-
-    stmt.executeAsync(new AsyncAddonListCallback(aCallback));
-  },
-
-  /**
-   * Asynchronously get an add-on by its Sync GUID.
-   *
-   * @param  aGUID
-   *         Sync GUID of add-on to fetch
-   * @param  aCallback
-   *         A callback to pass the DBAddonInternal record to. Receives null
-   *         if no add-on with that GUID is found.
-   *
-   */
-  getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
-    let stmt = this.getStatement("getAddonBySyncGUID");
-    stmt.params.syncGUID = aGUID;
-
-    stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
-      if (aAddons.length == 0) {
-        aCallback(null);
-        return;
-      }
-      aCallback(aAddons[0]);
-    }));
-  },
-
-  /**
-   * Synchronously gets all add-ons in the database.
-   *
-   * @return  an array of DBAddonInternals
-   */
-  getAddons: function XPIDB_getAddons() {
-    if (!this.connection)
-      return [];
-
-    let stmt = this.getStatement("getAddons");
-
-    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];;
-  },
-
-  /**
-   * Synchronously adds an AddonInternal's metadata to the database.
-   *
-   * @param  aAddon
-   *         AddonInternal to add
-   * @param  aDescriptor
-   *         The file descriptor of the add-on
-   */
-  addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) {
-    // If there is no DB yet then forcibly create one
-    if (!this.connection)
-      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;
-    }
-
-    // Any errors in here should rollback the transaction
-    try {
-
-      if (aAddon.visible) {
-        let stmt = this.getStatement("clearVisibleAddons");
-        stmt.params.id = aAddon.id;
-        executeStatement(stmt);
-      }
-
-      let stmt = this.getStatement("addAddonMetadata_addon");
-
-      stmt.params.locale = insertLocale(aAddon.defaultLocale);
-      stmt.params.location = aAddon._installLocation.name;
-      stmt.params.descriptor = aDescriptor;
-      copyProperties(aAddon, PROP_METADATA, stmt.params);
-      copyProperties(aAddon, DB_METADATA, stmt.params);
-      DB_BOOL_METADATA.forEach(function(aProp) {
-        stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
-      });
-      executeStatement(stmt);
-      let internal_id = this.connection.lastInsertRowID;
-
-      stmt = this.getStatement("addAddonMetadata_addon_locale");
-      aAddon.locales.forEach(function(aLocale) {
-        let id = insertLocale(aLocale);
-        aLocale.locales.forEach(function(aName) {
-          stmt.params.internal_id = internal_id;
-          stmt.params.name = aName;
-          stmt.params.locale = id;
-          executeStatement(stmt);
-        });
-      });
-
-      stmt = this.getStatement("addAddonMetadata_targetApplication");
-
-      aAddon.targetApplications.forEach(function(aApp) {
-        stmt.params.internal_id = internal_id;
-        stmt.params.id = aApp.id;
-        stmt.params.minVersion = aApp.minVersion;
-        stmt.params.maxVersion = aApp.maxVersion;
-        executeStatement(stmt);
-      });
-
-      stmt = this.getStatement("addAddonMetadata_targetPlatform");
-
-      aAddon.targetPlatforms.forEach(function(aPlatform) {
-        stmt.params.internal_id = internal_id;
-        stmt.params.os = aPlatform.os;
-        stmt.params.abi = aPlatform.abi;
-        executeStatement(stmt);
-      });
-
-      this.commitTransaction();
-    }
-    catch (e) {
-      this.rollbackTransaction();
-      throw e;
-    }
-  },
-
-  /**
-   * Synchronously updates an add-ons metadata in the database. Currently just
-   * removes and recreates.
-   *
-   * @param  aOldAddon
-   *         The DBAddonInternal to be replaced
-   * @param  aNewAddon
-   *         The new AddonInternal to add
-   * @param  aDescriptor
-   *         The file descriptor of the add-on
-   */
-  updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
-                                                          aDescriptor) {
-    this.beginTransaction();
-
-    // Any errors in here should rollback the transaction
-    try {
-      this.removeAddonMetadata(aOldAddon);
-      aNewAddon.syncGUID = aOldAddon.syncGUID;
-      aNewAddon.installDate = aOldAddon.installDate;
-      aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
-      aNewAddon.foreignInstall = aOldAddon.foreignInstall;
-      aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
-                          !aNewAddon.appDisabled && !aNewAddon.pendingUninstall)
-
-      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);
-      });
-      this.commitTransaction();
-    }
-    catch (e) {
-      this.rollbackTransaction();
-      throw e;
-    }
-  },
-
-  /**
-   * Synchronously removes an add-on from the database.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal being removed
-   */
-  removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
-    let stmt = this.getStatement("removeAddonMetadata");
-    stmt.params.internal_id = aAddon._internal_id;
-    executeStatement(stmt);
-  },
-
-  /**
-   * Synchronously marks a DBAddonInternal as visible marking all other
-   * instances with the same ID as not visible.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal to make visible
-   * @param  callback
-   *         A callback to pass the DBAddonInternal to
-   */
-  makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
-    let stmt = this.getStatement("clearVisibleAddons");
-    stmt.params.id = aAddon.id;
-    executeStatement(stmt);
-
-    stmt = this.getStatement("makeAddonVisible");
-    stmt.params.internal_id = aAddon._internal_id;
-    executeStatement(stmt);
-
-    aAddon.visible = true;
-  },
-
-  /**
-   * Synchronously sets properties for an add-on.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal being updated
-   * @param  aProperties
-   *         A dictionary of properties to set
-   */
-  setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
-    function convertBoolean(value) {
-      return value ? 1 : 0;
-    }
-
-    let stmt = this.getStatement("setAddonProperties");
-    stmt.params.internal_id = aAddon._internal_id;
-
-    ["userDisabled", "appDisabled", "softDisabled",
-     "pendingUninstall"].forEach(function(aProp) {
-      if (aProp in aProperties) {
-        stmt.params[aProp] = convertBoolean(aProperties[aProp]);
-        aAddon[aProp] = aProperties[aProp];
-      }
-      else {
-        stmt.params[aProp] = convertBoolean(aAddon[aProp]);
-      }
-    });
-
-    if ("applyBackgroundUpdates" in aProperties) {
-      stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
-      aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
-    }
-    else {
-      stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
-    }
-
-    executeStatement(stmt);
-  },
-
-  /**
-   * Synchronously sets the Sync GUID for an add-on.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal being updated
-   * @param  aGUID
-   *         GUID string to set the value to
-   */
-  setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) {
-    let stmt = this.getStatement("setAddonSyncGUID");
-    stmt.params.internal_id = aAddon._internal_id;
-    stmt.params.syncGUID = aGUID;
-
-    executeStatement(stmt);
-  },
-
-  /**
-   * Synchronously sets the file descriptor for an add-on.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal being updated
-   * @param  aProperties
-   *         A dictionary of properties to set
-   */
-  setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) {
-    let stmt = this.getStatement("setAddonDescriptor");
-    stmt.params.internal_id = aAddon._internal_id;
-    stmt.params.descriptor = aDescriptor;
-
-    executeStatement(stmt);
-  },
-
-  /**
-   * Synchronously updates an add-on's active flag in the database.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal to update
-   */
-  updateAddonActive: function XPIDB_updateAddonActive(aAddon) {
-    LOG("Updating add-on state");
-
-    let stmt = this.getStatement("updateAddonActive");
-    stmt.params.internal_id = aAddon._internal_id;
-    stmt.params.active = aAddon.active ? 1 : 0;
-    executeStatement(stmt);
-  },
-
-  /**
-   * Synchronously calculates and updates all the active flags in the database.
-   */
-  updateActiveAddons: function XPIDB_updateActiveAddons() {
-    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 = [];
-  },
-
-  /**
-   * Writes out the XPI add-ons list for the platform to read.
-   */
-  writeAddonsList: function XPIDB_writeAddonsList() {
-    Services.appinfo.invalidateCachesOnRestart();
-
-    let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
-                                       true);
-    if (!this.connection) {
-      try {
-        addonsList.remove(false);
-        LOG("Deleted add-ons list");
-      }
-      catch (e) {
-      }
-
-      Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
-      return;
-    }
-
-    let enabledAddons = [];
-    let text = "[ExtensionDirs]\r\n";
-    let count = 0;
-    let fullCount = 0;
-
-    let stmt = this.getStatement("getActiveAddons");
-
-    for (let row in resultRows(stmt)) {
-      text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
-      enabledAddons.push(row.id + ":" + row.version);
-    }
-    fullCount += count;
-
-    // The selected skin may come from an inactive theme (the default theme
-    // when a lightweight theme is applied for example)
-    text += "\r\n[ThemeDirs]\r\n";
-
-    let dssEnabled = false;
-    try {
-      dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED);
-    } catch (e) {}
-
-    if (dssEnabled) {
-      stmt = this.getStatement("getThemes");
-    }
-    else {
-      stmt = this.getStatement("getActiveTheme");
-      stmt.params.internalName = XPIProvider.selectedSkin;
-    }
-
-    if (stmt) {
-      count = 0;
-      for (let row in resultRows(stmt)) {
-        text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
-        enabledAddons.push(row.id + ":" + row.version);
-      }
-      fullCount += count;
-    }
-
-    if (fullCount > 0) {
-      LOG("Writing add-ons list");
-
-      let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
-                                            true);
-      var fos = FileUtils.openFileOutputStream(addonsListTmp);
-      fos.write(text, text.length);
-      fos.close();
-      addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
-
-      Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
-    }
-    else {
-      if (addonsList.exists()) {
-        LOG("Deleting add-ons list");
-        addonsList.remove(false);
-      }
-
-      Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
-    }
-  }
-};
 
 function getHashStringForCrypto(aCrypto) {
   // return the two-digit hexadecimal code for a byte
   function toHexString(charCode)
     ("0" + charCode.toString(16)).slice(-2);
 
   // convert the binary hash data to a hex string.
   let binary = aCrypto.finish(false);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/XPIProviderUtils.js
@@ -0,0 +1,2005 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+                                  "resource://gre/modules/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+
+
+["LOG", "WARN", "ERROR"].forEach(function(aName) {
+  this.__defineGetter__(aName, function() {
+    Components.utils.import("resource://gre/modules/AddonLogging.jsm");
+
+    LogManager.getLogger("addons.xpi-utils", this);
+    return this[aName];
+  })
+}, this);
+
+
+const KEY_PROFILEDIR                  = "ProfD";
+const FILE_DATABASE                   = "extensions.sqlite";
+const FILE_OLD_DATABASE               = "extensions.rdf";
+const FILE_XPI_ADDONS_LIST            = "extensions.ini";
+
+// The value for this is in Makefile.in
+#expand const DB_SCHEMA                       = __MOZ_EXTENSIONS_DB_SCHEMA__;
+
+const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
+const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
+const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
+const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
+
+
+// Properties that only exist in the database
+const DB_METADATA        = ["syncGUID",
+                            "installDate",
+                            "updateDate",
+                            "size",
+                            "sourceURI",
+                            "releaseNotesURI",
+                            "applyBackgroundUpdates"];
+const DB_BOOL_METADATA   = ["visible", "active", "userDisabled", "appDisabled",
+                            "pendingUninstall", "bootstrap", "skinnable",
+                            "softDisabled", "isForeignInstall",
+                            "hasBinaryComponents", "strictCompatibility"];
+
+const FIELDS_ADDON = "internal_id, 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, " +
+                     "isForeignInstall, hasBinaryComponents, strictCompatibility";
+
+
+// Properties that exist in the install manifest
+const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
+                            "updateKey", "optionsURL", "optionsType", "aboutURL",
+                            "iconURL", "icon64URL"];
+const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
+const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
+const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
+
+
+
+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#";
+
+
+var XPIProvider;
+
+
+this.__defineGetter__("gRDF", function() {
+  delete this.gRDF;
+  return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+                     getService(Ci.nsIRDFService);
+});
+
+function EM_R(aProperty) {
+  return gRDF.GetResource(PREFIX_NS_EM + aProperty);
+}
+
+/**
+ * Converts an RDF literal, resource or integer into a string.
+ *
+ * @param  aLiteral
+ *         The RDF object to convert
+ * @return a string if the object could be converted or null
+ */
+function getRDFValue(aLiteral) {
+  if (aLiteral instanceof Ci.nsIRDFLiteral)
+    return aLiteral.Value;
+  if (aLiteral instanceof Ci.nsIRDFResource)
+    return aLiteral.Value;
+  if (aLiteral instanceof Ci.nsIRDFInt)
+    return aLiteral.Value;
+  return null;
+}
+
+/**
+ * Gets an RDF property as a string
+ *
+ * @param  aDs
+ *         The RDF datasource to read the property from
+ * @param  aResource
+ *         The RDF resource to read the property from
+ * @param  aProperty
+ *         The property to read
+ * @return a string if the property existed or null
+ */
+function getRDFProperty(aDs, aResource, aProperty) {
+  return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
+}
+
+
+/**
+ * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal
+ * instances from the results it receives. Once the statement has completed
+ * executing and all of the metadata for all of the add-ons has been retrieved
+ * they will be passed as an array to aCallback.
+ *
+ * @param  aCallback
+ *         A callback function to pass the array of DBAddonInternals to
+ */
+function AsyncAddonListCallback(aCallback) {
+  this.callback = aCallback;
+  this.addons = [];
+}
+
+AsyncAddonListCallback.prototype = {
+  callback: null,
+  complete: false,
+  count: 0,
+  addons: null,
+
+  handleResult: function(aResults) {
+    let row = null;
+    while (row = aResults.getNextRow()) {
+      this.count++;
+      let self = this;
+      XPIDatabase.makeAddonFromRowAsync(row, function(aAddon) {
+        function completeAddon(aRepositoryAddon) {
+          aAddon._repositoryAddon = aRepositoryAddon;
+          aAddon.compatibilityOverrides = aRepositoryAddon ?
+                                            aRepositoryAddon.compatibilityOverrides :
+                                            null;
+          self.addons.push(aAddon);
+          if (self.complete && self.addons.length == self.count)
+           self.callback(self.addons);
+        }
+
+        if ("getCachedAddonByID" in AddonRepository)
+          AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
+        else
+          completeAddon(null);
+      });
+    }
+  },
+
+  handleError: asyncErrorLogger,
+
+  handleCompletion: function(aReason) {
+    this.complete = true;
+    if (this.addons.length == this.count)
+      this.callback(this.addons);
+  }
+};
+
+
+/**
+ * A generator to synchronously return result rows from an mozIStorageStatement.
+ *
+ * @param  aStatement
+ *         The statement to execute
+ */
+function resultRows(aStatement) {
+  try {
+    while (stepStatement(aStatement))
+      yield aStatement.row;
+  }
+  finally {
+    aStatement.reset();
+  }
+}
+
+
+/**
+ * A helper function to log an SQL error.
+ *
+ * @param  aError
+ *         The storage error code associated with the error
+ * @param  aErrorString
+ *         An error message
+ */
+function logSQLError(aError, aErrorString) {
+  ERROR("SQL error " + aError + ": " + aErrorString);
+}
+
+/**
+ * A helper function to log any errors that occur during async statements.
+ *
+ * @param  aError
+ *         A mozIStorageError to log
+ */
+function asyncErrorLogger(aError) {
+  logSQLError(aError.result, aError.message);
+}
+
+/**
+ * A helper function to execute a statement synchronously and log any error
+ * that occurs.
+ *
+ * @param  aStatement
+ *         A mozIStorageStatement to execute
+ */
+function executeStatement(aStatement) {
+  try {
+    aStatement.execute();
+  }
+  catch (e) {
+    logSQLError(XPIDatabase.connection.lastError,
+                XPIDatabase.connection.lastErrorString);
+    throw e;
+  }
+}
+
+/**
+ * A helper function to step a statement synchronously and log any error that
+ * occurs.
+ *
+ * @param  aStatement
+ *         A mozIStorageStatement to execute
+ */
+function stepStatement(aStatement) {
+  try {
+    return aStatement.executeStep();
+  }
+  catch (e) {
+    logSQLError(XPIDatabase.connection.lastError,
+                XPIDatabase.connection.lastErrorString);
+    throw e;
+  }
+}
+
+
+/**
+ * Copies properties from one object to another. If no target object is passed
+ * a new object will be created and returned.
+ *
+ * @param  aObject
+ *         An object to copy from
+ * @param  aProperties
+ *         An array of properties to be copied
+ * @param  aTarget
+ *         An optional target object to copy the properties to
+ * @return the object that the properties were copied onto
+ */
+function copyProperties(aObject, aProperties, aTarget) {
+  if (!aTarget)
+    aTarget = {};
+  aProperties.forEach(function(aProp) {
+    aTarget[aProp] = aObject[aProp];
+  });
+  return aTarget;
+}
+
+/**
+ * Copies properties from a mozIStorageRow to an object. If no target object is
+ * passed a new object will be created and returned.
+ *
+ * @param  aRow
+ *         A mozIStorageRow to copy from
+ * @param  aProperties
+ *         An array of properties to be copied
+ * @param  aTarget
+ *         An optional target object to copy the properties to
+ * @return the object that the properties were copied onto
+ */
+function copyRowProperties(aRow, aProperties, aTarget) {
+  if (!aTarget)
+    aTarget = {};
+  aProperties.forEach(function(aProp) {
+    aTarget[aProp] = aRow.getResultByName(aProp);
+  });
+  return aTarget;
+}
+
+/**
+ * A helper function to log an SQL error.
+ *
+ * @param  aError
+ *         The storage error code associated with the error
+ * @param  aErrorString
+ *         An error message
+ */
+function logSQLError(aError, aErrorString) {
+  ERROR("SQL error " + aError + ": " + aErrorString);
+}
+
+/**
+ * A helper function to log any errors that occur during async statements.
+ *
+ * @param  aError
+ *         A mozIStorageError to log
+ */
+function asyncErrorLogger(aError) {
+  logSQLError(aError.result, aError.message);
+}
+
+/**
+ * A helper function to execute a statement synchronously and log any error
+ * that occurs.
+ *
+ * @param  aStatement
+ *         A mozIStorageStatement to execute
+ */
+function executeStatement(aStatement) {
+  try {
+    aStatement.execute();
+  }
+  catch (e) {
+    logSQLError(XPIDatabase.connection.lastError,
+                XPIDatabase.connection.lastErrorString);
+    throw e;
+  }
+}
+
+/**
+ * A helper function to step a statement synchronously and log any error that
+ * occurs.
+ *
+ * @param  aStatement
+ *         A mozIStorageStatement to execute
+ */
+function stepStatement(aStatement) {
+  try {
+    return aStatement.executeStep();
+  }
+  catch (e) {
+    logSQLError(XPIDatabase.connection.lastError,
+                XPIDatabase.connection.lastErrorString);
+    throw e;
+  }
+}
+
+/**
+ * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal
+ * instances from the results it receives. Once the statement has completed
+ * executing and all of the metadata for all of the add-ons has been retrieved
+ * they will be passed as an array to aCallback.
+ *
+ * @param  aCallback
+ *         A callback function to pass the array of DBAddonInternals to
+ */
+function AsyncAddonListCallback(aCallback) {
+  this.callback = aCallback;
+  this.addons = [];
+}
+
+AsyncAddonListCallback.prototype = {
+  callback: null,
+  complete: false,
+  count: 0,
+  addons: null,
+
+  handleResult: function(aResults) {
+    let row = null;
+    while (row = aResults.getNextRow()) {
+      this.count++;
+      let self = this;
+      XPIDatabase.makeAddonFromRowAsync(row, function(aAddon) {
+        function completeAddon(aRepositoryAddon) {
+          aAddon._repositoryAddon = aRepositoryAddon;
+          aAddon.compatibilityOverrides = aRepositoryAddon ?
+                                            aRepositoryAddon.compatibilityOverrides :
+                                            null;
+          self.addons.push(aAddon);
+          if (self.complete && self.addons.length == self.count)
+            self.callback(self.addons);
+        }
+
+        if ("getCachedAddonByID" in AddonRepository)
+          AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
+        else
+          completeAddon(null);
+      });
+    }
+  },
+
+  handleError: asyncErrorLogger,
+
+  handleCompletion: function(aReason) {
+    this.complete = true;
+    if (this.addons.length == this.count)
+      this.callback(this.addons);
+  }
+};
+
+
+var XPIDatabase = {
+  // true if the database connection has been opened
+  initialized: false,
+  // A cache of statements that are used and need to be finalized on shutdown
+  statementCache: {},
+  // A cache of weak referenced DBAddonInternals so we can reuse objects where
+  // possible
+  addonCache: [],
+  // The nested transaction count
+  transactionCount: 0,
+  // The database file
+  dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true),
+
+  // The statements used by the database
+  statements: {
+    _getDefaultLocale: "SELECT id, name, description, creator, homepageURL " +
+                       "FROM locale WHERE id=:id",
+    _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " +
+                 "locale.description, locale.creator, locale.homepageURL " +
+                 "FROM addon_locale JOIN locale ON " +
+                 "addon_locale.locale_id=locale.id WHERE " +
+                 "addon_internal_id=:internal_id",
+    _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " +
+                            "maxVersion FROM targetApplication WHERE " +
+                            "addon_internal_id=:internal_id",
+    _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " +
+                         "addon_internal_id=:internal_id",
+    _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
+                        "WHERE locale_id=:id",
+
+    addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " +
+                            ":location, :version, :type, :internalName, " +
+                            ":updateURL, :updateKey, :optionsURL, " +
+                            ":optionsType, :aboutURL, " +
+                            ":iconURL, :icon64URL, :locale, :visible, :active, " +
+                            ":userDisabled, :appDisabled, :pendingUninstall, " +
+                            ":descriptor, :installDate, :updateDate, " +
+                            ":applyBackgroundUpdates, :bootstrap, :skinnable, " +
+                            ":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
+                            ":isForeignInstall, :hasBinaryComponents, " +
+                            ":strictCompatibility)",
+    addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
+                                   "(:internal_id, :name, :locale)",
+    addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
+                             "homepageURL) VALUES (:name, :description, " +
+                             ":creator, :homepageURL)",
+    addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
+                              ":type, :value)",
+    addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
+                                        "(:internal_id, :id, :minVersion, " +
+                                        ":maxVersion)",
+    addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " +
+                                     "(:internal_id, :os, :abi)",
+
+    clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id",
+    updateAddonActive: "UPDATE addon SET active=:active WHERE " +
+                       "internal_id=:internal_id",
+
+    getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
+                     "type<>'theme' AND bootstrap=0",
+    getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
+                    "internalName=:internalName AND type='theme'",
+    getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'",
+
+    getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " +
+                        "AND location=:location",
+    getAddons: "SELECT " + FIELDS_ADDON + " FROM addon",
+    getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type",
+    getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
+                         "location=:location",
+    getInstallLocations: "SELECT DISTINCT location FROM addon",
+    getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
+                          "visible=1 AND id=:id",
+    getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " +
+                                    "WHERE visible=1 AND internalName=:internalName",
+    getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1",
+    getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " +
+                                           "addon WHERE visible=1 " +
+                                           "AND (pendingUninstall=1 OR " +
+                                           "MAX(userDisabled,appDisabled)=active)",
+    getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " +
+                        "WHERE syncGUID=:syncGUID",
+    makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
+    removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
+    // Equates to active = visible && !userDisabled && !softDisabled &&
+    //                     !appDisabled && !pendingUninstall
+    setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
+                     "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)",
+    setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
+                        "appDisabled=:appDisabled, " +
+                        "softDisabled=:softDisabled, " +
+                        "pendingUninstall=:pendingUninstall, " +
+                        "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
+                        "internal_id=:internal_id",
+    setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " +
+                        "internal_id=:internal_id",
+    setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " +
+                      "internal_id=:internal_id",
+    updateTargetApplications: "UPDATE targetApplication SET " +
+                              "minVersion=:minVersion, maxVersion=:maxVersion " +
+                              "WHERE addon_internal_id=:internal_id AND id=:id",
+
+    createSavepoint: "SAVEPOINT 'default'",
+    releaseSavepoint: "RELEASE SAVEPOINT 'default'",
+    rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'"
+  },
+
+  get dbfileExists() {
+    delete this.dbfileExists;
+    return this.dbfileExists = this.dbfile.exists();
+  },
+  set dbfileExists(aValue) {
+    delete this.dbfileExists;
+    return this.dbfileExists = aValue;
+  },
+
+  /**
+   * Begins a new transaction in the database. Transactions may be nested. Data
+   * written by an inner transaction may be rolled back on its own. Rolling back
+   * an outer transaction will rollback all the changes made by inner
+   * transactions even if they were committed. No data is written to the disk
+   * until the outermost transaction is committed. Transactions can be started
+   * even when the database is not yet open in which case they will be started
+   * when the database is first opened.
+   */
+  beginTransaction: function XPIDB_beginTransaction() {
+    if (this.initialized)
+      this.getStatement("createSavepoint").execute();
+    this.transactionCount++;
+  },
+
+  /**
+   * Commits the most recent transaction. The data may still be rolled back if
+   * an outer transaction is rolled back.
+   */
+  commitTransaction: function XPIDB_commitTransaction() {
+    if (this.transactionCount == 0) {
+      ERROR("Attempt to commit one transaction too many.");
+      return;
+    }
+
+    if (this.initialized)
+      this.getStatement("releaseSavepoint").execute();
+    this.transactionCount--;
+  },
+
+  /**
+   * Rolls back the most recent transaction. The database will return to its
+   * state when the transaction was started.
+   */
+  rollbackTransaction: function XPIDB_rollbackTransaction() {
+    if (this.transactionCount == 0) {
+      ERROR("Attempt to rollback one transaction too many.");
+      return;
+    }
+
+    if (this.initialized) {
+      this.getStatement("rollbackSavepoint").execute();
+      this.getStatement("releaseSavepoint").execute();
+    }
+    this.transactionCount--;
+  },
+
+  /**
+   * Attempts to open the database file. If it fails it will try to delete the
+   * existing file and create an empty database. If that fails then it will
+   * open an in-memory database that can be used during this session.
+   *
+   * @param  aDBFile
+   *         The nsIFile to open
+   * @return the mozIStorageConnection for the database
+   */
+  openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) {
+    LOG("Opening database");
+    let connection = null;
+
+    // Attempt to open the database
+    try {
+      connection = Services.storage.openUnsharedDatabase(aDBFile);
+      this.dbfileExists = true;
+    }
+    catch (e) {
+      ERROR("Failed to open database (1st attempt)", e);
+      try {
+        aDBFile.remove(true);
+      }
+      catch (e) {
+        ERROR("Failed to remove database that could not be opened", e);
+      }
+      try {
+        connection = Services.storage.openUnsharedDatabase(aDBFile);
+      }
+      catch (e) {
+        ERROR("Failed to open database (2nd attempt)", e);
+
+        // If we have got here there seems to be no way to open the real
+        // database, instead open a temporary memory database so things will
+        // work for this session
+        return Services.storage.openSpecialDatabase("memory");
+      }
+    }
+
+    connection.executeSimpleSQL("PRAGMA synchronous = FULL");
+    connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
+
+    return connection;
+  },
+
+  /**
+   * Opens a new connection to the database file.
+   *
+   * @param  aRebuildOnError
+   *         A boolean indicating whether add-on information should be loaded
+   *         from the install locations if the database needs to be rebuilt.
+   * @return the migration data from the database if it was an old schema or
+   *         null otherwise.
+   */
+  openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
+    delete this.connection;
+
+    if (!aForceOpen && !this.dbfileExists) {
+      this.connection = null;
+      return {};
+    }
+
+    this.initialized = true;
+
+    this.connection = this.openDatabaseFile(this.dbfile);
+
+    let migrateData = null;
+    // If the database was corrupt or missing then the new blank database will
+    // have a schema version of 0.
+    let schemaVersion = this.connection.schemaVersion;
+    if (schemaVersion != DB_SCHEMA) {
+      // A non-zero schema version means that a schema has been successfully
+      // created in the database in the past so we might be able to get useful
+      // information from it
+      if (schemaVersion != 0) {
+        LOG("Migrating data from schema " + schemaVersion);
+        migrateData = this.getMigrateDataFromDatabase();
+
+        // Delete the existing database
+        this.connection.close();
+        try {
+          if (this.dbfileExists)
+            this.dbfile.remove(true);
+
+          // Reopen an empty database
+          this.connection = this.openDatabaseFile(this.dbfile);
+        }
+        catch (e) {
+          ERROR("Failed to remove old database", e);
+          // If the file couldn't be deleted then fall back to an in-memory
+          // database
+          this.connection = Services.storage.openSpecialDatabase("memory");
+        }
+      }
+      else {
+        let dbSchema = 0;
+        try {
+          dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA);
+        } catch (e) {}
+
+        if (dbSchema == 0) {
+          // Only migrate data from the RDF if we haven't done it before
+          migrateData = this.getMigrateDataFromRDF();
+        }
+      }
+
+      // At this point the database should be completely empty
+      this.createSchema();
+
+      if (aRebuildOnError) {
+        let activeBundles = this.getActiveBundles();
+        WARN("Rebuilding add-ons database from installed extensions.");
+        this.beginTransaction();
+        try {
+          let state = XPIProvider.getInstallLocationStates();
+          XPIProvider.processFileChanges(state, {}, false, undefined, undefined,
+                                         migrateData, activeBundles)
+          // Make sure to update the active add-ons and add-ons list on shutdown
+          Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+          this.commitTransaction();
+        }
+        catch (e) {
+          ERROR("Error processing file changes", e);
+          this.rollbackTransaction();
+        }
+      }
+    }
+
+    // If the database connection has a file open then it has the right schema
+    // by now so make sure the preferences reflect that. If not then there is
+    // an in-memory database open which means a problem opening and deleting the
+    // real database, clear the schema preference to force trying to load the
+    // database on the next startup
+    if (this.connection.databaseFile) {
+      Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
+    }
+    else {
+      try {
+        Services.prefs.clearUserPref(PREF_DB_SCHEMA);
+      }
+      catch (e) {
+        // The preference may not be defined
+      }
+    }
+    Services.prefs.savePrefFile(null);
+
+    // Begin any pending transactions
+    for (let i = 0; i < this.transactionCount; i++)
+      this.connection.executeSimpleSQL("SAVEPOINT 'default'");
+    return migrateData;
+  },
+
+  /**
+   * A lazy getter for the database connection.
+   */
+  get connection() {
+    this.openConnection(true);
+    return this.connection;
+  },
+
+  /**
+   * Gets the list of file descriptors of active extension directories or XPI
+   * files from the add-ons list. This must be loaded from disk since the
+   * directory service gives no easy way to get both directly. This list doesn't
+   * include themes as preferences already say which theme is currently active
+   *
+   * @return an array of persisitent descriptors for the directories
+   */
+  getActiveBundles: function XPIDB_getActiveBundles() {
+    let bundles = [];
+
+    let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
+                                       true);
+
+    let iniFactory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+                     getService(Ci.nsIINIParserFactory);
+    let parser = iniFactory.createINIParser(addonsList);
+
+    let keys = parser.getKeys("ExtensionDirs");
+
+    while (keys.hasMore())
+      bundles.push(parser.getString("ExtensionDirs", keys.getNext()));
+
+    // Also include the list of active bootstrapped extensions
+    for (let id in XPIProvider.bootstrappedAddons)
+      bundles.push(XPIProvider.bootstrappedAddons[id].descriptor);
+
+    return bundles;
+  },
+
+  /**
+   * Retrieves migration data from the old extensions.rdf database.
+   *
+   * @return an object holding information about what add-ons were previously
+   *         userDisabled and any updated compatibility information
+   */
+  getMigrateDataFromRDF: function XPIDB_getMigrateDataFromRDF(aDbWasMissing) {
+    let migrateData = {};
+
+    // Migrate data from extensions.rdf
+    let rdffile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_DATABASE], true);
+    if (rdffile.exists()) {
+      LOG("Migrating data from extensions.rdf");
+      let ds = gRDF.GetDataSourceBlocking(Services.io.newFileURI(rdffile).spec);
+      let root = Cc["@mozilla.org/rdf/container;1"].
+                 createInstance(Ci.nsIRDFContainer);
+      root.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT));
+      let elements = root.GetElements();
+      while (elements.hasMoreElements()) {
+        let source = elements.getNext().QueryInterface(Ci.nsIRDFResource);
+
+        let location = getRDFProperty(ds, source, "installLocation");
+        if (location) {
+          if (!(location in migrateData))
+            migrateData[location] = {};
+          let id = source.ValueUTF8.substring(PREFIX_ITEM_URI.length);
+          migrateData[location][id] = {
+            version: getRDFProperty(ds, source, "version"),
+            userDisabled: false,
+            targetApplications: []
+          }
+
+          let disabled = getRDFProperty(ds, source, "userDisabled");
+          if (disabled == "true" || disabled == "needs-disable")
+            migrateData[location][id].userDisabled = true;
+
+          let targetApps = ds.GetTargets(source, EM_R("targetApplication"),
+                                         true);
+          while (targetApps.hasMoreElements()) {
+            let targetApp = targetApps.getNext()
+                                      .QueryInterface(Ci.nsIRDFResource);
+            let appInfo = {
+              id: getRDFProperty(ds, targetApp, "id")
+            };
+
+            let minVersion = getRDFProperty(ds, targetApp, "updatedMinVersion");
+            if (minVersion) {
+              appInfo.minVersion = minVersion;
+              appInfo.maxVersion = getRDFProperty(ds, targetApp, "updatedMaxVersion");
+            }
+            else {
+              appInfo.minVersion = getRDFProperty(ds, targetApp, "minVersion");
+              appInfo.maxVersion = getRDFProperty(ds, targetApp, "maxVersion");
+            }
+            migrateData[location][id].targetApplications.push(appInfo);
+          }
+        }
+      }
+    }
+
+    return migrateData;
+  },
+
+  /**
+   * Retrieves migration data from a database that has an older or newer schema.
+   *
+   * @return an object holding information about what add-ons were previously
+   *         userDisabled and any updated compatibility information
+   */
+  getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() {
+    let migrateData = {};
+
+    // Attempt to migrate data from a different (even future!) version of the
+    // database
+    try {
+      var stmt = this.connection.createStatement("PRAGMA table_info(addon)");
+
+      const REQUIRED = ["internal_id", "id", "location", "userDisabled",
+                        "installDate", "version"];
+
+      let reqCount = 0;
+      let props = [];
+      for (let row in resultRows(stmt)) {
+        if (REQUIRED.indexOf(row.name) != -1) {
+          reqCount++;
+          props.push(row.name);
+        }
+        else if (DB_METADATA.indexOf(row.name) != -1) {
+          props.push(row.name);
+        }
+        else if (DB_BOOL_METADATA.indexOf(row.name) != -1) {
+          props.push(row.name);
+        }
+      }
+
+      if (reqCount < REQUIRED.length) {
+        ERROR("Unable to read anything useful from the database");
+        return migrateData;
+      }
+      stmt.finalize();
+
+      stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon");
+      for (let row in resultRows(stmt)) {
+        if (!(row.location in migrateData))
+          migrateData[row.location] = {};
+        let addonData = {
+          targetApplications: []
+        }
+        migrateData[row.location][row.id] = addonData;
+
+        props.forEach(function(aProp) {
+          if (DB_BOOL_METADATA.indexOf(aProp) != -1)
+            addonData[aProp] = row[aProp] == 1;
+          else
+            addonData[aProp] = row[aProp];
+        })
+      }
+
+      var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
+                                                   "maxVersion FROM " +
+                                                   "targetApplication WHERE " +
+                                                   "addon_internal_id=:internal_id");
+
+      for (let location in migrateData) {
+        for (let id in migrateData[location]) {
+          taStmt.params.internal_id = migrateData[location][id].internal_id;
+          delete migrateData[location][id].internal_id;
+          for (let row in resultRows(taStmt)) {
+            migrateData[location][id].targetApplications.push({
+              id: row.id,
+              minVersion: row.minVersion,
+              maxVersion: row.maxVersion
+            });
+          }
+        }
+      }
+    }
+    catch (e) {
+      // An error here means the schema is too different to read
+      ERROR("Error migrating data", e);
+    }
+    finally {
+      if (taStmt)
+        taStmt.finalize();
+      if (stmt)
+        stmt.finalize();
+    }
+
+    return migrateData;
+  },
+
+  /**
+   * Shuts down the database connection and releases all cached objects.
+   */
+  shutdown: function XPIDB_shutdown(aCallback) {
+    LOG("shutdown");
+    if (this.initialized) {
+      for each (let stmt in this.statementCache)
+        stmt.finalize();
+      this.statementCache = {};
+      this.addonCache = [];
+
+      if (this.transactionCount > 0) {
+        ERROR(this.transactionCount + " outstanding transactions, rolling back.");
+        while (this.transactionCount > 0)
+          this.rollbackTransaction();
+      }
+
+      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() {
+        this.openConnection(true);
+        return this.connection;
+      });
+
+      connection.asyncClose(function() {
+        LOG("Database closed");
+        aCallback();
+      });
+    }
+    else {
+      if (aCallback)
+        aCallback();
+    }
+  },
+
+  /**
+   * 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(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(aReason) {
+          aCallback();
+        }
+      });
+    }
+
+    function readDefaultLocale() {
+      delete aAddon.defaultLocale;
+      let stmt = XPIDatabase.getStatement("_getDefaultLocale");
+
+      stmt.params.id = aAddon._defaultLocale;
+      stmt.executeAsync({
+        handleResult: function(aResults) {
+          aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(),
+                                                   PROP_LOCALE_SINGLE);
+          aAddon.defaultLocale.id = aAddon._defaultLocale;
+        },
+
+        handleError: asyncErrorLogger,
+
+        handleCompletion: function(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(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(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(aResults) {
+          let row = null;
+          while (row = aResults.getNextRow())
+            aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP));
+        },
+
+        handleError: asyncErrorLogger,
+
+        handleCompletion: function(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(aResults) {
+          let row = null;
+          while (row = aResults.getNextRow())
+            aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"]));
+        },
+
+        handleError: asyncErrorLogger,
+
+        handleCompletion: function(aReason) {
+          let callbacks = aAddon._pendingCallbacks;
+          delete aAddon._pendingCallbacks;
+          callbacks.forEach(function(aCallback) {
+            aCallback(aAddon);
+          });
+        }
+      });
+    }
+
+    readDefaultLocale();
+  },
+
+  /**
+   * Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one
+   * from the cache.
+   *
+   * @param  aRow
+   *         The mozIStorageRow to make the DBAddonInternal from
+   * @return a DBAddonInternal
+   */
+  makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) {
+    let internal_id = aRow.getResultByName("internal_id");
+    if (this.addonCache[internal_id]) {
+      let addon = this.addonCache[internal_id].get();
+      if (addon) {
+        // If metadata is still pending for this instance add our callback to
+        // the list to be called when complete, otherwise pass the addon to
+        // our callback
+        if ("_pendingCallbacks" in addon)
+          addon._pendingCallbacks.push(aCallback);
+        else
+          aCallback(addon);
+        return;
+      }
+    }
+
+    let addon = new XPIProvider.DBAddonInternal();
+    addon._internal_id = internal_id;
+    let location = aRow.getResultByName("location");
+    addon._installLocation = XPIProvider.installLocationsByName[location];
+    addon._descriptor = aRow.getResultByName("descriptor");
+    copyRowProperties(aRow, PROP_METADATA, addon);
+    addon._defaultLocale = aRow.getResultByName("defaultLocale");
+    copyRowProperties(aRow, DB_METADATA, addon);
+    DB_BOOL_METADATA.forEach(function(aProp) {
+      addon[aProp] = aRow.getResultByName(aProp) != 0;
+    });
+    try {
+      addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
+    }
+    catch (e) {
+      // An exception will be thrown if the add-on appears in the database but
+      // not on disk. In general this should only happen during startup as
+      // this change is being detected.
+    }
+
+    this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
+    addon._pendingCallbacks = [aCallback];
+    this.fetchAddonMetadata(addon);
+  },
+
+  /**
+   * Synchronously reads all install locations known about by the database. This
+   * is often a a subset of the total install locations when not all have
+   * installed add-ons, occasionally a superset when an install location no
+   * longer exists.
+   *
+   * @return  an array of names of install locations
+   */
+  getInstallLocations: function XPIDB_getInstallLocations() {
+    if (!this.connection)
+      return [];
+
+    let stmt = this.getStatement("getInstallLocations");
+
+    return [row.location for each (row in resultRows(stmt))];
+  },
+
+  /**
+   * Synchronously reads all the add-ons in a particular install location.
+   *
+   * @param  location
+   *         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))];
+  },
+
+  /**
+   * Asynchronously gets an add-on with a particular ID in a particular
+   * install location.
+   *
+   * @param  aId
+   *         The ID of the add-on to retrieve
+   * @param  aLocation
+   *         The name of the install location
+   * @param  aCallback
+   *         A callback to pass the DBAddonInternal to
+   */
+  getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
+    if (!this.connection) {
+      aCallback(null);
+      return;
+    }
+
+    let stmt = this.getStatement("getAddonInLocation");
+
+    stmt.params.id = aId;
+    stmt.params.location = aLocation;
+    stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
+      if (aAddons.length == 0) {
+        aCallback(null);
+        return;
+      }
+      // This should never happen but indicates invalid data in the database if
+      // it does
+      if (aAddons.length > 1)
+        ERROR("Multiple addons with ID " + aId + " found in location " + aLocation);
+      aCallback(aAddons[0]);
+    }));
+  },
+
+  /**
+   * Asynchronously gets the add-on with an ID that is visible.
+   *
+   * @param  aId
+   *         The ID of the add-on to retrieve
+   * @param  aCallback
+   *         A callback to pass the DBAddonInternal to
+   */
+  getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
+    if (!this.connection) {
+      aCallback(null);
+      return;
+    }
+
+    let stmt = this.getStatement("getVisibleAddonForID");
+
+    stmt.params.id = aId;
+    stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
+      if (aAddons.length == 0) {
+        aCallback(null);
+        return;
+      }
+      // This should never happen but indicates invalid data in the database if
+      // it does
+      if (aAddons.length > 1)
+        ERROR("Multiple visible addons with ID " + aId + " found");
+      aCallback(aAddons[0]);
+    }));
+  },
+
+  /**
+   * Asynchronously gets the visible add-ons, optionally restricting by type.
+   *
+   * @param  aTypes
+   *         An array of types to include or null to include all types
+   * @param  aCallback
+   *         A callback to pass the array of DBAddonInternals to
+   */
+  getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
+    if (!this.connection) {
+      aCallback([]);
+      return;
+    }
+
+    let stmt = null;
+    if (!aTypes || aTypes.length == 0) {
+      stmt = this.getStatement("getVisibleAddons");
+    }
+    else {
+      let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " +
+                "type IN (";
+      for (let i = 1; i <= aTypes.length; i++) {
+        sql += "?" + i;
+        if (i < aTypes.length)
+          sql += ",";
+      }
+      sql += ")";
+
+      // Note that binding to index 0 sets the value for the ?1 parameter
+      stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql);
+      for (let i = 0; i < aTypes.length; i++)
+        stmt.bindByIndex(i, aTypes[i]);
+    }
+
+    stmt.executeAsync(new AsyncAddonListCallback(aCallback));
+  },
+
+  /**
+   * Synchronously gets all add-ons of a particular type.
+   *
+   * @param  aType
+   *         The type of add-on to retrieve
+   * @return an array of DBAddonInternals
+   */
+  getAddonsByType: function XPIDB_getAddonsByType(aType) {
+    if (!this.connection)
+      return [];
+
+    let stmt = this.getStatement("getAddonsByType");
+
+    stmt.params.type = aType;
+    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
+  },
+
+  /**
+   * Synchronously gets an add-on with a particular internalName.
+   *
+   * @param  aInternalName
+   *         The internalName of the add-on to retrieve
+   * @return a DBAddonInternal
+   */
+  getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
+    if (!this.connection)
+      return null;
+
+    let stmt = this.getStatement("getVisibleAddonForInternalName");
+
+    let addon = null;
+    stmt.params.internalName = aInternalName;
+
+    if (stepStatement(stmt))
+      addon = this.makeAddonFromRow(stmt.row);
+
+    stmt.reset();
+    return addon;
+  },
+
+  /**
+   * Asynchronously gets all add-ons with pending operations.
+   *
+   * @param  aTypes
+   *         The types of add-ons to retrieve or null to get all types
+   * @param  aCallback
+   *         A callback to pass the array of DBAddonInternal to
+   */
+  getVisibleAddonsWithPendingOperations:
+    function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
+    if (!this.connection) {
+      aCallback([]);
+      return;
+    }
+
+    let stmt = null;
+    if (!aTypes || aTypes.length == 0) {
+      stmt = this.getStatement("getVisibleAddonsWithPendingOperations");
+    }
+    else {
+      let sql = "SELECT * FROM addon WHERE visible=1 AND " +
+                "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " +
+                "AND type IN (";
+      for (let i = 1; i <= aTypes.length; i++) {
+        sql += "?" + i;
+        if (i < aTypes.length)
+          sql += ",";
+      }
+      sql += ")";
+
+      // Note that binding to index 0 sets the value for the ?1 parameter
+      stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" +
+                               aTypes.length, sql);
+      for (let i = 0; i < aTypes.length; i++)
+        stmt.bindByIndex(i, aTypes[i]);
+    }
+
+    stmt.executeAsync(new AsyncAddonListCallback(aCallback));
+  },
+
+  /**
+   * Asynchronously get an add-on by its Sync GUID.
+   *
+   * @param  aGUID
+   *         Sync GUID of add-on to fetch
+   * @param  aCallback
+   *         A callback to pass the DBAddonInternal record to. Receives null
+   *         if no add-on with that GUID is found.
+   *
+   */
+  getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
+    let stmt = this.getStatement("getAddonBySyncGUID");
+    stmt.params.syncGUID = aGUID;
+
+    stmt.executeAsync(new AsyncAddonListCallback(function(aAddons) {
+      if (aAddons.length == 0) {
+        aCallback(null);
+        return;
+      }
+      aCallback(aAddons[0]);
+    }));
+  },
+
+  /**
+   * Synchronously gets all add-ons in the database.
+   *
+   * @return  an array of DBAddonInternals
+   */
+  getAddons: function XPIDB_getAddons() {
+    if (!this.connection)
+      return [];
+
+    let stmt = this.getStatement("getAddons");
+
+    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];;
+  },
+
+  /**
+   * Synchronously adds an AddonInternal's metadata to the database.
+   *
+   * @param  aAddon
+   *         AddonInternal to add
+   * @param  aDescriptor
+   *         The file descriptor of the add-on
+   */
+  addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) {
+    // If there is no DB yet then forcibly create one
+    if (!this.connection)
+      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;
+    }
+
+    // Any errors in here should rollback the transaction
+    try {
+
+      if (aAddon.visible) {
+        let stmt = this.getStatement("clearVisibleAddons");
+        stmt.params.id = aAddon.id;
+        executeStatement(stmt);
+      }
+
+      let stmt = this.getStatement("addAddonMetadata_addon");
+
+      stmt.params.locale = insertLocale(aAddon.defaultLocale);
+      stmt.params.location = aAddon._installLocation.name;
+      stmt.params.descriptor = aDescriptor;
+      copyProperties(aAddon, PROP_METADATA, stmt.params);
+      copyProperties(aAddon, DB_METADATA, stmt.params);
+      DB_BOOL_METADATA.forEach(function(aProp) {
+        stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
+      });
+      executeStatement(stmt);
+      let internal_id = this.connection.lastInsertRowID;
+
+      stmt = this.getStatement("addAddonMetadata_addon_locale");
+      aAddon.locales.forEach(function(aLocale) {
+        let id = insertLocale(aLocale);
+        aLocale.locales.forEach(function(aName) {
+          stmt.params.internal_id = internal_id;
+          stmt.params.name = aName;
+          stmt.params.locale = id;
+          executeStatement(stmt);
+        });
+      });
+
+      stmt = this.getStatement("addAddonMetadata_targetApplication");
+
+      aAddon.targetApplications.forEach(function(aApp) {
+        stmt.params.internal_id = internal_id;
+        stmt.params.id = aApp.id;
+        stmt.params.minVersion = aApp.minVersion;
+        stmt.params.maxVersion = aApp.maxVersion;
+        executeStatement(stmt);
+      });
+
+      stmt = this.getStatement("addAddonMetadata_targetPlatform");
+
+      aAddon.targetPlatforms.forEach(function(aPlatform) {
+        stmt.params.internal_id = internal_id;
+        stmt.params.os = aPlatform.os;
+        stmt.params.abi = aPlatform.abi;
+        executeStatement(stmt);
+      });
+
+      this.commitTransaction();
+    }
+    catch (e) {
+      this.rollbackTransaction();
+      throw e;
+    }
+  },
+
+  /**
+   * Synchronously updates an add-ons metadata in the database. Currently just
+   * removes and recreates.
+   *
+   * @param  aOldAddon
+   *         The DBAddonInternal to be replaced
+   * @param  aNewAddon
+   *         The new AddonInternal to add
+   * @param  aDescriptor
+   *         The file descriptor of the add-on
+   */
+  updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
+                                                          aDescriptor) {
+    this.beginTransaction();
+
+    // Any errors in here should rollback the transaction
+    try {
+      this.removeAddonMetadata(aOldAddon);
+      aNewAddon.syncGUID = aOldAddon.syncGUID;
+      aNewAddon.installDate = aOldAddon.installDate;
+      aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
+      aNewAddon.foreignInstall = aOldAddon.foreignInstall;
+      aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
+                          !aNewAddon.appDisabled && !aNewAddon.pendingUninstall)
+
+      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);
+      });
+      this.commitTransaction();
+    }
+    catch (e) {
+      this.rollbackTransaction();
+      throw e;
+    }
+  },
+
+  /**
+   * Synchronously removes an add-on from the database.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal being removed
+   */
+  removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
+    let stmt = this.getStatement("removeAddonMetadata");
+    stmt.params.internal_id = aAddon._internal_id;
+    executeStatement(stmt);
+  },
+
+  /**
+   * Synchronously marks a DBAddonInternal as visible marking all other
+   * instances with the same ID as not visible.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal to make visible
+   * @param  callback
+   *         A callback to pass the DBAddonInternal to
+   */
+  makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
+    let stmt = this.getStatement("clearVisibleAddons");
+    stmt.params.id = aAddon.id;
+    executeStatement(stmt);
+
+    stmt = this.getStatement("makeAddonVisible");
+    stmt.params.internal_id = aAddon._internal_id;
+    executeStatement(stmt);
+
+    aAddon.visible = true;
+  },
+
+  /**
+   * Synchronously sets properties for an add-on.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal being updated
+   * @param  aProperties
+   *         A dictionary of properties to set
+   */
+  setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
+    function convertBoolean(value) {
+      return value ? 1 : 0;
+    }
+
+    let stmt = this.getStatement("setAddonProperties");
+    stmt.params.internal_id = aAddon._internal_id;
+
+    ["userDisabled", "appDisabled", "softDisabled",
+     "pendingUninstall"].forEach(function(aProp) {
+      if (aProp in aProperties) {
+        stmt.params[aProp] = convertBoolean(aProperties[aProp]);
+        aAddon[aProp] = aProperties[aProp];
+      }
+      else {
+        stmt.params[aProp] = convertBoolean(aAddon[aProp]);
+      }
+    });
+
+    if ("applyBackgroundUpdates" in aProperties) {
+      stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
+      aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
+    }
+    else {
+      stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
+    }
+
+    executeStatement(stmt);
+  },
+
+  /**
+   * Synchronously sets the Sync GUID for an add-on.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal being updated
+   * @param  aGUID
+   *         GUID string to set the value to
+   */
+  setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) {
+    let stmt = this.getStatement("setAddonSyncGUID");
+    stmt.params.internal_id = aAddon._internal_id;
+    stmt.params.syncGUID = aGUID;
+
+    executeStatement(stmt);
+  },
+
+  /**
+   * Synchronously sets the file descriptor for an add-on.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal being updated
+   * @param  aProperties
+   *         A dictionary of properties to set
+   */
+  setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) {
+    let stmt = this.getStatement("setAddonDescriptor");
+    stmt.params.internal_id = aAddon._internal_id;
+    stmt.params.descriptor = aDescriptor;
+
+    executeStatement(stmt);
+  },
+
+  /**
+   * Synchronously updates an add-on's active flag in the database.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal to update
+   */
+  updateAddonActive: function XPIDB_updateAddonActive(aAddon) {
+    LOG("Updating add-on state");
+
+    let stmt = this.getStatement("updateAddonActive");
+    stmt.params.internal_id = aAddon._internal_id;
+    stmt.params.active = aAddon.active ? 1 : 0;
+    executeStatement(stmt);
+  },
+
+  /**
+   * Synchronously calculates and updates all the active flags in the database.
+   */
+  updateActiveAddons: function XPIDB_updateActiveAddons() {
+    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 = [];
+  },
+
+  /**
+   * Writes out the XPI add-ons list for the platform to read.
+   */
+  writeAddonsList: function XPIDB_writeAddonsList() {
+    Services.appinfo.invalidateCachesOnRestart();
+
+    let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
+                                       true);
+    if (!this.connection) {
+      try {
+        addonsList.remove(false);
+        LOG("Deleted add-ons list");
+      }
+      catch (e) {
+      }
+
+      Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
+      return;
+    }
+
+    let enabledAddons = [];
+    let text = "[ExtensionDirs]\r\n";
+    let count = 0;
+    let fullCount = 0;
+
+    let stmt = this.getStatement("getActiveAddons");
+
+    for (let row in resultRows(stmt)) {
+      text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
+      enabledAddons.push(row.id + ":" + row.version);
+    }
+    fullCount += count;
+
+    // The selected skin may come from an inactive theme (the default theme
+    // when a lightweight theme is applied for example)
+    text += "\r\n[ThemeDirs]\r\n";
+
+    let dssEnabled = false;
+    try {
+      dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED);
+    } catch (e) {}
+
+    if (dssEnabled) {
+      stmt = this.getStatement("getThemes");
+    }
+    else {
+      stmt = this.getStatement("getActiveTheme");
+      stmt.params.internalName = XPIProvider.selectedSkin;
+    }
+
+    if (stmt) {
+      count = 0;
+      for (let row in resultRows(stmt)) {
+        text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
+        enabledAddons.push(row.id + ":" + row.version);
+      }
+      fullCount += count;
+    }
+
+    if (fullCount > 0) {
+      LOG("Writing add-ons list");
+
+      let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
+                                            true);
+      var fos = FileUtils.openFileOutputStream(addonsListTmp);
+      fos.write(text, text.length);
+      fos.close();
+      addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
+
+      Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
+    }
+    else {
+      if (addonsList.exists()) {
+        LOG("Deleting add-ons list");
+        addonsList.remove(false);
+      }
+
+      Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
+    }
+  }
+};