merge m-c to fx-team
authorTim Taubert <ttaubert@mozilla.com>
Sun, 11 Aug 2013 09:27:17 +0200
changeset 142101 3d20597e0a07b9913d62948f5568052045111352
parent 142069 6cd6960ea7f60ed69f4a7c5653eab901dbd48ae8 (current diff)
parent 142100 61e0fb09a73cd63aa2244883f344a66412d67a0d (diff)
child 142211 f057fca0962763dcdb50286e34ec05c9f768818d
child 142246 ae46de5551b3a19624f66c5102f00433766eba5e
push id25083
push userttaubert@mozilla.com
push dateSun, 11 Aug 2013 07:28:28 +0000
treeherdermozilla-central@3d20597e0a07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.0a1
first release with
nightly linux32
3d20597e0a07 / 26.0a1 / 20130811030225 / files
nightly linux64
3d20597e0a07 / 26.0a1 / 20130811030225 / files
nightly mac
3d20597e0a07 / 26.0a1 / 20130811030225 / files
nightly win32
3d20597e0a07 / 26.0a1 / 20130811030225 / files
nightly win64
3d20597e0a07 / 26.0a1 / 20130811030225 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge m-c to fx-team
toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js
--- a/browser/components/sessionstore/src/DocumentUtils.jsm
+++ b/browser/components/sessionstore/src/DocumentUtils.jsm
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 this.EXPORTED_SYMBOLS = [ "DocumentUtils" ];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
 
--- a/browser/components/sessionstore/src/SessionMigration.jsm
+++ b/browser/components/sessionstore/src/SessionMigration.jsm
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 this.EXPORTED_SYMBOLS = ["SessionMigration"];
 
 const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 
 // An encoder to UTF-8.
--- a/browser/components/sessionstore/src/SessionStorage.jsm
+++ b/browser/components/sessionstore/src/SessionStorage.jsm
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 this.EXPORTED_SYMBOLS = ["SessionStorage"];
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 this.EXPORTED_SYMBOLS = ["SessionStore"];
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 const STATE_STOPPED = 0;
@@ -299,20 +301,16 @@ let SessionStoreInternal = {
 
   // time in milliseconds when the session was started (saved across sessions),
   // defaults to now if no session was restored or timestamp doesn't exist
   _sessionStartTime: Date.now(),
 
   // states for all currently opened windows
   _windows: {},
 
-  // internal states for all open windows (data we need to associate,
-  // but not write to disk)
-  _internalWindows: {},
-
   // states for all recently closed windows
   _closedWindows: [],
 
   // collection of session states yet to be restored
   _statesToRestore: {},
 
   // counts the number of crashes since the last clean start
   _recentCrashes: 0,
@@ -714,19 +712,16 @@ let SessionStoreInternal = {
       return;
 
     // assign it a unique identifier (timestamp)
     aWindow.__SSi = "window" + Date.now();
 
     // and create its data object
     this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
 
-    // and create its internal data object
-    this._internalWindows[aWindow.__SSi] = { hosts: {} }
-
     let isPrivateWindow = false;
     if (PrivateBrowsingUtils.isWindowPrivate(aWindow))
       this._windows[aWindow.__SSi].isPrivate = isPrivateWindow = true;
     if (!this._isWindowLoaded(aWindow))
       this._windows[aWindow.__SSi]._restoring = true;
     if (!aWindow.toolbar.visible)
       this._windows[aWindow.__SSi].isPopup = true;
 
@@ -870,22 +865,22 @@ let SessionStoreInternal = {
   },
 
   /**
    * On window open
    * @param aWindow
    *        Window reference
    */
   onOpen: function ssi_onOpen(aWindow) {
-    var _this = this;
-    aWindow.addEventListener("load", function(aEvent) {
-      aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
-      _this.onLoad(aEvent.currentTarget);
-    }, false);
-    return;
+    let onload = () => {
+      aWindow.removeEventListener("load", onload);
+      this.onLoad(aWindow);
+    };
+
+    aWindow.addEventListener("load", onload);
   },
 
   /**
    * On window close...
    * - remove event listeners from tabs
    * - save all window data
    * @param aWindow
    *        Window reference
@@ -930,19 +925,17 @@ let SessionStoreInternal = {
     if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down
       // update all window data for a last time
       this._collectWindowData(aWindow);
 
       if (isFullyLoaded) {
         winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label;
         winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
                                                   tabbrowser.selectedTab);
-        let windows = {};
-        windows[aWindow.__SSi] = winData;
-        this._updateCookies(windows);
+        this._updateCookies([winData]);
       }
 
 #ifndef XP_MACOSX
       // Until we decide otherwise elsewhere, this window is part of a series
       // of closing windows to quit.
       winData._shouldRestore = true;
 #endif
 
@@ -954,17 +947,16 @@ let SessionStoreInternal = {
         delete winData.busy;
 
         this._closedWindows.unshift(winData);
         this._capClosedWindows();
       }
 
       // clear this window from the list
       delete this._windows[aWindow.__SSi];
-      delete this._internalWindows[aWindow.__SSi];
 
       // save the state without this window to disk
       this.saveStateDelayed();
     }
 
     for (let i = 0; i < tabbrowser.tabs.length; i++) {
       this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
     }
@@ -1050,43 +1042,37 @@ let SessionStoreInternal = {
     this._lastSessionState = null;
     let openWindows = {};
     this._forEachBrowserWindow(function(aWindow) {
       Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
         TabStateCache.delete(aTab);
         delete aTab.linkedBrowser.__SS_data;
         delete aTab.linkedBrowser.__SS_tabStillLoading;
         delete aTab.linkedBrowser.__SS_formDataSaved;
-        delete aTab.linkedBrowser.__SS_hostSchemeData;
         if (aTab.linkedBrowser.__SS_restoreState)
           this._resetTabRestoringState(aTab);
-      });
+      }, this);
       openWindows[aWindow.__SSi] = true;
     });
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
       if (ix in openWindows) {
         this._windows[ix]._closedTabs = [];
-      }
-      else {
+      } else {
         delete this._windows[ix];
-        delete this._internalWindows[ix];
       }
     }
     // also clear all data about closed windows
     this._closedWindows = [];
     // give the tabbrowsers a chance to clear their histories first
     var win = this._getMostRecentBrowserWindow();
     if (win)
       win.setTimeout(function() { _this.saveState(true); }, 0);
     else if (this._loadState == STATE_RUNNING)
       this.saveState(true);
-    // Delete the private browsing backed up state, if any
-    if ("_stateBackup" in this)
-      delete this._stateBackup;
 
     this._clearRestoringWindows();
   },
 
   /**
    * On purge of domain data
    * @param aData
    *        String domain data
@@ -1220,17 +1206,16 @@ let SessionStoreInternal = {
     browser.removeEventListener("load", this, true);
 
     let mm = browser.messageManager;
     MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
 
     delete browser.__SS_data;
     delete browser.__SS_tabStillLoading;
     delete browser.__SS_formDataSaved;
-    delete browser.__SS_hostSchemeData;
 
     // If this tab was in the middle of restoring or still needs to be restored,
     // we need to reset that state. If the tab was restoring, we will attempt to
     // restore the next tab.
     let previousState = browser.__SS_restoreState;
     if (previousState) {
       this._resetTabRestoringState(aTab);
       if (previousState == TAB_STATE_RESTORING)
@@ -1976,21 +1961,20 @@ let SessionStoreInternal = {
     if (history && browser.__SS_data &&
         browser.__SS_data.entries[history.index] &&
         browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
         history.index < this._sessionhistory_max_entries - 1 && !includePrivateData) {
       tabData = browser.__SS_data;
       tabData.index = history.index + 1;
     }
     else if (history && history.count > 0) {
-      browser.__SS_hostSchemeData = [];
       try {
         for (var j = 0; j < history.count; j++) {
           let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
-                                                  includePrivateData, aTab.pinned, browser.__SS_hostSchemeData);
+                                                  includePrivateData, aTab.pinned);
           tabData.entries.push(entry);
         }
         // If we make it through the for loop, then we're ok and we should clear
         // any indicator of brokenness.
         delete aTab.__SS_broken_history;
       }
       catch (ex) {
         // In some cases, getEntryAtIndex will throw. This seems to be due to
@@ -2072,33 +2056,22 @@ let SessionStoreInternal = {
    * Get an object that is a serialized representation of a History entry
    * Used for data storage
    * @param aEntry
    *        nsISHEntry instance
    * @param aIncludePrivateData
    *        always return privacy sensitive data (use with care)
    * @param aIsPinned
    *        the tab is pinned and should be treated differently for privacy
-   * @param aHostSchemeData
-   *        an array of objects with host & scheme keys
    * @returns object
    */
   _serializeHistoryEntry:
-    function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned, aHostSchemeData) {
+    function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned) {
     var entry = { url: aEntry.URI.spec };
 
-    try {
-      // throwing is expensive, we know that about: pages will throw
-      if (entry.url.indexOf("about:") != 0)
-        aHostSchemeData.push({ host: aEntry.URI.host, scheme: aEntry.URI.scheme });
-    }
-    catch (ex) {
-      // We just won't attempt to get cookies for this entry.
-    }
-
     if (aEntry.title && aEntry.title != entry.url) {
       entry.title = aEntry.title;
     }
     if (aEntry.isSubFrame) {
       entry.subframe = true;
     }
     if (!(aEntry instanceof Ci.nsISHEntry)) {
       return entry;
@@ -2199,17 +2172,17 @@ let SessionStoreInternal = {
         if (child) {
           // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
           if (child.URI.schemeIs("wyciwyg")) {
             children = [];
             break;
           }
 
           children.push(this._serializeHistoryEntry(child, aIncludePrivateData,
-                                                    aIsPinned, aHostSchemeData));
+                                                    aIsPinned));
         }
       }
 
       if (children.length)
         entry.children = children;
     }
 
     return entry;
