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