Backed out changeset 5861a7f63f25 (bug 853388)
authorTim Taubert <ttaubert@mozilla.com>
Fri, 09 Aug 2013 04:20:07 +0200
changeset 154747 2467fe99d9b0dde6b847c1cdb4cb191d719850e2
parent 154746 59a3d59d9fb7db15b980b8cdefec744ca957c799
child 154748 917f586dd1422be9869bfea5c6c2cc31cc9dc8b9
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs853388
milestone26.0a1
backs out5861a7f63f25374b5438f42ff10a316e5b3fdada
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 5861a7f63f25 (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_bad_json.js
toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.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/xpcshell.ini
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -590,18 +590,24 @@ function isUsableAddon(aAddon) {
 
   return true;
 }
 
 function isAddonDisabled(aAddon) {
   return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled;
 }
 
-XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
-                                   Ci.nsIRDFService);
+Object.defineProperty(this, "gRDF", {
+  get: function gRDFGetter() {
+    delete this.gRDF;
+    return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+                       getService(Ci.nsIRDFService);
+  },
+  configurable: true
+});
 
 function EM_R(aProperty) {
   return gRDF.GetResource(PREFIX_NS_EM + aProperty);
 }
 
 /**
  * Converts an RDF literal, resource or integer into a string.
  *
@@ -1754,17 +1760,17 @@ var XPIProvider = {
                                          this.defaultSkin);
     this.selectedSkin = this.currentSkin;
     this.applyThemeChange();
 
     this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
                                                      null);
     this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
                                                           null);
-    this.enabledAddons = "";
+    this.enabledAddons = [];
 
     Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
     Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
 
     let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
                                            aOldPlatformVersion);
 
     // Changes to installed extensions may have changed which theme is selected
@@ -2893,17 +2899,16 @@ var XPIProvider = {
       }
       else {
         newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon))
       }
 
       let newDBAddon = null;
       try {
         // Update the database.
-        // XXX I don't think this can throw any more
         newDBAddon = 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);
@@ -2995,18 +3000,19 @@ var XPIProvider = {
     aState.reverse().forEach(function(aSt) {
 
       // We can't include the install location directly in the state as it has
       // to be cached as JSON.
       let installLocation = this.installLocationsByName[aSt.name];
       let addonStates = aSt.addons;
 
       // Check if the database knows about any add-ons in this install location.
-      if (knownLocations.has(installLocation.name)) {
-        knownLocations.delete(installLocation.name);
+      let pos = knownLocations.indexOf(installLocation.name);
+      if (pos >= 0) {
+        knownLocations.splice(pos, 1);
         let addons = XPIDatabase.getAddonsInLocation(installLocation.name);
         // Iterate through the add-ons installed the last time the application
         // ran
         addons.forEach(function(aOldAddon) {
           // If a version of this add-on has been installed in an higher
           // priority install location then count it as changed
           if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
                           .indexOf(aOldAddon.id) != -1) {
@@ -3073,22 +3079,22 @@ var XPIProvider = {
                               locMigrateData[id]) || changed;
       }
     }, this);
 
     // 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.
-    for (let location of knownLocations) {
-      let addons = XPIDatabase.getAddonsInLocation(location);
+    knownLocations.forEach(function(aLocation) {
+      let addons = XPIDatabase.getAddonsInLocation(aLocation);
       addons.forEach(function(aOldAddon) {
         changed = removeMetadata(aOldAddon) || changed;
       }, this);
-    }
+    }, this);
 
     // Tell Telemetry what we found
     AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked);
     if (modifiedUnpacked > 0)
       AddonManagerPrivate.recordSimpleMeasure("modifiedExceptInstallRDF", modifiedExManifest);
     AddonManagerPrivate.recordSimpleMeasure("modifiedXPI", modifiedXPI);
 
     // Cache the new install location states
@@ -5368,18 +5374,16 @@ AddonInstall.prototype = {
 
         if (this.addon.bootstrap) {
           if (this.addon.active) {
             XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
                                             this.addon.type, file, "startup",
                                             reason, extraParams);
           }
           else {
-            // XXX this makes it dangerous to do many things in onInstallEnded
-            // listeners because important cleanup hasn't been done yet
             XPIProvider.unloadBootstrapScope(this.addon.id);
           }
         }
       }
     }
     catch (e) {
       WARN("Failed to install", e);
       if (stagedAddon.exists())
@@ -5747,18 +5751,18 @@ UpdateChecker.prototype = {
     this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
     this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
     this.callListener("onUpdateFinished", createWrapper(this.addon), aError);
   }
 };
 
 /**
  * The AddonInternal is an internal only representation of add-ons. It may
- * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm)
- * or an install manifest.
+ * have come from the database (see DBAddonInternal below) or an install
+ * manifest.
  */
 function AddonInternal() {
 }
 
 AddonInternal.prototype = {
   _selectedLocale: null,
   active: false,
   visible: false,
--- a/toolkit/mozapps/extensions/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/XPIProviderUtils.js
@@ -11,16 +11,17 @@ const Cr = Components.results;
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/AddonRepository.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
+
 ["LOG", "WARN", "ERROR"].forEach(function(aName) {
   Object.defineProperty(this, aName, {
     get: function logFuncGetter () {
       Components.utils.import("resource://gre/modules/AddonLogging.jsm");
 
       LogManager.getLogger("addons.xpi-utils", this);
       return this[aName];
     },
@@ -87,18 +88,24 @@ const PROP_JSON_FIELDS = ["id", "syncGUI
                           "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#";
 
-XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
-                                   Ci.nsIRDFService);
+Object.defineProperty(this, "gRDF", {
+  get: function gRDFGetter() {
+    delete this.gRDF;
+    return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+                       getService(Ci.nsIRDFService);
+  },
+  configurable: true
+});
 
 function EM_R(aProperty) {
   return gRDF.GetResource(PREFIX_NS_EM + aProperty);
 }
 
 /**
  * Converts an RDF literal, resource or integer into a string.
  *
@@ -126,16 +133,70 @@ function getRDFValue(aLiteral) {
  * @param  aProperty
  *         The property to read
  * @return a string if the property existed or null
  */
 function getRDFProperty(aDs, aResource, aProperty) {
   return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
 }
 
+
+/**
+ * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal
+ * instances from the results it receives. Once the statement has completed
+ * executing and all of the metadata for all of the add-ons has been retrieved
+ * they will be passed as an array to aCallback.
+ *
+ * @param  aCallback
+ *         A callback function to pass the array of DBAddonInternals to
+ */
+function AsyncAddonListCallback(aCallback) {
+  this.callback = aCallback;
+  this.addons = [];
+}
+
+AsyncAddonListCallback.prototype = {
+  callback: null,
+  complete: false,
+  count: 0,
+  addons: null,
+
+  handleResult: function AsyncAddonListCallback_handleResult(aResults) {
+    let row = null;
+    while ((row = aResults.getNextRow())) {
+      this.count++;
+      let self = this;
+      XPIDatabase.makeAddonFromRowAsync(row, function handleResult_makeAddonFromRowAsync(aAddon) {
+        function completeAddon(aRepositoryAddon) {
+          aAddon._repositoryAddon = aRepositoryAddon;
+          aAddon.compatibilityOverrides = aRepositoryAddon ?
+                                            aRepositoryAddon.compatibilityOverrides :
+                                            null;
+          self.addons.push(aAddon);
+          if (self.complete && self.addons.length == self.count)
+           self.callback(self.addons);
+        }
+
+        if ("getCachedAddonByID" in AddonRepository)
+          AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
+        else
+          completeAddon(null);
+      });
+    }
+  },
+
+  handleError: asyncErrorLogger,
+
+  handleCompletion: function 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;
   }
@@ -228,16 +289,34 @@ function logSQLError(aError, aErrorStrin
  * @param  aError
  *         A mozIStorageError to log
  */
 function asyncErrorLogger(aError) {
   logSQLError(aError.result, aError.message);
 }
 
 /**
+ * A helper function to execute a statement synchronously and log any error
+ * that occurs.
+ *
+ * @param  aStatement
+ *         A mozIStorageStatement to execute
+ */
+function executeStatement(aStatement) {
+  try {
+    aStatement.execute();
+  }
+  catch (e) {
+    logSQLError(XPIDatabase.connection.lastError,
+                XPIDatabase.connection.lastErrorString);
+    throw e;
+  }
+}
+
+/**
  * A helper function to step a statement synchronously and log any error that
  * occurs.
  *
  * @param  aStatement
  *         A mozIStorageStatement to execute
  */
 function stepStatement(aStatement) {
   try {
@@ -289,60 +368,63 @@ function copyRowProperties(aRow, aProper
     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.
   }
 
-  // XXX Can we redesign pendingUpgrade?
-  XPCOMUtils.defineLazyGetter(this, "pendingUpgrade",
-    function DBA_pendingUpgradeGetter() {
+  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) {
-          delete this.pendingUpgrade;
           return this.pendingUpgrade = install.addon;
         }
       };
-      return null;
-    });
+    },
+    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 ||
@@ -350,100 +432,215 @@ DBAddonInternal.prototype = {
           aTargetApp.minVersion = aUpdateTarget.minVersion;
           aTargetApp.maxVersion = aUpdateTarget.maxVersion;
         }
       });
     });
     XPIProvider.updateAddonDisabledState(this);
     XPIDatabase.commitTransaction();
   },