@@ -2420,18 +2393,18 @@ let SessionStoreInternal = {
     else if (aScheme == "file") {
       aHosts[aHost] = true;
     }
   },
 
   /**
    * Serialize cookie data
    * @param aWindows
-   *        JS object containing window data references
-   *        { id: winData, etc. }
+   *        An array of window data objects
+   *        { tabs: [ ... ], etc. }
    */
   _updateCookies: function ssi_updateCookies(aWindows) {
     function addCookieToHash(aHash, aHost, aPath, aName, aCookie) {
       // lazily build up a 3-dimensional hash, with
       // aHost, aPath, and aName as keys
       if (!aHash[aHost])
         aHash[aHost] = {};
       if (!aHash[aHost][aPath])
@@ -2439,22 +2412,28 @@ let SessionStoreInternal = {
       aHash[aHost][aPath][aName] = aCookie;
     }
 
     var jscookies = {};
     var _this = this;
     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
     var MAX_EXPIRY = Math.pow(2, 62);
 
-    for (let [id, window] in Iterator(aWindows)) {
+    for (let window of aWindows) {
       window.cookies = [];
-      let internalWindow = this._internalWindows[id];
-      if (!internalWindow.hosts)
-        return;
-      for (var [host, isPinned] in Iterator(internalWindow.hosts)) {
+
+      // Collect all hosts for the current window.
+      let hosts = {};
+      window.tabs.forEach(function(tab) {
+        tab.entries.forEach(function(entry) {
+          this._extractHostsForCookiesFromEntry(entry, hosts, true, tab.pinned);
+        }, this);
+      }, this);
+
+      for (var [host, isPinned] in Iterator(hosts)) {
         let list;
         try {
           list = Services.cookies.getCookiesFromHost(host);
         }
         catch (ex) {
           debug("getCookiesFromHost failed. Host: " + host);
         }
         while (list && list.hasMoreElements()) {
@@ -2537,30 +2516,34 @@ let SessionStoreInternal = {
         }
         else { // always update the window features (whose change alone never triggers a save operation)
           this._updateWindowFeatures(aWindow);
         }
       });
       DirtyWindows.clear();
     }
 
-    // collect the data for all windows
-    var total = [], windows = {}, ids = [];
+    // An array that at the end will hold all current window data.
+    var total = [];
+    // The ids of all windows contained in 'total' in the same order.
+    var ids = [];
+    // The number of window that are _not_ popups.
     var nonPopupCount = 0;
     var ix;
+
+    // collect the data for all windows
     for (ix in this._windows) {
       if (this._windows[ix]._restoring) // window data is still in _statesToRestore
         continue;
       total.push(this._windows[ix]);
       ids.push(ix);
-      windows[ix] = this._windows[ix];
       if (!this._windows[ix].isPopup)
         nonPopupCount++;
     }
-    this._updateCookies(windows);
+    this._updateCookies(total);
 
     // collect the data for all windows yet to be restored
     for (ix in this._statesToRestore) {
       for each (let winData in this._statesToRestore[ix].windows) {
         total.push(winData);
         if (!winData.isPopup)
           nonPopupCount++;
       }
@@ -2622,48 +2605,34 @@ let SessionStoreInternal = {
   _getWindowState: function ssi_getWindowState(aWindow) {
     if (!this._isWindowLoaded(aWindow))
       return this._statesToRestore[aWindow.__SS_restoreID];
 
     if (this._loadState == STATE_RUNNING) {
       this._collectWindowData(aWindow);
     }
 
-    var winData = this._windows[aWindow.__SSi];
-    let windows = {};
-    windows[aWindow.__SSi] = winData;
+    let windows = [this._windows[aWindow.__SSi]];
     this._updateCookies(windows);
 
-    return { windows: [winData] };
+    return { windows: windows };
   },
 
   _collectWindowData: function ssi_collectWindowData(aWindow) {
     if (!this._isWindowLoaded(aWindow))
       return;
 
     let tabbrowser = aWindow.gBrowser;
     let tabs = tabbrowser.tabs;
     let winData = this._windows[aWindow.__SSi];
     let tabsData = winData.tabs = [];
-    let hosts = this._internalWindows[aWindow.__SSi].hosts = {};
 
     // update the internal state data for this window
     for (let tab of tabs) {
       tabsData.push(this._collectTabData(tab));
-
-      // Since we are only ever called for open
-      // windows during a session, we can call into
-      // _extractHostsForCookiesFromHostScheme directly using data
-      // that is attached to each browser.
-      let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
-      for (let j = 0; j < hostSchemeData.length; j++) {
-        this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
-                                                   hostSchemeData[j].scheme,
-                                                   hosts, true, tab.pinned);
-      }
     }
     winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
 
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
     // or leaving PB mode.
     if (aWindow.__SS_lastSessionWindowID)
--- a/browser/components/sessionstore/src/XPathGenerator.jsm
+++ b/browser/components/sessionstore/src/XPathGenerator.jsm
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 this.EXPORTED_SYMBOLS = ["XPathGenerator"];
 
 this.XPathGenerator = {
   // these two hashes should be kept in sync
   namespaceURIs:     { "xhtml": "http://www.w3.org/1999/xhtml" },
   namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" },
 
   /**
--- a/browser/components/sessionstore/src/nsSessionStartup.js
+++ b/browser/components/sessionstore/src/nsSessionStartup.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 /**
  * Session Storage and Restoration
  *
  * Overview
  * This service reads user's session file at startup, and makes a determination
  * as to whether the session should be restored. It will restore the session
  * under the circumstances described below.  If the auto-start Private Browsing
  * mode is active, however, the session is never restored.
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 /**
  * Session Storage and Restoration
  *
  * Overview
  * This service keeps track of a user's session, storing the various bits
  * required to return the browser to its current state. The relevant data is
  * stored in memory, and is periodically saved to disk in a file in the
  * profile directory. The service is started at first window load, in
--- a/browser/metro/base/content/ContextCommands.js
+++ b/browser/metro/base/content/ContextCommands.js
@@ -32,69 +32,76 @@ var ContextCommands = {
    * Context menu handlers
    */
 
   // Text specific
 
   cut: function cc_cut() {
     let target = ContextMenuUI.popupState.target;
 
-    if (!target)
+    if (!target) {
       return;
+    }
 
     if (target.localName === "browser") {
       // content
       if (ContextMenuUI.popupState.string) {
         this.sendCommand("cut");
 
         SelectionHelperUI.closeEditSession(true);
       }
     } else {
       // chrome
-      target.editor.cut();
+      CommandUpdater.doCommand("cmd_cut");
     }
 
     target.focus();
   },
 
   copy: function cc_copy() {
     let target = ContextMenuUI.popupState.target;
 
-    if (!target)
+    if (!target) {
       return;
+    }
 
     if (target.localName == "browser") {
       // content
       if (ContextMenuUI.popupState.string) {
         this.sendCommand("copy");
 
         SelectionHelperUI.closeEditSession(true);
       }
     } else if (ContextMenuUI.popupState.string) {
       this.clipboard.copyString(ContextMenuUI.popupState.string, this.docRef);
     } else {
       // chrome
-      target.editor.copy();
+      CommandUpdater.doCommand("cmd_copy");
     }
 
     target.focus();
   },
 
   paste: function cc_paste() {
     let target = ContextMenuUI.popupState.target;
+
+    if (!target) {
+      return;
+    }
+
     if (target.localName == "browser") {
       // content
       let x = ContextMenuUI.popupState.x;
       let y = ContextMenuUI.popupState.y;
       let json = {x: x, y: y, command: "paste" };
       target.messageManager.sendAsyncMessage("Browser:ContextCommand", json);
       SelectionHelperUI.closeEditSession();
     } else {
       // chrome
-      target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
+      CommandUpdater.doCommand("cmd_paste");
       target.focus();
     }
   },
 
   pasteAndGo: function cc_pasteAndGo() {
     let target = ContextMenuUI.popupState.target;
     target.editor.selectAll();
     target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
--- a/browser/metro/base/content/bindings/bindings.xml
+++ b/browser/metro/base/content/bindings/bindings.xml
@@ -255,20 +255,16 @@
         <body><![CDATA[
           let selectionStart = aTextbox.selectionStart;
           let selectionEnd = aTextbox.selectionEnd;
 
           let json = { types: ["input-text"], string: "" };
           if (selectionStart != selectionEnd) {
             json.types.push("cut");
             json.types.push("copy");
-            json.string = aTextbox.value.slice(selectionStart, selectionEnd);
-          } else if (aTextbox.value) {
-            json.types.push("copy-all");
-            json.string = aTextbox.value;
           }
 
           if (selectionStart > 0 || selectionEnd < aTextbox.textLength)
             json.types.push("select-all");
 
           let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"].
                                             getService(Ci.nsIClipboard);
           let flavors = ["text/unicode"];
--- a/browser/metro/base/content/bindings/grid.xml
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -795,33 +795,34 @@
       </handler>
     </handlers>
   </binding>
 
   <binding id="richgrid-item">
     <content>
       <html:div anonid="anon-tile" class="tile-content" xbl:inherits="customImage">
         <html:div class="tile-start-container" xbl:inherits="customImage">
-          <html:div class="tile-icon-box"><xul:image anonid="anon-tile-icon" xbl:inherits="src=iconURI"/></html:div>
+          <html:div class="tile-icon-box" anonid="anon-tile-icon-box"><xul:image anonid="anon-tile-icon" xbl:inherits="src=iconURI"/></html:div>
         </html:div>
         <html:div anonid="anon-tile-label" class="tile-desc" xbl:inherits="xbl:text=label"/>
       </html:div>
     </content>
 
     <implementation>
       <property name="isBound" readonly="true" onget="return !!this._icon"/>
       <constructor>
         <![CDATA[
             this.refresh();
         ]]>
       </constructor>
       <property name="_contentBox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-content');"/>
       <property name="_textbox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-desc');"/>
       <property name="_top" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-start-container');"/>
       <property name="_icon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon');"/>
+      <property name="_iconBox" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon-box');"/>
       <property name="_label" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-label');"/>
       <property name="iconSrc"
                 onset="this._icon.src = val; this.setAttribute('iconURI', val);"
                 onget="return this._icon.src;" />
 
       <property name="selected"
                 onget="return this.hasAttribute('selected');"
                 onset="if (val) this.setAttribute('selected', val); else this.removeAttribute('selected');" />
@@ -871,19 +872,31 @@
         ]]></getter>
       </property>
 
       <property name="color" onget="return this.getAttribute('customColor');">
         <setter><![CDATA[
           if (val) {
             this.setAttribute("customColor", val);
             this._contentBox.style.backgroundColor = val;
+
+            // overridden in tiles.css for non-thumbnail types
+            this._label.style.backgroundColor = val.replace(/rgb\(([^\)]+)\)/, 'rgba($1, 0.8)');
+
+            // Small icons get a border+background-color treatment.
+            // See tiles.css for large icon overrides
+            this._iconBox.style.borderColor = val.replace(/rgb\(([^\)]+)\)/, 'rgba($1, 0.6)');
+            this._iconBox.style.backgroundColor = this.hasAttribute("tintColor") ?
+                                                    this.getAttribute("tintColor") : "#fff";
           } else {
             this.removeAttribute("customColor");
             this._contentBox.style.removeProperty("background-color");
+            this._label.style.removeProperty("background-color");
+            this._iconBox.style.removeProperty("border-color");
+            this._iconBox.style.removeProperty("background-color");
           }
         ]]></setter>
       </property>
 
       <property name="backgroundImage" onget="return this.getAttribute('customImage');">
         <setter><![CDATA[
           if (val) {
             this.setAttribute("customImage", val);
--- a/browser/metro/base/content/bindings/urlbar.xml
+++ b/browser/metro/base/content/bindings/urlbar.xml
@@ -152,17 +152,17 @@
         </body>
       </method>
 
       <method name="_getSelectedValueForClipboard">
         <body>
           <![CDATA[
             // Grab the actual input field's value, not our value, which could include moz-action:
             let inputVal = this.inputField.value;
-            let selectedVal = inputVal.substring(this.selectionStart, this.electionEnd);
+            let selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
 
             // If the selection doesn't start at the beginning or doesn't span the full domain or
             // the URL bar is modified, nothing else to do here.
             if (this.selectionStart > 0 || this.valueIsTyped)
               return selectedVal;
 
             // The selection doesn't span the full domain if it doesn't contain a slash and is
             // followed by some character other than a slash.
@@ -480,24 +480,27 @@
             return;
         ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="urlbar-autocomplete">
     <content orient="horizontal">
-      <xul:vbox id="results-vbox" flex="1">
-        <xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
-        <richgrid id="results-richgrid" rows="3" deferlayout="true" anonid="results" seltype="single" flex="1"/>
+      <xul:vbox id="results-vbox" anonid="results-container" flex="1">
+        <xul:label class="meta-section-title"
+                   value="&autocompleteResultsHeader.label;"/>
+        <richgrid id="results-richgrid" anonid="results" rows="3" flex="1"
+                  seltype="single" deferlayout="true"/>
       </xul:vbox>
 
       <xul:vbox id="searches-vbox" flex="1">
-        <xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
-        <richgrid id="searches-richgrid" rows="3" deferlayout="true" anonid="searches" seltype="single" flex="1"/>
+        <xul:label anonid="searches-header" class="meta-section-title"/>
+        <richgrid id="searches-richgrid" anonid="searches" rows="3" flex="1"
+                  seltype="single" deferlayout="true"/>
       </xul:vbox>
     </content>
 
     <implementation implements="nsIAutoCompletePopup, nsIObserver">
       <constructor>
         <![CDATA[
           this.hidden = true;
 
@@ -585,17 +588,21 @@
             Elements.urlbarState.removeAttribute("autocomplete");
           ]]>
         </body>
       </method>
 
       <!-- Updating grid content -->
 
       <field name="_grid">null</field>
+
       <field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field>
+      <field name="_resultsContainer" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results-container');</field>
+
+      <field name="_searchesHeader" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches-header');</field>
       <field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field>
 
       <property name="_otherGrid" readonly="true">
         <getter>
           <![CDATA[
             if (this._grid == null)
               return null;
 
@@ -615,32 +622,39 @@
 
       <method name="invalidate">
         <body>
           <![CDATA[
             if (!this.popupOpen)
               return;
 
             this.updateResults();
-            this.updateSearchEngineSubtitles();
+            this.updateSearchEngineHeader();
           ]]>
         </body>
       </method>
 
       <!-- Updating grid content: results -->
 
       <method name="updateResults">
         <body>
           <![CDATA[
             if (!this._isGridBound(this._results))
               return;
 
             if (!this.input)
               return;
 
+            let haveNoResults = (this.matchCount == 0);
+            this._resultsContainer.hidden = haveNoResults;
+
+            if (haveNoResults) {
+              return;
+            }
+
             let controller = this.input.controller;
             let lastMatch = this.matchCount - 1;
             let iterCount = Math.max(this._results.itemCount, this.matchCount);
 
             // Swap out existing items for new search hit results
             for (let i = 0; i < iterCount; i++) {
               if (i > lastMatch) {
                 let lastItem = this._results.itemCount - 1;
@@ -701,30 +715,27 @@
               item.setAttribute("iconURI", iconURI);
             }.bind(this));
 
             this._searches.arrangeItems();
           ]]>
         </body>
       </method>
 
-      <method name="updateSearchEngineSubtitles">
+      <method name="updateSearchEngineHeader">
         <body>
           <![CDATA[
             if (!this._isGridBound(this._searches))
               return;
 
             let searchString = this.input.controller.searchString;
-            let label = Strings.browser.formatStringFromName("opensearch.search", [searchString], 1);
+            let label = Strings.browser.formatStringFromName(
+                              "opensearch.search.header", [searchString], 1);
 
-            for (let i = 0, len = this._searches.itemCount; i < len; i++) {
-              let item = this._searches.getItemAtIndex(i);
-              item.setAttribute("label", label);
-              item.refresh && item.refresh();
-            }
+            this._searchesHeader.value = label;
           ]]>
         </body>
       </method>
 
       <!-- Selecting results -->
 
       <method name="selectBy">
         <parameter name="aReverse"/>
--- a/browser/metro/base/content/startui/HistoryView.js
+++ b/browser/metro/base/content/startui/HistoryView.js
@@ -104,17 +104,17 @@ HistoryView.prototype = Util.extend(Obje
     let item = this._set.insertItemAt(aPos || 0, aTitle, aURI, this._inBatch);
     this._setContextActions(item);
     this._updateFavicon(item, aURI);
   },
 
   _setContextActions: function bv__setContextActions(aItem) {
     let uri = aItem.getAttribute("value");
     aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(uri) ? "unpin" : "pin"));
-    if (aItem.refresh) aItem.refresh();
+    if ("refresh" in aItem) aItem.refresh();
   },
 
   _sendNeedsRefresh: function bv__sendNeedsRefresh(){
     // Event sent when all views need to refresh.
     let event = document.createEvent("Events");
     event.initEvent("HistoryNeedsRefresh", true, false);
     window.dispatchEvent(event);
   },
@@ -268,16 +268,18 @@ HistoryView.prototype = Util.extend(Obje
 
   onPageChanged: function(aURI, aWhat, aValue) {
     if (aWhat ==  Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
       let changedItems = this._set.getItemsByUrl(aURI.spec);
       for (let item of changedItems) {
         let currIcon = item.getAttribute("iconURI");
         if (currIcon != aValue) {
           item.setAttribute("iconURI", aValue);
+          if("refresh" in item)
+            item.refresh();
         }
       }
     }
   },
 
   onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
     if ((aReason ==  Ci.nsINavHistoryObserver.REASON_DELETED) && !this._inBatch) {
       this.populateGrid(true);
--- a/browser/metro/base/tests/mochiperf/browser_deck_01.js
+++ b/browser/metro/base/tests/mochiperf/browser_deck_01.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 function test() {
   let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
   Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 gTests.push({
   desc: "deck offset",
--- a/browser/metro/base/tests/mochiperf/browser_firstx.js
+++ b/browser/metro/base/tests/mochiperf/browser_firstx.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 function test() {
   let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
   Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this);
   runTests();
 }
 
 gTests.push({
   desc: "first x metrics",
--- a/browser/metro/base/tests/mochiperf/msgmanagerecho.js
+++ b/browser/metro/base/tests/mochiperf/msgmanagerecho.js
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * TestEchoReceiver - receives json data, reserializes it and send it back.
  */ 
 
+"use strict";
+
 var TestEchoReceiver = {
   init: function init() {
     addMessageListener("Test:EchoRequest", this);
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     let json = aMessage.json;
     switch (aMessage.name) {
--- a/browser/metro/base/tests/mochiperf/perfhelpers.js
+++ b/browser/metro/base/tests/mochiperf/perfhelpers.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 // Misc. constants
 const kInfoHeader = "PERF-TEST | ";
 const kDeclareId = "DECLARE ";
 const kResultsId = "RESULTS ";
 
 // Mochitest log data format version
 const kDataSetVersion = "1";
 
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -129,15 +129,46 @@ gTests.push({
     let editCoords = logicalCoordsForElement(edit);
     SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x, editCoords.y);
     edit.blur();
     ok(!SelectionHelperUI.isSelectionUIVisible, "selection no longer enabled");
     clearSelection(edit);
   }
 });
 
+function getClipboardCondition(aExpected) {
+  return () => aExpected == SpecialPowers.getClipboardData("text/unicode");
+}
+
+gTests.push({
+  desc: "bug 894715 - URLs selected by touch are copied with trimming",
+  run: function () {
+    gWindow = window;
+    yield showNavBar();
+
+    let edit = document.getElementById("urlbar-edit");
+    edit.value = "http://www.wikipedia.org/";
+
+    sendElementTap(window, edit);
+    edit.select();
+
+    let panel = ContextMenuUI._menuPopup._panel;
+    let promise = waitForEvent(panel, "popupshown")
+    sendContextMenuClickToElement(window, edit);
+    ok((yield promise), "show context menu");
+
+    let copy = document.getElementById("context-copy");
+    ok(!copy.hidden, "copy menu item is visible")
+
+    let condition = getClipboardCondition("http://www.wikipedia.org/");
+    let promise = waitForCondition(condition);
+    sendElementTap(window, copy);
+    ok((yield promise), "copy text onto clipboard")
+  }
+})
+
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
   runTests();
 }
--- a/browser/metro/base/tests/unit/test_util_extend.js
+++ b/browser/metro/base/tests/unit/test_util_extend.js
@@ -1,8 +1,10 @@
+"use strict";
+
 load('Util.js');
 
 function run_test() {
   do_print("Testing Util.extend");
   
   do_print("Check if function is defined");
   do_check_true(!!Util.extend);
 
--- a/browser/metro/locales/en-US/chrome/browser.dtd
+++ b/browser/metro/locales/en-US/chrome/browser.dtd
@@ -8,17 +8,16 @@
 <!ENTITY urlbar.accesskey      "d">
 
 <!ENTITY back.label            "Back">
 <!ENTITY forward.label         "Forward">
 <!ENTITY newtab.label          "New Tab">
 <!ENTITY closetab.label        "Close Tab">
 
 <!ENTITY autocompleteResultsHeader.label  "Your Results">
-<!ENTITY autocompleteSearchesHeader.label "Internet Searches">
 
 <!ENTITY appbarErrorConsole.label   "Open error console">
 <!ENTITY appbarJSShell.label        "Open JavaScript shell">
 <!ENTITY appbarFindInPage2.label    "Find in page">
 <!ENTITY appbarViewOnDesktop2.label "View on desktop">
 
 <!ENTITY topSitesHeader.label        "Top Sites">
 <!ENTITY bookmarksHeader.label       "Bookmarks">
--- a/browser/metro/locales/en-US/chrome/browser.properties
+++ b/browser/metro/locales/en-US/chrome/browser.properties
@@ -123,18 +123,19 @@ offlineApps.wantsTo=%S wants to store da
 
 # IndexedDB Quota increases
 indexedDBQuota.allow=Allow
 indexedDBQuota.wantsTo=%S wants to store a lot of data on your device for offline use.
 
 tabs.emptyTabTitle=New Tab
 
 # Open Search
-# LOCALIZATION NOTE (opensearch.search): %S is the word or phrase typed by the user
-opensearch.search=Search: %S
+# LOCALIZATION NOTE (opensearch.search.header): %S is the word or phrase
+# typed by the user in the urlbar to search
+opensearch.search.header=Search for ā€œ%Sā€ on:
 
 # Check for Updates in the About Panel - button labels and accesskeys
 # LOCALIZATION NOTE - all of the following update buttons labels will only be
 # displayed one at a time. So, if a button is displayed nothing else will
 # be displayed alongside of the button. The button when displayed is located
 # directly under the Firefox version in the about dialog (see bug 596813 for
 # screenshots).
 update.checkInsideButton.label=Check for Updates
--- a/browser/metro/modules/View.jsm
+++ b/browser/metro/modules/View.jsm
@@ -53,16 +53,23 @@ View.prototype = {
       aIconUri = makeURI(aIconUri);
     }
     aItem.iconSrc = aIconUri.spec;
     let faviconURL = (PlacesUtils.favicons.getFaviconLinkForIcon(aIconUri)).spec;
     let xpFaviconURI = makeURI(faviconURL.replace("moz-anno:favicon:",""));
     let successAction = function(foreground, background) {
       aItem.style.color = foreground; //color text
       aItem.setAttribute("customColor", background);
+      let matteColor =  0xffffff; // white
+      let alpha = 0.04; // the tint weight
+      let [,r,g,b] = background.match(/rgb\((\d+),(\d+),(\d+)/);
+      // get the rgb value that represents this color at given opacity over a white matte
+      let tintColor = ColorUtils.addRgbColors(matteColor, ColorUtils.createDecimalColorWord(r,g,b,alpha));
+      aItem.setAttribute("tintColor", ColorUtils.convertDecimalToRgbColor(tintColor));
+
       if (aItem.refresh) {
         aItem.refresh();
       }
     };
     let failureAction = function() {};
     ColorUtils.getForegroundAndBackgroundIconColors(xpFaviconURI, successAction, failureAction);
   }
 
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -444,17 +444,17 @@ documenttab[selected] .documenttab-selec
 #navbar {
   visibility: visible;
 }
 #navbar:not([hiding]):not([visible]) > #toolbar-overlay {
   visibility: hidden;
 }
 
 .circularprogressindicator-progressRing {
-  margin: 0 @toolbar_horizontal_spacing@;
+  margin: -2px 18px;
   pointer-events:none;
   position: absolute;
 }
 
 /* Progress meter ---------------------------------------------------------- */
 
 #progress-container {
   display: block;
index f0a4dbfdf948a8227a3076f6fa4876ee4b36b210..44de3fb173079de5bf89e9b94977d43f3e930e50
GIT binary patch
literal 363
zc%17D@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8Nb`TavfC3&Vd9T(EcfWS|IVfk$L9
z1A}ZE2s6em5^e+vvX^-Jy0YKp;1uQ3?A@~=9Vqm})5S3);_%y<rv(oiaJZZeoA@dy
zW|g=2Io0+Uk<XEgpE@?T*SdLlD6k)ndmsGu?q|h7&6#|jZPQCGDD0ThXmxNY>l((W
zOJ*V`{e6z?er@`J`TnCKFN=A5q=ML9UzrpX(Y|cTVX3VXLxX-k|L}s}IKgz{zoG>;
zo7{Ytgc&(Kxu~^^D_5pla+*i`{4EZ<OLing7^s?kV7qrf`Uc;fg)?|0E-dnp=sMY3
zw8KdEU&i9IcO5IYJM4Hk@ux)S{#l#D92W8)i%*a0ck4<lot_<fGhXJ*mx3Q%ja##}
zM=d^(l@b4A*TWME+_S8v%;dDoeRGt}Us~tfof%xe8Em3@ohN<|s|1DtgQu&X%Q~lo
FCIA^ulwkk>
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -572,33 +572,32 @@ arrowbox {
 
 .meta-section {
   margin: 0 @metro_spacing_large@;
 }
 
 .meta-section-title {
   font-size: @metro_font_large@;
   font-weight: 100;
-  display: none;
   cursor: default;
 }
 
 #start-container[viewstate="snapped"] {
   padding-top: 0;
 }
 
 #start-container[viewstate="snapped"] .meta-section-title,
 #start-container[viewstate="snapped"] richgrid {
   margin-top: @metro_spacing_xnormal@;
   padding: 0;
 }
 
-#start-container[viewstate="snapped"] .meta-section-title.narrow-title,
-#start-container:not([viewstate="snapped"]) .meta-section-title.wide-title {
-  display: block;
+#start-container:not([viewstate="snapped"]) .meta-section-title.narrow-title,
+#start-container[viewstate="snapped"] .meta-section-title.wide-title {
+  display: none;
 }
 
 .meta-section:not([expanded]) > .meta-section-title.narrow-title:-moz-locale-dir(ltr):after {
   content: ">";
 }
 
 .meta-section:not([expanded]) > .meta-section-title.narrow-title:-moz-locale-dir(rtl):before {
   content: "<";
--- a/browser/metro/theme/tiles.css
+++ b/browser/metro/theme/tiles.css
@@ -57,71 +57,98 @@ richgriditem {
   transition: 300ms height ease-out,
               150ms opacity ease-out,
               100ms transform ease-out;
 }
 
 .tile-content {
   display: block;
   position: absolute;
-  background-color: #fff;
+  /* background-color colors the tile-edge,
+     and will normally be overridden with a favicon-based color */
+  background-color: #ccc;
   background-origin: padding-box;
   /* content positioning within the grid "cell"
      gives us the gutters/spacing between tiles */
   top: 2px; right: 6px; bottom: 10px; left: 6px;
   border: @metro_border_thin@ solid @tile_border_color@;
   box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1);
   transition: 150ms transform ease-out;
 }
 
 .tile-start-container {
   position: absolute;
   top: 0;
   bottom: 0;
   right: 0;
-  left: 20px;
-  background: hsla(0,2%,98%,.95);
+  left: 10px;
   padding: 8px;
+  background-color: #fff;
 }
 
 richgriditem:not([tiletype="thumbnail"]) .tile-start-container {
   background-image: none!important;
 }
 
 .tile-icon-box {
-  display: inline-block;
-  padding: 4px;
-  background: #fff;
+  position: absolute;
+  top: 50%;
+  margin-top: -17px;
+  padding: 8px;
+  /* default color, may be overriden by a favicon-based color */
+  background-color: white;
+  border: 1px solid #ccc;
+  border-radius: 1px;
   opacity: 1.0;
 }
 
 .tile-icon-box > image {
+  display: block;
+  width: 16px;
+  height: 16px;
+  list-style-image: url("chrome://browser/skin/images/identity-icons-generic.png");
+}
+
+/* for larger favicons (which includes the fallback icon) */
+richgriditem:not([iconURI]) .tile-icon-box,
+richgriditem[iconURI=""] .tile-icon-box,
+richgriditem[iconsize="large"] .tile-icon-box {
+  background-color: transparent!important;
+  border-color: transparent!important;
+  padding: 4px;
+}
+
+richgriditem[iconsize="large"] .tile-icon-box > image,
+.tile-icon-box > image[src=""] {
   width: 24px;
   height: 24px;
-  list-style-image: url("chrome://browser/skin/images/identity-icons-generic.png");
 }
 
 .tile-desc {
   display: block;
   position: absolute;
-  bottom: 0;
+  top: 6px;
+  left: 52px; /* label goes to the right of the favicon */
   right: 0;
-  left: 20px; /* the colored bar in the default tile is the background color peeking through */
-  z-index: 1;
-  padding: 4px 8px;
+  padding: 1em 6px 6px 12px;
   color: #333;
   margin: 0;
   -moz-margin-start: 0;
   display: block;
-  font-size: 20px;
+  font-size: 16px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
 }
 
+richgriditem:not([tiletype="thumbnail"]) .tile-desc {
+  background-color: transparent!important;
+}
+
+
 richgriditem.collapsed {
   height: 0!important;
   overflow: hidden;
   opacity: 0;
 }
 
 richgriditem.collapsed > .tile-content {
   transform: scaleY(0);
@@ -142,40 +169,46 @@ richgriditem[tiletype="thumbnail"] {
   width: @grid_double_column_width@;
   height: @grid_double_row_height@;
   -moz-box-pack: end;
   padding: 0px;
   color: #1a1a1a;
 }
 
 richgriditem[tiletype="thumbnail"] .tile-desc {
-  background: transparent;
-  margin: 0px;
+  margin: 0;
+  top: auto;
+  bottom: 0;
   left: 0;
+  padding: 4px 8px 4px 56px;
 }
 
 richgriditem[tiletype="thumbnail"] > .tile-content > .tile-desc {
   /* ensure thumbnail labels get their color from the parent richgriditem element */
   color: inherit;
 }
 
-/* put the image in place of the icon if there is an image background */
+/* thumbnail tiles use a screenshot thumbnail as the background */
 richgriditem[tiletype="thumbnail"] > .tile-content > .tile-start-container {
   background-size: cover;
   background-position: top left;
   background-repeat: no-repeat;
   position: absolute;
   top: 0;
-  bottom: 32px; /* TODO: should be some em value? */;
+  bottom: 0;
   right: 0;
   left: 0;
-  background-color: hsla(0,2%,98%,.95);
 }
 richgriditem[tiletype="thumbnail"] .tile-icon-box {
-  visibility: collapse;
+  top: auto;
+  left: 10px;
+  bottom: 6px;
+  margin-top: 0;
+  z-index: 1;
+  box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.05), 0px 2px 0px rgba(0, 0, 0, 0.1);
 }
 
 /* selected tile indicator */
 richgriditem[selected] > .tile-content::after {
   content: "";
   pointer-events: none;
   display: block;
   position: absolute;
@@ -257,35 +290,17 @@ richgriditem[bending] > .tile-content {
 
   richgriditem {
     width: @grid_double_column_width@;
     overflow: hidden;
     height: @compactgrid_row_height@;
   }
 
   .tile-desc {
-    top: 0;
-    left: 44px; /* label goes to the right of the favicon */
-    right: 0;
-    padding: 8px;
-  }
-
-  .tile-start-container {
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    right: 0;
-    left: 6px;
-    background: #fff;
-    padding: 8px;
-  }
-  .tile-icon-box {
-    padding: 2px;
-    background: #fff;
-    opacity: 1.0;
+    padding: 0.5em 4px 4px 4px;
   }
 
   .tile-content {
     left: 0;
     right: 0;
   }
 
   richgriditem {
--- a/toolkit/components/passwordmgr/test/test_basic_form.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form.html
@@ -3,45 +3,44 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: simple form fill
+
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+/** Test for Login Manager: form fill, multiple forms. **/
+
+function startTest() {
+  is($_(1, "uname").value, "testuser", "Checking for filled username");
+  is($_(1, "pword").value, "testpass", "Checking for filled password");
+
+  SimpleTest.finish();
+}
+
+window.onload = startTest;
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
 
 </div>
 
-<pre id="test">
-<script class="testbody" type="text/javascript">
-commonInit();
-/** Test for Login Manager: form fill, multiple forms. **/
-
-// Make sure that all forms in a document are processed.
-
-function startTest() {
-  is($_(1, "uname").value, "testuser", "Checking for filled username");
-  is($_(1, "pword").value, "testpass", "Checking for filled password");
-
-  SimpleTest.finish();
-}
-
-window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
+<pre id="test"></pre>
 </body>
 </html>
 
--- a/toolkit/components/passwordmgr/test/test_basic_form_2.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_2.html
@@ -3,16 +3,26 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: simple form fill with autofillForms disabled
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
+                         .getService(SpecialPowers.Ci.nsILoginManager);
+// Assume that the pref starts out true, so set to false
+SpecialPowers.setBoolPref("signon.autofillForms", false);
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: block">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
@@ -23,47 +33,33 @@ Login Manager test: simple form fill wit
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: simple form fill with autofillForms disabled **/
 
-commonInit();
-
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-var pwmgr = Components.classes["@mozilla.org/login-manager;1"].
-            getService(Components.interfaces.nsILoginManager);
-var prefs = Components.classes["@mozilla.org/preferences-service;1"].
-            getService(Components.interfaces.nsIPrefService);
-prefs = prefs.getBranch("signon.");
-// Assume that the pref starts out true, so set to false
-prefs.setBoolPref("autofillForms", false);
-
 function startTest(){
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
   // Ensure the form is empty at start
   is($_(1, "uname").value, "", "Checking for blank username");
   is($_(1, "pword").value, "", "Checking for blank password");
 
   // Call the public method, check return value
   is(pwmgr.fillForm(document.getElementById("form1")), true,
      "Checking return value of fillForm");
 
   // Check that the form was filled
   is($_(1, "uname").value, "testuser", "Checking for filled username");
   is($_(1, "pword").value, "testpass", "Checking for filled password");
 
   // Reset pref (since we assumed it was true to start)
-  prefs.setBoolPref("autofillForms", true);
+  SpecialPowers.setBoolPref("signon.autofillForms", true);
 
   SimpleTest.finish();
 }
-
 window.onload = startTest;
 
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html
@@ -4,96 +4,24 @@
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: multiple login autocomplete
-<p id="display"></p>
 
-<!-- we presumably can't hide the content for this test. -->
-<div id="content">
-
-  <!-- form1 tests multiple matching logins -->
-  <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- other forms test single logins, with autocomplete=off set -->
-  <form id="form2" action="http://autocomplete2" onsubmit="return false;">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword" autocomplete="off">
-    <button type="submit">Submit</button>
-  </form>
-
-  <form id="form3" action="http://autocomplete2" onsubmit="return false;">
-    <input  type="text"       name="uname" autocomplete="off">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <form id="form4" action="http://autocomplete2" onsubmit="return false;" autocomplete="off">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <form id="form5" action="http://autocomplete2" onsubmit="return false;">
-    <input  type="text"       name="uname" autocomplete="off">
-    <input  type="password"   name="pword" autocomplete="off">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- control -->
-  <form id="form6" action="http://autocomplete2" onsubmit="return false;">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- This form will be manipulated to insert a different username field. -->
-  <form id="form7" action="http://autocomplete3" onsubmit="return false;">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- test for no autofill after onblur with blank username -->
-  <form id="form8" action="http://autocomplete4" onsubmit="return false;">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- test autocomplete dropdown -->
-  <form id="form9" action="http://autocomplete5" onsubmit="return false;">
-    <input  type="text"       name="uname">
-    <input  type="password"   name="pword">
-    <button type="submit">Submit</button>
-  </form>
-</div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Login Manager: multiple login autocomplete. **/
-
+<script>
 commonInit();
-
-var uname = $_(1, "uname");
-var pword = $_(1, "pword");
-const shiftModifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK;
+SimpleTest.waitForExplicitFinish();
 
 // Get the pwmgr service
-var pwmgr = SpecialPowers.wrap(Components).classes["@mozilla.org/login-manager;1"]
-                         .getService(Components.interfaces.nsILoginManager);
+var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
+                         .getService(SpecialPowers.Ci.nsILoginManager);
 ok(pwmgr != null, "nsLoginManager service");
 
 // Create some logins just for this form, since we'll be deleting them.
 var nsLoginInfo =
 SpecialPowers.wrap(Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
                           Components.interfaces.nsILoginInfo, "init");
 ok(nsLoginInfo != null, "nsLoginInfo constructor");
 
@@ -159,16 +87,92 @@ try {
     pwmgr.addLogin(login6B);
     pwmgr.addLogin(login7);
     pwmgr.addLogin(login8A);
     pwmgr.addLogin(login8B);
 } catch (e) {
     ok(false, "addLogin threw: " + e);
 }
 
+</script>
+<p id="display"></p>
+
+<!-- we presumably can't hide the content for this test. -->
+<div id="content">
+
+  <!-- form1 tests multiple matching logins -->
+  <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+
+  <!-- other forms test single logins, with autocomplete=off set -->
+  <form id="form2" action="http://autocomplete2" onsubmit="return false;">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword" autocomplete="off">
+    <button type="submit">Submit</button>
+  </form>
+
+  <form id="form3" action="http://autocomplete2" onsubmit="return false;">
+    <input  type="text"       name="uname" autocomplete="off">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+
+  <form id="form4" action="http://autocomplete2" onsubmit="return false;" autocomplete="off">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+
+  <form id="form5" action="http://autocomplete2" onsubmit="return false;">
+    <input  type="text"       name="uname" autocomplete="off">
+    <input  type="password"   name="pword" autocomplete="off">
+    <button type="submit">Submit</button>
+  </form>
+
+  <!-- control -->
+  <form id="form6" action="http://autocomplete2" onsubmit="return false;">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+
+  <!-- This form will be manipulated to insert a different username field. -->
+  <form id="form7" action="http://autocomplete3" onsubmit="return false;">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+
+  <!-- test for no autofill after onblur with blank username -->
+  <form id="form8" action="http://autocomplete4" onsubmit="return false;">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+
+  <!-- test autocomplete dropdown -->
+  <form id="form9" action="http://autocomplete5" onsubmit="return false;">
+    <input  type="text"       name="uname">
+    <input  type="password"   name="pword">
+    <button type="submit">Submit</button>
+  </form>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Login Manager: multiple login autocomplete. **/
+
+
+var uname = $_(1, "uname");
+var pword = $_(1, "pword");
+const shiftModifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK;
 
 // Restore the form to the default state.
 function restoreForm() {
     uname.value = "";
     pword.value = "";
     uname.focus();
 }
 
@@ -807,15 +811,13 @@ function startTest() {
     // shouldn't reach into browser internals like this and
     // shouldn't assume ID is consistent across products
     autocompletePopup = chromeWin.document.getElementById("PopupAutoComplete");
     ok(autocompletePopup, "Got autocomplete popup");
     runTest(1);
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
 
--- a/toolkit/components/passwordmgr/test/test_basic_form_html5.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_html5.html
@@ -3,16 +3,49 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: html5 input types (email, tel, url, etc.)
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+const Ci = SpecialPowers.Ci;
+const Cc = SpecialPowers.Cc;
+pwmgr = Cc["@mozilla.org/login-manager;1"].
+        getService(Ci.nsILoginManager);
+
+login1  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login2  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login3  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login4  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+
+login1.init("http://mochi.test:8888", "http://bug600551-1", null,
+            "testuser@example.com", "testpass1", "", "");
+login2.init("http://mochi.test:8888", "http://bug600551-2", null,
+            "555-555-5555", "testpass2", "", "");
+login3.init("http://mochi.test:8888", "http://bug600551-3", null,
+            "http://mozilla.org", "testpass3", "", "");
+login4.init("http://mochi.test:8888", "http://bug600551-4", null,
+            "123456789", "testpass4", "", "");
+
+pwmgr.addLogin(login1);
+pwmgr.addLogin(login2);
+pwmgr.addLogin(login3);
+pwmgr.addLogin(login4);
+</script>
+
 <p id="display"></p>
 <div id="content" style="display: none">
 
   <form id="form1" action="http://bug600551-1">
     <input  type="email"    name="uname">
     <input  type="password" name="pword">
     <button type="submit">Submit</button>
   </form>
@@ -94,48 +127,16 @@ Login Manager test: html5 input types (e
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /* Test for Login Manager: 600551
   (Password manager not working with input type=email)
  */
-commonInit();
-
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-pwmgr = Cc["@mozilla.org/login-manager;1"].
-        getService(Ci.nsILoginManager);
-
-login1  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-login2  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-login3  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);        
-login4  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-
-login1.init("http://mochi.test:8888", "http://bug600551-1", null,
-            "testuser@example.com", "testpass1", "", "");
-login2.init("http://mochi.test:8888", "http://bug600551-2", null,
-            "555-555-5555", "testpass2", "", "");
-login3.init("http://mochi.test:8888", "http://bug600551-3", null,
-            "http://mozilla.org", "testpass3", "", "");
-login4.init("http://mochi.test:8888", "http://bug600551-4", null,
-            "123456789", "testpass4", "", "");
-
-pwmgr.addLogin(login1);
-pwmgr.addLogin(login2);
-pwmgr.addLogin(login3);
-pwmgr.addLogin(login4);
-
 function startTest() {
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
 
   checkForm(1, "testuser@example.com", "testpass1");
   checkForm(2, "555-555-5555", "testpass2");
   checkForm(3, "http://mozilla.org", "testpass3");
   checkForm(4, "123456789", "testpass4");
   
@@ -161,15 +162,12 @@ function startTest() {
   pwmgr.removeLogin(login2);
   pwmgr.removeLogin(login3);
   pwmgr.removeLogin(login4);
  
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_observer_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_observer_autocomplete.html
@@ -3,53 +3,55 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: simple form with autocomplete off and notifying observers & normal form
+<script>
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+commonInit();
+SimpleTest.waitForExplicitFinish();
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: block">
 
   <form id="form1" action="formtest.js" autocomplete="off">
     <p>This is form 1.</p>
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
-  
+
   <form id="form2" action="formtest.js">
     <p>This is form 2.</p>
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: simple form with autocomplete off and notifying observers & normal form **/
-
-commonInit();
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
 var TestObserver = {
   receivedNotification1 : false,
   receivedNotification2 : false,
   data1 : "",
   data2 : "",
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
   observe : function (subject, topic, data) {
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
@@ -94,14 +96,12 @@ function startTest(){
 
   // Remove the observer
   os.removeObserver(TestObserver, "passwordmgr-found-form");
 
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_observer_autofillForms.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_observer_autofillForms.html
@@ -3,16 +3,31 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: simple form with autofillForms disabled and notifying observers
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Assume that the pref starts out true, so set to false
+SpecialPowers.setBoolPref("signon.autofillForms", false);
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: block">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
@@ -23,30 +38,16 @@ Login Manager test: simple form with aut
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: simple form with autofillForms disabled and notifying observers **/
 
-commonInit();
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-var prefs = Cc["@mozilla.org/preferences-service;1"].
-            getService(Ci.nsIPrefService);
-prefs = prefs.getBranch("signon.");
-// Assume that the pref starts out true, so set to false
-prefs.setBoolPref("autofillForms", false);
-
 var TestObserver = {
   receivedNotificationFoundForm : false,
   receivedNotificationFoundLogins : false,
   dataFoundForm : "",
   dataFoundLogins : null,
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
   observe : function (subject, topic, data) {
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
@@ -87,24 +88,22 @@ function startTest(){
   is(TestObserver.dataFoundLogins.get("passwordField"), $_(1, "pword"), "Checking password field is correct");
   is(TestObserver.dataFoundLogins.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array");
   is(TestObserver.dataFoundLogins.get("foundLogins").length, 1, "Checking foundLogins contains one login");
   ok(TestObserver.dataFoundLogins.get("selectedLogin").QueryInterface(Ci.nsILoginInfo), "Checking selectedLogin is nsILoginInfo");
   ok(TestObserver.dataFoundLogins.get("selectedLogin").equals(TestObserver.dataFoundLogins.get("foundLogins")[0]),
      "Checking selectedLogin is found login");
 
   // Reset pref (since we assumed it was true to start)
-  prefs.setBoolPref("autofillForms", true);
+  SpecialPowers.setBoolPref("signon.autofillForms", true);
 
   // Remove the observer
   os.removeObserver(TestObserver, "passwordmgr-found-form");
   os.removeObserver(TestObserver, "passwordmgr-found-logins");
 
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_observer_foundLogins.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_observer_foundLogins.html
@@ -3,29 +3,80 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: notifying observers of passwordmgr-found-logins
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Configure the login manager with two logins for one of the forms
+// so we can do a multiple logins test.
+var nsLoginInfo =
+  new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                             Components.interfaces.nsILoginInfo);
+var login1 = new nsLoginInfo();
+login1.init("http://mochi.test:8888", "http://www.example.com", null,
+            "testuser1", "testpass1", "uname", "pword");
+var login2 = new nsLoginInfo();
+login2.init("http://mochi.test:8888", "http://www.example.com", null,
+            "testuser2", "testpass2", "uname", "pword");
+var pwmgr = Cc["@mozilla.org/login-manager;1"].
+            getService(Ci.nsILoginManager);
+pwmgr.addLogin(login1);
+pwmgr.addLogin(login2);
+
+var TestObserver = {
+  results: {},
+  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
+  observe : function (subject, topic, data) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    if (topic == "passwordmgr-found-logins") {
+      var formInfo = subject.QueryInterface(Ci.nsIPropertyBag2);
+      var id = formInfo.get("passwordField").form.id;
+      this.results[id].receivedNotification = true;
+      this.results[id].data = formInfo;
+    }
+  }
+};
+
+// Initialize the object that stores the results of notifications.
+for (var formID of ["form1", "form2", "form3", "form4", "form5"])
+  TestObserver.results[formID] = { receivedNotification: false, data: null };
+
+// Add the observer
+var os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+os.addObserver(TestObserver, "passwordmgr-found-logins", false);
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: block">
 
   <form id="form1" action="formtest.js">
     <p>This is form 1.</p>
     <input  type="text"       name="uname">
     <input  type="password"   name="pword">
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
-  
+
   <form id="form2" action="formtest.js">
     <p>This is form 2.</p>
     <input  type="text"       name="uname" value="existing">
     <input  type="password"   name="pword">
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
@@ -59,63 +110,16 @@ Login Manager test: notifying observers 
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: notifying observers of passwordmgr-found-logins **/
 
-commonInit();
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-// Configure the login manager with two logins for one of the forms
-// so we can do a multiple logins test.
-var nsLoginInfo =
-  new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
-                             Components.interfaces.nsILoginInfo);
-var login1 = new nsLoginInfo();
-login1.init("http://mochi.test:8888", "http://www.example.com", null,
-            "testuser1", "testpass1", "uname", "pword");
-var login2 = new nsLoginInfo();
-login2.init("http://mochi.test:8888", "http://www.example.com", null,
-            "testuser2", "testpass2", "uname", "pword");
-var pwmgr = Cc["@mozilla.org/login-manager;1"].
-            getService(Ci.nsILoginManager);
-pwmgr.addLogin(login1);
-pwmgr.addLogin(login2);
-
-var TestObserver = {
-  results: {},
-  QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
-  observe : function (subject, topic, data) {
-    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-    if (topic == "passwordmgr-found-logins") {
-      var formInfo = subject.QueryInterface(Ci.nsIPropertyBag2);
-      var id = formInfo.get("passwordField").form.id;
-      this.results[id].receivedNotification = true;
-      this.results[id].data = formInfo;
-    }
-  }
-};
-
-// Initialize the object that stores the results of notifications.
-for (var formID of ["form1", "form2", "form3", "form4", "form5"])
-  TestObserver.results[formID] = { receivedNotification: false, data: null };
-
-// Add the observer
-var os = Cc["@mozilla.org/observer-service;1"].
-         getService(Ci.nsIObserverService);
-os.addObserver(TestObserver, "passwordmgr-found-logins", false);
-
 function startTest(){
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
 
   // Test notification of a form that was filled.
   is(TestObserver.results["form1"].receivedNotification, true, "Checking observer was notified");
   is(TestObserver.results["form1"].data.get("didntFillReason"), null, "Checking didntFillReason is null");
   is(TestObserver.results["form1"].data.get("usernameField"), $_(1, "uname"), "Checking username field is correct");
   is(TestObserver.results["form1"].data.get("passwordField"), $_(1, "pword"), "Checking password field is correct");
@@ -176,14 +180,12 @@ function startTest(){
   // Remove the logins added for the multiple logins test.
   pwmgr.removeLogin(login1);
   pwmgr.removeLogin(login2);
 
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_pwonly.html
+++ b/toolkit/components/passwordmgr/test/test_basic_form_pwonly.html
@@ -3,16 +3,49 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: forms and logins without a username.
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
+                         .getService(SpecialPowers.Ci.nsILoginManager);
+
+var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo);
+ok(nsLoginInfo != null, "nsLoginInfo constructor");
+
+// pwlogin1 uses a unique formSubmitURL, to check forms where no other logins
+// will apply. pwlogin2 uses the normal formSubmitURL, so that we can test
+// forms with a mix of username and non-username logins that might apply.
+//
+// Note: pwlogin2 is deleted at the end of the test.
+
+pwlogin1 = new nsLoginInfo();
+pwlogin2 = new nsLoginInfo();
+
+pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null,
+    "", "1234", "uname", "pword");
+
+pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null,
+    "", "1234", "uname", "pword");
+
+try {
+    pwmgr.addLogin(pwlogin1);
+    pwmgr.addLogin(pwlogin2);
+} catch (e) {
+    ok(false, "addLogin threw: " + e);
+}
+
+</script>
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 
 <!-- simple form: no username field, 1 password field -->
 <form id='form1' action='http://mochi.test:1111/formtest.js'> 1
     <input type='password' name='pname' value=''>
@@ -149,18 +182,16 @@ password-only, the other is username+pas
 
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: password-only logins **/
-
-commonInit();
 function startTest() {
 
     checkForm(1, "1234");
     checkForm(2, "1234", "");
     checkForm(3, "1234", "", "");
     checkUnmodifiedForm(4);
 
     checkForm(5, "", "1234");
@@ -176,55 +207,14 @@ function startTest() {
     checkUnmodifiedForm(12);
     checkUnmodifiedForm(13);
 
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
     pwmgr.removeLogin(pwlogin2);
     SimpleTest.finish();
 }
 
-
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-// Get the pwmgr service
-var Cc_pwmgr = Components.classes["@mozilla.org/login-manager;1"];
-ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]");
-
-var Ci_pwmgr = Components.interfaces.nsILoginManager;
-ok(Ci_pwmgr != null, "Access Ci.nsILoginManager");
-
-var pwmgr = Cc_pwmgr.getService(Ci_pwmgr);
-ok(pwmgr != null, "pwmgr getService()");
-
-
-var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo);
-ok(nsLoginInfo != null, "nsLoginInfo constructor");
-
-// pwlogin1 uses a unique formSubmitURL, to check forms where no other logins
-// will apply. pwlogin2 uses the normal formSubmitURL, so that we can test
-// forms with a mix of username and non-username logins that might apply.
-//
-// Note: pwlogin2 is deleted at the end of the test.
-
-pwlogin1 = new nsLoginInfo();
-pwlogin2 = new nsLoginInfo();
-
-pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null,
-    "", "1234", "uname", "pword");
-
-pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null,
-    "", "1234", "uname", "pword");
-
-try {
-    pwmgr.addLogin(pwlogin1);
-    pwmgr.addLogin(pwlogin2);
-} catch (e) {
-    ok(false, "addLogin threw: " + e);
-}
-
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
 
--- a/toolkit/components/passwordmgr/test/test_bug_227640.html
+++ b/toolkit/components/passwordmgr/test/test_bug_227640.html
@@ -3,19 +3,27 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: 227640
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
+                         .getService(SpecialPowers.Ci.nsILoginManager);
+</script>
+
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
   <!-- no autocomplete for password field -->
   <form id="form1" onsubmit="return checkSubmit(1)" method="get">
     <input  type="text"       name="uname" value="">
     <input  type="password"   name="pword" value="" autocomplete=off>
 
     <button type="submit">Submit</button>
     <button type="reset"> Reset </button>
   </form>
@@ -142,17 +150,16 @@ Login Manager test: 227640
   </form>
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: 227640 (password is saved even when the
     password field has autocomplete="off") **/
-commonInit();
 
 // This test ensures that pwmgr does not save a username or password when
 // autocomplete=off is present.
 
 var numStartingLogins = 0;
 var numSubmittedForms = 0;
 
 function startTest() {
@@ -225,31 +232,14 @@ function getFormSubmitButton(formNum) {
 // Counts the number of logins currently stored by password manager.
 function countLogins() {
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
 
   var logins = pwmgr.getAllLogins();
 
   return logins.length;
 }
-
-// Get the pwmgr service
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-var Cc_pwmgr = Components.classes["@mozilla.org/login-manager;1"];
-ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]");
-
-var Ci_pwmgr = Components.interfaces.nsILoginManager;
-ok(Ci_pwmgr != null, "Access Ci.nsILoginManager");
-
-var pwmgr = Cc_pwmgr.getService(Ci_pwmgr);
-ok(pwmgr != null, "pwmgr getService()");
-
-
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
 
--- a/toolkit/components/passwordmgr/test/test_bug_427033.html
+++ b/toolkit/components/passwordmgr/test/test_bug_427033.html
@@ -3,71 +3,49 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: form with JS submit action
+<script>
+SimpleTest.waitForExplicitFinish();
+var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
+                         .getService(SpecialPowers.Ci.nsILoginManager);
+var jslogin = SpecialPowers.Cc["@mozilla.org/login-manager/loginInfo;1"]
+                           .createInstance(SpecialPowers.Ci.nsILoginInfo);
+jslogin.init("http://mochi.test:8888", "javascript:", null,
+              "jsuser", "jspass123", "uname", "pword");
+pwmgr.addLogin(jslogin);
+
+/** Test for Login Manager: JS action URL **/
+
+function startTest() {
+    checkForm(1, "jsuser", "jspass123");
+
+    pwmgr.removeLogin(jslogin);
+    SimpleTest.finish();
+}
+
+window.onload = startTest;
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: none">
 
 
 <form id='form1' action='javascript:alert("never shows")'> 1
     <input name="uname">
     <input name="pword" type="password">
 
     <button type='submit'>Submit</button>
     <button type='reset'> Reset </button>
 </form>
 
 </div>
 
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Login Manager: JS action URL **/
-
-function startTest() {
-
-    checkForm(1, "jsuser", "jspass123");
-
-    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-    pwmgr.removeLogin(jslogin);
-    SimpleTest.finish();
-}
-
-
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-// Get the pwmgr service
-var Cc_pwmgr = Components.classes["@mozilla.org/login-manager;1"];
-ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]");
-
-var Ci_pwmgr = Components.interfaces.nsILoginManager;
-ok(Ci_pwmgr != null, "Access Ci.nsILoginManager");
-
-var pwmgr = Cc_pwmgr.getService(Ci_pwmgr);
-ok(pwmgr != null, "pwmgr getService()");
-
-var jslogin = Components.classes["@mozilla.org/login-manager/loginInfo;1"].
-              createInstance(Components.interfaces.nsILoginInfo);
-ok(jslogin != null, "create a login");
-
-jslogin.init("http://mochi.test:8888", "javascript:", null,
-              "jsuser", "jspass123", "uname", "pword");
-
-try {
-    pwmgr.addLogin(jslogin);
-} catch (e) {
-    ok(false, "addLogin threw: " + e);
-}
-
-window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
+<pre id="test"></pre>
 </body>
 </html>
 
--- a/toolkit/components/passwordmgr/test/test_bug_444968.html
+++ b/toolkit/components/passwordmgr/test/test_bug_444968.html
@@ -3,16 +3,55 @@
 <head>
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: 444968
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+const Ci = SpecialPowers.Ci;
+const Cc = SpecialPowers.Cc;
+pwmgr = Cc["@mozilla.org/login-manager;1"].
+        getService(Ci.nsILoginManager);
+
+login1A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login1B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login2A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login2B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+login2C  = Cc["@mozilla.org/login-manager/loginInfo;1"].
+           createInstance(Ci.nsILoginInfo);
+
+login1A.init("http://mochi.test:8888", "http://bug444968-1", null,
+            "testuser1A", "testpass1A", "", "");
+login1B.init("http://mochi.test:8888", "http://bug444968-1", null,
+            "", "testpass1B", "", "");
+
+login2A.init("http://mochi.test:8888", "http://bug444968-2", null,
+            "testuser2A", "testpass2A", "", "");
+login2B.init("http://mochi.test:8888", "http://bug444968-2", null,
+            "", "testpass2B", "", "");
+login2C.init("http://mochi.test:8888", "http://bug444968-2", null,
+            "testuser2C", "testpass2C", "", "");
+
+pwmgr.addLogin(login1A);
+pwmgr.addLogin(login1B);
+pwmgr.addLogin(login2A);
+pwmgr.addLogin(login2B);
+pwmgr.addLogin(login2C);
+</script>
+
 <p id="display"></p>
 <div id="content" style="display: none">
   <!-- first 3 forms have matching user+pass and pass-only logins -->
 
   <!-- user+pass form. -->
   <form id="form1" action="http://bug444968-1">
     <input  type="text"     name="uname">
     <input  type="password" name="pword">
@@ -64,75 +103,32 @@ Login Manager test: 444968
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /* Test for Login Manager: 444968 (password-only forms should prefer a
  * password-only login when present )
  */
-commonInit();
-
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-pwmgr = Cc["@mozilla.org/login-manager;1"].
-        getService(Ci.nsILoginManager);
-
-login1A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-login1B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-login2A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-login2B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-login2C  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-           createInstance(Ci.nsILoginInfo);
-
-login1A.init("http://mochi.test:8888", "http://bug444968-1", null,
-            "testuser1A", "testpass1A", "", "");
-login1B.init("http://mochi.test:8888", "http://bug444968-1", null,
-            "", "testpass1B", "", "");
-
-login2A.init("http://mochi.test:8888", "http://bug444968-2", null,
-            "testuser2A", "testpass2A", "", "");
-login2B.init("http://mochi.test:8888", "http://bug444968-2", null,
-            "", "testpass2B", "", "");
-login2C.init("http://mochi.test:8888", "http://bug444968-2", null,
-            "testuser2C", "testpass2C", "", "");
-
-pwmgr.addLogin(login1A);
-pwmgr.addLogin(login1B);
-pwmgr.addLogin(login2A);
-pwmgr.addLogin(login2B);
-pwmgr.addLogin(login2C);
-
 function startTest() {
-  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
   checkForm(1, "testuser1A", "testpass1A");
   checkForm(2, "testpass1B");
   checkForm(3, "testuser1A", "testpass1A");
 
   checkUnmodifiedForm(4); // 2 logins match
   checkForm(5, "testpass2B");
   checkForm(6, "testuser2A", "testpass2A");
   checkForm(7, "testuser2C", "testpass2C");
 
   pwmgr.removeLogin(login1A);
   pwmgr.removeLogin(login1B);
   pwmgr.removeLogin(login2A);
   pwmgr.removeLogin(login2B);
   pwmgr.removeLogin(login2C);
- 
+
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
-
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/test_master_password.html
+++ b/toolkit/components/passwordmgr/test/test_master_password.html
@@ -4,28 +4,54 @@
   <title>Test for Login Manager</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <script type="text/javascript" src="prompt_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: master password.
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
+
+var pwmgr   = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
+                           .getService(SpecialPowers.Ci.nsILoginManager);
+var pwcrypt = SpecialPowers.Cc["@mozilla.org/login-manager/crypto/SDR;1"]
+                           .getService(Ci.nsILoginManagerCrypto);
+
+var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
+
+var exampleCom = "http://example.com/tests/toolkit/components/passwordmgr/test/";
+var exampleOrg = "http://example.org/tests/toolkit/components/passwordmgr/test/";
+
+var login1 = new nsLoginInfo();
+var login2 = new nsLoginInfo();
+
+login1.init("http://example.com", "http://example.com", null,
+            "user1", "pass1", "uname", "pword");
+login2.init("http://example.org", "http://example.org", null,
+            "user2", "pass2", "uname", "pword");
+
+pwmgr.addLogin(login1);
+pwmgr.addLogin(login2);
+</script>
+
 <p id="display"></p>
 
 <div id="content" style="display: none">
 <iframe id="iframe1"></iframe>
 <iframe id="iframe2"></iframe>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
-
-commonInit();
 var testNum = 1;
+var iframe1 = document.getElementById("iframe1");
+var iframe2 = document.getElementById("iframe2");
 
 /*
  * handleDialog
  *
  * Invoked a short period of time after calling startCallbackTimer(), and
  * allows testing the actual auth dialog while it's being displayed. Tests
  * should call startCallbackTimer() each time the auth dialog is expected (the
  * timer is a one-shot).
@@ -74,18 +100,22 @@ function handleDialog(doc, testNum) {
             dialog.acceptDialog();
         else
             dialog.cancelDialog();
     }
 
     ok(true, "handleDialog done");
     didDialog = true;
 
-    if (testNum == 4)
+    if (testNum == 3)
+        SimpleTest.executeSoon(checkTest3);
+    else if (testNum == 4)
         checkTest4A();
+    else if (testNum == 5)
+        SimpleTest.executeSoon(checkTest4C);
 }
 
 
 function startTest1() {
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
 
     ok(pwcrypt.isLoggedIn, "should be initially logged in (no MP)");
     enableMasterPassword();
@@ -114,22 +144,16 @@ function startTest1() {
     ok(didDialog, "handleDialog was invoked");
     ok(failedAsExpected, "getAllLogins should have thrown");
     is(logins, null, "shouldn't have gotten logins");
     ok(!pwcrypt.isLoggedIn, "should still be logged out");
 
     // --- Test 3 ---
     // Load a single iframe to trigger a MP
     testNum++;
-
-    // Note that because DOMContentLoaded is dispatched synchronously, the
-    // document's load event is blocked until after the MP entry (because
-    // pwmgr's listener doesn't return until after it processes the form,
-    // which is blocked waiting on a MP entry).
-    iframe1.onload = checkTest3;
     iframe1.src = exampleCom + "subtst_master_pass.html";
     startCallbackTimer();
 }
 
 function checkTest3() {
     ok(true, "checkTest3 starting");
     ok(didDialog, "handleDialog was invoked");
 
@@ -142,17 +166,16 @@ function checkTest3() {
     ok(pwcrypt.isLoggedIn, "should be logged in");
     logoutMasterPassword();
     ok(!pwcrypt.isLoggedIn, "should be logged out");
 
 
     // --- Test 4 ---
     // first part of loading 2 MP-triggering iframes
     testNum++;
-    iframe1.onload = checkTest4C;
     iframe1.src = exampleOrg + "subtst_master_pass.html";
     // start the callback, but we'll not enter the MP, just call checkTest4A
     startCallbackTimer();
 }
 
 function checkTest4A() {
     ok(true, "checkTest4A starting");
     ok(didDialog, "handleDialog was invoked");
@@ -165,17 +188,19 @@ function checkTest4A() {
 
 
     ok(!pwcrypt.isLoggedIn, "should be logged out");
 
     // XXX check that there's 1 MP window open
 
     // Load another iframe with a login form
     // This should detect that there's already a pending MP prompt, and not
-    // put up a second one. The load event will fire.
+    // put up a second one. The load event will fire (note that when pwmgr is
+    // driven from DOMContentLoaded, if that blocks due to prompting for a MP,
+    // the load even will also be blocked until the prompt is dismissed).
     iframe2.onload = checkTest4B;
     iframe2.src = exampleCom + "subtst_master_pass.html";
 }
 
 function checkTest4B() {
     ok(true, "checkTest4B starting");
     // iframe2 should load without having triggered a MP prompt (because one
     // is already waiting)
@@ -191,17 +216,16 @@ function checkTest4B() {
 
     // Ok, now enter the MP. The MP prompt is already up, but we'll just reuse startCallBackTimer.
     // --- Test 5 ---
     testNum++;
     startCallbackTimer();
 }
 
 function checkTest4C() {
-    // iframe1 finally loads after the MP entry.
     ok(true, "checkTest4C starting");
     ok(didDialog, "handleDialog was invoked");
 
     // We shouldn't have to worry about iframe1's load event racing with
     // filling of iframe2's data. We notify observers synchronously, so
     // iframe2's observer will process iframe2 before iframe1 even finishes
     // processing the form (which is blocking its load event).
     ok(pwcrypt.isLoggedIn, "should be logged in");
@@ -227,48 +251,14 @@ function finishTest() {
     disableMasterPassword();
     ok(pwcrypt.isLoggedIn, "should be logged in");
 
     pwmgr.removeLogin(login1);
     pwmgr.removeLogin(login2);
     SimpleTest.finish();
 }
 
-
-netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-
-// Get the pwmgr service
-var pwmgr = Cc["@mozilla.org/login-manager;1"].
-            getService(Ci.nsILoginManager);
-ok(pwmgr != null, "pwmgr getService()");
-
-var pwcrypt = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
-             getService(Ci.nsILoginManagerCrypto);
-ok(pwcrypt != null, "pwcrypt getService()");
-
-var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
-ok(nsLoginInfo != null, "nsLoginInfo constructor");
-
-var exampleCom = "http://example.com/tests/toolkit/components/passwordmgr/test/";
-var exampleOrg = "http://example.org/tests/toolkit/components/passwordmgr/test/";
-
-var login1 = new nsLoginInfo();
-var login2 = new nsLoginInfo();
-
-login1.init("http://example.com", "http://example.com", null,
-            "user1", "pass1", "uname", "pword");
-login2.init("http://example.org", "http://example.org", null,
-            "user2", "pass2", "uname", "pword");
-
-pwmgr.addLogin(login1);
-pwmgr.addLogin(login2);
-
-var iframe1 = document.getElementById("iframe1");
-var iframe2 = document.getElementById("iframe2");
-
 window.onload = startTest1;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
 
--- a/toolkit/components/passwordmgr/test/test_master_password_cleanup.html
+++ b/toolkit/components/passwordmgr/test/test_master_password_cleanup.html
@@ -6,18 +6,16 @@
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: master password cleanup
 <p id="display"></p>
 
 <div id="content" style="display: none">
-<iframe id="iframe1"></iframe>
-<iframe id="iframe2"></iframe>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 
 /*
  * The entire purpose of this test is to make sure that, if the previous master
--- a/toolkit/components/passwordmgr/test/test_maxforms_1.html
+++ b/toolkit/components/passwordmgr/test/test_maxforms_1.html
@@ -7,18 +7,20 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Test limiting number of forms filled.
 
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
 
-<script>
 var FORMS_TO_CREATE = 39;
 
 function createForm(id) {
     var template1 = "<form id='form"
     var template2 = "'><input name='u'><input type='password' name='p'></form>\n";
     return id + template1 + id + template2;
 }
 
@@ -29,17 +31,16 @@ for (var i = 1; i <= FORMS_TO_CREATE; i+
 
 var theDiv = document.getElementById("content");
 theDiv.innerHTML = formsHtml;
 </script>
 
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
-commonInit();
 /** Test for Login Manager: form fill, multiple forms. **/
 
 function startTest() {
   for (var i = 1; i <= FORMS_TO_CREATE; i++) {
     if (true) {
       is($_(i, "u").value, "testuser", "Checking for filled username in form " + i);
       is($_(i, "p").value, "testpass", "Checking for filled password in form " + i);
     } else {
@@ -47,15 +48,12 @@ function startTest() {
       is($_(i, "p").value, "", "Checking for unfilled password in form " + i);
     }
   }
 
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
-
--- a/toolkit/components/passwordmgr/test/test_maxforms_2.html
+++ b/toolkit/components/passwordmgr/test/test_maxforms_2.html
@@ -7,18 +7,20 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Test limiting number of forms filled.
 
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
 
-<script>
 var FORMS_TO_CREATE = 40;
 
 function createForm(id) {
     var template1 = "<form id='form"
     var template2 = "'><input name='u'><input type='password' name='p'></form>\n";
     return id + template1 + id + template2;
 }
 
@@ -29,17 +31,16 @@ for (var i = 1; i <= FORMS_TO_CREATE; i+
 
 var theDiv = document.getElementById("content");
 theDiv.innerHTML = formsHtml;
 </script>
 
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
-commonInit();
 /** Test for Login Manager: form fill, multiple forms. **/
 
 function startTest() {
   for (var i = 1; i <= FORMS_TO_CREATE; i++) {
     if (true) {
       is($_(i, "u").value, "testuser", "Checking for filled username in form " + i);
       is($_(i, "p").value, "testpass", "Checking for filled password in form " + i);
     } else {
@@ -47,15 +48,12 @@ function startTest() {
       is($_(i, "p").value, "", "Checking for unfilled password in form " + i);
     }
   }
 
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
-
--- a/toolkit/components/passwordmgr/test/test_maxforms_3.html
+++ b/toolkit/components/passwordmgr/test/test_maxforms_3.html
@@ -7,18 +7,20 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Test limiting number of forms filled.
 
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 
+<script>
+commonInit();
+SimpleTest.waitForExplicitFinish();
 
-<script>
 var FORMS_TO_CREATE = 41;
 
 function createForm(id) {
     var template1 = "<form id='form"
     var template2 = "'><input name='u'><input type='password' name='p'></form>\n";
     return id + template1 + id + template2;
 }
 
@@ -29,17 +31,16 @@ for (var i = 1; i <= FORMS_TO_CREATE; i+
 
 var theDiv = document.getElementById("content");
 theDiv.innerHTML = formsHtml;
 </script>
 
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
-commonInit();
 /** Test for Login Manager: form fill, multiple forms. **/
 
 function startTest() {
   for (var i = 1; i <= FORMS_TO_CREATE; i++) {
     if (i != 21) {
       is($_(i, "u").value, "testuser", "Checking for filled username in form " + i);
       is($_(i, "p").value, "testpass", "Checking for filled password in form " + i);
     } else {
@@ -47,15 +48,12 @@ function startTest() {
       is($_(i, "p").value, "", "Checking for unfilled password in form " + i);
     }
   }
 
   SimpleTest.finish();
 }
 
 window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
-
--- a/toolkit/mozapps/extensions/DeferredSave.jsm
+++ b/toolkit/mozapps/extensions/DeferredSave.jsm
@@ -68,32 +68,36 @@ function DeferredSave(aPath, aDataProvid
   this._path = aPath;
   this._dataProvider = aDataProvider;
 
   this._timer = null;
 
   // Some counters for telemetry
   // The total number of times the file was written
   this.totalSaves = 0;
+
   // The number of times the data became dirty while
   // another save was in progress
   this.overlappedSaves = 0;
 
+  // Error returned by the most recent write (if any)
+  this._lastError = null;
+
   if (aDelay && (aDelay > 0))
     this._delay = aDelay;
   else
     this._delay = DEFAULT_SAVE_DELAY_MS;
 }
 
 DeferredSave.prototype = {
   get dirty() {
     return this._pending || this.writeInProgress;
   },
 
-  get error() {
+  get lastError() {
     return this._lastError;
   },
 
   // Start the pending timer if data is dirty
   _startTimer: function() {
     if (!this._pending) {
       return;
     }
--- a/toolkit/mozapps/extensions/Makefile.in
+++ b/toolkit/mozapps/extensions/Makefile.in
@@ -10,17 +10,17 @@ VPATH     = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 ifeq (,$(filter aurora beta release esr,$(MOZ_UPDATE_CHANNEL)))
 DEFINES += -DMOZ_COMPATIBILITY_NIGHTLY=1
 endif
 
 # This is used in multiple places, so is defined here to avoid it getting
 # out of sync.
-DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=14
+DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=15
 
 # Additional debugging info is exposed in debug builds, or by setting the
 # MOZ_EM_DEBUG environment variable when building.
 ifneq (,$(MOZ_EM_DEBUG))
 DEFINES += -DMOZ_EM_DEBUG=1
 else ifdef MOZ_DEBUG
 DEFINES += -DMOZ_EM_DEBUG=1
 endif
--- 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.sqlite";
+const FILE_DATABASE                   = "extensions.json";
 const FILE_OLD_CACHE                  = "extensions.cache";
 const FILE_INSTALL_MANIFEST           = "install.rdf";
 const FILE_XPI_ADDONS_LIST            = "extensions.ini";
 
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_APPDIR                      = "XCurProcD";
 const KEY_TEMPDIR                     = "TmpD";
 const KEY_APP_DISTRIBUTION            = "XREAppDist";
@@ -115,17 +115,22 @@ 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", "isForeignInstall", "syncGUID"];
+                            "releaseNotesURI", "foreignInstall", "syncGUID"];
+// Properties to cache and reload when an addon installation is pending
+const PENDING_INSTALL_METADATA =
+    ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
+     "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
+     "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"];
 
 // Note: When adding/changing/removing items here, remember to change the
 // DB schema version to ensure changes are picked up ASAP.
 const STATIC_BLOCKLIST_PATTERNS = [
   { creator: "Mozilla Corp.",
     level: Ci.nsIBlocklistService.STATE_BLOCKED,
     blockID: "i162" },
   { creator: "Mozilla.org",
@@ -164,22 +169,25 @@ 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) {
-  this.__defineGetter__(aName, function logFuncGetter() {
-    Components.utils.import("resource://gre/modules/AddonLogging.jsm");
-
-    LogManager.getLogger("addons.xpi", this);
-    return this[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);
 
 
 const LAZY_OBJECTS = ["XPIDatabase"];
 
 var gLazyObjectsLoaded = false;
 
 function loadLazyObjects() {
@@ -192,19 +200,22 @@ function loadLazyObjects() {
     delete gGlobalScope[name];
     gGlobalScope[name] = scope[name];
   }
   gLazyObjectsLoaded = true;
   return scope;
 }
 
 for (let name of LAZY_OBJECTS) {
-  gGlobalScope.__defineGetter__(name, function lazyObjectGetter() {
-    let objs = loadLazyObjects();
-    return objs[name];
+  Object.defineProperty(gGlobalScope, name, {
+    get: function lazyObjectGetter() {
+      let objs = loadLazyObjects();
+      return objs[name];
+    },
+    configurable: true
   });
 }
 
 
 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) ||
@@ -493,16 +504,19 @@ function findClosestLocale(aLocales) {
 }
 
 /**
  * Sets the userDisabled and softDisabled properties of an add-on based on what
  * values those properties had for a previous instance of the add-on. The
  * previous instance may be a previous install or in the case of an application
  * version change the same add-on.
  *
+ * NOTE: this may modify aNewAddon in place; callers should save the database if
+ * necessary
+ *
  * @param  aOldAddon
  *         The previous instance of the add-on
  * @param  aNewAddon
  *         The new instance of the add-on
  * @param  aAppVersion
  *         The optional application version to use when checking the blocklist
  *         or undefined to use the current application
  * @param  aPlatformVersion
@@ -579,21 +593,18 @@ function isUsableAddon(aAddon) {
 
   return true;
 }
 
 function isAddonDisabled(aAddon) {
   return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled;
 }
 
-this.__defineGetter__("gRDF", function gRDFGetter() {
-  delete this.gRDF;
-  return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
-                     getService(Ci.nsIRDFService);
-});
+XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
+                                   Ci.nsIRDFService);
 
 function EM_R(aProperty) {
   return gRDF.GetResource(PREFIX_NS_EM + aProperty);
 }
 
 /**
  * Converts an RDF literal, resource or integer into a string.
  *
@@ -689,18 +700,21 @@ function loadManifestFromRDF(aUri, aStre
       }
     }
 
     PROP_LOCALE_SINGLE.forEach(function(aProp) {
       locale[aProp] = getRDFProperty(aDs, aSource, aProp);
     });
 
     PROP_LOCALE_MULTI.forEach(function(aProp) {
-      locale[aProp] = getPropertyArray(aDs, aSource,
-                                       aProp.substring(0, aProp.length - 1));
+      // Don't store empty arrays
+      let props = getPropertyArray(aDs, aSource,
+                                   aProp.substring(0, aProp.length - 1));
+      if (props.length > 0)
+        locale[aProp] = props;
     });
 
     return locale;
   }
 
   let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
                   createInstance(Ci.nsIRDFXMLParser)
   let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
@@ -1316,29 +1330,34 @@ function recursiveRemove(aFile) {
  * Returns the timestamp of the most recently modified file in a directory,
  * or simply the file's own timestamp if it is not a directory.
  *
  * @param  aFile
  *         A non-null nsIFile object
  * @return Epoch time, as described above. 0 for an empty directory.
  */
 function recursiveLastModifiedTime(aFile) {
-  if (aFile.isFile())
-    return aFile.lastModifiedTime;
-
-  if (aFile.isDirectory()) {
-    let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
-    let entry, time;
-    let maxTime = aFile.lastModifiedTime;
-    while ((entry = entries.nextFile)) {
-      time = recursiveLastModifiedTime(entry);
-      maxTime = Math.max(time, maxTime);
-    }
-    entries.close();
-    return maxTime;
+  try {
+    if (aFile.isFile())
+      return aFile.lastModifiedTime;
+
+    if (aFile.isDirectory()) {
+      let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+      let entry, time;
+      let maxTime = aFile.lastModifiedTime;
+      while ((entry = entries.nextFile)) {
+        time = recursiveLastModifiedTime(entry);
+        maxTime = Math.max(time, maxTime);
+      }
+      entries.close();
+      return maxTime;
+    }
+  }
+  catch (e) {
+    WARN("Problem getting last modified time for " + aFile.path, e);
   }
 
   // If the file is something else, just ignore it.
   return 0;
 }
 
 /**
  * Gets a snapshot of directory entries.
@@ -1743,17 +1762,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
@@ -1860,21 +1879,23 @@ var XPIProvider = {
 
     // This is needed to allow xpcshell tests to simulate a restart
     this.extensionsActive = false;
 
     // Remove URI mappings again
     delete this._uriMappings;
 
     if (gLazyObjectsLoaded) {
-      XPIDatabase.shutdown(function shutdownCallback() {
-        Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
+      XPIDatabase.shutdown(function shutdownCallback(saveError) {
+        LOG("Notifying XPI shutdown observers");
+        Services.obs.notifyObservers(null, "xpi-provider-shutdown", saveError);
       });
     }
     else {
+      LOG("Notifying XPI shutdown observers");
       Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
     }
   },
 
   /**
    * Applies any pending theme change to the preferences.
    */
   applyThemeChange: function XPI_applyThemeChange() {
@@ -1922,17 +1943,17 @@ var XPIProvider = {
     }
 
     // Ensure any changes to the add-ons list are flushed to disk
     XPIDatabase.writeAddonsList();
     Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
   },
 
   /**
-   * Persists changes to XPIProvider.bootstrappedAddons to it's store (a pref).
+   * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref).
    */
   persistBootstrappedAddons: function XPI_persistBootstrappedAddons() {
     Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
                                JSON.stringify(this.bootstrappedAddons));
   },
 
   /**
    * Adds a list of currently active add-ons to the next crash report.
@@ -2482,17 +2503,17 @@ var XPIProvider = {
       try {
         // If not load it
         if (!newAddon) {
           let file = aInstallLocation.getLocationForID(aOldAddon.id);
           newAddon = loadManifestFromFile(file);
           applyBlocklistChanges(aOldAddon, newAddon);
 
           // Carry over any pendingUninstall state to add-ons modified directly
-          // in the profile. This is impoprtant when the attempt to remove the
+          // in the profile. This is important when the attempt to remove the
           // add-on in processPendingFileChanges failed and caused an mtime
           // change to the add-ons files.
           newAddon.pendingUninstall = aOldAddon.pendingUninstall;
         }
 
         // The ID in the manifest that was loaded must match the ID of the old
         // add-on.
         if (newAddon.id != aOldAddon.id)
@@ -2513,41 +2534,43 @@ 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
-      XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor);
-      if (newAddon.visible) {
-        visibleAddons[newAddon.id] = newAddon;
+      let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
+                                                       aAddonState.descriptor);
+      if (newDBAddon.visible) {
+        visibleAddons[newDBAddon.id] = newDBAddon;
         // Remember add-ons that were changed during startup
         AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                             newAddon.id);
+                                             newDBAddon.id);
 
         // If this was the active theme and it is now disabled then enable the
         // default theme
-        if (aOldAddon.active && isAddonDisabled(newAddon))
+        if (aOldAddon.active && isAddonDisabled(newDBAddon))
           XPIProvider.enableDefaultTheme();
 
         // If the new add-on is bootstrapped and active then call its install method
-        if (newAddon.active && newAddon.bootstrap) {
+        if (newDBAddon.active && newDBAddon.bootstrap) {
           // Startup cache must be flushed before calling the bootstrap script
           flushStartupCache();
 
-          let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ?
+          let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
                               BOOTSTRAP_REASONS.ADDON_UPGRADE :
                               BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
 
           let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
           file.persistentDescriptor = aAddonState.descriptor;
-          XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file,
-                                          "install", installReason, { oldVersion: aOldAddon.version });
+          XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version,
+                                          newDBAddon.type, file, "install",
+                                          installReason, { oldVersion: aOldAddon.version });
           return false;
         }
 
         return true;
       }
 
       return false;
     }
@@ -2564,21 +2587,20 @@ 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);
+      XPIDatabase.saveChanges();
+
       if (aOldAddon.visible) {
         visibleAddons[aOldAddon.id] = aOldAddon;
 
         if (aOldAddon.bootstrap && aOldAddon.active) {
           let bootstrap = oldBootstrappedAddons[aOldAddon.id];
           bootstrap.descriptor = aAddonState.descriptor;
           XPIProvider.bootstrappedAddons[aOldAddon.id] = bootstrap;
         }
@@ -2625,78 +2647,65 @@ 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)) {
-              aOldAddon.active = true;
-              XPIDatabase.updateAddonActive(aOldAddon);
+              XPIDatabase.updateAddonActive(aOldAddon, true);
             }
             else {
               XPIProvider.unloadBootstrapScope(newAddon.id);
             }
           }
           else {
             // Otherwise a restart is necessary
             changed = true;
           }
         }
       }
 
       // App version changed, we may need to update the appDisabled property.
       if (aUpdateCompatibility) {
-        // Create a basic add-on object for the new state to save reproducing
-        // the applyBlocklistChanges code
-        let newAddon = new AddonInternal();
-        newAddon.id = aOldAddon.id;
-        newAddon.syncGUID = aOldAddon.syncGUID;
-        newAddon.version = aOldAddon.version;
-        newAddon.type = aOldAddon.type;
-        newAddon.appDisabled = !isUsableAddon(aOldAddon);
-
-        // Sync the userDisabled flag to the selectedSkin
-        if (aOldAddon.type == "theme")
-          newAddon.userDisabled = aOldAddon.internalName != XPIProvider.selectedSkin;
-
-        applyBlocklistChanges(aOldAddon, newAddon, aOldAppVersion,
+        let wasDisabled = isAddonDisabled(aOldAddon);
+        let wasAppDisabled = aOldAddon.appDisabled;
+        let wasUserDisabled = aOldAddon.userDisabled;
+        let wasSoftDisabled = aOldAddon.softDisabled;
+
+        // This updates the addon's JSON cached data in place
+        applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
                               aOldPlatformVersion);
-
-        let wasDisabled = isAddonDisabled(aOldAddon);
-        let isDisabled = isAddonDisabled(newAddon);
+        aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
+
+        let isDisabled = isAddonDisabled(aOldAddon);
 
         // If either property has changed update the database.
-        if (newAddon.appDisabled != aOldAddon.appDisabled ||
-            newAddon.userDisabled != aOldAddon.userDisabled ||
-            newAddon.softDisabled != aOldAddon.softDisabled) {
+        if (wasAppDisabled != aOldAddon.appDisabled ||
+            wasUserDisabled != aOldAddon.userDisabled ||
+            wasSoftDisabled != aOldAddon.softDisabled) {
           LOG("Add-on " + aOldAddon.id + " changed appDisabled state to " +
-              newAddon.appDisabled + ", userDisabled state to " +
-              newAddon.userDisabled + " and softDisabled state to " +
-              newAddon.softDisabled);
-          XPIDatabase.setAddonProperties(aOldAddon, {
-            appDisabled: newAddon.appDisabled,
-            userDisabled: newAddon.userDisabled,
-            softDisabled: newAddon.softDisabled
-          });
+              aOldAddon.appDisabled + ", userDisabled state to " +
+              aOldAddon.userDisabled + " and softDisabled state to " +
+              aOldAddon.softDisabled);
+          XPIDatabase.saveChanges();
         }
 
         // If this is a visible add-on and it has changed disabled state then we
         // may need a restart or to update the bootstrap list.
         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
-            aOldAddon.active = !isDisabled;
-            XPIDatabase.updateAddonActive(aOldAddon);
+            XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
           }
           else {
             changed = true;
           }
         }
       }
 
       if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) {
@@ -2708,27 +2717,25 @@ 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(aInstallLocation, aOldAddon) {
+    function removeMetadata(aOldAddon) {
       // This add-on has disappeared
-      LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation);
+      LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
       XPIDatabase.removeAddonMetadata(aOldAddon);
 
       // Remember add-ons that were uninstalled during startup
       if (aOldAddon.visible) {
         AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
                                              aOldAddon.id);
       }
       else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
@@ -2881,95 +2888,84 @@ var XPIProvider = {
           else
             newAddon.userDisabled = true;
         }
       }
       else {
         newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon))
       }
 
-      try {
-        // Update the database.
-        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 (newAddon.visible) {
+      let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
+
+      if (newDBAddon.visible) {
         // Remember add-ons that were first detected during startup.
         if (isDetectedInstall) {
           // If a copy from a higher priority location was removed then this
           // add-on has changed
           if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED)
-                          .indexOf(newAddon.id) != -1) {
+                          .indexOf(newDBAddon.id) != -1) {
             AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                                 newAddon.id);
+                                                 newDBAddon.id);
           }
           else {
             AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
-                                                 newAddon.id);
+                                                 newDBAddon.id);
           }
         }
 
         // Note if any visible add-on is not in the application install location
-        if (newAddon._installLocation.name != KEY_APP_GLOBAL)
+        if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
           XPIProvider.allAppGlobal = false;
 
-        visibleAddons[newAddon.id] = newAddon;
+        visibleAddons[newDBAddon.id] = newDBAddon;
 
         let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
         let extraParams = {};
 
         // If we're hiding a bootstrapped add-on then call its uninstall method
-        if (newAddon.id in oldBootstrappedAddons) {
-          let oldBootstrap = oldBootstrappedAddons[newAddon.id];
+        if (newDBAddon.id in oldBootstrappedAddons) {
+          let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
           extraParams.oldVersion = oldBootstrap.version;
-          XPIProvider.bootstrappedAddons[newAddon.id] = oldBootstrap;
+          XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
 
           // If the old version is the same as the new version, or we're
           // recovering from a corrupt DB, don't call uninstall and install
           // methods.
           if (sameVersion || !isNewInstall)
             return false;
 
-          installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ?
+          installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
                           BOOTSTRAP_REASONS.ADDON_UPGRADE :
                           BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
 
           let oldAddonFile = Cc["@mozilla.org/file/local;1"].
                              createInstance(Ci.nsIFile);
           oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
 
-          XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version,
+          XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version,
                                           oldBootstrap.type, oldAddonFile, "uninstall",
-                                          installReason, { newVersion: newAddon.version });
-          XPIProvider.unloadBootstrapScope(newAddon.id);
+                                          installReason, { newVersion: newDBAddon.version });
+          XPIProvider.unloadBootstrapScope(newDBAddon.id);
 
           // If the new add-on is bootstrapped then we must flush the caches
           // before calling the new bootstrap script
-          if (newAddon.bootstrap)
+          if (newDBAddon.bootstrap)
             flushStartupCache();
         }
 
-        if (!newAddon.bootstrap)
+        if (!newDBAddon.bootstrap)
           return true;
 
         // Visible bootstrapped add-ons need to have their install method called
         let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
         file.persistentDescriptor = aAddonState.descriptor;
-        XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file,
+        XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file,
                                         "install", installReason, extraParams);
-        if (!newAddon.active)
-          XPIProvider.unloadBootstrapScope(newAddon.id);
+        if (!newDBAddon.active)
+          XPIProvider.unloadBootstrapScope(newDBAddon.id);
       }
 
       return false;
     }
 
     let changed = false;
     let knownLocations = XPIDatabase.getInstallLocations();
 
@@ -2984,19 +2980,18 @@ 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.
-      let pos = knownLocations.indexOf(installLocation.name);
-      if (pos >= 0) {
-        knownLocations.splice(pos, 1);
+      if (knownLocations.has(installLocation.name)) {
+        knownLocations.delete(installLocation.name);
         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) {
@@ -3029,30 +3024,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(installLocation.name, aOldAddon) || changed;
+            changed = removeMetadata(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 = {};
@@ -3063,22 +3058,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.
-    knownLocations.forEach(function(aLocation) {
-      let addons = XPIDatabase.getAddonsInLocation(aLocation);
+    for (let location of knownLocations) {
+      let addons = XPIDatabase.getAddonsInLocation(location);
       addons.forEach(function(aOldAddon) {
-        changed = removeMetadata(aLocation, aOldAddon) || changed;
+        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
@@ -3187,18 +3182,16 @@ var XPIProvider = {
       // If the state has changed then we must update the database
       let cache = Prefs.getCharPref(PREF_INSTALL_CACHE, null);
       updateDatabase = cache != JSON.stringify(state);
     }
 
     // If the database doesn't exist and there are add-ons installed then we
     // must update the database however if there are no add-ons then there is
     // no need to update the database.
-    // Avoid using XPIDatabase.dbFileExists, as that code is lazy-loaded,
-    // and we want to avoid loading it until absolutely necessary.
     let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
     if (!dbFile.exists())
       updateDatabase = state.length > 0;
 
     if (!updateDatabase) {
       let bootstrapDescriptors = [this.bootstrappedAddons[b].descriptor
                                   for (b in this.bootstrappedAddons)];
 
@@ -3211,85 +3204,72 @@ var XPIProvider = {
       });
 
       if (bootstrapDescriptors.length > 0) {
         WARN("Bootstrap state is invalid (missing add-ons: " + bootstrapDescriptors.toSource() + ")");
         updateDatabase = true;
       }
     }
 
-    // Catch any errors during the main startup and rollback the database changes
-    let transationBegun = false;
+    // Catch and log any errors during the main startup
     try {
       let extensionListChanged = false;
       // If the database needs to be updated then open it and then update it
       // from the filesystem
       if (updateDatabase || hasPendingChanges) {
-        XPIDatabase.beginTransaction();
-        transationBegun = true;
-        XPIDatabase.openConnection(false, true);
-
+        XPIDatabase.syncLoadDB(false);
         try {
           extensionListChanged = this.processFileChanges(state, manifests,
                                                          aAppChanged,
                                                          aOldAppVersion,
                                                          aOldPlatformVersion);
         }
         catch (e) {
-          ERROR("Error processing file changes", e);
+          ERROR("Failed to process extension changes at startup", e);
         }
       }
       AddonManagerPrivate.recordSimpleMeasure("installedUnpacked", this.unpackedAddons);
 
       if (aAppChanged) {
         // When upgrading the app and using a custom skin make sure it is still
         // compatible otherwise switch back the default
         if (this.currentSkin != this.defaultSkin) {
-          if (!transationBegun) {
-            XPIDatabase.beginTransaction();
-            transationBegun = true;
-          }
           let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
           if (!oldSkin || isAddonDisabled(oldSkin))
             this.enableDefaultTheme();
         }
 
         // When upgrading remove the old extensions cache to force older
         // versions to rescan the entire list of extensions
-        let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
-        if (oldCache.exists())
-          oldCache.remove(true);
+        try {
+          let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
+          if (oldCache.exists())
+            oldCache.remove(true);
+        }
+        catch (e) {
+          WARN("Unable to remove old extension cache " + oldCache.path, e);
+        }
       }
 
       // If the application crashed before completing any pending operations then
       // we should perform them now.
       if (extensionListChanged || hasPendingChanges) {
         LOG("Updating database with changes to installed add-ons");
-        if (!transationBegun) {
-          XPIDatabase.beginTransaction();
-          transationBegun = true;
-        }
         XPIDatabase.updateActiveAddons();
-        XPIDatabase.commitTransaction();
         XPIDatabase.writeAddonsList();
         Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
         Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
                                    JSON.stringify(this.bootstrappedAddons));
         return true;
       }
 
       LOG("No changes found");
-      if (transationBegun)
-        XPIDatabase.commitTransaction();
     }
     catch (e) {
-      ERROR("Error during startup file checks, rolling back any database " +
-            "changes", e);
-      if (transationBegun)
-        XPIDatabase.rollbackTransaction();
+      ERROR("Error during startup file checks", e);
     }
 
     // Check that the add-ons list still exists
     let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
                                        true);
     if (addonsList.exists() == (state.length == 0)) {
       LOG("Add-ons list is invalid, rebuilding");
       XPIDatabase.writeAddonsList();
@@ -3460,17 +3440,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 instanceof DBAddonInternal))
+            !(aInstall.addon.inDatabase))
           results.push(createWrapper(aInstall.addon));
       });
       aCallback(results);
     });
   },
 
   /**
    * Called to get the current AddonInstalls, optionally limiting to a list of
@@ -3668,17 +3648,17 @@ var XPIProvider = {
   observe: function XPI_observe(aSubject, aTopic, aData) {
     switch (aData) {
     case PREF_EM_MIN_COMPAT_APP_VERSION:
     case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
       this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
                                                        null);
       this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
                                                             null);
-      this.updateAllAddonDisabledStates();
+      this.updateAddonAppDisabledStates();
       break;
     }
   },
 
   /**
    * Tests whether enabling an add-on will require a restart.
    *
    * @param  aAddon
@@ -3778,17 +3758,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 instanceof DBAddonInternal)
+    if (aAddon.inDatabase)
       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
@@ -3987,57 +3967,46 @@ var XPIProvider = {
       }
 
       LOG("Calling bootstrap method " + aMethod + " on " + aId + " version " +
           aVersion);
       try {
         this.bootstrapScopes[aId][aMethod](params, aReason);
       }
       catch (e) {
-        WARN("Exception running bootstrap method " + aMethod + " on " +
-             aId, e);
+        WARN("Exception running bootstrap method " + aMethod + " on " + aId, e);
       }
     }
     finally {
       if (aMethod == "shutdown") {
         LOG("Removing manifest for " + aFile.path);
         Components.manager.removeBootstrappedManifestLocation(aFile);
       }
     }
   },
 
   /**
-   * Updates the appDisabled property for all add-ons.
-   */
-  updateAllAddonDisabledStates: function XPI_updateAllAddonDisabledStates() {
-    let addons = XPIDatabase.getAddons();
-    addons.forEach(function(aAddon) {
-      this.updateAddonDisabledState(aAddon);
-    }, this);
-  },
-
-  /**
    * Updates the disabled state for an add-on. Its appDisabled property will be
-   * calculated and if the add-on is changed appropriate notifications will be
-   * sent out to the registered AddonListeners.
+   * calculated and if the add-on is changed the database will be saved and
+   * appropriate notifications will be sent out to the registered AddonListeners.
    *
    * @param  aAddon
    *         The DBAddonInternal to update
    * @param  aUserDisabled
    *         Value for the userDisabled property. If undefined the value will
    *         not change
    * @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 instanceof DBAddonInternal))
+    if (!(aAddon.inDatabase))
       throw new Error("Can only update addon states for installed addons.");
     if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
       throw new Error("Cannot change userDisabled and softDisabled at the " +
                       "same time");
     }
 
     if (aUserDisabled === undefined) {
       aUserDisabled = aAddon.userDisabled;
@@ -4100,18 +4069,17 @@ var XPIProvider = {
       }
       else {
         needsRestart = this.enableRequiresRestart(aAddon);
         AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
                                                needsRestart);
       }
 
       if (!needsRestart) {
-        aAddon.active = !isDisabled;
-        XPIDatabase.updateAddonActive(aAddon);
+        XPIDatabase.updateAddonActive(aAddon, !isDisabled);
         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);
@@ -4137,17 +4105,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 instanceof DBAddonInternal))
+    if (!(aAddon.inDatabase))
       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();
 
@@ -4179,18 +4147,17 @@ 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)) {
-        aAddon.active = true;
-        XPIDatabase.updateAddonActive(aAddon);
+        XPIDatabase.updateAddonActive(aAddon, true);
       }
 
       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) {
@@ -4250,17 +4217,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 instanceof DBAddonInternal))
+    if (!(aAddon.inDatabase))
       throw new Error("Can only cancel uninstall for installed addons.");
 
     cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]);
 
     XPIDatabase.setAddonProperties(aAddon, {
       pendingUninstall: false
     });
 
@@ -5239,17 +5206,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 compatibiltiy info
+        // Cache the AddonInternal as it may have updated compatibility info
         let stagedJSON = stagedAddon.clone();
         stagedJSON.leafName = this.addon.id + ".json";
         if (stagedJSON.exists())
           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);
@@ -5308,78 +5275,73 @@ AddonInstall.prototype = {
                                             this.existingAddon.type, file,
                                             "uninstall", reason,
                                             { newVersion: this.addon.version });
             XPIProvider.unloadBootstrapScope(this.existingAddon.id);
             flushStartupCache();
           }
 
           if (!isUpgrade && this.existingAddon.active) {
-            this.existingAddon.active = false;
-            XPIDatabase.updateAddonActive(this.existingAddon);
+            XPIDatabase.updateAddonActive(this.existingAddon, false);
           }
         }
 
         // 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.updateDate = recursiveLastModifiedTime(file); // XXX sync recursive scan
         this.addon.visible = true;
         if (isUpgrade) {
-          XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
-                                          file.persistentDescriptor);
+          this.addon =  XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
+                                                        file.persistentDescriptor);
         }
         else {
           this.addon.installDate = this.addon.updateDate;
           this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon))
-          XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
+          this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
+        }
+
+        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);
         }
 
-        // 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",
+        AddonManagerPrivate.callAddonListeners("onInstalled",
+                                               createWrapper(this.addon));
+
+        LOG("Install of " + this.sourceURI.spec + " completed.");
+        this.state = AddonManager.STATE_INSTALLED;
+        AddonManagerPrivate.callInstallListeners("onInstallEnded",
+                                                 this.listeners, this.wrapper,
+                                                 createWrapper(this.addon));
+
+        if (this.addon.bootstrap) {
+          if (this.addon.active) {
+            XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
+                                            this.addon.type, file, "startup",
                                             reason, extraParams);
           }
-
-          AddonManagerPrivate.callAddonListeners("onInstalled",
-                                                 createWrapper(self.addon));
-
-          LOG("Install of " + self.sourceURI.spec + " completed.");
-          self.state = AddonManager.STATE_INSTALLED;
-          AddonManagerPrivate.callInstallListeners("onInstallEnded",
-                                                   self.listeners, self.wrapper,
-                                                   createWrapper(self.addon));
-
-          if (self.addon.bootstrap) {
-            if (self.addon.active) {
-              XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
-                                              self.addon.type, file, "startup",
-                                              reason, extraParams);
-            }
-            else {
-              XPIProvider.unloadBootstrapScope(self.addon.id);
-            }
+          else {
+            // 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())
         recursiveRemove(stagedAddon);
       this.state = AddonManager.STATE_INSTALL_FAILED;
       this.error = AddonManager.ERROR_FILE_ACCESS;
@@ -5744,40 +5706,33 @@ 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 below) or an install
- * manifest.
+ * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm)
+ * or an install manifest.
  */
 function AddonInternal() {
 }
 
 AddonInternal.prototype = {
   _selectedLocale: null,
   active: false,
   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,103 +5917,29 @@ 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) {
-    ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
-     "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
-     "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]
-    .forEach(function(aProp) {
+    PENDING_INSTALL_METADATA.forEach(function(aProp) {
       if (!(aProp in aObj))
         return;
 
       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)
@@ -6299,17 +6180,17 @@ function AddonWrapper(aAddon) {
 
     return val;
   });
 
   this.__defineSetter__("syncGUID", function AddonWrapper_syncGUIDGetter(val) {
     if (aAddon.syncGUID == val)
       return val;
 
-    if (aAddon instanceof DBAddonInternal)
+    if (aAddon.inDatabase)
       XPIDatabase.setAddonSyncGUID(aAddon, val);
 
     aAddon.syncGUID = val;
 
     return val;
   });
 
   this.__defineGetter__("install", function AddonWrapper_installGetter() {
@@ -6326,17 +6207,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 instanceof DBAddonInternal)) {
+    if (!(aAddon.inDatabase)) {
       // Add-on is pending install if there is no associated install (shouldn't
       // happen here) or if the install is in the process of or has successfully
       // completed the install. If an add-on is pending install then we ignore
       // any other pending operations.
       if (!aAddon._install || aAddon._install.state == AddonManager.STATE_INSTALLING ||
           aAddon._install.state == AddonManager.STATE_INSTALLED)
         return AddonManager.PENDING_INSTALL;
     }
@@ -6370,17 +6251,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 instanceof DBAddonInternal))
+    if (!(aAddon.inDatabase))
       return permissions;
 
     if (!aAddon.appDisabled) {
       if (this.userDisabled)
         permissions |= AddonManager.PERM_CAN_ENABLE;
       else if (aAddon.type != "theme")
         permissions |= AddonManager.PERM_CAN_DISABLE;
     }
@@ -6405,17 +6286,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 instanceof DBAddonInternal) {
+    if (aAddon.inDatabase) {
       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);
       }
@@ -6429,17 +6310,17 @@ function AddonWrapper(aAddon) {
 
     return val;
   });
 
   this.__defineSetter__("softDisabled", function AddonWrapper_softDisabledSetter(val) {
     if (val == aAddon.softDisabled)
       return val;
 
-    if (aAddon instanceof DBAddonInternal) {
+    if (aAddon.inDatabase) {
       // When softDisabling a theme just enable the active theme
       if (aAddon.type == "theme" && val && !aAddon.userDisabled) {
         if (aAddon.internalName == XPIProvider.defaultSkin)
           throw new Error("Cannot disable the default theme");
         XPIProvider.enableDefaultTheme();
       }
       else {
         XPIProvider.updateAddonDisabledState(aAddon, undefined, val);
@@ -6454,25 +6335,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 instanceof DBAddonInternal))
+    if (!(aAddon.inDatabase))
       throw new Error("Cannot uninstall an add-on that isn't installed");
     if (aAddon.pendingUninstall)
       throw new Error("Add-on is already marked to be uninstalled");
     XPIProvider.uninstallAddon(aAddon);
   };
 
   this.cancelUninstall = function AddonWrapper_cancelUninstall() {
-    if (!(aAddon instanceof DBAddonInternal))
+    if (!(aAddon.inDatabase))
       throw new Error("Cannot cancel uninstall for an add-on that isn't installed");
     if (!aAddon.pendingUninstall)
       throw new Error("Add-on is not marked to be uninstalled");
     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
@@ -2,44 +2,56 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
+const Cu = Components.utils;
 
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/AddonRepository.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
-
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
+                                  "resource://gre/modules/DeferredSave.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
 
 ["LOG", "WARN", "ERROR"].forEach(function(aName) {
-  this.__defineGetter__(aName, function logFuncGetter () {
-    Components.utils.import("resource://gre/modules/AddonLogging.jsm");
+  Object.defineProperty(this, aName, {
+    get: function logFuncGetter () {
+      Cu.import("resource://gre/modules/AddonLogging.jsm");
 
-    LogManager.getLogger("addons.xpi-utils", this);
-    return this[aName];
-  })
+      LogManager.getLogger("addons.xpi-utils", this);
+      return this[aName];
+    },
+    configurable: true
+  });
 }, this);
 
 
 const KEY_PROFILEDIR                  = "ProfD";
 const FILE_DATABASE                   = "extensions.sqlite";
+const FILE_JSON_DB                    = "extensions.json";
 const FILE_OLD_DATABASE               = "extensions.rdf";
 const FILE_XPI_ADDONS_LIST            = "extensions.ini";
 
 // The value for this is in Makefile.in
 #expand const DB_SCHEMA                       = __MOZ_EXTENSIONS_DB_SCHEMA__;
 
+// The last version of DB_SCHEMA implemented in SQLITE
+const LAST_SQLITE_DB_SCHEMA           = 14;
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
 const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
 const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
 
 
 // Properties that only exist in the database
 const DB_METADATA        = ["syncGUID",
@@ -67,27 +79,37 @@ 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"];
 
+// Time to wait before async save of XPI JSON database, in milliseconds
+const ASYNC_SAVE_DELAY_MS = 20;
 
 const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
 const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
 const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
 
-this.__defineGetter__("gRDF", function gRDFGetter() {
-  delete this.gRDF;
-  return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
-                     getService(Ci.nsIRDFService);
-});
+XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
+                                   Ci.nsIRDFService);
 
 function EM_R(aProperty) {
   return gRDF.GetResource(PREFIX_NS_EM + aProperty);
 }
 
 /**
  * Converts an RDF literal, resource or integer into a string.
  *
@@ -115,70 +137,91 @@ 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));
 }
 
+/**
+ * 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 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
+ * Wrap an API-supplied function in an exception handler to make it safe to call
  */
-function AsyncAddonListCallback(aCallback) {
-  this.callback = aCallback;
-  this.addons = [];
+function safeCallback(aCallback) {
+  return function(...aArgs) {
+    try {
+      aCallback.apply(null, aArgs);
+    }
+    catch(ex) {
+      WARN("XPI Database callback failed", ex);
+    }
+  }
 }
 
-AsyncAddonListCallback.prototype = {
-  callback: null,
-  complete: false,
-  count: 0,
-  addons: null,
+/**
+ * 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;
+  }
 
-  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);
-        }
+  function asyncMap_gotValue(aIndex, aValue) {
+    results[aIndex] = aValue;
+    if (--resultsPending == 0) {
+      aCallback(results);
+    }
+  }
 
-        if ("getCachedAddonByID" in AddonRepository)
-          AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
-        else
-          completeAddon(null);
+  aObjects.map(function asyncMap_each(aObject, aIndex, aArray) {
+    try {
+      aMethod(aObject, function asyncMap_callback(aResult) {
+        asyncMap_gotValue(aIndex, aResult);
       });
     }
-  },
-
-  handleError: asyncErrorLogger,
-
-  handleCompletion: function AsyncAddonListCallback_handleCompletion(aReason) {
-    this.complete = true;
-    if (this.addons.length == this.count)
-      this.callback(this.addons);
-  }
-};
-
+    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) {
@@ -210,34 +253,16 @@ 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 {
@@ -288,355 +313,463 @@ function copyRowProperties(aRow, aProper
   if (!aTarget)
     aTarget = {};
   aProperties.forEach(function(aProp) {
     aTarget[aProp] = aRow.getResultByName(aProp);
   });
   return aTarget;
 }
 
+/**
+ * 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.
+  }
+
+  XPCOMUtils.defineLazyGetter(this, "pendingUpgrade",
+    function DBA_pendingUpgradeGetter() {
+      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;
+    });
+}
+
+DBAddonInternal.prototype = {
+  applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
+    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;
+          XPIDatabase.saveChanges();
+        }
+      });
+    });
+    XPIProvider.updateAddonDisabledState(this);
+  },
+
+  get inDatabase() {
+    return true;
+  },
+
+  toJSON: function() {
+    return copyProperties(this, PROP_JSON_FIELDS);
+  }
+}
+
+DBAddonInternal.prototype.__proto__ = AddonInternal.prototype;
+
+/**
+ * Internal interface: find an addon from an already loaded addonDB
+ */
+function _findAddon(addonDB, aFilter) {
+  for (let [, addon] of addonDB) {
+    if (aFilter(addon)) {
+      return addon;
+    }
+  }
+  return null;
+}
+
+/**
+ * Internal interface to get a filtered list of addons from a loaded addonDB
+ */
+function _filterDB(addonDB, aFilter) {
+  let addonList = [];
+  for (let [, addon] of addonDB) {
+    if (aFilter(addon)) {
+      addonList.push(addon);
+    }
+  }
+
+  return addonList;
+}
+
 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 " +
-                       "FROM locale WHERE id=:id",
-    _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " +
-                 "locale.description, locale.creator, locale.homepageURL " +
-                 "FROM addon_locale JOIN locale ON " +
-                 "addon_locale.locale_id=locale.id WHERE " +
-                 "addon_internal_id=:internal_id",
-    _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " +
-                            "maxVersion FROM targetApplication WHERE " +
-                            "addon_internal_id=:internal_id",
-    _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " +
-                         "addon_internal_id=:internal_id",
-    _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
-                        "WHERE locale_id=:id",
-
-    addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " +
-                            ":location, :version, :type, :internalName, " +
-                            ":updateURL, :updateKey, :optionsURL, " +
-                            ":optionsType, :aboutURL, " +
-                            ":iconURL, :icon64URL, :locale, :visible, :active, " +
-                            ":userDisabled, :appDisabled, :pendingUninstall, " +
-                            ":descriptor, :installDate, :updateDate, " +
-                            ":applyBackgroundUpdates, :bootstrap, :skinnable, " +
-                            ":size, :sourceURI, :releaseNotesURI, :softDisabled, " +
-                            ":isForeignInstall, :hasBinaryComponents, " +
-                            ":strictCompatibility)",
-    addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
-                                   "(:internal_id, :name, :locale)",
-    addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
-                             "homepageURL) VALUES (:name, :description, " +
-                             ":creator, :homepageURL)",
-    addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
-                              ":type, :value)",
-    addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
-                                        "(:internal_id, :id, :minVersion, " +
-                                        ":maxVersion)",
-    addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " +
-                                     "(:internal_id, :os, :abi)",
-
-    clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id",
-    updateAddonActive: "UPDATE addon SET active=:active WHERE " +
-                       "internal_id=:internal_id",
-
-    getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " +
-                     "type<>'theme' AND bootstrap=0",
-    getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
-                    "internalName=:internalName AND type='theme'",
-    getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'",
+  // Saved error object if we fail to read an existing database
+  _loadError: null,
 
-    getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " +
-                        "AND location=:location",
-    getAddons: "SELECT " + FIELDS_ADDON + " FROM addon",
-    getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type",
-    getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
-                         "location=:location",
-    getInstallLocations: "SELECT DISTINCT location FROM addon",
-    getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " +
-                          "visible=1 AND id=:id",
-    getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " +
-                                    "WHERE visible=1 AND internalName=:internalName",
-    getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1",
-    getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " +
-                                           "addon WHERE visible=1 " +
-                                           "AND (pendingUninstall=1 OR " +
-                                           "MAX(userDisabled,appDisabled)=active)",
-    getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " +
-                        "WHERE syncGUID=:syncGUID",
-    makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
-    removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
-    // Equates to active = visible && !userDisabled && !softDisabled &&
-    //                     !appDisabled && !pendingUninstall
-    setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
-                     "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)",
-    setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
-                        "appDisabled=:appDisabled, " +
-                        "softDisabled=:softDisabled, " +
-                        "pendingUninstall=:pendingUninstall, " +
-                        "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
-                        "internal_id=:internal_id",
-    setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " +
-                        "internal_id=:internal_id",
-    setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " +
-                      "internal_id=:internal_id",
-    updateTargetApplications: "UPDATE targetApplication SET " +
-                              "minVersion=:minVersion, maxVersion=:maxVersion " +
-                              "WHERE addon_internal_id=:internal_id AND id=:id",
-
-    createSavepoint: "SAVEPOINT 'default'",
-    releaseSavepoint: "RELEASE SAVEPOINT 'default'",
-    rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'"
-  },
-
-  get dbfileExists() {
-    delete this.dbfileExists;
-    return this.dbfileExists = this.dbfile.exists();
-  },
-  set dbfileExists(aValue) {
-    delete this.dbfileExists;
-    return this.dbfileExists = aValue;
+  // Error reported by our most recent attempt to read or write the database, if any
+  get lastError() {
+    if (this._loadError)
+      return this._loadError;
+    if (this._deferredSave)
+      return this._deferredSave.lastError;
+    return null;
   },
 
   /**
-   * 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.
+   * Mark the current stored data dirty, and schedule a flush to disk
    */
-  beginTransaction: function XPIDB_beginTransaction() {
-    if (this.initialized)
-      this.getStatement("createSavepoint").execute();
-    this.transactionCount++;
+  saveChanges: function() {
+    if (!this.initialized) {
+      throw new Error("Attempt to use XPI database when it is not initialized");
+    }
+
+    if (!this._deferredSave) {
+      this._deferredSave = new DeferredSave(this.jsonFile.path,
+                                            () => JSON.stringify(this),
+                                            ASYNC_SAVE_DELAY_MS);
+    }
+
+    let promise = this._deferredSave.saveChanges();
+    if (!this._schemaVersionSet) {
+      this._schemaVersionSet = true;
+      promise.then(
+        count => {
+          // Update the XPIDB schema version preference the first time we successfully
+          // save the database.
+          LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
+          Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
+          // Reading the DB worked once, so we don't need the load error
+          this._loadError = null;
+        },
+        error => {
+          // Need to try setting the schema version again later
+          this._schemaVersionSet = false;
+          WARN("Failed to save XPI database", error);
+          // this._deferredSave.lastError has the most recent error so we don't
+          // need this any more
+          this._loadError = null;
+        });
+    }
+  },
+
+  flush: function() {
+    // handle the "in memory only" and "saveChanges never called" cases
+    if (!this._deferredSave) {
+      return Promise.resolve(0);
+    }
+
+    return this._deferredSave.flush();
   },
 
   /**
-   * Commits the most recent transaction. The data may still be rolled back if
-   * an outer transaction is rolled back.
+   * Converts the current internal state of the XPI addon database to
+   * a JSON.stringify()-ready structure
    */
-  commitTransaction: function XPIDB_commitTransaction() {
-    if (this.transactionCount == 0) {
-      ERROR("Attempt to commit one transaction too many.");
-      return;
+  toJSON: function() {
+    if (!this.addonDB) {
+      // We never loaded the database?
+      throw new Error("Attempt to save database without loading it first");
     }
 
-    if (this.initialized)
-      this.getStatement("releaseSavepoint").execute();
-    this.transactionCount--;
+    let toSave = {
+      schemaVersion: DB_SCHEMA,
+      addons: [...this.addonDB.values()]
+    };
+    return toSave;
   },
 
   /**
-   * Rolls back the most recent transaction. The database will return to its
-   * state when the transaction was started.
+   * Pull upgrade information from an existing SQLITE database
+   *
+   * @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
    */
-  rollbackTransaction: function XPIDB_rollbackTransaction() {
-    if (this.transactionCount == 0) {
-      ERROR("Attempt to rollback one transaction too many.");
-      return;
+  getMigrateDataFromSQLITE: function XPIDB_getMigrateDataFromSQLITE() {
+    let connection = null;
+    let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
+    // Attempt to open the database
+    try {
+      connection = Services.storage.openUnsharedDatabase(dbfile);
     }
-
-    if (this.initialized) {
-      this.getStatement("rollbackSavepoint").execute();
-      this.getStatement("releaseSavepoint").execute();
+    catch (e) {
+      WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e);
+      return null;
     }
-    this.transactionCount--;
+    LOG("Migrating data from sqlite");
+    let migrateData = this.getMigrateDataFromDatabase(connection);
+    connection.close();
+    return migrateData;
   },
 
   /**
-   * 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.
+   * Synchronously opens and reads the database file, upgrading from old
+   * databases or making a new DB if needed.
    *
-   * @param  aDBFile
-   *         The nsIFile to open
-   * @return the mozIStorageConnection for the database
-   */
-  openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) {
-    LOG("Opening database");
-    let connection = null;
-
-    // Attempt to open the database
-    try {
-      connection = Services.storage.openUnsharedDatabase(aDBFile);
-      this.dbfileExists = true;
-    }
-    catch (e) {
-      ERROR("Failed to open database (1st attempt)", e);
-      // 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");
-      }
-    }
-
-    connection.executeSimpleSQL("PRAGMA synchronous = FULL");
-    connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
-
-    return connection;
-  },
-
-  /**
-   * 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.
-   * @return the migration data from the database if it was an old schema or
-   *         null otherwise.
+   *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
    */
-  openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) {
-    delete this.connection;
-
-    if (!aForceOpen && !this.dbfileExists) {
-      this.connection = null;
-      return {};
-    }
-
-    this.initialized = true;
+  syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) {
+    // XXX TELEMETRY report synchronous opens (startup time) vs. delayed opens
     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);
-
-          // Reopen an empty database
-          this.connection = this.openDatabaseFile(this.dbfile);
+    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);
         }
-        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");
-        }
+        this.parseDB(data, aRebuildOnError);
+      }
+      catch(e) {
+        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) {
+        this.upgradeDB(aRebuildOnError);
       }
       else {
-        let dbSchema = 0;
-        try {
-          dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA);
-        } catch (e) {}
-
-        if (dbSchema == 0) {
-          // Only migrate data from the RDF if we haven't done it before
-          this.migrateData = this.getMigrateDataFromRDF();
-        }
-      }
-
-      // At this point the database should be completely empty
-      try {
-        this.createSchema();
-      }
-      catch (e) {
-        // 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();
-        }
+        this.rebuildUnreadableDB(e, aRebuildOnError);
       }
     }
+    finally {
+      if (fstream)
+        fstream.close();
+    }
+    // If an async load was also in progress, resolve that promise with our DB;
+    // otherwise create a resolved promise
+    if (this._dbPromise)
+      this._dbPromise.resolve(this.addonDB);
+    else
+      this._dbPromise = Promise.resolve(this.addonDB);
+  },
 
-    // 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);
+  /**
+   * Parse loaded data, reconstructing the database if the loaded data is not valid
+   * @param aRebuildOnError
+   *        If true, synchronously reconstruct the database from installed add-ons
+   */
+  parseDB: function(aData, aRebuildOnError) {
+    try {
+      // dump("Loaded JSON:\n" + aData + "\n");
+      let inputAddons = JSON.parse(aData);
+      // 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);
+        return;
+      }
+      if (inputAddons.schemaVersion != DB_SCHEMA) {
+        // Handle mismatched JSON schema version. For now, we assume
+        // compatibility for JSON data, though we throw away any fields we
+        // don't know about
+        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();
+      for (let loadedAddon of inputAddons.addons) {
+        let newAddon = new DBAddonInternal(loadedAddon);
+        addonDB.set(newAddon._key, newAddon);
+      };
+      this.addonDB = addonDB;
+      LOG("Successfully read XPI database");
+      this.initialized = true;
     }
-
-    // Begin any pending transactions
-    for (let i = 0; i < this.transactionCount; i++)
-      this.connection.executeSimpleSQL("SAVEPOINT 'default'");
+    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);
+    }
   },
 
   /**
-   * A lazy getter for the database connection.
+   * Upgrade database from earlier (sqlite or RDF) version if available
+   */
+  upgradeDB: function(aRebuildOnError) {
+    try {
+      let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA);
+      if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) {
+        // we should have an older SQLITE database
+        this.migrateData = this.getMigrateDataFromSQLITE();
+      }
+      // else we've upgraded before but the JSON file is gone, fall through
+      // and rebuild from scratch
+    }
+    catch(e) {
+      // No schema version pref means either a really old upgrade (RDF) or
+      // a new profile
+      this.migrateData = this.getMigrateDataFromRDF();
+    }
+
+    this.rebuildDatabase(aRebuildOnError);
+  },
+
+  /**
+   * Reconstruct when the DB file exists but is unreadable
+   * (for example because read permission is denied)
+   */
+  rebuildUnreadableDB: function(aError, aRebuildOnError) {
+    WARN("Extensions database " + this.jsonFile.path +
+        " exists but is not readable; rebuilding in memory", aError);
+    // Remember the error message until we try and write at least once, so
+    // we know at shutdown time that there was a problem
+    this._loadError = aError;
+    // XXX TELEMETRY report when this happens?
+    this.rebuildDatabase(aRebuildOnError);
+  },
+
+  /**
+   * Open and read the XPI database asynchronously, upgrading if
+   * necessary. If any DB load operation fails, we need to
+   * synchronously rebuild the DB from the installed extensions.
+   *
+   * @return Promise<Map> resolves to the Map of loaded JSON data stored
+   *         in this.addonDB; never rejects.
    */
-  get connection() {
-    this.openConnection(true);
-    return this.connection;
+  asyncLoadDB: function XPIDB_asyncLoadDB(aDBCallback) {
+    // Already started (and possibly finished) loading
+    if (this._dbPromise) {
+      return this._dbPromise;
+    }
+
+    LOG("Starting async load of XPI database " + this.jsonFile.path);
+    return this._dbPromise = OS.File.read(this.jsonFile.path).then(
+      byteArray => {
+        if (this._addonDB) {
+          LOG("Synchronous load completed while waiting for async load");
+          return this.addonDB;
+        }
+        LOG("Finished async read of XPI database, parsing...");
+        let decoder = new TextDecoder();
+        let data = decoder.decode(byteArray);
+        this.parseDB(data, true);
+        return this.addonDB;
+      })
+    .then(null,
+      error => {
+        if (this._addonDB) {
+          LOG("Synchronous load completed while waiting for async load");
+          return this.addonDB;
+        }
+        if (error.becauseNoSuchFile) {
+          this.upgradeDB(true);
+        }
+        else {
+          // it's there but unreadable
+          this.rebuildUnreadableDB(error, true);
+        }
+        return this.addonDB;
+      });
+  },
+
+  /**
+   * 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) {
+    this.addonDB = new Map();
+    this.initialized = true;
+
+    // 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.");
+      try {
+        let state = XPIProvider.getInstallLocationStates();
+        XPIProvider.processFileChanges(state, {}, false);
+      }
+      catch (e) {
+        ERROR("Failed to rebuild XPI database from installed extensions", e);
+      }
+      // Make to update the active add-ons and add-ons list on shutdown
+      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+    }
   },
 
   /**
    * Gets the list of file descriptors of active extension directories or XPI
    * files from the add-ons list. This must be loaded from disk since the
    * directory service gives no easy way to get both directly. This list doesn't
    * include themes as preferences already say which theme is currently active
    *
-   * @return an array of persisitent descriptors for the directories
+   * @return an array of persistent descriptors for the directories
    */
   getActiveBundles: function XPIDB_getActiveBundles() {
     let bundles = [];
 
     let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
                                        true);
 
     if (!addonsList.exists())
@@ -736,23 +869,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() {
+  getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase(aConnection) {
     let migrateData = {};
 
     // Attempt to migrate data from a different (even future!) version of the
     // database
     try {
-      var stmt = this.connection.createStatement("PRAGMA table_info(addon)");
+      var stmt = aConnection.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) {
@@ -768,34 +901,36 @@ this.XPIDatabase = {
       }
 
       if (reqCount < REQUIRED.length) {
         ERROR("Unable to read anything useful from the database");
         return null;
       }
       stmt.finalize();
 
-      stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon");
+      stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon");
       for (let row in resultRows(stmt)) {
         if (!(row.location in migrateData))
           migrateData[row.location] = {};
         let addonData = {
           targetApplications: []
         }
         migrateData[row.location][row.id] = addonData;
 
         props.forEach(function(aProp) {
+          if (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, " +
+      var taStmt = aConnection.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;
@@ -825,1078 +960,469 @@ 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
-      if (!this.connection.databaseFile)
-        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+      // If our last database I/O had an error, try one last time to save.
+      if (this.lastError)
+        this.saveChanges();
 
       this.initialized = false;
-      let connection = this.connection;
-      delete this.connection;
 
-      // Re-create the connection smart getter to allow the database to be
-      // re-loaded during testing.
-      this.__defineGetter__("connection", function connectionGetter() {
-        this.openConnection(true);
-        return this.connection;
-      });
+      // Make sure any pending writes of the DB are complete, and we
+      // finish cleaning up, and then call back
+      this.flush()
+        .then(null, error => {
+          ERROR("Flush of XPI database failed", error);
+          return 0;
+        })
+        .then(count => {
+          // If our last attempt to read or write the DB failed, force a new
+          // extensions.ini to be written to disk on the next startup
+          let lastSaveFailed = this.lastError;
+          if (lastSaveFailed)
+            Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
 
-      connection.asyncClose(function shutdown_asyncClose() {
-        LOG("Database closed");
-        aCallback();
-      });
+          // Clear out the cached addons data loaded from JSON
+          delete this.addonDB;
+          delete this._dbPromise;
+          // same for the deferred save
+          delete this._deferredSave;
+          // re-enable the schema version setter
+          delete this._schemaVersionSet;
+
+          if (aCallback)
+            aCallback(lastSaveFailed);
+        });
     }
     else {
       if (aCallback)
-        aCallback();
-    }
-  },
-
-  /**
-   * Gets a cached statement or creates a new statement if it doesn't already
-   * exist.
-   *
-   * @param  key
-   *         A unique key to reference the statement
-   * @param  aSql
-   *         An optional SQL string to use for the query, otherwise a
-   *         predefined sql string for the key will be used.
-   * @return a mozIStorageStatement for the passed SQL
-   */
-  getStatement: function XPIDB_getStatement(aKey, aSql) {
-    if (aKey in this.statementCache)
-      return this.statementCache[aKey];
-    if (!aSql)
-      aSql = this.statements[aKey];
-
-    try {
-      return this.statementCache[aKey] = this.connection.createStatement(aSql);
-    }
-    catch (e) {
-      ERROR("Error creating statement " + aKey + " (" + aSql + ")");
-      throw e;
-    }
-  },
-
-  /**
-   * Creates the schema in the database.
-   */
-  createSchema: function XPIDB_createSchema() {
-    LOG("Creating database schema");
-    this.beginTransaction();
-
-    // Any errors in here should rollback the transaction
-    try {
-      this.connection.createTable("addon",
-                                  "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                                  "id TEXT, syncGUID TEXT, " +
-                                  "location TEXT, version TEXT, " +
-                                  "type TEXT, internalName TEXT, updateURL TEXT, " +
-                                  "updateKey TEXT, optionsURL TEXT, " +
-                                  "optionsType TEXT, aboutURL TEXT, iconURL TEXT, " +
-                                  "icon64URL TEXT, defaultLocale INTEGER, " +
-                                  "visible INTEGER, active INTEGER, " +
-                                  "userDisabled INTEGER, appDisabled INTEGER, " +
-                                  "pendingUninstall INTEGER, descriptor TEXT, " +
-                                  "installDate INTEGER, updateDate INTEGER, " +
-                                  "applyBackgroundUpdates INTEGER, " +
-                                  "bootstrap INTEGER, skinnable INTEGER, " +
-                                  "size INTEGER, sourceURI TEXT, " +
-                                  "releaseNotesURI TEXT, softDisabled INTEGER, " +
-                                  "isForeignInstall INTEGER, " +
-                                  "hasBinaryComponents INTEGER, " +
-                                  "strictCompatibility INTEGER, " +
-                                  "UNIQUE (id, location), " +
-                                  "UNIQUE (syncGUID)");
-      this.connection.createTable("targetApplication",
-                                  "addon_internal_id INTEGER, " +
-                                  "id TEXT, minVersion TEXT, maxVersion TEXT, " +
-                                  "UNIQUE (addon_internal_id, id)");
-      this.connection.createTable("targetPlatform",
-                                  "addon_internal_id INTEGER, " +
-                                  "os, abi TEXT, " +
-                                  "UNIQUE (addon_internal_id, os, abi)");
-      this.connection.createTable("addon_locale",
-                                  "addon_internal_id INTEGER, "+
-                                  "locale TEXT, locale_id INTEGER, " +
-                                  "UNIQUE (addon_internal_id, locale)");
-      this.connection.createTable("locale",
-                                  "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                                  "name TEXT, description TEXT, creator TEXT, " +
-                                  "homepageURL TEXT");
-      this.connection.createTable("locale_strings",
-                                  "locale_id INTEGER, type TEXT, value TEXT");
-      this.connection.executeSimpleSQL("CREATE INDEX locale_strings_idx ON " +
-        "locale_strings (locale_id)");
-      this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
-        "ON addon BEGIN " +
-        "DELETE FROM targetApplication WHERE addon_internal_id=old.internal_id; " +
-        "DELETE FROM targetPlatform WHERE addon_internal_id=old.internal_id; " +
-        "DELETE FROM addon_locale WHERE addon_internal_id=old.internal_id; " +
-        "DELETE FROM locale WHERE id=old.defaultLocale; " +
-        "END");
-      this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon_locale AFTER " +
-        "DELETE ON addon_locale WHEN NOT EXISTS " +
-        "(SELECT * FROM addon_locale WHERE locale_id=old.locale_id) BEGIN " +
-        "DELETE FROM locale WHERE id=old.locale_id; " +
-        "END");
-      this.connection.executeSimpleSQL("CREATE TRIGGER delete_locale AFTER " +
-        "DELETE ON locale BEGIN " +
-        "DELETE FROM locale_strings WHERE locale_id=old.id; " +
-        "END");
-      this.connection.schemaVersion = DB_SCHEMA;
-      this.commitTransaction();
-    }
-    catch (e) {
-      ERROR("Failed to create database schema", e);
-      logSQLError(this.connection.lastError, this.connection.lastErrorString);
-      this.rollbackTransaction();
-      this.connection.close();
-      this.connection = null;
-      throw e;
-    }
-  },
-
-  /**
-   * Synchronously reads the multi-value locale strings for a locale
-   *
-   * @param  aLocale
-   *         The locale object to read into
-   */
-  _readLocaleStrings: function XPIDB__readLocaleStrings(aLocale) {
-    let stmt = this.getStatement("_readLocaleStrings");
-
-    stmt.params.id = aLocale.id;
-    for (let row in resultRows(stmt)) {
-      if (!(row.type in aLocale))
-        aLocale[row.type] = [];
-      aLocale[row.type].push(row.value);
+        aCallback(null);
     }
   },
 
   /**
-   * Synchronously reads the locales for an add-on
+   * 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. Only called from XPIProvider.processFileChanges, when
+   * the database should already be loaded.
    *
-   * @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
+   * @return  a Set of names of install locations
    */
-  _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))];
-  },
+  getInstallLocations: function XPIDB_getInstallLocations() {
+    let locations = new Set();
+    if (!this.addonDB)
+      return locations;
 
-  /**
-   * 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;
+    for (let [, addon] of this.addonDB) {
+      locations.add(addon.location);
     }
-
-    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;
+    return locations;
   },
 
   /**
-   * Asynchronously fetches additional metadata for a DBAddonInternal.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal
+   * Asynchronously 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
    * @param  aCallback
-   *         The callback to call when the metadata is completely retrieved
+   *         Called back with an array of addons matching aFilter
+   *         or an empty array if none match
    */
-  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();
+  getAddonList: function(aFilter, aCallback) {
+    this.asyncLoadDB().then(
+      addonDB => {
+        let addonList = _filterDB(addonDB, aFilter);
+        asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback));
+      })
+    .then(null,
+        error => {
+          ERROR("getAddonList failed", e);
+          safeCallback(aCallback)([]);
+        });
   },
 
   /**
-   * 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
+   * (Possibly asynchronously) get 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
+   * @param  aCallback
+   *         Called back with the addon, or null if no matching addon is found
    */
-  makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) {
-    let internal_id = aRow.getResultByName("internal_id");
-    if (this.addonCache[internal_id]) {
-      let addon = this.addonCache[internal_id].get();
-      if (addon) {
-        // If metadata is still pending for this instance add our callback to
-        // the list to be called when complete, otherwise pass the addon to
-        // our callback
-        if ("_pendingCallbacks" in addon)
-          addon._pendingCallbacks.push(aCallback);
-        else
-          aCallback(addon);
-        return;
-      }
-    }
-
-    let addon = new XPIProvider.DBAddonInternal();
-    addon._internal_id = internal_id;
-    let location = aRow.getResultByName("location");
-    addon._installLocation = XPIProvider.installLocationsByName[location];
-    addon._descriptor = aRow.getResultByName("descriptor");
-    copyRowProperties(aRow, PROP_METADATA, addon);
-    addon._defaultLocale = aRow.getResultByName("defaultLocale");
-    copyRowProperties(aRow, DB_METADATA, addon);
-    DB_BOOL_METADATA.forEach(function(aProp) {
-      addon[aProp] = aRow.getResultByName(aProp) != 0;
-    });
-    try {
-      addon._sourceBundle = addon._installLocation.getLocationForID(addon.id);
-    }
-    catch (e) {
-      // An exception will be thrown if the add-on appears in the database but
-      // not on disk. In general this should only happen during startup as
-      // this change is being detected.
-    }
-
-    this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
-    addon._pendingCallbacks = [aCallback];
-    this.fetchAddonMetadata(addon);
-  },
-
-  /**
-   * Synchronously reads all install locations known about by the database. This
-   * is often a a subset of the total install locations when not all have
-   * installed add-ons, occasionally a superset when an install location no
-   * longer exists.
-   *
-   * @return  an array of names of install locations
-   */
-  getInstallLocations: function XPIDB_getInstallLocations() {
-    if (!this.connection)
-      return [];
-
-    let stmt = this.getStatement("getInstallLocations");
-
-    return [row.location for each (row in resultRows(stmt))];
+  getAddon: function(aFilter, aCallback) {
+    return this.asyncLoadDB().then(
+      addonDB => {
+        getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback));
+      })
+    .then(null,
+        error => {
+          ERROR("getAddon failed", e);
+          safeCallback(aCallback)(null);
+        });
   },
 
   /**
    * Synchronously reads all the add-ons in a particular install location.
+   * Always called with the addon database already loaded.
    *
-   * @param  location
+   * @param  aLocation
    *         The name of the install location
    * @return an array of DBAddonInternals
    */
   getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) {
-    if (!this.connection)
-      return [];
-
-    let stmt = this.getStatement("getAddonsInLocation");
-
-    stmt.params.location = aLocation;
-    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
+    return _filterDB(this.addonDB, aAddon => (aAddon.location == aLocation));
   },
 
   /**
    * Asynchronously gets an add-on with a particular ID in a particular
    * install location.
    *
    * @param  aId
    *         The ID of the add-on to retrieve
    * @param  aLocation
    *         The name of the install location
    * @param  aCallback
    *         A callback to pass the DBAddonInternal to
    */
   getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
-    if (!this.connection) {
-      aCallback(null);
-      return;
-    }
-
-    let stmt = this.getStatement("getAddonInLocation");
-
-    stmt.params.id = aId;
-    stmt.params.location = aLocation;
-    stmt.executeAsync(new AsyncAddonListCallback(function 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]);
-    }));
+    this.asyncLoadDB().then(
+        addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId),
+                                      safeCallback(aCallback)));
   },
 
   /**
-   * Asynchronously gets the add-on with an ID that is visible.
+   * Asynchronously gets the add-on with the specified ID that is visible.
    *
    * @param  aId
    *         The ID of the add-on to retrieve
    * @param  aCallback
    *         A callback to pass the DBAddonInternal to
    */
   getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
-    if (!this.connection) {
-      aCallback(null);
-      return;
-    }
-
-    let stmt = this.getStatement("getVisibleAddonForID");
-
-    stmt.params.id = aId;
-    stmt.executeAsync(new AsyncAddonListCallback(function 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]);
-    }));
+    this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible),
+                  aCallback);
   },
 
   /**
    * Asynchronously gets the visible add-ons, optionally restricting by type.
    *
    * @param  aTypes
    *         An array of types to include or null to include all types
    * @param  aCallback
    *         A callback to pass the array of DBAddonInternals to
    */
   getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
-    if (!this.connection) {
-      aCallback([]);
-      return;
-    }
-
-    let stmt = null;
-    if (!aTypes || aTypes.length == 0) {
-      stmt = this.getStatement("getVisibleAddons");
-    }
-    else {
-      let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " +
-                "type IN (";
-      for (let i = 1; i <= aTypes.length; i++) {
-        sql += "?" + i;
-        if (i < aTypes.length)
-          sql += ",";
-      }
-      sql += ")";
-
-      // Note that binding to index 0 sets the value for the ?1 parameter
-      stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql);
-      for (let i = 0; i < aTypes.length; i++)
-        stmt.bindByIndex(i, aTypes[i]);
-    }
-
-    stmt.executeAsync(new AsyncAddonListCallback(aCallback));
+    this.getAddonList(aAddon => (aAddon.visible &&
+                                 (!aTypes || (aTypes.length == 0) ||
+                                  (aTypes.indexOf(aAddon.type) > -1))),
+                      aCallback);
   },
 
   /**
    * Synchronously gets all add-ons of a particular type.
    *
    * @param  aType
    *         The type of add-on to retrieve
    * @return an array of DBAddonInternals
    */
   getAddonsByType: function XPIDB_getAddonsByType(aType) {
-    if (!this.connection)
-      return [];
-
-    let stmt = this.getStatement("getAddonsByType");
-
-    stmt.params.type = aType;
-    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
+    if (!this.addonDB) {
+      // jank-tastic! Must synchronously load DB if the theme switches from
+      // an XPI theme to a lightweight theme before the DB has loaded,
+      // because we're called from sync XPIProvider.addonChanged
+      WARN("Synchronous load of XPI database due to getAddonsByType(" + aType + ")");
+      this.syncLoadDB(true);
+    }
+    return _filterDB(this.addonDB, aAddon => (aAddon.type == aType));
   },
 
   /**
    * Synchronously gets an add-on with a particular internalName.
    *
    * @param  aInternalName
    *         The internalName of the add-on to retrieve
    * @return a DBAddonInternal
    */
   getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
-    if (!this.connection)
-      return null;
-
-    let stmt = this.getStatement("getVisibleAddonForInternalName");
-
-    let addon = null;
-    stmt.params.internalName = aInternalName;
-
-    if (stepStatement(stmt))
-      addon = this.makeAddonFromRow(stmt.row);
-
-    stmt.reset();
-    return addon;
+    if (!this.addonDB) {
+      // This may be called when the DB hasn't otherwise been loaded
+      // XXX TELEMETRY
+      WARN("Synchronous load of XPI database due to getVisibleAddonForInternalName");
+      this.syncLoadDB(true);
+    }
+    
+    return _findAddon(this.addonDB,
+                      aAddon => aAddon.visible &&
+                                (aAddon.internalName == aInternalName));
   },
 
   /**
    * Asynchronously gets all add-ons with pending operations.
    *
    * @param  aTypes
    *         The types of add-ons to retrieve or null to get all types
    * @param  aCallback
    *         A callback to pass the array of DBAddonInternal to
    */
   getVisibleAddonsWithPendingOperations:
     function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
-    if (!this.connection) {
-      aCallback([]);
-      return;
-    }
 
-    let stmt = null;
-    if (!aTypes || aTypes.length == 0) {
-      stmt = this.getStatement("getVisibleAddonsWithPendingOperations");
-    }
-    else {
-      let sql = "SELECT * FROM addon WHERE visible=1 AND " +
-                "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " +
-                "AND type IN (";
-      for (let i = 1; i <= aTypes.length; i++) {
-        sql += "?" + i;
-        if (i < aTypes.length)
-          sql += ",";
-      }
-      sql += ")";
-
-      // Note that binding to index 0 sets the value for the ?1 parameter
-      stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" +
-                               aTypes.length, sql);
-      for (let i = 0; i < aTypes.length; i++)
-        stmt.bindByIndex(i, aTypes[i]);
-    }
-
-    stmt.executeAsync(new AsyncAddonListCallback(aCallback));
+    this.getAddonList(
+        aAddon => (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))),
+        aCallback);
   },
 
   /**
    * Asynchronously get an add-on by its Sync GUID.
    *
    * @param  aGUID
    *         Sync GUID of add-on to fetch
    * @param  aCallback
    *         A callback to pass the DBAddonInternal record to. Receives null
    *         if no add-on with that GUID is found.
    *
    */
   getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
-    let stmt = this.getStatement("getAddonBySyncGUID");
-    stmt.params.syncGUID = aGUID;
-
-    stmt.executeAsync(new AsyncAddonListCallback(function getAddonBySyncGUID_executeAsync(aAddons) {
-      if (aAddons.length == 0) {
-        aCallback(null);
-        return;
-      }
-      aCallback(aAddons[0]);
-    }));
+    this.getAddon(aAddon => aAddon.syncGUID == aGUID,
+                  aCallback);
   },
 
   /**
    * Synchronously gets all add-ons in the database.
+   * This is only called from the preference observer for the default
+   * compatibility version preference, so we can return an empty list if
+   * we haven't loaded the database yet.
    *
    * @return  an array of DBAddonInternals
    */
   getAddons: function XPIDB_getAddons() {
-    if (!this.connection)
+    if (!this.addonDB) {
       return [];
-
-    let stmt = this.getStatement("getAddons");
-
-    return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))];
+    }
+    return _filterDB(this.addonDB, aAddon => true);
   },
 
   /**
    * 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
-    if (!this.connection)
-      this.openConnection(false, true);
-
-    this.beginTransaction();
-
-    var self = this;
-    function insertLocale(aLocale) {
-      let localestmt = self.getStatement("addAddonMetadata_locale");
-      let stringstmt = self.getStatement("addAddonMetadata_strings");
-
-      copyProperties(aLocale, PROP_LOCALE_SINGLE, localestmt.params);
-      executeStatement(localestmt);
-      let row = XPIDatabase.connection.lastInsertRowID;
-
-      PROP_LOCALE_MULTI.forEach(function(aProp) {
-        aLocale[aProp].forEach(function(aStr) {
-          stringstmt.params.locale = row;
-          stringstmt.params.type = aProp;
-          stringstmt.params.value = aStr;
-          executeStatement(stringstmt);
-        });
-      });
-      return row;
+    if (!this.addonDB) {
+      // XXX telemetry. Should never happen on platforms that have a default theme
+      this.syncLoadDB(false);
     }
 
-    // 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;
+    let newAddon = new DBAddonInternal(aAddon);
+    newAddon.descriptor = aDescriptor;
+    this.addonDB.set(newAddon._key, newAddon);
+    if (newAddon.visible) {
+      this.makeAddonVisible(newAddon);
+    }
 
-      stmt = this.getStatement("addAddonMetadata_addon_locale");
-      aAddon.locales.forEach(function(aLocale) {
-        let id = insertLocale(aLocale);
-        aLocale.locales.forEach(function(aName) {
-          stmt.params.internal_id = internal_id;
-          stmt.params.name = aName;
-          stmt.params.locale = id;
-          executeStatement(stmt);
-        });
-      });
-
-      stmt = this.getStatement("addAddonMetadata_targetApplication");
-
-      aAddon.targetApplications.forEach(function(aApp) {
-        stmt.params.internal_id = internal_id;
-        stmt.params.id = aApp.id;
-        stmt.params.minVersion = aApp.minVersion;
-        stmt.params.maxVersion = aApp.maxVersion;
-        executeStatement(stmt);
-      });
-
-      stmt = this.getStatement("addAddonMetadata_targetPlatform");
-
-      aAddon.targetPlatforms.forEach(function(aPlatform) {
-        stmt.params.internal_id = internal_id;
-        stmt.params.os = aPlatform.os;
-        stmt.params.abi = aPlatform.abi;
-        executeStatement(stmt);
-      });
-
-      this.commitTransaction();
-    }
-    catch (e) {
-      this.rollbackTransaction();
-      throw e;
-    }
+    this.saveChanges();
+    return newAddon;
   },
 
   /**
-   * Synchronously updates an add-ons metadata in the database. Currently just
+   * Synchronously updates an add-on's 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)
-
-      this.addAddonMetadata(aNewAddon, aDescriptor);
-      this.commitTransaction();
-    }
-    catch (e) {
-      this.rollbackTransaction();
-      throw e;
-    }
-  },
+    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);
 
-  /**
-   * 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;
-    }
+    // addAddonMetadata does a saveChanges()
+    return this.addAddonMetadata(aNewAddon, aDescriptor);
   },
 
   /**
    * Synchronously removes an add-on from the database.
    *
    * @param  aAddon
    *         The DBAddonInternal being removed
    */
   removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
-    let stmt = this.getStatement("removeAddonMetadata");
-    stmt.params.internal_id = aAddon._internal_id;
-    executeStatement(stmt);
+    this.addonDB.delete(aAddon._key);
+    this.saveChanges();
   },
 
   /**
    * Synchronously marks a DBAddonInternal as visible marking all other
    * instances with the same ID as not visible.
    *
    * @param  aAddon
    *         The DBAddonInternal to make visible
    * @param  callback
    *         A callback to pass the DBAddonInternal to
    */
   makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
-    let stmt = this.getStatement("clearVisibleAddons");
-    stmt.params.id = aAddon.id;
-    executeStatement(stmt);
-
-    stmt = this.getStatement("makeAddonVisible");
-    stmt.params.internal_id = aAddon._internal_id;
-    executeStatement(stmt);
-
+    LOG("Make addon " + aAddon._key + " visible");
+    for (let [, otherAddon] of this.addonDB) {
+      if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
+        LOG("Hide addon " + otherAddon._key);
+        otherAddon.visible = false;
+      }
+    }
     aAddon.visible = true;
+    this.saveChanges();
   },
 
   /**
    * Synchronously sets properties for an add-on.
    *
    * @param  aAddon
    *         The DBAddonInternal being updated
    * @param  aProperties
    *         A dictionary of properties to set
    */
   setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
-    function convertBoolean(value) {
-      return value ? 1 : 0;
+    for (let key in aProperties) {
+      aAddon[key] = aProperties[key];
     }
-
-    let stmt = this.getStatement("setAddonProperties");
-    stmt.params.internal_id = aAddon._internal_id;
-
-    ["userDisabled", "appDisabled", "softDisabled",
-     "pendingUninstall"].forEach(function(aProp) {
-      if (aProp in aProperties) {
-        stmt.params[aProp] = convertBoolean(aProperties[aProp]);
-        aAddon[aProp] = aProperties[aProp];
-      }
-      else {
-        stmt.params[aProp] = convertBoolean(aAddon[aProp]);
-      }
-    });
-
-    if ("applyBackgroundUpdates" in aProperties) {
-      stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
-      aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates;
-    }
-    else {
-      stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
-    }
-
-    executeStatement(stmt);
+    this.saveChanges();
   },
 
   /**
    * Synchronously sets the Sync GUID for an add-on.
+   * Only called when the database is already loaded.
    *
    * @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) {
-    let stmt = this.getStatement("setAddonSyncGUID");
-    stmt.params.internal_id = aAddon._internal_id;
-    stmt.params.syncGUID = aGUID;
-
-    executeStatement(stmt);
-  },
-
-  /**
-   * Synchronously sets the file descriptor for an add-on.
-   *
-   * @param  aAddon
-   *         The DBAddonInternal being updated
-   * @param  aProperties
-   *         A dictionary of properties to set
-   */
-  setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) {
-    let stmt = this.getStatement("setAddonDescriptor");
-    stmt.params.internal_id = aAddon._internal_id;
-    stmt.params.descriptor = aDescriptor;
-
-    executeStatement(stmt);
+    // Need to make sure no other addon has this GUID
+    function excludeSyncGUID(otherAddon) {
+      return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID);
+    }
+    let otherAddon = _findAddon(this.addonDB, excludeSyncGUID);
+    if (otherAddon) {
+      throw new Error("Addon sync GUID conflict for addon " + aAddon._key +
+          ": " + otherAddon._key + " already has GUID " + aGUID);
+    }
+    aAddon.syncGUID = aGUID;
+    this.saveChanges();
   },
 
   /**
    * Synchronously updates an add-on's active flag in the database.
    *
    * @param  aAddon
    *         The DBAddonInternal to update
    */