-
   get inDatabase() {
     return true;
-  },
-
-  toJSON: function() {
-    return copyProperties(this, PROP_JSON_FIELDS);
   }
 }
 
 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,
-  // Special handling for when the database is locked at first load
-  lockedDatabase: false,
+
+  // The statements used by the database
+  statements: {
+    _getDefaultLocale: "SELECT id, name, description, creator, homepageURL " +
+                       "FROM locale WHERE id=:id",
+    _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " +
+                 "locale.description, locale.creator, locale.homepageURL " +
+                 "FROM addon_locale JOIN locale ON " +
+                 "addon_locale.locale_id=locale.id WHERE " +
+                 "addon_internal_id=:internal_id",
+    _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " +
+                            "maxVersion FROM targetApplication WHERE " +
+                            "addon_internal_id=:internal_id",
+    _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " +
+                         "addon_internal_id=:internal_id",
+    _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
+                        "WHERE locale_id=:id",
+
+    clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id",
+    updateAddonActive: "UPDATE addon SET active=:active WHERE " +
+                       "internal_id=:internal_id",
+
+    getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
+                     "type<>'theme' AND bootstrap=0",
+    getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
+                    "internalName=:internalName AND type='theme'",
+    getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'",
 
-  // XXX may be able to refactor this away
+    getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " +
+                        "AND location=:location",
+    getAddons: "SELECT " + FIELDS_ADDON + " FROM addon",
+    getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type",
+    getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
+                         "location=:location",
+    getInstallLocations: "SELECT DISTINCT location FROM addon",
+    getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
+                          "visible=1 AND id=:id",
+    getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " +
+                                    "WHERE visible=1 AND internalName=:internalName",
+    getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1",
+    getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " +
+                                           "addon WHERE visible=1 " +
+                                           "AND (pendingUninstall=1 OR " +
+                                           "MAX(userDisabled,appDisabled)=active)",
+    getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " +
+                        "WHERE syncGUID=:syncGUID",
+    makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
+    removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
+    // Equates to active = visible && !userDisabled && !softDisabled &&
+    //                     !appDisabled && !pendingUninstall
+    setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
+                     "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)",
+    setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
+                        "appDisabled=:appDisabled, " +
+                        "softDisabled=:softDisabled, " +
+                        "pendingUninstall=:pendingUninstall, " +
+                        "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
+                        "internal_id=:internal_id",
+    setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " +
+                        "internal_id=:internal_id",
+    setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " +
+                      "internal_id=:internal_id",
+    updateTargetApplications: "UPDATE targetApplication SET " +
+                              "minVersion=:minVersion, maxVersion=:maxVersion " +
+                              "WHERE addon_internal_id=:internal_id AND id=:id",
+
+    createSavepoint: "SAVEPOINT 'default'",
+    releaseSavepoint: "RELEASE SAVEPOINT 'default'",
+    rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'"
+  },
+
   get dbfileExists() {
     delete this.dbfileExists;
-    return this.dbfileExists = this.jsonFile.exists();
+    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.
-   * XXX should we remove the JSON file if it would be empty? Not sure if that
-   * would ever happen, given the default theme
    */
   writeJSON: function XPIDB_writeJSON() {
     // XXX should have a guard here for if the addonDB hasn't been auto-loaded yet
-
-    // Don't mess with an existing database on disk, if it was locked at start up
-    if (this.lockedDatabase)
-      return;
-
     let addons = [];
-    for (let [key, addon] of this.addonDB) {
-      addons.push(addon);
+    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
-      let out = JSON.stringify(toSave, null, 2);
-      // dump("Writing JSON:\n" + out + "\n");
-      converter.writeString(out);
+      converter.writeString(JSON.stringify(toSave, null, 2));
       converter.flush();
       // nsConverterOutputStream doesn't finish() safe output streams on close()
       FileUtils.closeSafeFileOutputStream(stream);
       converter.close();
-      this.dbfileExists = true;
-      // XXX probably only want to do this if the version is different
-      Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
-      Services.prefs.savePrefFile(null);   // XXX is this bad sync I/O?
     }
     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.
    */
@@ -479,207 +676,180 @@ this.XPIDatabase = {
       return;
     }
 
     this.transactionCount--;
     // XXX IRVING we don't handle rollback in the JSON store
   },
 
   /**
-   * Pull upgrade information from an existing SQLITE database
+   * 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.
    *
-   * @return false if there is no SQLITE database
-   *         true and sets this.migrateData to null if the SQLITE DB exists
-   *              but does not contain useful information
-   *         true and sets this.migrateData to
-   *              {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...}
-   *              if there is useful information
+   * @param  aDBFile
+   *         The nsIFile to open
+   * @return the mozIStorageConnection for the database
    */
-  loadSqliteData: function XPIDB_loadSqliteData() {
+  openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) {
+    LOG("Opening database");
     let connection = null;
-    let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
-    if (!dbfile.exists()) {
-      return false;
-    }
+
     // Attempt to open the database
     try {
-      connection = Services.storage.openUnsharedDatabase(dbfile);
+      connection = Services.storage.openUnsharedDatabase(aDBFile);
+      this.dbfileExists = true;
     }
     catch (e) {
-      // exists but SQLITE can't open it
-      WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e);
-      this.migrateData = null;
-      return true;
+      ERROR("Failed to open database (1st attempt)", e);
+      // If the database was locked for some reason then assume it still
+      // has some good data and we should try to load it the next time around.
+      if (e.result != Cr.NS_ERROR_STORAGE_BUSY) {
+        try {
+          aDBFile.remove(true);
+        }
+        catch (e) {
+          ERROR("Failed to remove database that could not be opened", e);
+        }
+        try {
+          connection = Services.storage.openUnsharedDatabase(aDBFile);
+        }
+        catch (e) {
+          ERROR("Failed to open database (2nd attempt)", e);
+
+          // If we have got here there seems to be no way to open the real
+          // database, instead open a temporary memory database so things will
+          // work for this session.
+          return Services.storage.openSpecialDatabase("memory");
+        }
+      }
+      else {
+        return Services.storage.openSpecialDatabase("memory");
+      }
     }
-    LOG("Migrating data from sqlite");
-    this.migrateData = this.getMigrateDataFromDatabase(connection);
-    connection.close();
-    return true;
+
+    connection.executeSimpleSQL("PRAGMA synchronous = FULL");
+    connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
+
+    return connection;
   },
 
   /**
-   * Opens and reads the database file, upgrading from old
-   * databases or making a new DB if needed.
+   * Opens a new connection to the database file.
    *
-   * The possibilities, in order of priority, are:
-   * 1) Perfectly good, up to date database
-   * 2) Out of date JSON database needs to be upgraded => upgrade
-   * 3) JSON database exists but is mangled somehow => build new JSON
-   * 4) no JSON DB, but a useable SQLITE db we can upgrade from => upgrade
-   * 5) useless SQLITE DB => build new JSON
-   * 6) useable RDF DB => upgrade
-   * 7) useless RDF DB => build new JSON
-   * 8) Nothing at all => build new JSON
    * @param  aRebuildOnError
    *         A boolean indicating whether add-on information should be loaded
    *         from the install locations if the database needs to be rebuilt.
-   *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
    */
   openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
-    // XXX TELEMETRY report opens with aRebuildOnError true (which implies delayed open)
-    // vs. aRebuildOnError false (DB loaded during startup)
-    delete this.addonDB;
-    this.migrateData = null;
-    let fstream = null;
-    let data = "";
-    try {
-      LOG("Opening XPI database " + this.jsonFile.path);
-      fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
-              createInstance(Components.interfaces.nsIFileInputStream);
-      fstream.init(this.jsonFile, -1, 0, 0);
-      let cstream = null;
-      try {
-        cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
-                createInstance(Components.interfaces.nsIConverterInputStream);
-        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);
-        }
-        // dump("Loaded JSON:\n" + data + "\n");
-        let inputAddons = JSON.parse(data);
-        // Now do some sanity checks on our JSON db
-        if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
-          // Content of JSON file is bad, need to rebuild from scratch
-          ERROR("bad JSON file contents");
-          this.rebuildDatabase(aRebuildOnError);
-        }
-        if (inputAddons.schemaVersion != DB_SCHEMA) {
-          // Handle mismatched JSON schema version. For now, we assume backward/forward
-          // compatibility as long as we preserve unknown fields during save & restore
-          // XXX preserve schema version and unknown fields during save/restore
-          LOG("JSON schema mismatch: expected " + DB_SCHEMA +
-              ", actual " + inputAddons.schemaVersion);
-        }
-        // If we got here, we probably have good data
-        // Make AddonInternal instances from the loaded data and save them
-        let addonDB = new Map();
-        inputAddons.addons.forEach(function(loadedAddon) {
-          let newAddon = new DBAddonInternal(loadedAddon);
-          addonDB.set(newAddon._key, newAddon);
-        });
-        this.addonDB = addonDB;
-        LOG("Successfully read XPI database");
-      }
-      catch(e) {
-        // If we catch and log a SyntaxError from the JSON
-        // parser, the xpcshell test harness fails the test for us: bug 870828
-        if (e.name == "SyntaxError") {
-          ERROR("Syntax error parsing saved XPI JSON data");
-        }
-        else {
-          ERROR("Failed to load XPI JSON data from profile", e);
-        }
-        this.rebuildDatabase(aRebuildOnError);
-      }
-      finally {
-        if (cstream)
-          cstream.close();
-      }
-    }
-    catch (e) {
-      if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
-        // XXX re-implement logic to decide whether to upgrade database
-        // by checking the DB_SCHEMA_VERSION preference.
-        // Fall back to attempting database upgrades
-        WARN("Extensions database not found; attempting to upgrade");
-        // See if there is SQLITE to migrate from
-        if (!this.loadSqliteData()) {
-          // Nope, try RDF
-          this.migrateData = this.getMigrateDataFromRDF();
-        }
-
-        this.rebuildDatabase(aRebuildOnError);
-      }
-      else {
-        WARN("Extensions database " + this.jsonFile.path +
-            " exists but is not readable; rebuilding in memory", e);
-        // XXX open question - if we can overwrite at save time, should we, or should we
-        // leave the locked database in case we can recover from it next time we start up?
-        this.lockedDatabase = true;
-        // XXX TELEMETRY report when this happens?
-        this.rebuildDatabase(aRebuildOnError);
-      }
-    }
-    finally {
-      if (fstream)
-        fstream.close();
-    }
-
+    this.openJSONDatabase();
     this.initialized = true;
     return;