-  updateAddonActive: function XPIDB_updateAddonActive(aAddon) {
-    LOG("Updating add-on state");
+  updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) {
+    LOG("Updating active state for add-on " + aAddon.id + " to " + aActive);
 
-    let stmt = this.getStatement("updateAddonActive");
-    stmt.params.internal_id = aAddon._internal_id;
-    stmt.params.active = aAddon.active ? 1 : 0;
-    executeStatement(stmt);
+    aAddon.active = aActive;
+    this.saveChanges();
   },
 
   /**
    * Synchronously calculates and updates all the active flags in the database.
    */
   updateActiveAddons: function XPIDB_updateActiveAddons() {
     LOG("Updating add-on states");
-    let stmt = this.getStatement("setActiveAddons");
-    executeStatement(stmt);
-
-    // Note that this does not update the active property on cached
-    // DBAddonInternal instances so we throw away the cache. This should only
-    // happen during shutdown when everything is going away anyway or during
-    // startup when the only references are internal.
-    this.addonCache = [];
+    for (let [, addon] of this.addonDB) {
+      let newActive = (addon.visible && !addon.userDisabled &&
+                      !addon.softDisabled && !addon.appDisabled &&
+                      !addon.pendingUninstall);
+      if (newActive != addon.active) {
+        addon.active = newActive;
+        this.saveChanges();
+      }
+    }
   },
 
   /**
    * Writes out the XPI add-ons list for the platform to read.
    */
   writeAddonsList: function XPIDB_writeAddonsList() {
+    if (!this.addonDB) {
+      // Unusual condition, force the DB to load
+      this.syncLoadDB(true);
+    }
     Services.appinfo.invalidateCachesOnRestart();
 
     let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
                                        true);