+    // XXX IRVING deal with the migration logic below and in openDatabaseFile...
 
-    // XXX what about aForceOpen? Appears to handle the case of "don't open DB file if there aren't any extensions"?
+    delete this.connection;
+
     if (!aForceOpen && !this.dbfileExists) {
       this.connection = null;
       return;
     }
-  },
+
+    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) {
+      // A non-zero schema version means that a schema has been successfully
+      // created in the database in the past so we might be able to get useful
+      // information from it
+      if (schemaVersion != 0) {
+        LOG("Migrating data from schema " + schemaVersion);
+        this.migrateData = this.getMigrateDataFromDatabase();
+
+        // Delete the existing database
+        this.connection.close();
+        try {
+          if (this.dbfileExists)
+            this.dbfile.remove(true);
 
-  /**
-   * Rebuild the database from addon install directories. If this.migrateData
-   * is available, uses migrated information for settings on the addons found
-   * during rebuild
-   * @param aRebuildOnError
-   *         A boolean indicating whether add-on information should be loaded
-   *         from the install locations if the database needs to be rebuilt.
-   *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
-   */
-  rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) {
-    // If there is no migration data then load the list of add-on directories
-    // that were active during the last run
-    this.addonDB = new Map();
-    if (!this.migrateData)
-      this.activeBundles = this.getActiveBundles();
+          // Reopen an empty database
+          this.connection = this.openDatabaseFile(this.dbfile);
+        }
+        catch (e) {
+          ERROR("Failed to remove old database", e);
+          // If the file couldn't be deleted then fall back to an in-memory
+          // database
+          this.connection = Services.storage.openSpecialDatabase("memory");
+        }
+      }
+      else {
+        let dbSchema = 0;
+        try {
+          dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA);
+        } catch (e) {}
 
-    if (aRebuildOnError) {
-      WARN("Rebuilding add-ons database from installed extensions.");
-      this.beginTransaction();
+        if (dbSchema == 0) {
+          // Only migrate data from the RDF if we haven't done it before
+          this.migrateData = this.getMigrateDataFromRDF();
+        }
+      }
+
+      // At this point the database should be completely empty
       try {
-        let state = XPIProvider.getInstallLocationStates();
-        XPIProvider.processFileChanges(state, {}, false);
-        // Make sure to update the active add-ons and add-ons list on shutdown
-        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
-        this.commitTransaction();
+        this.createSchema();
       }
       catch (e) {
-        ERROR("Error processing file changes", e);
-        this.rollbackTransaction();
+        // If creating the schema fails, then the database is unusable,
+        // fall back to an in-memory database.
+        this.connection = Services.storage.openSpecialDatabase("memory");
+      }
+
+      // If there is no migration data then load the list of add-on directories
+      // that were active during the last run
+      if (!this.migrateData)
+        this.activeBundles = this.getActiveBundles();
+
+      if (aRebuildOnError) {
+        WARN("Rebuilding add-ons database from installed extensions.");
+        this.beginTransaction();
+        try {
+          let state = XPIProvider.getInstallLocationStates();
+          XPIProvider.processFileChanges(state, {}, false);
+          // Make sure to update the active add-ons and add-ons list on shutdown
+          Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+          this.commitTransaction();
+        }
+        catch (e) {
+          ERROR("Error processing file changes", e);
+          this.rollbackTransaction();
+        }
       }
     }