-    if (!this.connection) {
-      try {
-        addonsList.remove(false);
-        LOG("Deleted add-ons list");
-      }
-      catch (e) {
-      }
-
-      Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
-      return;
-    }
-
     let enabledAddons = [];
     let text = "[ExtensionDirs]\r\n";
     let count = 0;
     let fullCount = 0;
 
-    let stmt = this.getStatement("getActiveAddons");
+    let activeAddons = _filterDB(
+      this.addonDB,
+      aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"));
 
-    for (let row in resultRows(stmt)) {
+    for (let row of activeAddons) {
       text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
       enabledAddons.push(encodeURIComponent(row.id) + ":" +
                          encodeURIComponent(row.version));
     }
     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) {
-      stmt = this.getStatement("getThemes");
+      themes = _filterDB(this.addonDB, aAddon => aAddon.type == "theme");
     }
     else {
-      stmt = this.getStatement("getActiveTheme");
-      stmt.params.internalName = XPIProvider.selectedSkin;
+      let activeTheme = _findAddon(
+        this.addonDB,
+        aAddon => (aAddon.type == "theme") &&
+                  (aAddon.internalName == XPIProvider.selectedSkin));
+      if (activeTheme) {
+        themes.push(activeTheme);
+      }
     }
 
-    if (stmt) {
+    if (themes.length > 0) {
       count = 0;
-      for (let row in resultRows(stmt)) {
+      for (let row of themes) {
         text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
         enabledAddons.push(encodeURIComponent(row.id) + ":" +
                            encodeURIComponent(row.version));
       }
       fullCount += count;
     }
 
     if (fullCount > 0) {
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -339,16 +339,20 @@ function do_check_compatibilityoverride(
 }
 
 function do_check_icons(aActual, aExpected) {
   for (var size in aExpected) {
     do_check_eq(remove_port(aActual[size]), remove_port(aExpected[size]));
   }
 }
 
+// Record the error (if any) from trying to save the XPI
+// database at shutdown time
+let gXPISaveError = null;
+
 /**
  * Starts up the add-on manager as if it was started by the application.
  *
  * @param  aAppChanged
  *         An optional boolean parameter to simulate the case where the
  *         application has changed version since the last run. If not passed it
  *         defaults to true
  */
@@ -391,56 +395,51 @@ function restartManager(aNewVersion) {
     startupManager(false);
   }
 }
 
 function shutdownManager() {
   if (!gInternalManager)
     return;
 
-  let obs = AM_Cc["@mozilla.org/observer-service;1"].
-            getService(AM_Ci.nsIObserverService);
-
   let xpiShutdown = false;
-  obs.addObserver({
+  Services.obs.addObserver({
     observe: function(aSubject, aTopic, aData) {
       xpiShutdown = true;
-      obs.removeObserver(this, "xpi-provider-shutdown");
+      gXPISaveError = aData;
+      Services.obs.removeObserver(this, "xpi-provider-shutdown");
     }
   }, "xpi-provider-shutdown", false);
 
   let repositoryShutdown = false;
-  obs.addObserver({
+  Services.obs.addObserver({
     observe: function(aSubject, aTopic, aData) {
       repositoryShutdown = true;
-      obs.removeObserver(this, "addon-repository-shutdown");
+      Services.obs.removeObserver(this, "addon-repository-shutdown");
     }
   }, "addon-repository-shutdown", false);
 
-  obs.notifyObservers(null, "quit-application-granted", null);
+  Services.obs.notifyObservers(null, "quit-application-granted", null);
   let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
   scope.AddonManagerInternal.shutdown();
   gInternalManager = null;
 
   AddonRepository.shutdown();
 
   // Load the add-ons list as it was after application shutdown
   loadAddonsList();
 
   // Clear any crash report annotations
   gAppInfo.annotations = {};
 
-  let thr = AM_Cc["@mozilla.org/thread-manager;1"].
-            getService(AM_Ci.nsIThreadManager).
-            mainThread;
+  let thr = Services.tm.mainThread;
 
   // Wait until we observe the shutdown notifications
   while (!repositoryShutdown || !xpiShutdown) {
-    if (thr.hasPendingEvents())
-      thr.processNextEvent(false);
+    thr.processNextEvent(true);
   }
 
   // Force the XPIProvider provider to reload to better
   // simulate real-world usage.
   scope = Components.utils.import("resource://gre/modules/XPIProvider.jsm");
   AddonManagerPrivate.unregisterProvider(scope.XPIProvider);
   Components.utils.unload("resource://gre/modules/XPIProvider.jsm");
 }
@@ -1389,16 +1388,70 @@ function do_exception_wrap(func) {
       func.apply(null, arguments);
     }
     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);
+  jData.schemaVersion = aNewVersion;
+  saveJSON(jData, gExtensionsJSON);
+}
+
+/**
+ * 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.path);
+  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_AddonRepository_cache.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js
@@ -507,17 +507,17 @@ function check_cache(aExpectedToFind, aE
  *
  * @param  aExpectedToFind
  *         An array of booleans representing which REPOSITORY_ADDONS are
  *         expected to be found in the cache
  * @param  aCallback
  *         A callback to call once the checks are complete
  */
 function check_initialized_cache(aExpectedToFind, aCallback) {
-  check_cache(aExpectedToFind, true, function() {
+  check_cache(aExpectedToFind, true, function restart_initialized_cache() {
     restartManager();
 
     // If cache is disabled, then expect results immediately
     let cacheEnabled = Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED);
     check_cache(aExpectedToFind, !cacheEnabled, aCallback);
   });
 }
 
@@ -529,35 +529,35 @@ function waitForFlushedData(aCallback) {
       Services.obs.removeObserver(this, "addon-repository-data-written");
       aCallback(aData == "true");
     }
   }, "addon-repository-data-written", false);
 }
 
 function run_test() {
   // Setup for test
-  do_test_pending();
+  do_test_pending("test_AddonRepository_cache");
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
   startupManager();
 
   // Install XPI add-ons
-  installAllFiles(ADDON_FILES, function() {
+  installAllFiles(ADDON_FILES, function first_installs() {
     restartManager();
 
     gServer = new HttpServer();
     gServer.registerDirectory("/data/", do_get_file("data"));
     gServer.start(PORT);
 
     do_execute_soon(run_test_1);
   });
 }
 
 function end_test() {
-  gServer.stop(do_test_finished);
+  gServer.stop(function() {do_test_finished("test_AddonRepository_cache");});
 }
 
 // Tests AddonRepository.cacheEnabled
 function run_test_1() {
   Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false);
   do_check_false(AddonRepository.cacheEnabled);
   Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
   do_check_true(AddonRepository.cacheEnabled);
@@ -573,17 +573,17 @@ function run_test_2() {
 }
 
 // Tests repopulateCache when the search fails
 function run_test_3() {
   check_database_exists(true);
   Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
   Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED);
 
-  AddonRepository.repopulateCache(ADDON_IDS, function() {
+  AddonRepository.repopulateCache(ADDON_IDS, function test_3_repopulated() {
     check_initialized_cache([false, false, false], run_test_4);
   });
 }
 
 // Tests repopulateCache when search returns no results
 function run_test_4() {
   Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY);
 
@@ -690,17 +690,17 @@ function run_test_11() {
 
 // Tests that XPI add-ons do not use any of the repository properties if
 // caching is disabled, even if there are repository properties available
 function run_test_12() {
   check_database_exists(true);
   Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false);
   Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS);
 
-  AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
+  AddonManager.getAddonsByIDs(ADDON_IDS, function test_12_check(aAddons) {
     check_results(aAddons, WITHOUT_CACHE);
     do_execute_soon(run_test_13);
   });
 }
 
 // Tests that a background update with caching disabled deletes the add-ons
 // database, and that XPI add-ons still do not use any of repository properties
 function run_test_13() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js
@@ -98,11 +98,11 @@ function run_test_1() {
     manifest = ChromeManifestParser.parseSync(manifestURI);
 
     do_check_true(Array.isArray(manifest));
     do_check_eq(manifest.length, expected.length);
     for (let i = 0; i < manifest.length; i++) {
       do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i]));
     }
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js
@@ -32,18 +32,18 @@ function DeferredSaveTester(aDelay, aDat
       return tester.saver.saveChanges();
     },
 
     flush: (aWriteHandler) => {
       tester.writeHandler = aWriteHandler || writer;
       return tester.saver.flush();
     },
 
-    get error() {
-      return tester.saver.error;
+    get lastError() {
+      return tester.saver.lastError;
     }
   };
 
   // Default write handler for most cases where the test case doesn't need
   // to do anything while the write is in progress; just completes the write
   // on the next event loop
   function writer(aTester) {
     do_print("default write callback");
@@ -149,21 +149,21 @@ add_task(function test_error_immediate()
   function writeFail(aTester) {
     aTester.waDeferred.reject(testError);
   }
 
   yield tester.save("test_error_immediate", writeFail).then(
     count => do_throw("Did not get expected error"),
     error => do_check_eq(testError.message, error.message)
     );
-  do_check_eq(testError, tester.error);
+  do_check_eq(testError, tester.lastError);
 
   // This write should succeed and clear the error
   yield tester.save("test_error_immediate succeeds");
-  do_check_eq(null, tester.error);
+  do_check_eq(null, tester.lastError);
   // The failed save attempt counts in our total
   do_check_eq(2, tester.saver.totalSaves);
 });
 
 // Save one set of changes, then while the write is in progress, modify the
 // data two more times. Test that we re-write the dirty data exactly once
 // after the first write succeeds
 add_task(function dirty_while_writing() {
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js
@@ -0,0 +1,54 @@
+/* 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_badschema.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js
@@ -249,24 +249,21 @@ 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 and so can be replaced with
-    // a bad file
-    restartManager();
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    var db = Services.storage.openDatabase(dbfile);
-    db.schemaVersion = 100;
-    db.close();
+    // After restarting the database won't be open so we can alter
+    // the schema
+    shutdownManager();
+    changeXPIDBVersion(100);
+    startupManager();
 
     // Accessing the add-ons should open and recover the database. Since
     // migration occurs everything should be recovered correctly
     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
@@ -330,22 +330,22 @@ const ADDON_IDS = ["softblock1@tests.moz
                    "softblock4@tests.mozilla.org",
                    "softblock5@tests.mozilla.org",
                    "hardblock@tests.mozilla.org",
                    "regexpblock@tests.mozilla.org"];
 
 // Don't need the full interface, attempts to call other methods will just
 // throw which is just fine
 var WindowWatcher = {
-  openWindow: function(parent, url, name, features, arguments) {
+  openWindow: function(parent, url, name, features, openArgs) {
     // Should be called to list the newly blocklisted items
     do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG);
 
     // Simulate auto-disabling any softblocks
-    var list = arguments.wrappedJSObject.list;
+    var list = openArgs.wrappedJSObject.list;
     list.forEach(function(aItem) {
       if (!aItem.blocked)
         aItem.disable = true;
     });
 
     //run the code after the blocklist is closed
     Services.obs.notifyObservers(null, "addon-blocklist-closed", null);
 
@@ -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,21 +701,17 @@ 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();
 
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    var db = Services.storage.openDatabase(dbfile);
-    db.schemaVersion = 100;
-    db.close();
+    changeXPIDBVersion(100);
     gAppInfo.version = "2";
     startupManager(true);
 
     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);
@@ -733,21 +729,17 @@ add_test(function run_app_update_schema_
       do_execute_soon(update_schema_3);
     });
   }
 
   function update_schema_3() {
     restartManager();
 
     shutdownManager();
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    var db = Services.storage.openDatabase(dbfile);
-    db.schemaVersion = 100;
-    db.close();
+    changeXPIDBVersion(100);
     gAppInfo.version = "2.5";
     startupManager(true);
 
     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);
@@ -759,21 +751,17 @@ add_test(function run_app_update_schema_
 
       do_execute_soon(update_schema_4);
     });
   }
 
   function update_schema_4() {
     shutdownManager();
 
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    var db = Services.storage.openDatabase(dbfile);
-    db.schemaVersion = 100;
-    db.close();
+    changeXPIDBVersion(100);
     startupManager(false);
 
     AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
 
       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);
@@ -784,21 +772,17 @@ add_test(function run_app_update_schema_
 
       do_execute_soon(update_schema_5);
     });
   }
 
   function update_schema_5() {
     shutdownManager();
 
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    var db = Services.storage.openDatabase(dbfile);
-    db.schemaVersion = 100;
-    db.close();
+    changeXPIDBVersion(100);
     gAppInfo.version = "1";
     startupManager(true);
 
     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);
@@ -1340,17 +1324,17 @@ add_test(function run_manual_update_2_te
   writeInstallRDFForExtension(softblock3_1, profileDir);
   writeInstallRDFForExtension(softblock4_1, profileDir);
   writeInstallRDFForExtension(softblock5_1, profileDir);
   writeInstallRDFForExtension(hardblock_1, profileDir);
   writeInstallRDFForExtension(regexpblock_1, profileDir);
 
   startupManager(false);
 
-  AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
+  AddonManager.getAddonsByIDs(ADDON_IDS, callback_soon(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);
     check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
     check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
 
     s2.userDisabled = false;
@@ -1358,17 +1342,18 @@ add_test(function run_manual_update_2_te
     check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
     s3.userDisabled = false;
     check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
     restartManager();
 
     manual_update("2", function manual_update_2_2() {
       restartManager();
 
-      AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) {
+      AddonManager.getAddonsByIDs(ADDON_IDS,
+       callback_soon(function([s1, s2, s3, s4, s5, h, r]) {
 
         check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
         check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
         check_addon(s3, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
         // Can't manually update to a hardblocked add-on
         check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
         check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
 
@@ -1386,19 +1371,19 @@ add_test(function run_manual_update_2_te
             check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
 
             s1.userDisabled = false;
             s2.userDisabled = false;
             s4.userDisabled = true;
             run_next_test();
           });
         });
-      });
+      }));
     });
-  });
+  }));
 });
 
 // Uses the API to install blocked add-ons from the local filesystem
 add_test(function run_local_install_test() {
   do_print("Test: " + arguments.callee.name + "\n");
   shutdownManager();
 
   getFileForAddon(profileDir, softblock1_1.id).remove(true);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -6,18 +6,16 @@ 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");
@@ -143,20 +141,19 @@ 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());
-
   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() {
@@ -203,19 +200,16 @@ function run_test_1() {
       do_check_eq(getActiveVersion(), -1);
     });
     install.install();
   });
 }
 
 function check_test_1(installSyncGUID) {
   let file = gProfD.clone();
-  file.append(EXTENSIONS_DB);
-  do_check_true(file.exists());
-
   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);
 
@@ -351,16 +345,19 @@ function run_test_4() {
       do_check_bootstrappedPref(run_test_5);
     });
   });
 }
 
 // Tests that a restart shuts down and restarts the add-on
 function run_test_5() {
   shutdownManager();
+  // By the time we've shut down, the database must have been written
+  do_check_true(gExtensionsJSON.exists());
+
   do_check_eq(getInstalledVersion(), 1);
   do_check_eq(getActiveVersion(), 0);
   do_check_eq(getShutdownReason(), APP_SHUTDOWN);
   do_check_eq(getShutdownNewVersion(), 0);
   do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
   startupManager(false);
   do_check_eq(getInstalledVersion(), 1);
   do_check_eq(getActiveVersion(), 1);
@@ -453,27 +450,27 @@ function run_test_7() {
 function check_test_7() {
   ensure_test_completed();
   do_check_eq(getInstalledVersion(), 0);
   do_check_eq(getActiveVersion(), 0);
   do_check_eq(getShutdownReason(), ADDON_UNINSTALL);
   do_check_eq(getShutdownNewVersion(), 0);
   do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
 
-  AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+  AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
     do_check_eq(b1, null);
 
     restartManager();
 
     AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(newb1) {
       do_check_eq(newb1, null);
 
       do_check_bootstrappedPref(run_test_8);
     });
-  });
+  }));
 }
 
 // Test that a bootstrapped extension dropped into the profile loads properly
 // on startup and doesn't cause an EM restart
 function run_test_8() {
   shutdownManager();
 
   manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir,
@@ -669,20 +666,23 @@ function run_test_12() {
     do_check_true(b1.isActive);
     do_check_eq(getInstalledVersion(), 1);
     do_check_eq(getActiveVersion(), 1);
     do_check_eq(getStartupReason(), ADDON_INSTALL);
     do_check_eq(getStartupOldVersion(), 0);
     do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
 
     b1.uninstall();
-    restartManager();
+    do_execute_soon(test_12_restart);
+  });
+}
 
-    do_check_bootstrappedPref(run_test_13);
-  });
+function test_12_restart() {
+  restartManager();
+  do_check_bootstrappedPref(run_test_13);
 }
 
 
 // Tests that installing a bootstrapped extension with an invalid application
 // entry doesn't call it's startup method
 function run_test_13() {
   prepare_test({ }, [
     "onNewInstall"
@@ -701,17 +701,17 @@ function run_test_13() {
     prepare_test({
       "bootstrap1@tests.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
-    ], function() {do_execute_soon(check_test_13)});
+    ], callback_soon(check_test_13));
     install.install();
   });
 }
 
 function check_test_13() {
   AddonManager.getAllInstalls(function(installs) {
     // There should be no active installs now since the install completed and
     // doesn't require a restart.
@@ -722,33 +722,37 @@ function check_test_13() {
       do_check_eq(b1.version, "3.0");
       do_check_true(b1.appDisabled);
       do_check_false(b1.userDisabled);
       do_check_false(b1.isActive);
       do_check_eq(getInstalledVersion(), 3);  // We call install even for disabled add-ons
       do_check_eq(getActiveVersion(), 0);     // Should not have called startup though
       do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0");
 
-      restartManager();
+      do_execute_soon(test_13_restart);
+    });
+  });
+}
+
+function test_13_restart() {
+  restartManager();
 
-      AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
-        do_check_neq(b1, null);
-        do_check_eq(b1.version, "3.0");
-        do_check_true(b1.appDisabled);
-        do_check_false(b1.userDisabled);
-        do_check_false(b1.isActive);
-        do_check_eq(getInstalledVersion(), 3);  // We call install even for disabled add-ons
-        do_check_eq(getActiveVersion(), 0);     // Should not have called startup though
-        do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0");
+  AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+    do_check_neq(b1, null);
+    do_check_eq(b1.version, "3.0");
+    do_check_true(b1.appDisabled);
+    do_check_false(b1.userDisabled);
+    do_check_false(b1.isActive);
+    do_check_eq(getInstalledVersion(), 3);  // We call install even for disabled add-ons
+    do_check_eq(getActiveVersion(), 0);     // Should not have called startup though
+    do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0");
 
-        do_check_bootstrappedPref(function() {
-          b1.uninstall();
-          do_execute_soon(run_test_14);
-        });
-      });
+    do_check_bootstrappedPref(function() {
+      b1.uninstall();
+      do_execute_soon(run_test_14);
     });
   });
 }
 
 // Tests that a bootstrapped extension with an invalid target application entry
 // does not get loaded when detected during startup
 function run_test_14() {
   restartManager();
@@ -810,17 +814,17 @@ function run_test_15() {
         prepare_test({
           "bootstrap1@tests.mozilla.org": [
             ["onInstalling", false],
             "onInstalled"
           ]
         }, [
           "onInstallStarted",
           "onInstallEnded",
-        ], function() {do_execute_soon(check_test_15)});
+        ], callback_soon(check_test_15));
         install.install();
       });
     });
   });
   installAllFiles([do_get_addon("test_bootstrap1_1")], function test_15_addon_installed() { });
 }
 
 function check_test_15() {
@@ -852,17 +856,17 @@ function check_test_15() {
     });
   });
 }
 
 // Tests that bootstrapped extensions don't get loaded when in safe mode
 function run_test_16() {
   resetPrefs();
   waitForPref("bootstraptest.startup_reason", function test_16_after_startup() {
-    AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+    AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
       // Should have installed and started
       do_check_eq(getInstalledVersion(), 1);
       do_check_eq(getActiveVersion(), 1);
       do_check_true(b1.isActive);
       do_check_eq(b1.iconURL, "chrome://foo/skin/icon.png");
       do_check_eq(b1.aboutURL, "chrome://foo/content/about.xul");
       do_check_eq(b1.optionsURL, "chrome://foo/content/options.xul");
 
@@ -870,17 +874,17 @@ function run_test_16() {
 
       // Should have stopped
       do_check_eq(getInstalledVersion(), 1);
       do_check_eq(getActiveVersion(), 0);
 
       gAppInfo.inSafeMode = true;
       startupManager(false);
 
-      AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+      AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
         // Should still be stopped
         do_check_eq(getInstalledVersion(), 1);
         do_check_eq(getActiveVersion(), 0);
         do_check_false(b1.isActive);
         do_check_eq(b1.iconURL, null);
         do_check_eq(b1.aboutURL, null);
         do_check_eq(b1.optionsURL, null);
 
@@ -890,20 +894,20 @@ function run_test_16() {
 
         // Should have started
         do_check_eq(getInstalledVersion(), 1);
         do_check_eq(getActiveVersion(), 1);
 
         AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
           b1.uninstall();
 
-          run_test_17();
+          do_execute_soon(run_test_17);
         });
-      });
-    });
+      }));
+    }));
   });
   installAllFiles([do_get_addon("test_bootstrap1_1")], function() { });
 }
 
 // Check that a bootstrapped extension in a non-profile location is loaded
 function run_test_17() {
   shutdownManager();
 
@@ -1020,17 +1024,17 @@ function run_test_20() {
     do_check_eq(getInstallReason(), ADDON_UPGRADE);
     do_check_eq(getStartupReason(), APP_STARTUP);
 
     do_check_eq(getShutdownNewVersion(), 0);
     do_check_eq(getUninstallNewVersion(), 2);
     do_check_eq(getInstallOldVersion(), 1);
     do_check_eq(getStartupOldVersion(), 0);
 
-    run_test_21();
+    do_execute_soon(run_test_21);
   });
 }
 
 // Check that a detected removal reveals the non-profile one
 function run_test_21() {
   resetPrefs();
   shutdownManager();
 
@@ -1077,17 +1081,17 @@ function run_test_22() {
   let file = manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir,
                              "bootstrap1@tests.mozilla.org");
 
   // Make it look old so changes are detected
   setExtensionModifiedTime(file, file.lastModifiedTime - 5000);
 
   startupManager();
 
-  AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+  AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
     // Should have installed and started
     do_check_eq(getInstalledVersion(), 1);
     do_check_eq(getActiveVersion(), 1);
     do_check_neq(b1, null);
     do_check_eq(b1.version, "1.0");
     do_check_true(b1.isActive);
 
     resetPrefs();
@@ -1121,17 +1125,17 @@ function run_test_22() {
       do_check_eq(getStartupOldVersion(), 0);
 
       do_check_bootstrappedPref(function() {
         b1.uninstall();
 
         run_test_23();
       });
     });
-  });
+  }));
 }
 
 
 // Tests that installing from a URL doesn't require a restart
 function run_test_23() {
   prepare_test({ }, [
     "onNewInstall"
   ]);
@@ -1195,27 +1199,27 @@ function check_test_23() {
       do_check_true(b1.hasResource("install.rdf"));
       do_check_true(b1.hasResource("bootstrap.js"));
       do_check_false(b1.hasResource("foo.bar"));
       do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
 
       let dir = do_get_addon_root_uri(profileDir, "bootstrap1@tests.mozilla.org");
       do_check_eq(b1.getResourceURI("bootstrap.js").spec, dir + "bootstrap.js");
 
-      AddonManager.getAddonsWithOperationsByTypes(null, function(list) {
+      AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) {
         do_check_eq(list.length, 0);
 
         restartManager();
-        AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+        AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
           b1.uninstall();
           restartManager();
 
           testserver.stop(run_test_24);
-        });
-      });
+        }));
+      }));
      });
     });
   });
 }
 
 // Tests that we recover from a broken preference
 function run_test_24() {
   resetPrefs();
@@ -1272,17 +1276,17 @@ function run_test_25() {
       do_check_eq(getInstalledVersion(), 1);
       do_check_eq(getActiveVersion(), 1);
 
       installAllFiles([do_get_addon("test_bootstrap1_4")], function() {
         // Needs a restart to complete this so the old version stays running
         do_check_eq(getInstalledVersion(), 1);
         do_check_eq(getActiveVersion(), 1);
 
-        AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+        AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
           do_check_neq(b1, null);
           do_check_eq(b1.version, "1.0");
           do_check_true(b1.isActive);
           do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
 
           restartManager();
 
           do_check_eq(getInstalledVersion(), 0);
@@ -1293,33 +1297,33 @@ function run_test_25() {
           AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
             do_check_neq(b1, null);
             do_check_eq(b1.version, "4.0");
             do_check_true(b1.isActive);
             do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
             do_check_bootstrappedPref(run_test_26);
           });
-        });
+        }));
       });
   });
   installAllFiles([do_get_addon("test_bootstrap1_1")], function test_25_installed() {
     do_print("test 25 install done");
   });
 }
 
 // Tests that updating from a normal add-on to a bootstrappable add-on calls
 // the install method
 function run_test_26() {
   installAllFiles([do_get_addon("test_bootstrap1_1")], function() {
     // Needs a restart to complete this
     do_check_eq(getInstalledVersion(), 0);
     do_check_eq(getActiveVersion(), 0);
 
-    AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+    AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
       do_check_neq(b1, null);
       do_check_eq(b1.version, "4.0");
       do_check_true(b1.isActive);
       do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
 
       restartManager();
 
       do_check_eq(getInstalledVersion(), 1);
@@ -1330,17 +1334,17 @@ function run_test_26() {
       AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
         do_check_neq(b1, null);
         do_check_eq(b1.version, "1.0");
         do_check_true(b1.isActive);
         do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
         do_check_bootstrappedPref(run_test_27);
       });
-    });
+    }));
   });
 }
 
 // Tests that updating from a bootstrappable add-on to a normal add-on while
 // disabled calls the uninstall method
 function run_test_27() {
   AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
     do_check_neq(b1, null);
@@ -1353,17 +1357,17 @@ function run_test_27() {
 
     installAllFiles([do_get_addon("test_bootstrap1_4")], function() {
       // Updating disabled things happens immediately
       do_check_eq(getInstalledVersion(), 0);
       do_check_eq(getUninstallReason(), ADDON_UPGRADE);
       do_check_eq(getUninstallNewVersion(), 4);
       do_check_eq(getActiveVersion(), 0);
 
-      AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+      AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
         do_check_neq(b1, null);
         do_check_eq(b1.version, "4.0");
         do_check_false(b1.isActive);
         do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
         restartManager();
 
         do_check_eq(getInstalledVersion(), 0);
@@ -1372,33 +1376,33 @@ function run_test_27() {
         AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
           do_check_neq(b1, null);
           do_check_eq(b1.version, "4.0");
           do_check_false(b1.isActive);
           do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
           do_check_bootstrappedPref(run_test_28);
         });
-      });
+      }));
     });
   });
 }
 
 // Tests that updating from a normal add-on to a bootstrappable add-on when
 // disabled calls the install method but not the startup method
 function run_test_28() {
   installAllFiles([do_get_addon("test_bootstrap1_1")], function() {
    do_execute_soon(function bootstrap_disabled_downgrade_check() {
     // Doesn't need a restart to complete this
     do_check_eq(getInstalledVersion(), 1);
     do_check_eq(getInstallReason(), ADDON_DOWNGRADE);
     do_check_eq(getInstallOldVersion(), 4);
     do_check_eq(getActiveVersion(), 0);
 
-    AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+    AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) {
       do_check_neq(b1, null);
       do_check_eq(b1.version, "1.0");
       do_check_false(b1.isActive);
       do_check_true(b1.userDisabled);
       do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
       restartManager();
 
@@ -1412,12 +1416,12 @@ function run_test_28() {
         do_check_eq(b1.version, "1.0");
         do_check_true(b1.isActive);
         do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
         do_check_eq(getInstalledVersion(), 1);
         do_check_eq(getActiveVersion(), 1);
 
         do_check_bootstrappedPref(do_test_finished);
       });
-    });
+    }));
    });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js
@@ -133,17 +133,17 @@ function run_test() {
   testserver = new HttpServer();
   testserver.registerDirectory("/data/", dataDir);
   testserver.start(4444);
 
   startupManager();
 
   installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() {
     restartManager();
-    AddonManager.getAddonByID(ADDONS[0].id, function(addon) {
+    AddonManager.getAddonByID(ADDONS[0].id, callback_soon(function(addon) {
       do_check_true(!(!addon));
       addon.userDisabled = true;
       restartManager();
 
       AddonManager.getAddonsByTypes(["extension"], function(installedItems) {
         var items = [];
 
         for (let addon of ADDONS) {
@@ -163,16 +163,16 @@ function run_test() {
               updateListener.pendingCount++;
               installedItem.findUpdates(updateListener,
                                             AddonManager.UPDATE_WHEN_USER_REQUESTED,
                                             "3", "3");
             }
           }
         }
       });
-    });
+    }));
   });
 }
 
 function test_complete() {
   do_check_eq(gItemsNotChecked.length, 0);
   testserver.stop(do_test_finished);
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js
@@ -160,22 +160,22 @@ function run_test() {
 
   Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false);
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "en-US");
 
   startupManager();
   installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() {
 
     restartManager();
-    AddonManager.getAddonByID(ADDONS[1].id, function(addon) {
+    AddonManager.getAddonByID(ADDONS[1].id, callback_soon(function(addon) {
       do_check_true(!(!addon));
       addon.userDisabled = true;
       restartManager();
 
       AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(installedItems) {
         installedItems.forEach(function(item) {
           updateListener.pendingCount++;
           item.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
         });
       });
-    });
+    }));
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js
@@ -23,13 +23,13 @@ function run_test()
       do_check_eq(addon.name, "Test theme");
       restartManager();
 
       AddonManager.getAddonByID(ID, function(addon) {
         do_check_neq(addon, null);
         do_check_eq(addon.optionsURL, null);
         do_check_eq(addon.aboutURL, null);
 
-        do_test_finished();
+        do_execute_soon(do_test_finished);
       });
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js
@@ -91,13 +91,13 @@ function run_test()
 
       onUpdateAvailable: function(addon, install) {
         do_throw("Should not have seen an available update");
       },
 
       onUpdateFinished: function(addon, error) {
         do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR);
         do_check_true(gSeenExpectedURL);
-        shutdownTest();
+        do_execute_soon(shutdownTest);
       }
     }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js
@@ -22,96 +22,96 @@ function run_test()
   installAllFiles([do_get_addon(ADDON)], function() {
     restartManager();
 
     run_test_1();
   });
 }
 
 function run_test_1() {
-  AddonManager.getAddonByID(ID, function(addon) {
+  AddonManager.getAddonByID(ID, callback_soon(function(addon) {
     do_check_neq(addon, null);
     do_check_eq(addon.name, "fr Name");
     do_check_eq(addon.description, "fr Description");
 
     // Disable item
     addon.userDisabled = true;
     restartManager();
 
     AddonManager.getAddonByID(ID, function(newAddon) {
       do_check_neq(newAddon, null);
       do_check_eq(newAddon.name, "fr Name");
 
-      run_test_2();
+      do_execute_soon(run_test_2);
     });
-  });
+  }));
 }
 
 function run_test_2() {
   // Change locale. The more specific de-DE is the best match
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "de");
   restartManager();
 
   AddonManager.getAddonByID(ID, function(addon) {
     do_check_neq(addon, null);
     do_check_eq(addon.name, "de-DE Name");
     do_check_eq(addon.description, null);
 
-    run_test_3();
+    do_execute_soon(run_test_3);
   });
 }
 
 function run_test_3() {
   // Change locale. Locale case should have no effect
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "DE-de");
   restartManager();
 
   AddonManager.getAddonByID(ID, function(addon) {
     do_check_neq(addon, null);
     do_check_eq(addon.name, "de-DE Name");
     do_check_eq(addon.description, null);
 
-    run_test_4();
+    do_execute_soon(run_test_4);
   });
 }
 
 function run_test_4() {
   // Change locale. es-ES should closely match
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "es-AR");
   restartManager();
 
   AddonManager.getAddonByID(ID, function(addon) {
     do_check_neq(addon, null);
     do_check_eq(addon.name, "es-ES Name");
     do_check_eq(addon.description, "es-ES Description");
 
-    run_test_5();
+    do_execute_soon(run_test_5);
   });
 }
 
 function run_test_5() {
   // Change locale. Either zh-CN or zh-TW could match
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "zh");
   restartManager();
 
   AddonManager.getAddonByID(ID, function(addon) {
     do_check_neq(addon, null);
     if (addon.name != "zh-TW Name" && addon.name != "zh-CN Name")
       do_throw("zh matched to " + addon.name);
 
-    run_test_6();
+    do_execute_soon(run_test_6);
   });
 }
 
 function run_test_6() {
   // Unknown locale should try to match against en-US as well. Of en,en-GB
   // en should match as being less specific
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "nl-NL");
   restartManager();
 
   AddonManager.getAddonByID(ID, function(addon) {
     do_check_neq(addon, null);
     do_check_eq(addon.name, "en Name");
     do_check_eq(addon.description, "en Description");
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js
@@ -16,12 +16,12 @@ function run_test()
   startupManager();
   installAllFiles([do_get_addon(ADDON)], function() {
     restartManager();
     AddonManager.getAddonByID(ID, function(addon) {
       do_check_neq(addon, null);
       do_check_eq(addon.name, "Deutsches W\u00f6rterbuch");
       do_check_eq(addon.name.length, 20);
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
@@ -280,17 +280,17 @@ function check_initial_state(callback) {
     callback();
   });
 }
 
 // Tests the add-ons were installed and the initial blocklist applied as expected
 function check_test_pt1() {
   dump("Checking pt 1\n");
 
-  AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) {
+  AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) {
     for (var i = 0; i < ADDONS.length; i++) {
       if (!addons[i])
         do_throw("Addon " + (i + 1) + " did not get installed correctly");
     }
   
     do_check_eq(check_addon_state(addons[0]), "false,false,false");
     do_check_eq(check_addon_state(addons[1]), "false,false,false");
     do_check_eq(check_addon_state(addons[2]), "false,false,false");
@@ -311,17 +311,17 @@ function check_test_pt1() {
     addons[4].userDisabled = false;
 
     restartManager();
     check_initial_state(function() {
       gNotificationCheck = check_notification_pt2;
       gTestCheck = check_test_pt2;
       load_blocklist("bug455906_warn.xml");
     });
-  });
+  }));
 }
 
 function check_notification_pt2(args) {
   dump("Checking notification pt 2\n");
   do_check_eq(args.list.length, 4);
 
   for (let addon of args.list) {
     if (addon.item instanceof Ci.nsIPluginTag) {
@@ -352,17 +352,17 @@ function check_notification_pt2(args) {
     }
   }
 }
 
 function check_test_pt2() {
   restartManager();
   dump("Checking results pt 2\n");
 
-  AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) {
+  AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) {
     // Should have disabled this add-on as requested
     do_check_eq(check_addon_state(addons[2]), "true,true,false");
     do_check_eq(check_plugin_state(PLUGINS[2]), "true,false");
 
     // The blocked add-on should have changed to soft disabled
     do_check_eq(check_addon_state(addons[5]), "true,true,false");
     do_check_eq(check_plugin_state(PLUGINS[5]), "true,false");
 
@@ -381,17 +381,17 @@ function check_test_pt2() {
     addons[2].userDisabled = false;
     addons[5].userDisabled = false;
     PLUGINS[2].enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     PLUGINS[5].enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     restartManager();
     gNotificationCheck = null;
     gTestCheck = run_test_pt3;
     load_blocklist("bug455906_start.xml");
-  });
+  }));
 }
 
 function run_test_pt3() {
   restartManager();
   check_initial_state(function() {
     gNotificationCheck = check_notification_pt3;
     gTestCheck = check_test_pt3;
     load_blocklist("bug455906_block.xml");
@@ -480,26 +480,26 @@ function check_test_pt3() {
     // Back to starting state
     gNotificationCheck = null;
     gTestCheck = run_test_pt4;
     load_blocklist("bug455906_start.xml");
   });
 }
 
 function run_test_pt4() {
-  AddonManager.getAddonByID(ADDONS[4].id, function(addon) {
+  AddonManager.getAddonByID(ADDONS[4].id, callback_soon(function(addon) {
     addon.userDisabled = false;
     PLUGINS[4].enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     restartManager();
     check_initial_state(function() {
       gNotificationCheck = check_notification_pt4;
       gTestCheck = check_test_pt4;
       load_blocklist("bug455906_empty.xml");
     });
-  });
+  }));
 }
 
 function check_notification_pt4(args) {
   dump("Checking notification pt 4\n");
 
   // Should be just the dummy add-on to force this notification
   do_check_eq(args.list.length, 1);
   do_check_false(args.list[0].item instanceof Ci.nsIPluginTag);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js
@@ -85,11 +85,11 @@ function run_test_2() {
     do_check_true(a2.isActive);
     do_check_neq(a3, null);
     do_check_true(a3.isActive);
     do_check_neq(a4, null);
     do_check_true(a4.isActive);
     do_check_neq(a5, null);
     do_check_true(a5.isActive);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js
@@ -84,11 +84,11 @@ function run_test_2() {
     do_check_true(a2.isActive);
     do_check_neq(a3, null);
     do_check_true(a3.isActive);
     do_check_neq(a4, null);
     do_check_true(a4.isActive);
     do_check_neq(a5, null);
     do_check_true(a5.isActive);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js
@@ -82,11 +82,11 @@ function run_test_2() {
     do_check_false(a2.isActive);
     do_check_neq(a3, null);
     do_check_false(a3.isActive);
     do_check_neq(a4, null);
     do_check_true(a4.isActive);
     do_check_neq(a5, null);
     do_check_true(a5.isActive);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js
@@ -49,11 +49,11 @@ function run_test_1() {
 function run_test_2() {
   Services.prefs.setBoolPref("extensions.checkCompatibility.2.0p", false);
 
   restartManager();
   AddonManager.getAddonByID(ID, function(addon) {
     do_check_neq(addon, null);
     do_check_false(addon.isActive);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js
@@ -11,17 +11,17 @@ function run_test() {
 
   installAllFiles([do_get_file("data/test_bug526598_1.xpi"),
                    do_get_file("data/test_bug526598_2.xpi")], function() {
 
     restartManager();
 
     AddonManager.getAddonsByIDs(["bug526598_1@tests.mozilla.org",
                                  "bug526598_2@tests.mozilla.org"],
-                                 function([a1, a2]) {
+                                 callback_soon(function([a1, a2]) {
 
       do_check_neq(a1, null);
       do_check_true(a1.hasResource("install.rdf"));
       let uri = a1.getResourceURI("install.rdf");
       do_check_true(uri instanceof AM_Ci.nsIFileURL);
       let file = uri.file;
       do_check_true(file.exists());
       do_check_true(file.isReadable());
@@ -42,13 +42,13 @@ function run_test() {
       restartManager();
 
       AddonManager.getAddonsByIDs(["bug526598_1@tests.mozilla.org",
                                    "bug526598_2@tests.mozilla.org"],
                                    function([newa1, newa2]) {
         do_check_eq(newa1, null);
         do_check_eq(newa2, null);
 
-        do_test_finished();
+        do_execute_soon(do_test_finished);
       });
-    });
+    }));
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js
@@ -26,12 +26,12 @@ function run_test() {
 
       // We don't understand executable permissions on Windows since we don't
       // support NTFS permissions so we don't need to test there. OSX's isExecutable
       // only tests if the file is an application so it is better to just check the
       // raw permission bits
       if (!("nsIWindowsRegKey" in Components.interfaces))
         do_check_true((file.permissions & 0100) == 0100);
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js
@@ -327,34 +327,34 @@ function run_test() {
     check_startup_changes("installed", []);
     check_startup_changes("updated", []);
     check_startup_changes("uninstalled", ["addon1@tests.mozilla.org"]);
     check_startup_changes("disabled", []);
     check_startup_changes("enabled", []);
 
     AddonManager.getAddonsByIDs(["bug542391_2@tests.mozilla.org",
                                  "bug542391_4@tests.mozilla.org"],
-                                 function disable_and_restart([a2, a4]) {
+                                 callback_soon(function disable_and_restart([a2, a4]) {
       do_check_true(a2 != null && a4 != null);
       a2.userDisabled = true;
       a4.userDisabled = true;
       restartManager();
       check_startup_changes("installed", []);
       check_startup_changes("updated", []);
       check_startup_changes("uninstalled", []);
       check_startup_changes("disabled", []);
       check_startup_changes("enabled", []);
 
       AddonManager.getAddonsByIDs(["bug542391_1@tests.mozilla.org",
                                    "bug542391_2@tests.mozilla.org",
                                    "bug542391_3@tests.mozilla.org",
                                    "bug542391_4@tests.mozilla.org",
                                    "bug542391_5@tests.mozilla.org",
                                    "bug542391_6@tests.mozilla.org"],
-                                   function(addons) {
+                                   callback_soon(function(addons) {
         check_state_v1(addons);
 
         WindowWatcher.expected = true;
         restartManager("2");
         check_startup_changes("installed", []);
         check_startup_changes("updated", []);
         check_startup_changes("uninstalled", []);
         check_startup_changes("disabled", ["bug542391_1@tests.mozilla.org"]);
@@ -367,18 +367,18 @@ function run_test() {
                                      "bug542391_4@tests.mozilla.org",
                                      "bug542391_5@tests.mozilla.org",
                                      "bug542391_6@tests.mozilla.org"],
                                      function(addons) {
           check_state_v2(addons);
 
           do_execute_soon(run_test_1);
         });
-      });
-    });
+      }));
+    }));
   });
 }
 
 function end_test() {
   testserver.stop(do_test_finished);
 }
 
 // Upgrade to version 3 which will appDisable two more add-ons. Check that the
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js
@@ -1,17 +1,15 @@
 /* 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;
@@ -38,37 +36,36 @@ function run_test() {
   run_test_1();
 }
 
 function end_test() {
   gServer.stop(do_test_finished);
 }
 
 function run_test_1() {
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     shutdownManager();
 
-    let db = gProfD.clone();
-    db.append(EXTENSIONS_DB);
-    db.remove(true);
+    gExtensionsJSON.remove(true);
 
     do_execute_soon(check_test_1);
-  });
+  }));
 }
 
 function check_test_1() {
   startupManager(false);
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
-    let db = gProfD.clone();
-    db.append(EXTENSIONS_DB);
-    do_check_true(db.exists());
-    do_check_true(db.fileSize > 0);
+    // due to delayed write, the file may not exist until
+    // after shutdown
+    shutdownManager();
+    do_check_true(gExtensionsJSON.exists());
+    do_check_true(gExtensionsJSON.fileSize > 0);
 
     end_test();
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js
@@ -60,17 +60,17 @@ function run_test() {
     do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE));
     do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE));
 
     run_test_1(d, a);
   });
 }
 
 function end_test() {
-  do_test_finished();
+  do_execute_soon(do_test_finished);
 }
 
 // Checks switching to a different theme and back again leaves everything the
 // same
 function run_test_1(d, a) {
   a.userDisabled = false;
 
   do_check_true(d.userDisabled);
@@ -191,17 +191,17 @@ function run_test_2() {
 
     do_execute_soon(check_test_2);
   });
 }
 
 function check_test_2() {
   restartManager();
   AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
-                               "alternate@tests.mozilla.org"], function([d, a]) {
+                               "alternate@tests.mozilla.org"], callback_soon(function([d, a]) {
     do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "alternate/1.0");
 
     do_check_true(d.userDisabled);
     do_check_false(d.appDisabled);
     do_check_false(d.isActive);
     do_check_false(isThemeInAddonsList(profileDir, d.id));
     do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE));
     do_check_true(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE));
@@ -250,10 +250,10 @@ function check_test_2() {
       do_check_false(a.appDisabled);
       do_check_false(a.isActive);
       do_check_false(isThemeInAddonsList(profileDir, a.id));
       do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE));
       do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE));
 
       end_test();
     });
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js
@@ -23,17 +23,17 @@ function run_test() {
     }]
   }, profileDir);
   // Attempt to make this look like it was added some time in the past so
   // the update makes the last modified time change.
   setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
 
   startupManager();
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a) {
     do_check_neq(a, null);
     do_check_eq(a.version, "1.0");
     do_check_false(a.userDisabled);
     do_check_true(a.appDisabled);
     do_check_false(a.isActive);
     do_check_false(isExtensionInAddonsList(profileDir, a.id));
 
     writeInstallRDFForExtension({
@@ -52,12 +52,12 @@ function run_test() {
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a) {
       do_check_neq(a, null);
       do_check_eq(a.version, "2.0");
       do_check_false(a.userDisabled);
       do_check_false(a.appDisabled);
       do_check_true(a.isActive);
       do_check_true(isExtensionInAddonsList(profileDir, a.id));
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js
@@ -101,12 +101,12 @@ function run_test_1() {
 // Verifies that a subsequent call gets the same add-on from the cache
 function run_test_2() {
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.name, "Test 1");
 
     do_check_eq(a1, gAddon);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js
@@ -42,12 +42,12 @@ function check_test_1() {
     do_check_eq(installs.length, 0);
 
     AddonManager.getAddonByID("bug567184@tests.mozilla.org", function(b1) {
       do_check_neq(b1, null);
       do_check_true(b1.appDisabled);
       do_check_false(b1.userDisabled);
       do_check_false(b1.isActive);
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js
@@ -136,12 +136,12 @@ function run_test() {
     do_check_neq(a6, null);
     do_check_true(a6.appDisabled);
     do_check_false(a6.isActive);
 
     do_check_neq(a6, null);
     do_check_true(a6.appDisabled);
     do_check_false(a6.isActive);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
 
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js
@@ -33,17 +33,17 @@ profileDir.append("extensions");
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2");
 
   writeInstallRDFForExtension(addon1, profileDir);
 
   startupManager();
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     a1.uninstall();
 
     shutdownManager();
 
     var dest = profileDir.clone();
     dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
     dest.remove(true);
 
@@ -55,12 +55,12 @@ function run_test() {
                                  "addon2@tests.mozilla.org"],
                                 function([a1, a2]) {
       // Addon1 should no longer be installed
       do_check_eq(a1, null);
 
       // Addon2 should have been detected
       do_check_neq(a2, null);
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js
@@ -91,49 +91,49 @@ function run_test_1() {
       fstream.init(uri.QueryInterface(AM_Ci.nsIFileURL).file, -1, 0, 0);
 
       installAllFiles([do_get_addon("test_bug587088_2")], function() {
 
         check_addon_upgrading(a1);
 
         restartManager();
 
-        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+        AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
           check_addon_upgrading(a1);
 
           restartManager();
 
-          AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+          AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
             check_addon_upgrading(a1);
 
             fstream.close();
 
             restartManager();
 
             AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
               check_addon(a1, "2.0");
 
               a1.uninstall();
               do_execute_soon(run_test_2);
             });
-          });
-        });
+          }));
+        }));
       });
     });
   });
 }
 
 // Test that a failed uninstall gets rolled back
 function run_test_2() {
   restartManager();
 
   installAllFiles([do_get_addon("test_bug587088_1")], function() {
     restartManager();
 
-    AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+    AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
       check_addon(a1, "1.0");
 
       // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons.
       let uri = a1.getResourceURI("install.rdf");
       if (uri.schemeIs("jar"))
         uri = a1.getResourceURI();
 
       let fstream = AM_Cc["@mozilla.org/network/file-input-stream;1"].
@@ -141,34 +141,34 @@ function run_test_2() {
       fstream.init(uri.QueryInterface(AM_Ci.nsIFileURL).file, -1, 0, 0);
 
       a1.uninstall();
 
       check_addon_uninstalling(a1);
 
       restartManager();
 
-      AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+      AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
         check_addon_uninstalling(a1, true);
 
         restartManager();
 
-        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+        AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
           check_addon_uninstalling(a1, true);
 
           fstream.close();
 
           restartManager();
 
           AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
             do_check_eq(a1, null);
             var dir = profileDir.clone();
             dir.append(do_get_expected_addon_name("addon1@tests.mozilla.org"));
             do_check_false(dir.exists());
             do_check_false(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org"));
 
-            do_test_finished();
+            do_execute_soon(do_test_finished);
           });
-        });
-      });
-    });
+        }));
+      }));
+    }));
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js
@@ -30,11 +30,11 @@ function run_test_1() {
 
 function run_test_2() {
   restartManager();
 
   AddonManager.getAddonByID("{2f69dacd-03df-4150-a9f1-e8a7b2748829}", function(a1) {
     do_check_neq(a1, null);
     do_check_true(isExtensionInAddonsList(profileDir, a1.id));
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js
@@ -130,11 +130,11 @@ function run_test_3() {
 
     do_check_neq(a2, null);
     do_check_true(a2.isActive);
     do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL));
     do_check_eq(a2.scope, AddonManager.SCOPE_USER);
 
     do_check_eq(a3, null);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js
@@ -16,11 +16,11 @@ function run_test() {
   startupManager();
 
   do_test_pending();
 
   test_string_compare();
 
   AddonManager.getAddonByID("foo", function(aAddon) {
     test_string_compare();
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug619730.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug619730.js
@@ -9,17 +9,23 @@ const Cu = Components.utils;
 
 Cu.import("resource://testing-common/httpd.js");
 
 var gTestserver = new HttpServer();
 gTestserver.start(-1);
 gPort = gTestserver.identity.primaryPort;
 mapFile("/data/test_bug619730.xml", gTestserver);
 
-function load_blocklist(file) {
+function load_blocklist(file, aCallback) {
+  Services.obs.addObserver(function() {
+    Services.obs.removeObserver(arguments.callee, "blocklist-updated");
+
+    do_execute_soon(aCallback);
+  }, "blocklist-updated", false);
+  
   Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
                              gPort + "/data/" + file);
   var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
                   getService(Ci.nsITimerCallback);
   blocklist.notify(null);
 }
 
 var gSawGFX = false;
@@ -44,14 +50,15 @@ function run_test() {
     do_check_eq(aSubject.getAttribute("testattr"), "FOO");
     do_check_eq(aSubject.childNodes.length, 3);
     gSawTest = true;
   }, "blocklist-data-testItems", false);
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     do_check_true(gSawGFX);
     do_check_true(gSawTest);
-
-    gTestserver.stop(do_test_finished);
   }, "blocklist-data-fooItems", false);
 
-  load_blocklist("test_bug619730.xml");
+  // Need to wait for the blocklist to load; Bad Things happen if the test harness
+  // shuts down AddonManager before the blocklist service is done telling it about
+  // changes
+  load_blocklist("test_bug619730.xml", () => gTestserver.stop(do_test_finished));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js
@@ -79,17 +79,17 @@ function run_test_1() {
     do_check_true(a2.isActive);
     do_check_false(isExtensionInAddonsList(userDir, a2.id));
     do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1);
 
     a1.findUpdates({
       onUpdateFinished: function() {
         restartManager();
 
-        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+        AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
           do_check_neq(a1, null);
           do_check_false(a1.appDisabled);
           do_check_true(a1.isActive);
           do_check_true(isExtensionInAddonsList(userDir, a1.id));
 
           shutdownManager();
 
           do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0);
@@ -110,27 +110,27 @@ function run_test_1() {
             do_check_true(isExtensionInAddonsList(userDir, a1.id));
 
             do_check_neq(a2, null);
             do_check_false(a2.appDisabled);
             do_check_true(a2.isActive);
             do_check_false(isExtensionInAddonsList(userDir, a2.id));
             do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1);
 
-            run_test_2();
+            do_execute_soon(run_test_2);
           });
-        });
+        }));
       }
     }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
   });
 }
 
 //Set up the profile
 function run_test_2() {
-  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) {
    do_check_neq(a2, null);
    do_check_false(a2.appDisabled);
    do_check_true(a2.isActive);
    do_check_false(isExtensionInAddonsList(userDir, a2.id));
    do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1);
 
    a2.userDisabled = true;
    do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0);
@@ -155,10 +155,10 @@ function run_test_2() {
      do_check_neq(a2, null);
      do_check_true(a2.userDisabled);
      do_check_false(a2.isActive);
      do_check_false(isExtensionInAddonsList(userDir, a2.id));
      do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0);
 
      end_test();
    });
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
@@ -103,24 +103,18 @@ 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
-      let dbfile = gProfD.clone();
-      dbfile.append("extensions.sqlite");
-      let db = AM_Cc["@mozilla.org/storage/service;1"].
-               getService(AM_Ci.mozIStorageService).
-               openDatabase(dbfile);
-      db.schemaVersion = 1;
+      changeXPIDBVersion(1);
       Services.prefs.setIntPref("extensions.databaseSchema", 1);
-      db.close();
 
       let jsonfile = gProfD.clone();
       jsonfile.append("extensions");
       jsonfile.append("staged");
       jsonfile.append("addon3@tests.mozilla.org.json");
       do_check_true(jsonfile.exists());
 
       // Remove an unnecessary property from the cached manifest
@@ -168,20 +162,20 @@ function run_test_1() {
         do_check_false(a2.userDisabled);
         do_check_true(a2.isActive);
         do_check_true(isExtensionInAddonsList(profileDir, addon2.id));
 
         // Should stay enabled because we migrate the compat info from
         // the previous version of the DB
         do_check_neq(a3, null);
         do_check_eq(a3.version, "2.0");
-        do_check_false(a3.appDisabled);
+        todo_check_false(a3.appDisabled); // XXX unresolved issue
         do_check_false(a3.userDisabled);
-        do_check_true(a3.isActive);
-        do_check_true(isExtensionInAddonsList(profileDir, addon3.id));
+        todo_check_true(a3.isActive); // XXX same
+        todo_check_true(isExtensionInAddonsList(profileDir, addon3.id)); // XXX same
 
         do_check_neq(a4, null);
         do_check_eq(a4.version, "2.0");
         do_check_true(a4.appDisabled);
         do_check_false(a4.userDisabled);
         do_check_false(a4.isActive);
         do_check_false(isExtensionInAddonsList(profileDir, addon4.id));
 
@@ -250,24 +244,18 @@ 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
-      let dbfile = gProfD.clone();
-      dbfile.append("extensions.sqlite");
-      let db = AM_Cc["@mozilla.org/storage/service;1"].
-               getService(AM_Ci.mozIStorageService).
-               openDatabase(dbfile);
-      db.schemaVersion = 1;
+      changeXPIDBVersion(1);
       Services.prefs.setIntPref("extensions.databaseSchema", 1);
-      db.close();
 
       let jsonfile = gProfD.clone();
       jsonfile.append("extensions");
       jsonfile.append("staged");
       jsonfile.append("addon3@tests.mozilla.org.json");
       do_check_true(jsonfile.exists());
 
       // Remove an unnecessary property from the cached manifest
@@ -297,17 +285,17 @@ function run_test_2() {
 
       gAppInfo.version = "2";
       startupManager(true);
 
       AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                    "addon2@tests.mozilla.org",
                                    "addon3@tests.mozilla.org",
                                    "addon4@tests.mozilla.org"],
-                                  function([a1, a2, a3, a4]) {
+                                  callback_soon(function([a1, a2, a3, a4]) {
         do_check_neq(a1, null);
         do_check_eq(a1.version, "2.0");
         do_check_true(a1.appDisabled);
         do_check_false(a1.userDisabled);
         do_check_false(a1.isActive);
         do_check_false(isExtensionInAddonsList(profileDir, addon1.id));
 
         do_check_neq(a2, null);
@@ -316,20 +304,20 @@ function run_test_2() {
         do_check_false(a2.userDisabled);
         do_check_true(a2.isActive);
         do_check_true(isExtensionInAddonsList(profileDir, addon2.id));
 
         // Should become appDisabled because we migrate the compat info from
         // the previous version of the DB
         do_check_neq(a3, null);
         do_check_eq(a3.version, "2.0");
-        do_check_true(a3.appDisabled);
+        todo_check_true(a3.appDisabled);
         do_check_false(a3.userDisabled);
-        do_check_false(a3.isActive);
-        do_check_false(isExtensionInAddonsList(profileDir, addon3.id));
+        todo_check_false(a3.isActive);
+        todo_check_false(isExtensionInAddonsList(profileDir, addon3.id));
 
         do_check_neq(a4, null);
         do_check_eq(a4.version, "2.0");
         do_check_false(a4.appDisabled);
         do_check_false(a4.userDisabled);
         do_check_true(a4.isActive);
         do_check_true(isExtensionInAddonsList(profileDir, addon4.id));
 
@@ -341,12 +329,12 @@ function run_test_2() {
         a2.uninstall();
         a3.uninstall();
         a4.uninstall();
         restartManager();
 
         shutdownManager();
 
         do_test_finished();
-      });
+      }));
     };
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js
@@ -19,19 +19,17 @@ function run_test() {
     prepare_test({
       "bug675371@tests.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
-    ], function() {
-      do_execute_soon(check_test)
-    });
+    ], callback_soon(check_test));
     install.install();
   });
 }
 
 function check_test() {
   AddonManager.getAddonByID("bug675371@tests.mozilla.org", do_exception_wrap(function(addon) {
     do_check_neq(addon, null);
     do_check_true(addon.isActive);
@@ -83,11 +81,11 @@ function check_test() {
     target.active = false;
     try {
       Services.scriptloader.loadSubScript("chrome://bug675371/content/test.js", target);
       do_throw("Chrome file should not have been found");
     } catch (e) {
       do_check_false(target.active);
     }
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js
@@ -30,11 +30,11 @@ function run_test() {
   AddonManager.getAddonsByIDs(["bug740612_1@tests.mozilla.org",
                                "bug740612_2@tests.mozilla.org"],
                                function([a1, a2]) {
     do_check_neq(a1, null);
     do_check_neq(a2, null);
     do_check_eq(getInstalledVersion(), "1.0");
     do_check_eq(getActiveVersion(), "1.0");
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js
@@ -76,11 +76,11 @@ function run_test() {
     do_check_in_crash_annotation(addon1.id, addon1.version);
     do_check_neq(a2, null);
     do_check_in_crash_annotation(addon2.id, addon2.version);
     do_check_neq(a3, null);
     do_check_in_crash_annotation(addon3.id, addon3.version);
     do_check_neq(a4, null);
     do_check_in_crash_annotation(addon4.id, addon4.version);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js
@@ -102,11 +102,11 @@ function run_test_2() {
     listener2.sawEvent = false;
     do_check_true(listener3.sawEvent);
     listener3.sawEvent = false;
 
     AddonManager.removeInstallListener(listener1);
     AddonManager.removeInstallListener(listener2);
     AddonManager.removeInstallListener(listener3);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js
@@ -112,11 +112,11 @@ function run_test_4() {
     gExpectedFile = gProfD.clone();
     gExpectedFile.append("extensions");
     gExpectedFile.append("addon2@tests.mozilla.org.xpi");
 
     a2.uninstall();
     do_check_true(gCacheFlushed);
     gCacheFlushed = false;
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js
@@ -186,11 +186,11 @@ function run_test_4() {
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org",
                                "addon5@tests.mozilla.org"],
                                function([a1, a2, a3, a4, a5]) {
     check_state(false, a1, a2, a3, a4, a5);
 
-    do_test_finished("checkcompatibility.js");
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
@@ -246,20 +246,18 @@ 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();
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    dbfile.remove(true);
-    dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
+    gExtensionsJSON.remove(true);
+    gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
     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,20 +247,18 @@ 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();
-    var dbfile = gProfD.clone();
-    dbfile.append("extensions.sqlite");
-    dbfile.remove(true);
-    dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
+    gExtensionsJSON.remove(true);
+    gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
     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",
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// This tests the data in extensions.sqlite for general sanity, making sure
-// rows in one table only reference rows in another table that actually exist.
-
-
-function check_db() {
-  do_print("Checking DB sanity...");
-  var dbfile = gProfD.clone();
-  dbfile.append("extensions.sqlite");
-  var db = Services.storage.openDatabase(dbfile);
-
-  do_print("Checking locale_strings references rows in locale correctly...");
-  let localeStringsStmt = db.createStatement("SELECT * FROM locale_strings");
-  let localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id");
-  let i = 0;
-  while (localeStringsStmt.executeStep()) {
-    i++;
-    localeStmt.params.locale_id = localeStringsStmt.row.locale_id;
-    do_check_true(localeStmt.executeStep());
-    do_check_eq(localeStmt.row.count, 1);
-    localeStmt.reset();
-  }
-  localeStmt.finalize();
-  localeStringsStmt.finalize();
-  do_print("Done. " + i + " rows in locale_strings checked.");
-
-
-  do_print("Checking locale references rows in addon_locale and addon correctly...");
-  localeStmt = db.createStatement("SELECT * FROM locale");
-  let addonLocaleStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon_locale WHERE locale_id=:locale_id");
-  let addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE defaultLocale=:locale_id");
-  i = 0;
-  while (localeStmt.executeStep()) {
-    i++;
-    addonLocaleStmt.params.locale_id = localeStmt.row.id;
-    do_check_true(addonLocaleStmt.executeStep());
-    if (addonLocaleStmt.row.count == 0) {
-      addonStmt.params.locale_id = localeStmt.row.id;
-      do_check_true(addonStmt.executeStep());
-      do_check_eq(addonStmt.row.count, 1);
-    } else {
-      do_check_eq(addonLocaleStmt.row.count, 1);
-    }
-    addonLocaleStmt.reset();
-    addonStmt.reset();
-  }
-  addonLocaleStmt.finalize();
-  localeStmt.finalize();
-  addonStmt.finalize();
-  do_print("Done. " + i + " rows in locale checked.");
-
-
-  do_print("Checking addon_locale references rows in locale correctly...");
-  addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale");
-  localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id");
-  i = 0;
-  while (addonLocaleStmt.executeStep()) {
-    i++;
-    localeStmt.params.locale_id = addonLocaleStmt.row.locale_id;
-    do_check_true(localeStmt.executeStep());
-    do_check_eq(localeStmt.row.count, 1);
-    localeStmt.reset();
-  }
-  addonLocaleStmt.finalize();
-  localeStmt.finalize();
-  do_print("Done. " + i + " rows in addon_locale checked.");
-
-
-  do_print("Checking addon_locale references rows in addon correctly...");
-  addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale");
-  addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id");
-  i = 0;
-  while (addonLocaleStmt.executeStep()) {
-    i++;
-    addonStmt.params.addon_internal_id = addonLocaleStmt.row.addon_internal_id;
-    do_check_true(addonStmt.executeStep());
-    do_check_eq(addonStmt.row.count, 1);
-    addonStmt.reset();
-  }
-  addonLocaleStmt.finalize();
-  addonStmt.finalize();
-  do_print("Done. " + i + " rows in addon_locale checked.");
-
-
-  do_print("Checking addon references rows in locale correctly...");
-  addonStmt = db.createStatement("SELECT * FROM addon");
-  localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:defaultLocale");
-  i = 0;
-  while (addonStmt.executeStep()) {
-    i++;
-    localeStmt.params.defaultLocale = addonStmt.row.defaultLocale;
-    do_check_true(localeStmt.executeStep());
-    do_check_eq(localeStmt.row.count, 1);
-    localeStmt.reset();
-  }
-  addonStmt.finalize();
-  localeStmt.finalize();
-  do_print("Done. " + i + " rows in addon checked.");
-
-
-  do_print("Checking targetApplication references rows in addon correctly...");
-  let targetAppStmt = db.createStatement("SELECT * FROM targetApplication");
-  addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id");
-  i = 0;
-  while (targetAppStmt.executeStep()) {
-    i++;
-    addonStmt.params.addon_internal_id = targetAppStmt.row.addon_internal_id;
-    do_check_true(addonStmt.executeStep());
-    do_check_eq(addonStmt.row.count, 1);
-    addonStmt.reset();
-  }
-  targetAppStmt.finalize();
-  addonStmt.finalize();
-  do_print("Done. " + i + " rows in targetApplication checked.");
-
-
-  do_print("Checking targetPlatform references rows in addon correctly...");
-  let targetPlatformStmt = db.createStatement("SELECT * FROM targetPlatform");
-  addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id");
-  i = 0;
-  while (targetPlatformStmt.executeStep()) {
-    i++;
-    addonStmt.params.addon_internal_id = targetPlatformStmt.row.addon_internal_id;
-    do_check_true(addonStmt.executeStep());
-    do_check_eq(addonStmt.row.count, 1);
-    addonStmt.reset();
-  }
-  targetPlatformStmt.finalize();
-  addonStmt.finalize();
-  do_print("Done. " + i + " rows in targetPlatform checked.");
-
-
-  db.close();
-  do_print("Done checking DB sanity.");
-}
-
-function run_test() {
-  do_test_pending();
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
-  startupManager();
-
-  installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_1);
-}
-
-function run_test_1() {
-  shutdownManager();
-  check_db();
-  startupManager();
-
-  AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) {
-    aAddon.uninstall();
-
-    shutdownManager();
-    check_db();
-    startupManager();
-
-    installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_2);
-  });
-}
-
-function run_test_2() {
-  installAllFiles([do_get_addon("test_db_sanity_1_2")], function() {
-    shutdownManager();
-    check_db();
-    startupManager();
-    run_test_3();
-  });
-}
-
-function run_test_3() {
-  AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) {
-    aAddon.uninstall();
-
-    shutdownManager();
-    check_db();
-
-    do_test_finished();
-  });
-}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js
@@ -144,17 +144,17 @@ function run_test_1() {
     }, [
       "onInstallStarted",
       "onInstallEnded",
     ], function() {
       do_check_true(addon.hasResource("install.rdf"));
       HunspellEngine.listener = function(aEvent) {
         HunspellEngine.listener = null;
         do_check_eq(aEvent, "addDirectory");
-        check_test_1();
+        do_execute_soon(check_test_1);
       };
     });
     install.install();
   });
 }
 
 function check_test_1() {
   AddonManager.getAllInstalls(function(installs) {
@@ -312,27 +312,28 @@ function run_test_7() {
   });
 }
 
 function check_test_7() {
   ensure_test_completed();
   do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
   do_check_not_in_crash_annotation("ab-CD@dictionaries.addons.mozilla.org", "1.0");
 
-  AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+  AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org",
+   callback_soon(function(b1) {
     do_check_eq(b1, null);
 
     restartManager();
 
     AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(newb1) {
       do_check_eq(newb1, null);
 
       do_execute_soon(run_test_8);
     });
-  });
+  }));
 }
 
 // Test that a bootstrapped extension dropped into the profile loads properly
 // on startup and doesn't cause an EM restart
 function run_test_8() {
   shutdownManager();
 
   let dir = profileDir.clone();
@@ -422,47 +423,49 @@ function run_test_12() {
 
 // Tests that bootstrapped extensions don't get loaded when in safe mode
 function run_test_16() {
   restartManager();
 
   installAllFiles([do_get_addon("test_dictionary")], function() {
     // spin the event loop to let the addon finish starting
    do_execute_soon(function check_installed_dictionary() {
-    AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+    AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org",
+     callback_soon(function(b1) {
       // Should have installed and started
       do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
 
       shutdownManager();
 
       // Should have stopped
       do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
 
       gAppInfo.inSafeMode = true;
       startupManager(false);
 
-      AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+      AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org",
+       callback_soon(function(b1) {
         // Should still be stopped
         do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
         do_check_false(b1.isActive);
 
         shutdownManager();
         gAppInfo.inSafeMode = false;
         startupManager(false);
 
         // Should have started
         do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
 
         AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
           b1.uninstall();
 
           do_execute_soon(run_test_17);
         });
-      });
-    });
+      }));
+    }));
    });
   });
 }
 
 // Check that a bootstrapped extension in a non-profile location is loaded
 function run_test_17() {
   shutdownManager();
 
@@ -478,32 +481,33 @@ function run_test_17() {
   dir.append("dictionaries");
   dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
   dir.append("ab-CD.dic");
   zip.extract("dictionaries/ab-CD.dic", dir);
   zip.close();
 
   startupManager();
 
-  AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+  AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org",
+   callback_soon(function(b1) {
     // Should have installed and started
     do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
     do_check_neq(b1, null);
     do_check_eq(b1.version, "1.0");
     do_check_true(b1.isActive);
 
     // From run_test_21
     dir = userExtDir.clone();
     dir.append("ab-CD@dictionaries.addons.mozilla.org");
     dir.remove(true);
 
     restartManager();
 
     run_test_23();
-  });
+  }));
 }
 
 // Tests that installing from a URL doesn't require a restart
 function run_test_23() {
   prepare_test({ }, [
     "onNewInstall"
   ]);
 
@@ -560,25 +564,25 @@ function check_test_23() {
       do_check_true(b1.isActive);
       do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
       do_check_true(b1.hasResource("install.rdf"));
       do_check_false(b1.hasResource("bootstrap.js"));
       do_check_in_crash_annotation("ab-CD@dictionaries.addons.mozilla.org", "1.0");
 
       let dir = do_get_addon_root_uri(profileDir, "ab-CD@dictionaries.addons.mozilla.org");
 
-      AddonManager.getAddonsWithOperationsByTypes(null, function(list) {
+      AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) {
         do_check_eq(list.length, 0);
 
         restartManager();
         AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
           b1.uninstall();
           do_execute_soon(run_test_25);
         });
-      });
+      }));
     });
   });
 }
 
 // Tests that updating from a bootstrappable add-on to a normal add-on calls
 // the uninstall method
 function run_test_25() {
   restartManager();
@@ -587,17 +591,18 @@ function run_test_25() {
     HunspellEngine.listener = null;
     do_check_eq(aEvent, "addDirectory");
     do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
 
     installAllFiles([do_get_addon("test_dictionary_2")], function test_25_installed2() {
       // Needs a restart to complete this so the old version stays running
       do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
 
-      AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+      AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org",
+       callback_soon(function(b1) {
         do_check_neq(b1, null);
         do_check_eq(b1.version, "1.0");
         do_check_true(b1.isActive);
         do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
 
         restartManager();
 
         do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
@@ -605,31 +610,32 @@ function run_test_25() {
         AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
           do_check_neq(b1, null);
           do_check_eq(b1.version, "2.0");
           do_check_true(b1.isActive);
           do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
           do_execute_soon(run_test_26);
         });
-      });
+      }));
     });
   };
 
   installAllFiles([do_get_addon("test_dictionary")], function test_25_installed() { });
 }
 
 // Tests that updating from a normal add-on to a bootstrappable add-on calls
 // the install method
 function run_test_26() {
   installAllFiles([do_get_addon("test_dictionary")], function test_26_install() {
     // Needs a restart to complete this
     do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
 
-    AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+    AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org",
+     callback_soon(function(b1) {
       do_check_neq(b1, null);
       do_check_eq(b1.version, "2.0");
       do_check_true(b1.isActive);
       do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
 
       restartManager();
 
       do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
@@ -639,17 +645,17 @@ function run_test_26() {
         do_check_eq(b1.version, "1.0");
         do_check_true(b1.isActive);
         do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
 
         HunspellEngine.deactivate();
         b1.uninstall();
         do_execute_soon(run_test_27);
       });
-    });
+    }));
   });
 }
 
 // Tests that an update check from a normal add-on to a bootstrappable add-on works
 function run_test_27() {
   restartManager();
   writeInstallRDFForExtension({
     id: "ab-CD@dictionaries.addons.mozilla.org",
@@ -669,17 +675,17 @@ function run_test_27() {
       "onInstalling"
     ]
   }, [
     "onNewInstall",
     "onDownloadStarted",
     "onDownloadEnded",
     "onInstallStarted",
     "onInstallEnded"
-  ], check_test_27);
+  ], callback_soon(check_test_27));
 
   AddonManagerPrivate.backgroundUpdateCheck();
 }
 
 function check_test_27(install) {
   do_check_eq(install.existingAddon.pendingUpgrade.install, install);
 
   restartManager();
@@ -715,17 +721,17 @@ function run_test_28() {
       "onInstalling"
     ]
   }, [
     "onNewInstall",
     "onDownloadStarted",
     "onDownloadEnded",
     "onInstallStarted",
     "onInstallEnded"
-  ], check_test_28);
+  ], callback_soon(check_test_28));
 
   AddonManagerPrivate.backgroundUpdateCheck();
 }
 
 function check_test_28(install) {
   do_check_eq(install.existingAddon.pendingUpgrade.install, install);
 
   restartManager();
@@ -779,17 +785,17 @@ function check_test_29(install) {
     do_check_eq(b2.type, "dictionary");
 
     prepare_test({
       "gh@dictionaries.addons.mozilla.org": [
         ["onUninstalling", false],
         ["onUninstalled", false],
       ]
     }, [
-    ], finish_test_29);
+    ], callback_soon(finish_test_29));
 
     b2.uninstall();
   });
 }
 
 function finish_test_29() {
   testserver.stop(do_test_finished);
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js
@@ -27,17 +27,17 @@ var gIconURL = null;
 
 // Sets up the profile by installing an add-on.
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   startupManager();
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     do_check_eq(a1, null);
     do_check_not_in_crash_annotation(addon1.id, addon1.version);
 
     writeInstallRDFForExtension(addon1, profileDir, addon1.id, "icon.png");
     gIconURL = do_get_addon_root_uri(profileDir.clone(), addon1.id) + "icon.png";
 
     restartManager();
 
@@ -52,17 +52,17 @@ function run_test() {
       do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
       do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
       do_check_eq(newa1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE |
                                                     AddonManager.OP_NEEDS_RESTART_UNINSTALL);
       do_check_in_crash_annotation(addon1.id, addon1.version);
 
       run_test_1();
     });
-  });
+  }));
 }
 
 // Disabling an add-on should work
 function run_test_1() {
   prepare_test({
     "addon1@tests.mozilla.org": [
       "onDisabling"
     ]
@@ -78,17 +78,17 @@ function run_test_1() {
     do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_DISABLE));
     do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_ENABLE));
     do_check_eq(a1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE |
                                                AddonManager.OP_NEEDS_RESTART_UNINSTALL);
     do_check_in_crash_annotation(addon1.id, addon1.version);
 
     ensure_test_completed();
 
-    AddonManager.getAddonsWithOperationsByTypes(null, function(list) {
+    AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) {
       do_check_eq(list.length, 1);
       do_check_eq(list[0].id, "addon1@tests.mozilla.org");
 
       restartManager();
 
       AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) {
         do_check_neq(newa1, null);
         do_check_false(newa1.isActive);
@@ -99,17 +99,17 @@ function run_test_1() {
         do_check_false(isExtensionInAddonsList(profileDir, newa1.id));
         do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
         do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
         do_check_eq(newa1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE);
         do_check_not_in_crash_annotation(addon1.id, addon1.version);
 
         run_test_2();
       });
-    });
+    }));
   });
 }
 
 // Enabling an add-on should work.
 function run_test_2() {
   prepare_test({
     "addon1@tests.mozilla.org": [
       "onEnabling"
@@ -122,17 +122,17 @@ function run_test_2() {
     do_check_eq(a1.optionsURL, null);
     do_check_eq(a1.iconURL, gIconURL);
     do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_DISABLE));
     do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_ENABLE));
     do_check_eq(a1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE);
 
     ensure_test_completed();
 
-    AddonManager.getAddonsWithOperationsByTypes(null, function(list) {
+    AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) {
       do_check_eq(list.length, 1);
       do_check_eq(list[0].id, "addon1@tests.mozilla.org");
 
       restartManager();
 
       AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) {
         do_check_neq(newa1, null);
         do_check_true(newa1.isActive);
@@ -144,29 +144,29 @@ function run_test_2() {
         do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
         do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
         do_check_eq(newa1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE |
                                                       AddonManager.OP_NEEDS_RESTART_UNINSTALL);
         do_check_in_crash_annotation(addon1.id, addon1.version);
 
         run_test_3();
       });
-    });
+    }));
   });
 }
 
 // Disabling then enabling without restart should fire onOperationCancelled.
 function run_test_3() {
   prepare_test({
     "addon1@tests.mozilla.org": [
       "onDisabling"
     ]
   });
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     a1.userDisabled = true;
     ensure_test_completed();
     prepare_test({
       "addon1@tests.mozilla.org": [
         "onOperationCancelled"
       ]
     });
     a1.userDisabled = false;
@@ -183,12 +183,12 @@ function run_test_3() {
       do_check_false(newa1.userDisabled);
       do_check_eq(newa1.aboutURL, "chrome://foo/content/about.xul");
       do_check_eq(newa1.optionsURL, "chrome://foo/content/options.xul");
       do_check_eq(newa1.iconURL, "chrome://foo/content/icon.png");
       do_check_true(isExtensionInAddonsList(profileDir, newa1.id));
       do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
       do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js
@@ -144,27 +144,27 @@ function run_test_4() {
     do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE);
 
     do_execute_soon(run_test_5);
   });
 }
 
 // Tests that after uninstalling a restart doesn't re-install the extension
 function run_test_5() {
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     a1.uninstall();
 
     restartManager();
 
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
       do_check_eq(a1, null);
 
       do_execute_soon(run_test_6);
     });
-  });
+  }));
 }
 
 // Tests that upgrading the application still doesn't re-install the uninstalled
 // extension
 function run_test_6() {
   restartManager("4");
 
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
@@ -252,11 +252,11 @@ function run_test_9() {
     do_check_true(addonDir.exists());
     do_check_true(addonDir.isDirectory());
     addonDir.append("dummy2.txt");
     do_check_true(addonDir.exists());
     do_check_true(addonDir.isFile());
 
     a2.uninstall();
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_dss.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_dss.js
@@ -110,17 +110,17 @@ function run_test() {
     do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_DISABLE));
     do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE));
 
     do_execute_soon(run_test_1);
   });
 }
 
 function end_test() {
-  do_test_finished();
+  do_execute_soon(do_test_finished);
 }
 
 // Checks enabling one theme disables the others
 function run_test_1() {
   prepare_test({
     "theme1@tests.mozilla.org": [
       ["onDisabling", false],
       "onDisabled"
@@ -587,17 +587,17 @@ function run_test_9() {
 
       do_execute_soon(run_test_10);
     });
   });
 }
 
 // Uninstalling a custom theme in use should require a restart
 function run_test_10() {
-  AddonManager.getAddonByID("theme2@tests.mozilla.org", function(oldt2) {
+  AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) {
     prepare_test({
       "theme2@tests.mozilla.org": [
         ["onEnabling", false],
         "onEnabled"
       ],
       "default@tests.mozilla.org": [
         ["onDisabling", false],
         "onDisabled"
@@ -606,17 +606,18 @@ function run_test_10() {
 
     oldt2.userDisabled = false;
 
     ensure_test_completed();
 
     restartManager();
 
     AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
-                                 "theme2@tests.mozilla.org"], function([d, t2]) {
+                                 "theme2@tests.mozilla.org"],
+                                 callback_soon(function([d, t2]) {
       do_check_true(t2.isActive);
       do_check_false(t2.userDisabled);
       do_check_false(t2.appDisabled);
       do_check_false(d.isActive);
       do_check_true(d.userDisabled);
       do_check_false(d.appDisabled);
 
       prepare_test({
@@ -633,18 +634,18 @@ function run_test_10() {
       t2.uninstall();
 
       ensure_test_completed();
       do_check_false(gLWThemeChanged);
 
       restartManager();
 
       do_execute_soon(run_test_11);
-    });
-  });
+    }));
+  }));
 }
 
 // Installing a custom theme not in use should not require a restart
 function run_test_11() {
   prepare_test({ }, [
     "onNewInstall"
   ]);
 
@@ -660,17 +661,17 @@ function run_test_11() {
     prepare_test({
       "theme1@tests.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
-    ], check_test_11);
+    ], callback_soon(check_test_11));
     install.install();
   });
 }
 
 function check_test_11() {
   restartManager();
   AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) {
     do_check_neq(t1, null);
@@ -717,17 +718,17 @@ function check_test_12() {
     do_check_false(gLWThemeChanged);
 
     do_execute_soon(run_test_13);
   });
 }
 
 // Updating a custom theme in use should require a restart
 function run_test_13() {
-  AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) {
+  AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) {
     prepare_test({
       "theme1@tests.mozilla.org": [
         ["onEnabling", false],
         "onEnabled"
       ],
       "default@tests.mozilla.org": [
         ["onDisabling", false],
         "onDisabled"
@@ -753,39 +754,39 @@ function run_test_13() {
 
       prepare_test({
         "theme1@tests.mozilla.org": [
           "onInstalling",
         ]
       }, [
         "onInstallStarted",
         "onInstallEnded",
-      ], check_test_13);
+      ], callback_soon(check_test_13));
       install.install();
     });
-  });
+  }));
 }
 
 function check_test_13() {
   restartManager();
 
   AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) {
     do_check_neq(t1, null);
     do_check_true(t1.isActive);
     do_check_false(gLWThemeChanged);
     t1.uninstall();
-    restartManager();
 
     do_execute_soon(run_test_14);
   });
 }
 
 // Switching from a lightweight theme to the default theme should not require
 // a restart
 function run_test_14() {
+  restartManager();
   LightweightThemeManager.currentTheme = {
     id: "1",
     version: "1",
     name: "Test LW Theme",
     description: "A test theme",
     author: "Mozilla",
     homepageURL: "http://localhost:" + gPort + "/data/index.html",
     headerURL: "http://localhost:" + gPort + "/data/header.png",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js
@@ -159,26 +159,26 @@ function run_test_2() {
     do_execute_soon(run_test_3);
   });
 }
 
 // Test that IDs persist across restart
 function run_test_3() {
   restartManager();
 
-  AddonManager.getAddonByID(gPluginIDs[0], function(p) {
+  AddonManager.getAddonByID(gPluginIDs[0], callback_soon(function(p) {
     do_check_neq(p, null);
     do_check_eq(p.name, "Duplicate Plugin 1");
     do_check_eq(p.description, "A duplicate plugin");
 
     // Reorder the plugins and restart again
     [PLUGINS[0], PLUGINS[1]] = [PLUGINS[1], PLUGINS[0]];
     restartManager();
 
     AddonManager.getAddonByID(gPluginIDs[0], function(p) {
       do_check_neq(p, null);
       do_check_eq(p.name, "Duplicate Plugin 1");
       do_check_eq(p.description, "A duplicate plugin");
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_error.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_error.js
@@ -80,11 +80,11 @@ function run_test_4() {
 
 // Checks that an add-on with an illegal ID shows an error
 function run_test_5() {
   AddonManager.getInstallForFile(do_get_addon("test_bug567173"), function(install) {
     do_check_neq(install, null);
     do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
     do_check_eq(install.error, AddonManager.ERROR_CORRUPT_FILE);
 
-    do_test_finished();
+    do_execute_soon(do_test_finished);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js
@@ -145,17 +145,17 @@ function run_test_2() {
       "addon1@tests.mozilla.org": [
         "onInstalling"
       ]
     }, [
       "onDownloadStarted",
       "onDownloadEnded",
       "onInstallStarted",
       "onInstallEnded"
-    ], check_test_2);
+    ], callback_soon(check_test_2));
 
     install.install();
   }, "application/x-xpinstall");
 }
 
 function check_test_2() {
   restartManager();
 
@@ -185,30 +185,30 @@ function check_test_2() {
 // Tests that uninstalling doesn't clobber the original sources
 function run_test_3() {
   restartManager();
 
   writePointer(addon1.id);
 
   restartManager();
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     a1.uninstall();
 
     restartManager();
 
     let source = sourceDir.clone();
     source.append(addon1.id);
     do_check_true(source.exists());
 
     do_execute_soon(run_test_4);
-  });
+  }));
 }
 
 // Tests that misnaming a pointer doesn't clobber the sources
 function run_test_4() {
   writePointer("addon2@tests.mozilla.org", addon1.id);
 
   restartManager();
 
@@ -233,17 +233,17 @@ function run_test_4() {
 function run_test_5() {
   var dest = writeInstallRDFForExtension(addon1, sourceDir);
   // Make sure the modification time changes enough to be detected.
   setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
   writePointer(addon1.id);
 
   restartManager();
 
-  AddonManager.getAddonByID(addon1.id, function(a1) {
+  AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     writeInstallRDFForExtension(addon2, sourceDir, addon1.id);
 
     restartManager();
 
     AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@@ -256,53 +256,53 @@ function run_test_5() {
       do_check_true(source.exists());
 
       let pointer = profileDir.clone();
       pointer.append(addon1.id);
       do_check_false(pointer.exists());
 
       do_execute_soon(run_test_6);
     });
-  });
+  }));
 }
 
 // Removing the pointer file should uninstall the add-on
 function run_test_6() {
   var dest = writeInstallRDFForExtension(addon1, sourceDir);
   // Make sure the modification time changes enough to be detected in run_test_8.
   setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
   writePointer(addon1.id);
 
   restartManager();
 
-  AddonManager.getAddonByID(addon1.id, function(a1) {
+  AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     let pointer = profileDir.clone();
     pointer.append(addon1.id);
     pointer.remove(false);
 
     restartManager();
 
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
       do_check_eq(a1, null);
 
       do_execute_soon(run_test_7);
     });
-  });
+  }));
 }
 
 // Removing the pointer file and replacing it with a directory should work
 function run_test_7() {
   writePointer(addon1.id);
 
   restartManager();
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     let pointer = profileDir.clone();
     pointer.append(addon1.id);
     pointer.remove(false);
 
     writeInstallRDFForExtension(addon1_2, profileDir);
@@ -310,75 +310,75 @@ function run_test_7() {
     restartManager();
 
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
       do_check_neq(a1, null);
       do_check_eq(a1.version, "2.0");
 
       a1.uninstall();
 
-      restartManager();
-
       do_execute_soon(run_test_8);
     });
-  });
+  }));
 }
 
 // Changes to the source files should be detected
 function run_test_8() {
+  restartManager();
+
   writePointer(addon1.id);
 
   restartManager();
 
-  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     writeInstallRDFForExtension(addon1_2, sourceDir);
 
     restartManager();
 
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
       do_check_neq(a1, null);
       do_check_eq(a1.version, "2.0");
 
       a1.uninstall();
 
-      restartManager();
-
       do_execute_soon(run_test_9);
     });
-  });
+  }));
 }
 
 // Removing the add-on the pointer file points at should uninstall the add-on
 function run_test_9() {
+  restartManager();
+
   var dest = writeInstallRDFForExtension(addon1, sourceDir);
   writePointer(addon1.id);
 
   restartManager();
 
-  AddonManager.getAddonByID(addon1.id, function(a1) {
+  AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
 
     dest.remove(true);
 
     restartManager();
 
     AddonManager.getAddonByID(addon1.id, function(a1) {
       do_check_eq(a1, null);
 
       let pointer = profileDir.clone();
       pointer.append(addon1.id);
       do_check_false(pointer.exists());
 
       do_execute_soon(run_test_10);
     });
-  });
+  }));
 }
 
 // Tests that installing a new add-on by pointer with a relative path works
 function run_test_10() {
   writeInstallRDFForExtension(addon1, sourceDir);
   writeRelativePointer(addon1.id);
 
   restartManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js
@@ -155,10 +155,10 @@ function onPrefChange(evt) {
     inspector3.prefs.get("fuel.fuel-test").events.addListener("change", onPrefChange2);
     inspector3.prefs.setValue("fuel.fuel-test", "change event2");
   });
 }
 
 function onPrefChange2(evt) {
   do_check_eq(evt.data, testdata.dummy);
 
-  do_test_finished();
+  do_execute_soon(do_test_finished);
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_general.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_general.js
@@ -13,17 +13,17 @@ function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   var count = 0;
   startupManager();
   AddonManager.getAddonsByTypes(null, function(list) {
     gCount = list.length;
 
-    run_test_1();
+    do_execute_soon(run_test_1);
   });
 }
 
 function run_test_1() {
   restartManager();
 
   AddonManager.getAddonsByTypes(null, function(addons) {
     do_check_eq(gCount, addons.length);
@@ -46,14 +46,13 @@ function run_test_2() {
 
     do_execute_soon(run_test_3);
   });
 }
 
 function run_test_3() {
   restartManager();
 
-  AddonManager.getAddonsByTypes(null, function(addons) {
+  AddonManager.getAddonsByTypes(null, callback_soon(function(addons) {
     do_check_eq(gCount, addons.length);
-    shutdownManager();
     do_test_finished();
-  });
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js
@@ -70,24 +70,25 @@ function run_test() {
 
         try {
           // hasResource should never throw an exception.
           do_check_false(a1.hasResource("icon.png"));
         } catch (e) {
           do_check_true(false);
         }
 
-        AddonManager.getInstallForFile(do_get_addon("test_getresource"), function(aInstall) {
+        AddonManager.getInstallForFile(do_get_addon("test_getresource"),
+            callback_soon(function(aInstall) {
           do_check_false(a1.hasResource("icon.png"));
           do_check_true(aInstall.addon.hasResource("icon.png"));
 
           restartManager();
 
           AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) {
             do_check_eq(newa1, null);
 
-            do_test_finished();
+            do_execute_soon(do_test_finished);
           });
-        });
+        }));
       });
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
@@ -71,12 +71,12 @@ function run_test() {
         do_check_true(a5.hasBinaryComponents);
       else
         do_check_false(a5.hasBinaryComponents);
       do_check_true(a5.isCompatible);
       do_check_false(a5.appDisabled);
       do_check_true(a5.isActive);
       do_check_true(isExtensionInAddonsList(profileDir, a5.id));
 
-      do_test_finished();
+      do_execute_soon(do_test_finished);
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js
@@ -45,17 +45,17 @@ function run_test_1() {
       "onInstalling"
     ]
   }, [
     "onNewInstall",
     "onDownloadStarted",
     "onDownloadEnded",
     "onInstallStarted",
     "onInstallEnded",
-  ], check_test_1);
+  ], callback_soon(check_test_1));
 
   // Fake a timer event
   gInternalManager.notify(null);
 }
 
 function check_test_1() {
   restartManager();
 
@@ -100,17 +100,17 @@ function run_test_3() {
       "onInstalling"
     ]
   }, [
     "onNewInstall",
     "onDownloadStarted",
     "onDownloadEnded",
     "onInstallStarted",
     "onInstallEnded",
-  ], check_test_3);
+  ], callback_soon(check_test_3));
 
   // Fake a timer event
   gInternalManager.notify(null);
 }
 
 function check_test_3() {
   restartManager();
 
@@ -184,17 +184,17 @@ function run_test_6() {
       "onInstalling"
     ]
   }, [
     "onNewInstall",
     "onDownloadStarted",
     "onDownloadEnded",
     "onInstallStarted",
     "onInstallEnded",
-  ], check_test_6);
+  ], callback_soon(check_test_6));
 
   // Fake a timer event
   gInternalManager.notify(null);
 }
 
 function check_test_6() {
   AddonManager.addInstallListener({
     onNewInstall: function() {
@@ -256,17 +256,17 @@ function check_test_7(aInstall) {
       "onInstalling"
     ]
   }, [
     "onNewInstall",
     "onDownloadStarted",
     "onDownloadEnded",
     "onInstallStarted",
     "onInstallEnded",
-  ], finish_test_7);
+  ], callback_soon(finish_test_7));
 
   // Fake a timer event
   gInternalManager.notify(null);
 }
 
 function finish_test_7() {
   restartManager();
 
@@ -321,25 +321,25 @@ function check_test_8() {
     "onInstallEnded",
   ], finish_test_8);
 
   // Fake a timer event
   gInternalManager.notify(null);
 }
 
 function finish_test_8() {
-  AddonManager.getAllInstalls(function(aInstalls) {
+  AddonManager.getAllInstalls(callback_soon(function(aInstalls) {
     do_check_eq(aInstalls.length, 1);
     do_check_eq(aInstalls[0].version, "2.0");
 
     restartManager();
 
-    AddonManager.getAddonByID("hotfix@tests.mozilla.org", function(aAddon) {
+    AddonManager.getAddonByID("hotfix@tests.mozilla.org", callback_soon(function(aAddon) {
       do_check_neq(aAddon, null);
       do_check_eq(aAddon.version, "2.0");
 
       aAddon.uninstall();
       restartManager();
 
       end_test();
-    });
-  });
+    }));
+  }));
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -119,17 +119,17 @@ function run_test_1() {
   });
 }
 
 function check_test_1(installSyncGUID) {
   ensure_test_completed();
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) {
     do_check_eq(olda1, null);
 
-    AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) {
+    AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) {
       do_check_eq(pendingAddons.length, 1);
       do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org");
       let uri = NetUtil.newURI(pendingAddons[0].iconURL);
       if (uri instanceof AM_Ci.nsIJARURI) {
         let jarURI = uri.QueryInterface(AM_Ci.nsIJARURI);
         let archiveURI = jarURI.JARFile;
         let archiveFile = archiveURI.QueryInterface(AM_Ci.nsIFileURL).file;
         let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
@@ -157,17 +157,17 @@ function check_test_1(installSyncGUID) {
       do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE));
       do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE));
 
       restartManager();
 
       AddonManager.getAllInstalls(function(activeInstalls) {
         do_check_eq(activeInstalls, 0);
 
-        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+        AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) {
           do_check_neq(a1, null);
           do_check_neq(a1.syncGUID, null);
           do_check_true(a1.syncGUID.length >= 9);
           do_check_eq(a1.syncGUID, installSyncGUID);
           do_check_eq(a1.type, "extension");
           do_check_eq(a1.version, "1.0");
           do_check_eq(a1.name, "Test 1");
           do_check_true(isExtensionInAddonsList(profileDir, a1.id));
@@ -193,20 +193,20 @@ function check_test_1(installSyncGUID) {
           do_check_eq(a1.getResourceURI("install.rdf").spec, uri + "install.rdf");
           do_check_eq(a1.iconURL, uri + "icon.png");
           do_check_eq(a1.icon64URL, uri + "icon64.png");
 
           a1.uninstall();
           restartManager();
           do_check_not_in_crash_annotation(a1.id, a1.version);
 
-          run_test_2();
-        });
+          do_execute_soon(run_test_2);
+        }));
       });
-    });
+    }));
   });
 }
 
 // Tests that an install from a url downloads.
 function run_test_2() {
   let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi";
   AddonManager.getInstallForURL(url, function(install) {
     do_check_neq(install, null);
@@ -270,17 +270,17 @@ function run_test_3(install) {
 function check_test_3(aInstall) {
   // Make the pending install have a sensible date
   let updateDate = Date.now();
   let extURI = aInstall.addon.getResourceURI("");
   let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file;
   setExtensionModifiedTime(ext, updateDate);
 
   ensure_test_completed();
-  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) {
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) {
     do_check_eq(olda2, null);
     restartManager();
 
     AddonManager.getAllInstalls(function(installs) {
       do_check_eq(installs, 0);
 
       AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
         do_check_neq(a2, null);
@@ -302,17 +302,17 @@ function check_test_3(aInstall) {
         if (Math.abs(difference) > MAX_TIME_DIFFERENCE)
           do_throw("Add-on update time was out by " + difference + "ms");
 
         gInstallDate = a2.installDate.getTime();
 
         run_test_4();
       });
     });