+
+    // If the database connection has a file open then it has the right schema
+    // by now so make sure the preferences reflect that.
+    if (this.connection.databaseFile) {
+      Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
+      Services.prefs.savePrefFile(null);
+    }
+
+    // Begin any pending transactions
+    for (let i = 0; i < this.transactionCount; i++)
+      this.connection.executeSimpleSQL("SAVEPOINT 'default'");
   },
 
   /**
    * Lazy getter for the addons database
    */
   get addonDB() {
-    this.openConnection(true);
+    delete this.addonDB;
+    this.openJSONDatabase();
     return this.addonDB;
   },
 
   /**
    * 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
@@ -789,23 +959,23 @@ this.XPIDatabase = {
   },
 
   /**
    * Retrieves migration data from a database that has an older or newer schema.
    *
    * @return an object holding information about what add-ons were previously
    *         userDisabled and any updated compatibility information
    */
-  getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase(aConnection) {
+  getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() {
     let migrateData = {};
 
     // Attempt to migrate data from a different (even future!) version of the
     // database
     try {
-      var stmt = aConnection.createStatement("PRAGMA table_info(addon)");
+      var stmt = this.connection.createStatement("PRAGMA table_info(addon)");
 
       const REQUIRED = ["internal_id", "id", "location", "userDisabled",
                         "installDate", "version"];
 
       let reqCount = 0;
       let props = [];
       for (let row in resultRows(stmt)) {
         if (REQUIRED.indexOf(row.name) != -1) {
@@ -821,17 +991,17 @@ this.XPIDatabase = {
       }
 
       if (reqCount < REQUIRED.length) {
         ERROR("Unable to read anything useful from the database");
         return null;
       }
       stmt.finalize();
 
-      stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon");
+      stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon");
       for (let row in resultRows(stmt)) {
         if (!(row.location in migrateData))
           migrateData[row.location] = {};
         let addonData = {
           targetApplications: []
         }
         migrateData[row.location][row.id] = addonData;
 
@@ -840,17 +1010,17 @@ this.XPIDatabase = {
             addonData.foreignInstall = (row[aProp] == 1);
           if (DB_BOOL_METADATA.indexOf(aProp) != -1)
             addonData[aProp] = row[aProp] == 1;
           else
             addonData[aProp] = row[aProp];
         })
       }
 
-      var taStmt = aConnection.createStatement("SELECT id, minVersion, " +
+      var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
                                                    "maxVersion FROM " +
                                                    "targetApplication WHERE " +
                                                    "addon_internal_id=:internal_id");
 
       for (let location in migrateData) {
         for (let id in migrateData[location]) {
           taStmt.params.internal_id = migrateData[location][id].internal_id;
           delete migrateData[location][id].internal_id;
@@ -888,27 +1058,29 @@ this.XPIDatabase = {
       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
-      if (this.lockedDatabase)
-        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+      // XXX IRVING special case for if we fail to save extensions.json?
+      // XXX maybe doesn't need to be at shutdown?
+      // if (!this.connection.databaseFile)
+      //   Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
 
       this.initialized = false;
 
       // 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.openConnection(true);
+          this.openJSONDatabase();
           return this.addonDB;
         },
         configurable: true
       });
       // 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)
@@ -921,42 +1093,43 @@ this.XPIDatabase = {
   },
 
   /**
    * Return a list of all install locations known about by the database. This
    * is often a a subset of the total install locations when not all have
    * installed add-ons, occasionally a superset when an install location no
    * longer exists.
    *
-   * @return  a Set of names of install locations
+   * @return  an array of names of install locations
    */
   getInstallLocations: function XPIDB_getInstallLocations() {
-    let locations = new Set();
     if (!this.addonDB)
-      return locations;
+      return [];
 
-    for (let [, addon] of this.addonDB) {
-      locations.add(addon.location);
+    let locations = {};
+    for each (let addon in this.addonDB) {
+      locations[addon.location] = 1;
     }
-    return locations;
+    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)
       return [];
 
     let addonList = [];
-    for (let [key, addon] of this.addonDB) {
+    for (let key in this.addonDB) {
+      let addon = this.addonDB[key];
       if (aFilter(addon)) {
         addonList.push(addon);
       }
     }
 
     return addonList;
   },
 
@@ -966,17 +1139,18 @@ this.XPIDatabase = {
    *         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, addon] of this.addonDB) {
+    for (let key in this.addonDB) {
+      let addon = this.addonDB[key];
       if (aFilter(addon)) {
         return addon;
       }
     }
 
     return null;
   },
 
@@ -999,17 +1173,17 @@ this.XPIDatabase = {
    * @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.get(aLocation + ":" + aId), aCallback);
+    getRepositoryAddon(this.addonDB[aLocation + ":" + aId], aCallback);
   },
 
   /**
    * Asynchronously gets the add-on with an ID that is visible.
    * XXX IRVING sync
    *
    * @param  aId
    *         The ID of the add-on to retrieve
@@ -1126,17 +1300,17 @@ this.XPIDatabase = {
     // creates itself.
     if (!this.addonDB)
       this.openConnection(false, true);
 
     this.beginTransaction();
 
     let newAddon = new DBAddonInternal(aAddon);
     newAddon.descriptor = aDescriptor;
-    this.addonDB.set(newAddon._key, newAddon);
+    this.addonDB[newAddon._key] = newAddon;
     if (newAddon.visible) {
       this.makeAddonVisible(newAddon);
     }
 
     this.commitTransaction();
     return newAddon;
   },
 
@@ -1179,33 +1353,34 @@ this.XPIDatabase = {
   /**
    * Synchronously removes an add-on from the database.
    *
    * @param  aAddon
    *         The DBAddonInternal being removed
    */
   removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
     this.beginTransaction();
-    this.addonDB.delete(aAddon._key);
+    delete this.addonDB[aAddon._key];
     this.commitTransaction();
   },
 
   /**
    * 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, otherAddon] of this.addonDB) {
+    for (let key in this.addonDB) {
+      let otherAddon = this.addonDB[key];
       if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
         LOG("Hide addon " + otherAddon._key);
         otherAddon.visible = false;
       }
     }
     aAddon.visible = true;
     this.commitTransaction();
   },
@@ -1281,30 +1456,24 @@ this.XPIDatabase = {
 
   /**
    * Synchronously calculates and updates all the active flags in the database.
    */
   updateActiveAddons: function XPIDB_updateActiveAddons() {
     // XXX IRVING this may get called during XPI-utils shutdown
     // XXX need to make sure PREF_PENDING_OPERATIONS handling is clean
     LOG("Updating add-on states");
-    let changed = false;
-    for (let [key, addon] of this.addonDB) {
-      let newActive = (addon.visible && !addon.userDisabled &&
+    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);
-      if (newActive != addon.active) {
-        addon.active = newActive;
-        changed = true;
-      }
     }
-    if (changed) {
-      this.beginTransaction();
-      this.commitTransaction();
-    }
+    this.commitTransaction();
   },
 
   /**
    * Writes out the XPI add-ons list for the platform to read.
    */
   writeAddonsList: function XPIDB_writeAddonsList() {
     Services.appinfo.invalidateCachesOnRestart();
 
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -1390,26 +1390,26 @@ function do_exception_wrap(func) {
     }
     catch(e) {
       do_report_unexpected_exception(e);
     }
   };
 }
 
 const EXTENSIONS_DB = "extensions.json";
-let gExtensionsJSON = gProfD.clone();
-gExtensionsJSON.append(EXTENSIONS_DB);
 
 /**
  * Change the schema version of the JSON extensions database
  */
 function changeXPIDBVersion(aNewVersion) {
-  let jData = loadJSON(gExtensionsJSON);
+  let dbfile = gProfD.clone();
+  dbfile.append(EXTENSIONS_DB);
+  let jData = loadJSON(dbfile);
   jData.schemaVersion = aNewVersion;
-  saveJSON(jData, gExtensionsJSON);
+  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"].
@@ -1421,17 +1421,17 @@ function loadJSON(aFile) {
   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.path);
+  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);
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests that we rebuild the database correctly if it contains
-// JSON data that parses correctly but doesn't contain required fields
-
-var addon1 = {
-  id: "addon1@tests.mozilla.org",
-  version: "2.0",
-  name: "Test 1",
-  targetApplications: [{
-    id: "xpcshell@tests.mozilla.org",
-    minVersion: "1",
-    maxVersion: "1"
-  }]
-};
-
-const profileDir = gProfD.clone();
-profileDir.append("extensions");
-
-function run_test() {
-  do_test_pending("Bad JSON");
-
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
-
-  // This addon will be auto-installed at startup
-  writeInstallRDFForExtension(addon1, profileDir);
-
-  startupManager();
-
-  shutdownManager();
-
-  // First startup/shutdown finished
-  // Replace the JSON store with something bogus
-  saveJSON({not: "what we expect to find"}, gExtensionsJSON);
-
-  startupManager(false);
-  // Retrieve an addon to force the database to rebuild
-  AddonManager.getAddonsByIDs([addon1.id], callback_soon(after_db_rebuild));
-}
-
-function after_db_rebuild([a1]) {
-  do_check_eq(a1.id, addon1.id);
-
-  shutdownManager();
-
-  // Make sure our JSON database has schemaVersion and our installed extension
-  let data = loadJSON(gExtensionsJSON);
-  do_check_true("schemaVersion" in data);
-  do_check_eq(data.addons[0].id, addon1.id);
-
-  do_test_finished("Bad JSON");
-}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -141,19 +141,20 @@ function do_check_bootstrappedPref(aCall
 
 function run_test() {
   do_test_pending();
 
   resetPrefs();
 
   startupManager();
 
-  do_check_false(gExtensionsJSON.exists());
+  let file = gProfD.clone();
+  file.append(EXTENSIONS_DB);
+  do_check_false(file.exists());
 
-  let file = gProfD.clone();
   file.leafName = "extensions.ini";
   do_check_false(file.exists());
 
   do_check_bootstrappedPref(run_test_1);
 }
 
 // Tests that installing doesn't require a restart
 function run_test_1() {
@@ -199,19 +200,20 @@ function run_test_1() {
       // startup should not have been called yet.
       do_check_eq(getActiveVersion(), -1);
     });
     install.install();
   });
 }
 
 function check_test_1(installSyncGUID) {
-  do_check_true(gExtensionsJSON.exists());
+  let file = gProfD.clone();
+  file.append(EXTENSIONS_DB);
+  do_check_true(file.exists());
 
-  let file = gProfD.clone();
   file.leafName = "extensions.ini";
   do_check_false(file.exists());
 
   AddonManager.getAllInstalls(function(installs) {
     // There should be no active installs now since the install completed and
     // doesn't require a restart.
     do_check_eq(installs.length, 0);
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
@@ -42,27 +42,31 @@ function end_test() {
 
 function run_test_1() {
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     shutdownManager();
 
-    gExtensionsJSON.remove(true);
+    let db = gProfD.clone();
+    db.append(EXTENSIONS_DB);
+    db.remove(true);
 
     do_execute_soon(check_test_1);
   });
 }
 
 function check_test_1() {
   startupManager(false);
 
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
-    do_check_true(gExtensionsJSON.exists());
-    do_check_true(gExtensionsJSON.fileSize > 0);
+    let db = gProfD.clone();
+    db.append(EXTENSIONS_DB);
+    do_check_true(db.exists());
+    do_check_true(db.fileSize > 0);
 
     end_test();
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
@@ -246,18 +246,20 @@ function run_test_1() {
     do_check_false(t2.userDisabled);
     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();
-    gExtensionsJSON.remove(true);
-    gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+    var dbfile = gProfD.clone();
+    dbfile.append("extensions.json");
+    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",
                                  "addon4@tests.mozilla.org",
                                  "addon5@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js
@@ -247,18 +247,20 @@ function run_test_1() {
     do_check_false(t2.userDisabled);
     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();
-    gExtensionsJSON.remove(true);
-    gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+    var dbfile = gProfD.clone();
+    dbfile.append("extensions.json");
+    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",
                                  "addon4@tests.mozilla.org",
                                  "addon5@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked.js
@@ -252,18 +252,20 @@ 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
     // mess with permissions
     shutdownManager();
-    var savedPermissions = gExtensionsJSON.permissions;
-    gExtensionsJSON.permissions = 0;
+    var dbfile = gProfD.clone();
+    dbfile.append(EXTENSIONS_DB);
+    var savedPermissions = dbfile.permissions;
+    dbfile.permissions = 0;
 
     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",
@@ -421,22 +423,21 @@ 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;
+
         // After allowing access to the original DB things should go back to as
         // they were previously
-        shutdownManager();
-        gExtensionsJSON.permissions = savedPermissions;
-        startupManager();
-
+        restartManager();
 
         // Shouldn't have seen any startup changes
         check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
 
         AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                      "addon2@tests.mozilla.org",
                                      "addon3@tests.mozilla.org",
                                      "addon4@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked2.js
@@ -139,18 +139,20 @@ function run_test() {
         do_check_true(a5.isActive);
         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 savedPermissions = gExtensionsJSON.permissions;
-        gExtensionsJSON.permissions = 0;
+        var dbfile = gProfD.clone();
+        dbfile.append(EXTENSIONS_DB);
+        var savedPermissions = dbfile.permissions;
+        dbfile.permissions = 0;
 
         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",
@@ -192,21 +194,21 @@ 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;
+
           // After allowing access to the original DB things should still be
           // applied correctly
-          shutdownManager();
-          gExtensionsJSON.permissions = savedPermissions;
-          startupManager();
+          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"]);
           check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, ["addon4@tests.mozilla.org"]);
 
           AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                        "addon2@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_locked_strictcompat.js
@@ -251,18 +251,20 @@ function run_test_1() {
     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 lock it
     shutdownManager();
-    var savedPermissions = gExtensionsJSON.permissions;
-    gExtensionsJSON.permissions = 0;
+    var dbfile = gProfD.clone();
+    dbfile.append(EXTENSIONS_DB);
+    var savedPermissions = dbfile.permissions;
+    dbfile.permissions = 0;
 
     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",
@@ -418,21 +420,21 @@ 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;
+
         // After allowing access to the original DB things should go back to as
         // they were previously
-        shutdownManager();
-        gExtensionsJSON.permissions = savedPermissions;
-        startupManager(false);
+        restartManager();
 
         // Shouldn't have seen any startup changes
         check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
 
         AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                      "addon2@tests.mozilla.org",
                                      "addon3@tests.mozilla.org",
                                      "addon4@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -11,19 +11,22 @@ skip-if = os == "android"
 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_bad_json.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]
@@ -134,26 +137,32 @@ 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_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]
@@ -188,27 +197,43 @@ 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"