Backed out changesets ddad4d54c31e and ef85fdfd2fc7 (bug 1043863) for making browser_thumbnails_storage.js permafail on WinXP opt.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 26 May 2015 14:05:18 -0400
changeset 276466 c67f61497e3e4ebff41fd75943dbc39635ce7087
parent 276465 a7c9a6e1394ee981352080ec9632968c49a1a9be
child 276467 e840013600aa92a7715c2b11c36132a0785a0ded
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1043863
milestone41.0a1
backs outddad4d54c31ee9d0c714a9378976cdfbf260ac10
ef85fdfd2fc7ea4a7c80ce687b2e0b3677c61b3c
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changesets ddad4d54c31e and ef85fdfd2fc7 (bug 1043863) for making browser_thumbnails_storage.js permafail on WinXP opt.
browser/components/places/tests/unit/test_clearHistory_shutdown.js
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/History.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsPIPlacesDatabase.idl
toolkit/components/places/tests/PlacesTestUtils.jsm
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/history/test_remove.js
toolkit/components/places/tests/unit/test_PlacesUtils_invalidateCachedGuidFor.js
toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js
toolkit/components/places/tests/unit/test_history_notifications.js
toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js
toolkit/components/thumbnails/test/browser_thumbnails_storage.js
toolkit/components/thumbnails/test/head.js
toolkit/modules/Sqlite.jsm
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -31,24 +31,66 @@ const UNEXPECTED_NOTIFICATIONS = [
 const FTP_URL = "ftp://localhost/clearHistoryOnShutdown/";
 
 // Send the profile-after-change notification to the form history component to ensure
 // that it has been initialized.
 var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"].
                          getService(Ci.nsIObserver);
 formHistoryStartup.observe(null, "profile-after-change", null);
 
+let notificationIndex = 0;
+
+let notificationsObserver = {
+  observe: function observe(aSubject, aTopic, aData) {
+    print("Received notification: " + aTopic);
+
+    // Note that some of these notifications could arrive multiple times, for
+    // example in case of sync, we allow that.
+    if (EXPECTED_NOTIFICATIONS[notificationIndex] != aTopic)
+      notificationIndex++;
+    do_check_eq(EXPECTED_NOTIFICATIONS[notificationIndex], aTopic);
+
+    if (aTopic != TOPIC_CONNECTION_CLOSED)
+      return;
+
+    getDistinctNotifications().forEach(
+      function (topic) Services.obs.removeObserver(notificationsObserver, topic)
+    );
+
+    print("Looking for uncleared stuff.");
+
+    let stmt = DBConn().createStatement(
+      "SELECT id FROM moz_places WHERE url = :page_url "
+    );
+
+    try {
+      URIS.forEach(function(aUrl) {
+        stmt.params.page_url = aUrl;
+        do_check_false(stmt.executeStep());
+        stmt.reset();
+      });
+    } finally {
+      stmt.finalize();
+    }
+
+    // Check cache.
+    checkCache(FTP_URL);
+  }
+}
+
 let timeInMicroseconds = Date.now() * 1000;
 
 function run_test() {
   run_next_test();
 }
 
-add_task(function* test_execute() {
-  do_print("Initialize browserglue before Places");
+add_task(function test_execute() {
+  do_test_pending();
+
+  print("Initialize browserglue before Places");
 
   // Avoid default bookmarks import.
   let glue = Cc["@mozilla.org/browser/browserglue;1"].
              getService(Ci.nsIObserver);
   glue.observe(null, "initial-migration-will-import-default-bookmarks", null);
 
   Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
@@ -58,100 +100,86 @@ add_task(function* test_execute() {
   Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.formData", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.passwords", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.siteSettings", true);
 
   Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
 
-  do_print("Add visits.");
+  print("Add visits.");
   for (let aUrl of URIS) {
     yield PlacesTestUtils.addVisits({
       uri: uri(aUrl), visitDate: timeInMicroseconds++,
       transition: PlacesUtils.history.TRANSITION_TYPED
     });
   }
-  do_print("Add cache.");
-  yield storeCache(FTP_URL, "testData");
+  print("Add cache.");
+  storeCache(FTP_URL, "testData");
 });
 
-add_task(function* run_test_continue() {
-  do_print("Simulate and wait shutdown.");
-  yield shutdownPlaces();
-
-  let stmt = DBConn().createStatement(
-    "SELECT id FROM moz_places WHERE url = :page_url "
+function run_test_continue()
+{
+  print("Simulate and wait shutdown.");
+  getDistinctNotifications().forEach(
+    function (topic)
+      Services.obs.addObserver(notificationsObserver, topic, false)
   );
 
-  try {
-    URIS.forEach(function(aUrl) {
-      stmt.params.page_url = aUrl;
-      do_check_false(stmt.executeStep());
-      stmt.reset();
-    });
-  } finally {
-    stmt.finalize();
-  }
+  shutdownPlaces();
 
-  do_print("Check cache");
-  // Check cache.
-  let promiseCacheChecked = checkCache(FTP_URL);
-
-  do_print("Shutdown the download manager");
   // Shutdown the download manager.
   Services.obs.notifyObservers(null, "quit-application", null);
+}
 
-  yield promiseCacheChecked;
-});
+function getDistinctNotifications() {
+  let ar = EXPECTED_NOTIFICATIONS.concat(UNEXPECTED_NOTIFICATIONS);
+  return [ar[i] for (i in ar) if (ar.slice(0, i).indexOf(ar[i]) == -1)];
+}
 
 function storeCache(aURL, aContent) {
   let cache = Services.cache2;
   let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
 
-  return new Promise(resolve => {
-    let storeCacheListener = {
-      onCacheEntryCheck: function (entry, appcache) {
-        return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
-      },
+  var storeCacheListener = {
+    onCacheEntryCheck: function (entry, appcache) {
+      return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+    },
 
-      onCacheEntryAvailable: function (entry, isnew, appcache, status) {
-        do_check_eq(status, Cr.NS_OK);
+    onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+      do_check_eq(status, Cr.NS_OK);
 
-        entry.setMetaDataElement("servertype", "0");
-        var os = entry.openOutputStream(0);
+      entry.setMetaDataElement("servertype", "0");
+      var os = entry.openOutputStream(0);
 
-        var written = os.write(aContent, aContent.length);
-        if (written != aContent.length) {
-          do_throw("os.write has not written all data!\n" +
-                   "  Expected: " + written  + "\n" +
-                   "  Actual: " + aContent.length + "\n");
-        }
-        os.close();
-        entry.close();
-        resolve();
+      var written = os.write(aContent, aContent.length);
+      if (written != aContent.length) {
+        do_throw("os.write has not written all data!\n" +
+                 "  Expected: " + written  + "\n" +
+                 "  Actual: " + aContent.length + "\n");
       }
-    };
+      os.close();
+      entry.close();
+      do_execute_soon(run_test_continue);
+    }
+  };
 
-    storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
-                         Ci.nsICacheStorage.OPEN_NORMALLY,
-                         storeCacheListener);
-  });
+  storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
+                       Ci.nsICacheStorage.OPEN_NORMALLY,
+                       storeCacheListener);
 }
 
 
 function checkCache(aURL) {
   let cache = Services.cache2;
   let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
 
-  return new Promise(resolve => {
-    let checkCacheListener = {
-      onCacheEntryAvailable: function (entry, isnew, appcache, status) {
-        do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
-        resolve();
-      }
-    };
+  var checkCacheListener = {
+    onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+      do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+      do_test_finished();
+    }
+  };
 
-    storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
-                        Ci.nsICacheStorage.OPEN_READONLY,
-                        checkCacheListener);
-  });
+  storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
+                       Ci.nsICacheStorage.OPEN_READONLY,
+                       checkCacheListener);
 }
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -253,116 +253,114 @@ let Bookmarks = Object.freeze({
       updateInfo = validateBookmarkObject(updateInfo,
         { url: { validIf: () => item.type == this.TYPE_BOOKMARK }
         , title: { validIf: () => [ this.TYPE_BOOKMARK
                                   , this.TYPE_FOLDER ].indexOf(item.type) != -1 }
         , lastModified: { defaultValue: new Date()
                         , validIf: b => b.lastModified >= item.dateAdded }
         });
 
-      return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: update",
-        Task.async(function*(db) {
-        let parent;
-        if (updateInfo.hasOwnProperty("parentGuid")) {
-          if (item.type == this.TYPE_FOLDER) {
-            // Make sure we are not moving a folder into itself or one of its
-            // descendants.
-            let rows = yield db.executeCached(
-              `WITH RECURSIVE
-               descendants(did) AS (
-                 VALUES(:id)
-                 UNION ALL
-                 SELECT id FROM moz_bookmarks
-                 JOIN descendants ON parent = did
-                 WHERE type = :type
-               )
-               SELECT guid FROM moz_bookmarks
-               WHERE id IN descendants
-              `, { id: item._id, type: this.TYPE_FOLDER });
-            if ([r.getResultByName("guid") for (r of rows)].indexOf(updateInfo.parentGuid) != -1)
-              throw new Error("Cannot insert a folder into itself or one of its descendants");
-          }
-
-          parent = yield fetchBookmark({ guid: updateInfo.parentGuid });
-          if (!parent)
-            throw new Error("No bookmarks found for the provided parentGuid");
-        }
-
-        if (updateInfo.hasOwnProperty("index")) {
-          // If at this point we don't have a parent yet, we are moving into
-          // the same container.  Thus we know it exists.
-          if (!parent)
-            parent = yield fetchBookmark({ guid: item.parentGuid });
-
-          if (updateInfo.index >= parent._childCount ||
-              updateInfo.index == this.DEFAULT_INDEX) {
-             updateInfo.index = parent._childCount;
-
-            // Fix the index when moving within the same container.
-            if (parent.guid == item.parentGuid)
-               updateInfo.index--;
-          }
+      let db = yield PlacesUtils.promiseWrappedConnection();
+      let parent;
+      if (updateInfo.hasOwnProperty("parentGuid")) {
+        if (item.type == this.TYPE_FOLDER) {
+          // Make sure we are not moving a folder into itself or one of its
+          // descendants.
+          let rows = yield db.executeCached(
+            `WITH RECURSIVE
+             descendants(did) AS (
+               VALUES(:id)
+               UNION ALL
+               SELECT id FROM moz_bookmarks
+               JOIN descendants ON parent = did
+               WHERE type = :type
+             )
+             SELECT guid FROM moz_bookmarks
+             WHERE id IN descendants
+            `, { id: item._id, type: this.TYPE_FOLDER });
+          if ([r.getResultByName("guid") for (r of rows)].indexOf(updateInfo.parentGuid) != -1)
+            throw new Error("Cannot insert a folder into itself or one of its descendants");
         }
 
-        let updatedItem = yield updateBookmark(updateInfo, item, parent);
-
-        if (item.type == this.TYPE_BOOKMARK &&
-            item.url.href != updatedItem.url.href) {
-          // ...though we don't wait for the calculation.
-          updateFrecency(db, [item.url]).then(null, Cu.reportError);
-          updateFrecency(db, [updatedItem.url]).then(null, Cu.reportError);
-        }
+        parent = yield fetchBookmark({ guid: updateInfo.parentGuid });
+        if (!parent)
+          throw new Error("No bookmarks found for the provided parentGuid");
+      }
 
-        // Notify onItemChanged to listeners.
-        let observers = PlacesUtils.bookmarks.getObservers();
-        // For lastModified, we only care about the original input, since we
-        // should not notify implciit lastModified changes.
-        if (info.hasOwnProperty("lastModified") &&
-            updateInfo.hasOwnProperty("lastModified") &&
-            item.lastModified != updatedItem.lastModified) {
-          notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
-                                               false,
-                                               `${toPRTime(updatedItem.lastModified)}`,
-                                               toPRTime(updatedItem.lastModified),
-                                               updatedItem.type,
-                                               updatedItem._parentId,
-                                               updatedItem.guid,
-                                               updatedItem.parentGuid ]);
+      if (updateInfo.hasOwnProperty("index")) {
+        // If at this point we don't have a parent yet, we are moving into
+        // the same container.  Thus we know it exists.
+        if (!parent)
+          parent = yield fetchBookmark({ guid: item.parentGuid });
+
+        if (updateInfo.index >= parent._childCount ||
+            updateInfo.index == this.DEFAULT_INDEX) {
+           updateInfo.index = parent._childCount;
+
+          // Fix the index when moving within the same container.
+          if (parent.guid == item.parentGuid)
+             updateInfo.index--;
         }
-        if (updateInfo.hasOwnProperty("title")) {
-          notify(observers, "onItemChanged", [ updatedItem._id, "title",
-                                               false, updatedItem.title,
-                                               toPRTime(updatedItem.lastModified),
-                                               updatedItem.type,
-                                               updatedItem._parentId,
-                                               updatedItem.guid,
-                                               updatedItem.parentGuid ]);
-        }
-        if (updateInfo.hasOwnProperty("url")) {
-          notify(observers, "onItemChanged", [ updatedItem._id, "uri",
-                                               false, updatedItem.url.href,
-                                               toPRTime(updatedItem.lastModified),
-                                               updatedItem.type,
-                                               updatedItem._parentId,
-                                               updatedItem.guid,
-                                               updatedItem.parentGuid ]);
-        }
-        // If the item was moved, notify onItemMoved.
-        if (item.parentGuid != updatedItem.parentGuid ||
-            item.index != updatedItem.index) {
-          notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
-                                             item.index, updatedItem._parentId,
-                                             updatedItem.index, updatedItem.type,
-                                             updatedItem.guid, item.parentGuid,
+      }
+
+      let updatedItem = yield updateBookmark(updateInfo, item, parent);
+
+      if (item.type == this.TYPE_BOOKMARK &&
+          item.url.href != updatedItem.url.href) {
+        // ...though we don't wait for the calculation.
+        updateFrecency(db, [item.url]).then(null, Cu.reportError);
+        updateFrecency(db, [updatedItem.url]).then(null, Cu.reportError);
+      }
+
+      // Notify onItemChanged to listeners.
+      let observers = PlacesUtils.bookmarks.getObservers();
+      // For lastModified, we only care about the original input, since we
+      // should not notify implciit lastModified changes.
+      if (info.hasOwnProperty("lastModified") &&
+          updateInfo.hasOwnProperty("lastModified") &&
+          item.lastModified != updatedItem.lastModified) {
+        notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
+                                             false,
+                                             `${toPRTime(updatedItem.lastModified)}`,
+                                             toPRTime(updatedItem.lastModified),
+                                             updatedItem.type,
+                                             updatedItem._parentId,
+                                             updatedItem.guid,
                                              updatedItem.parentGuid ]);
-        }
+      }
+      if (updateInfo.hasOwnProperty("title")) {
+        notify(observers, "onItemChanged", [ updatedItem._id, "title",
+                                             false, updatedItem.title,
+                                             toPRTime(updatedItem.lastModified),
+                                             updatedItem.type,
+                                             updatedItem._parentId,
+                                             updatedItem.guid,
+                                             updatedItem.parentGuid ]);
+      }
+      if (updateInfo.hasOwnProperty("url")) {
+        notify(observers, "onItemChanged", [ updatedItem._id, "uri",
+                                             false, updatedItem.url.href,
+                                             toPRTime(updatedItem.lastModified),
+                                             updatedItem.type,
+                                             updatedItem._parentId,
+                                             updatedItem.guid,
+                                             updatedItem.parentGuid ]);
+      }
+      // If the item was moved, notify onItemMoved.
+      if (item.parentGuid != updatedItem.parentGuid ||
+          item.index != updatedItem.index) {
+        notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
+                                           item.index, updatedItem._parentId,
+                                           updatedItem.index, updatedItem.type,
+                                           updatedItem.guid, item.parentGuid,
+                                           updatedItem.parentGuid ]);
+      }
 
-        // Remove non-enumerable properties.
-        return Object.assign({}, updatedItem);
-      }.bind(this)));
+      // Remove non-enumerable properties.
+      return Object.assign({}, updatedItem);
     }.bind(this));
   },
 
   /**
    * Removes a bookmark-item.
    *
    * @param guidOrInfo
    *        The globally unique identifier of the item to remove, or an
@@ -422,31 +420,30 @@ let Bookmarks = Object.freeze({
   /**
    * Removes ALL bookmarks, resetting the bookmarks storage to an empty tree.
    *
    * Note that roots are preserved, only their children will be removed.
    *
    * @return {Promise} resolved when the removal is complete.
    * @resolves once the removal is complete.
    */
-  eraseEverything: function() {
-    return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: eraseEverything",
-      db => db.executeTransaction(function* () {
-        const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid];
-        yield removeFoldersContents(db, folderGuids);
-        const time = toPRTime(new Date());
-        for (let folderGuid of folderGuids) {
-          yield db.executeCached(
-            `UPDATE moz_bookmarks SET lastModified = :time
-             WHERE id IN (SELECT id FROM moz_bookmarks WHERE guid = :folderGuid )
-            `, { folderGuid, time });
-        }
-      }.bind(this))
-    );
-  },
+  eraseEverything: Task.async(function* () {
+    let db = yield PlacesUtils.promiseWrappedConnection();
+    yield db.executeTransaction(function* () {
+      const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid];
+      yield removeFoldersContents(db, folderGuids);
+      const time = toPRTime(new Date());
+      for (let folderGuid of folderGuids) {
+        yield db.executeCached(
+          `UPDATE moz_bookmarks SET lastModified = :time
+           WHERE id IN (SELECT id FROM moz_bookmarks WHERE guid = :folderGuid )
+          `, { folderGuid, time });
+      }
+    }.bind(this));
+  }),
 
   /**
    * Fetches information about a bookmark-item.
    *
    * REMARK: any successful call to this method resolves to a single
    *         bookmark-item (or null), even when multiple bookmarks may exist
    *         (e.g. fetching by url).  If you wish to retrieve all of the
    *         bookmarks for a given match, use the callback instead.
@@ -671,365 +668,350 @@ function notify(observers, notification,
       observer[notification](...args);
     } catch (ex) {}
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Update implementation.
 
-function updateBookmark(info, item, newParent) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
-    Task.async(function*(db) {
-
-    let tuples = new Map();
-    if (info.hasOwnProperty("lastModified"))
-      tuples.set("lastModified", { value: toPRTime(info.lastModified) });
-    if (info.hasOwnProperty("title"))
-      tuples.set("title", { value: info.title });
+function* updateBookmark(info, item, newParent) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
 
-    yield db.executeTransaction(function* () {
-      if (info.hasOwnProperty("url")) {
-        // Ensure a page exists in moz_places for this URL.
-        yield db.executeCached(
-          `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) 
-           VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
-          `, { url: info.url ? info.url.href : null,
-               rev_host: PlacesUtils.getReversedHost(info.url),
-               frecency: info.url.protocol == "place:" ? 0 : -1 });
-        tuples.set("url", { value: info.url.href
-                          , fragment: "fk = (SELECT id FROM moz_places WHERE url = :url)" });
-      }
-
-      if (newParent) {
-        // For simplicity, update the index regardless.
-        let newIndex = info.hasOwnProperty("index") ? info.index : item.index;
-        tuples.set("position", { value: newIndex });
+  let tuples = new Map();
+  if (info.hasOwnProperty("lastModified"))
+    tuples.set("lastModified", { value: toPRTime(info.lastModified) });
+  if (info.hasOwnProperty("title"))
+    tuples.set("title", { value: info.title });
 
-        if (newParent.guid == item.parentGuid) {
-          // Moving inside the original container.
-          // When moving "up", add 1 to each index in the interval.
-          // Otherwise when moving down, we subtract 1.
-          let sign = newIndex < item.index ? +1 : -1;
-          yield db.executeCached(
-            `UPDATE moz_bookmarks SET position = position + :sign
-             WHERE parent = :newParentId
-               AND position BETWEEN :lowIndex AND :highIndex
-            `, { sign: sign, newParentId: newParent._id,
-                 lowIndex: Math.min(item.index, newIndex),
-                 highIndex: Math.max(item.index, newIndex) });
-        } else {
-          // Moving across different containers.
-          tuples.set("parent", { value: newParent._id} );
-          yield db.executeCached(
-            `UPDATE moz_bookmarks SET position = position + :sign
-             WHERE parent = :oldParentId
-               AND position >= :oldIndex
-            `, { sign: -1, oldParentId: item._parentId, oldIndex: item.index });
-          yield db.executeCached(
-            `UPDATE moz_bookmarks SET position = position + :sign
-             WHERE parent = :newParentId
-               AND position >= :newIndex
-            `, { sign: +1, newParentId: newParent._id, newIndex: newIndex });
-
-          yield setAncestorsLastModified(db, item.parentGuid, info.lastModified);
-        }
-        yield setAncestorsLastModified(db, newParent.guid, info.lastModified);
-      }
-
+  yield db.executeTransaction(function* () {
+    if (info.hasOwnProperty("url")) {
+      // Ensure a page exists in moz_places for this URL.
       yield db.executeCached(
-        `UPDATE moz_bookmarks
-         SET ${[tuples.get(v).fragment || `${v} = :${v}` for (v of tuples.keys())].join(", ")}
-         WHERE guid = :guid
-        `, Object.assign({ guid: info.guid },
-                         [...tuples.entries()].reduce((p, c) => { p[c[0]] = c[1].value; return p; }, {})));
-    });
-
-    // If the parent changed, update related non-enumerable properties.
-    let additionalParentInfo = {};
-    if (newParent) {
-      Object.defineProperty(additionalParentInfo, "_parentId",
-                            { value: newParent._id, enumerable: false });
-      Object.defineProperty(additionalParentInfo, "_grandParentId",
-                            { value: newParent._parentId, enumerable: false });
+        `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) 
+         VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
+        `, { url: info.url ? info.url.href : null,
+             rev_host: PlacesUtils.getReversedHost(info.url),
+             frecency: info.url.protocol == "place:" ? 0 : -1 });
+      tuples.set("url", { value: info.url.href
+                        , fragment: "fk = (SELECT id FROM moz_places WHERE url = :url)" });
     }
 
-    let updatedItem = mergeIntoNewObject(item, info, additionalParentInfo);
+    if (newParent) {
+      // For simplicity, update the index regardless.
+      let newIndex = info.hasOwnProperty("index") ? info.index : item.index;
+      tuples.set("position", { value: newIndex });
 
-    // Don't return an empty title to the caller.
-    if (updatedItem.hasOwnProperty("title") && updatedItem.title === null)
-      delete updatedItem.title;
+      if (newParent.guid == item.parentGuid) {
+        // Moving inside the original container.
+        // When moving "up", add 1 to each index in the interval.
+        // Otherwise when moving down, we subtract 1.
+        let sign = newIndex < item.index ? +1 : -1;
+        yield db.executeCached(
+          `UPDATE moz_bookmarks SET position = position + :sign
+           WHERE parent = :newParentId
+             AND position BETWEEN :lowIndex AND :highIndex
+          `, { sign: sign, newParentId: newParent._id,
+               lowIndex: Math.min(item.index, newIndex),
+               highIndex: Math.max(item.index, newIndex) });
+      } else {
+        // Moving across different containers.
+        tuples.set("parent", { value: newParent._id} );
+        yield db.executeCached(
+          `UPDATE moz_bookmarks SET position = position + :sign
+           WHERE parent = :oldParentId
+             AND position >= :oldIndex
+          `, { sign: -1, oldParentId: item._parentId, oldIndex: item.index });
+        yield db.executeCached(
+          `UPDATE moz_bookmarks SET position = position + :sign
+           WHERE parent = :newParentId
+             AND position >= :newIndex
+          `, { sign: +1, newParentId: newParent._id, newIndex: newIndex });
 
-    return updatedItem;
-  }));
+        yield setAncestorsLastModified(db, item.parentGuid, info.lastModified);
+      }
+      yield setAncestorsLastModified(db, newParent.guid, info.lastModified);
+    }
+
+    yield db.executeCached(
+      `UPDATE moz_bookmarks
+       SET ${[tuples.get(v).fragment || `${v} = :${v}` for (v of tuples.keys())].join(", ")}
+       WHERE guid = :guid
+      `, Object.assign({ guid: info.guid },
+                       [...tuples.entries()].reduce((p, c) => { p[c[0]] = c[1].value; return p; }, {})));
+  });
+
+  // If the parent changed, update related non-enumerable properties.
+  let additionalParentInfo = {};
+  if (newParent) {
+    Object.defineProperty(additionalParentInfo, "_parentId",
+                          { value: newParent._id, enumerable: false });
+    Object.defineProperty(additionalParentInfo, "_grandParentId",
+                          { value: newParent._parentId, enumerable: false });
+  }
+
+  let updatedItem = mergeIntoNewObject(item, info, additionalParentInfo);
+
+  // Don't return an empty title to the caller.
+  if (updatedItem.hasOwnProperty("title") && updatedItem.title === null)
+    delete updatedItem.title;
+
+  return updatedItem;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Insert implementation.
 
-function insertBookmark(item, parent) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: insertBookmark",
-    Task.async(function*(db) {
-
-    // If a guid was not provided, generate one, so we won't need to fetch the
-    // bookmark just after having created it.
-    if (!item.hasOwnProperty("guid"))
-      item.guid = (yield db.executeCached("SELECT GENERATE_GUID() AS guid"))[0].getResultByName("guid");
+function* insertBookmark(item, parent) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
 
-    yield db.executeTransaction(function* transaction() {
-      if (item.type == Bookmarks.TYPE_BOOKMARK) {
-        // Ensure a page exists in moz_places for this URL.
-        yield db.executeCached(
-          `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) 
-           VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
-          `, { url: item.url.href, rev_host: PlacesUtils.getReversedHost(item.url),
-               frecency: item.url.protocol == "place:" ? 0 : -1 });
-      }
+  // If a guid was not provided, generate one, so we won't need to fetch the
+  // bookmark just after having created it.
+  if (!item.hasOwnProperty("guid"))
+    item.guid = (yield db.executeCached("SELECT GENERATE_GUID() AS guid"))[0].getResultByName("guid");
 
-      // Adjust indices.
+  yield db.executeTransaction(function* transaction() {
+    if (item.type == Bookmarks.TYPE_BOOKMARK) {
+      // Ensure a page exists in moz_places for this URL.
       yield db.executeCached(
-        `UPDATE moz_bookmarks SET position = position + 1
-         WHERE parent = :parent
-         AND position >= :index
-        `, { parent: parent._id, index: item.index });
-
-      // Insert the bookmark into the database.
-      yield db.executeCached(
-        `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
-                                    dateAdded, lastModified, guid)
-         VALUES ((SELECT id FROM moz_places WHERE url = :url), :type, :parent,
-                 :index, :title, :date_added, :last_modified, :guid)
-        `, { url: item.hasOwnProperty("url") ? item.url.href : "nonexistent",
-             type: item.type, parent: parent._id, index: item.index,
-             title: item.title, date_added: toPRTime(item.dateAdded),
-             last_modified: toPRTime(item.lastModified), guid: item.guid });
-
-      yield setAncestorsLastModified(db, item.parentGuid, item.dateAdded);
-    });
-
-    // If not a tag recalculate frecency...
-    let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
-    if (item.type == Bookmarks.TYPE_BOOKMARK && !isTagging) {
-      // ...though we don't wait for the calculation.
-      updateFrecency(db, [item.url]).then(null, Cu.reportError);
+        `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) 
+         VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
+        `, { url: item.url.href, rev_host: PlacesUtils.getReversedHost(item.url),
+             frecency: item.url.protocol == "place:" ? 0 : -1 });
     }
 
-    // Don't return an empty title to the caller.
-    if (item.hasOwnProperty("title") && item.title === null)
-      delete item.title;
+    // Adjust indices.
+    yield db.executeCached(
+      `UPDATE moz_bookmarks SET position = position + 1
+       WHERE parent = :parent
+       AND position >= :index
+      `, { parent: parent._id, index: item.index });
 
-    return item;
-  }));
+    // Insert the bookmark into the database.
+    yield db.executeCached(
+      `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
+                                  dateAdded, lastModified, guid)
+       VALUES ((SELECT id FROM moz_places WHERE url = :url), :type, :parent,
+               :index, :title, :date_added, :last_modified, :guid)
+      `, { url: item.hasOwnProperty("url") ? item.url.href : "nonexistent",
+           type: item.type, parent: parent._id, index: item.index,
+           title: item.title, date_added: toPRTime(item.dateAdded),
+           last_modified: toPRTime(item.lastModified), guid: item.guid });
+
+    yield setAncestorsLastModified(db, item.parentGuid, item.dateAdded);
+  });
+
+  // If not a tag recalculate frecency...
+  let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
+  if (item.type == Bookmarks.TYPE_BOOKMARK && !isTagging) {
+    // ...though we don't wait for the calculation.
+    updateFrecency(db, [item.url]).then(null, Cu.reportError);
+  }
+
+  // Don't return an empty title to the caller.
+  if (item.hasOwnProperty("title") && item.title === null)
+    delete item.title;
+  return item;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Fetch implementation.
 
-function fetchBookmark(info) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmark",
-    Task.async(function*(db) {
+function* fetchBookmark(info) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
 
-    let rows = yield db.executeCached(
-      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
-              b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
-              b.id AS _id, b.parent AS _parentId,
-              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
-              p.parent AS _grandParentId
-       FROM moz_bookmarks b
-       LEFT JOIN moz_bookmarks p ON p.id = b.parent
-       LEFT JOIN moz_places h ON h.id = b.fk
-       WHERE b.guid = :guid
-      `, { guid: info.guid });
+  let rows = yield db.executeCached(
+    `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+            b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+            b.id AS _id, b.parent AS _parentId,
+            (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+            p.parent AS _grandParentId
+     FROM moz_bookmarks b
+     LEFT JOIN moz_bookmarks p ON p.id = b.parent
+     LEFT JOIN moz_places h ON h.id = b.fk
+     WHERE b.guid = :guid
+    `, { guid: info.guid });
 
-    return rows.length ? rowsToItemsArray(rows)[0] : null;
-  }));
+  return rows.length ? rowsToItemsArray(rows)[0] : null;
 }
 
-function fetchBookmarkByPosition(info) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarkByPosition",
-    Task.async(function*(db) {
-    let index = info.index == Bookmarks.DEFAULT_INDEX ? null : info.index;
+function* fetchBookmarkByPosition(info) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
+  let index = info.index == Bookmarks.DEFAULT_INDEX ? null : info.index;
 
-    let rows = yield db.executeCached(
-      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
-              b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
-              b.id AS _id, b.parent AS _parentId,
-              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
-              p.parent AS _grandParentId
-       FROM moz_bookmarks b
-       LEFT JOIN moz_bookmarks p ON p.id = b.parent
-       LEFT JOIN moz_places h ON h.id = b.fk
-       WHERE p.guid = :parentGuid
-       AND b.position = IFNULL(:index, (SELECT count(*) - 1
-                                        FROM moz_bookmarks
-                                        WHERE parent = p.id))
-      `, { parentGuid: info.parentGuid, index });
+  let rows = yield db.executeCached(
+    `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+            b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+            b.id AS _id, b.parent AS _parentId,
+            (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+            p.parent AS _grandParentId
+     FROM moz_bookmarks b
+     LEFT JOIN moz_bookmarks p ON p.id = b.parent
+     LEFT JOIN moz_places h ON h.id = b.fk
+     WHERE p.guid = :parentGuid
+     AND b.position = IFNULL(:index, (SELECT count(*) - 1
+                                      FROM moz_bookmarks
+                                      WHERE parent = p.id))
+    `, { parentGuid: info.parentGuid, index });
 
-    return rows.length ? rowsToItemsArray(rows)[0] : null;
-  }));
+  return rows.length ? rowsToItemsArray(rows)[0] : null;
 }
 
-function fetchBookmarksByURL(info) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByURL",
-    Task.async(function*(db) {
+function* fetchBookmarksByURL(info) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
 
-    let rows = yield db.executeCached(
-      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
-              b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
-              b.id AS _id, b.parent AS _parentId,
-              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
-              p.parent AS _grandParentId
-       FROM moz_bookmarks b
-       LEFT JOIN moz_bookmarks p ON p.id = b.parent
-       LEFT JOIN moz_places h ON h.id = b.fk
-       WHERE h.url = :url
-       AND _grandParentId <> :tags_folder
-       ORDER BY b.lastModified DESC
-      `, { url: info.url.href,
-           tags_folder: PlacesUtils.tagsFolderId });
+  let rows = yield db.executeCached(
+    `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+            b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+            b.id AS _id, b.parent AS _parentId,
+            (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+            p.parent AS _grandParentId
+     FROM moz_bookmarks b
+     LEFT JOIN moz_bookmarks p ON p.id = b.parent
+     LEFT JOIN moz_places h ON h.id = b.fk
+     WHERE h.url = :url
+     AND _grandParentId <> :tags_folder
+     ORDER BY b.lastModified DESC
+    `, { url: info.url.href,
+         tags_folder: PlacesUtils.tagsFolderId });
 
-    return rows.length ? rowsToItemsArray(rows) : null;
-  }));
+  return rows.length ? rowsToItemsArray(rows) : null;
 }
 
-function fetchBookmarksByParent(info) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByParent",
-    Task.async(function*(db) {
+function* fetchBookmarksByParent(info) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
 
-    let rows = yield db.executeCached(
-      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
-              b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
-              b.id AS _id, b.parent AS _parentId,
-              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
-              p.parent AS _grandParentId
-       FROM moz_bookmarks b
-       LEFT JOIN moz_bookmarks p ON p.id = b.parent
-       LEFT JOIN moz_places h ON h.id = b.fk
-       WHERE p.guid = :parentGuid
-       ORDER BY b.position ASC
-      `, { parentGuid: info.parentGuid });
+  let rows = yield db.executeCached(
+    `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+            b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+            b.id AS _id, b.parent AS _parentId,
+            (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+            p.parent AS _grandParentId
+     FROM moz_bookmarks b
+     LEFT JOIN moz_bookmarks p ON p.id = b.parent
+     LEFT JOIN moz_places h ON h.id = b.fk
+     WHERE p.guid = :parentGuid
+     ORDER BY b.position ASC
+    `, { parentGuid: info.parentGuid });
 
-    return rowsToItemsArray(rows);
-  }));
+  return rowsToItemsArray(rows);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Remove implementation.
 
-function removeBookmark(item) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
-    Task.async(function*(db) {
+function* removeBookmark(item) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
 
-    let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
-
-    yield db.executeTransaction(function* transaction() {
-      // If it's a folder, remove its contents first.
-      if (item.type == Bookmarks.TYPE_FOLDER)
-        yield removeFoldersContents(db, [item.guid]);
+  let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
 
-      // Remove annotations first.  If it's a tag, we can avoid paying that cost.
-      if (!isUntagging) {
-        // We don't go through the annotations service for this cause otherwise
-        // we'd get a pointless onItemChanged notification and it would also
-        // set lastModified to an unexpected value.
-        yield removeAnnotationsForItem(db, item._id);
-      }
-
-      // Remove the bookmark from the database.
-      yield db.executeCached(
-        `DELETE FROM moz_bookmarks WHERE guid = :guid`, { guid: item.guid });
+  yield db.executeTransaction(function* transaction() {
+    // If it's a folder, remove its contents first.
+    if (item.type == Bookmarks.TYPE_FOLDER)
+      yield removeFoldersContents(db, [item.guid]);
 
-      // Fix indices in the parent.
-      yield db.executeCached(
-        `UPDATE moz_bookmarks SET position = position - 1 WHERE
-         parent = :parentId AND position > :index
-        `, { parentId: item._parentId, index: item.index });
-
-      yield setAncestorsLastModified(db, item.parentGuid, new Date());
-    });
-
-    // If not a tag recalculate frecency...
-    if (item.type == Bookmarks.TYPE_BOOKMARK && !isUntagging) {
-      // ...though we don't wait for the calculation.
-      updateFrecency(db, [item.url]).then(null, Cu.reportError);
+    // Remove annotations first.  If it's a tag, we can avoid paying that cost.
+    if (!isUntagging) {
+      // We don't go through the annotations service for this cause otherwise
+      // we'd get a pointless onItemChanged notification and it would also
+      // set lastModified to an unexpected value.
+      yield removeAnnotationsForItem(db, item._id);
     }
 
-    return item;
-  }));
+    // Remove the bookmark from the database.
+    yield db.executeCached(
+      `DELETE FROM moz_bookmarks WHERE guid = :guid`, { guid: item.guid });
+
+    // Fix indices in the parent.
+    yield db.executeCached(
+      `UPDATE moz_bookmarks SET position = position - 1 WHERE
+       parent = :parentId AND position > :index
+      `, { parentId: item._parentId, index: item.index });
+
+    yield setAncestorsLastModified(db, item.parentGuid, new Date());
+  });
+
+  // If not a tag recalculate frecency...
+  if (item.type == Bookmarks.TYPE_BOOKMARK && !isUntagging) {
+    // ...though we don't wait for the calculation.
+    updateFrecency(db, [item.url]).then(null, Cu.reportError);
+  }
+
+  return item;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Reorder implementation.
 
-function reorderChildren(parent, orderedChildrenGuids) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
-    db => db.executeTransaction(function* () {
-      // Select all of the direct children for the given parent.
-      let children = yield fetchBookmarksByParent({ parentGuid: parent.guid });
-      if (!children.length)
-        return;
+function* reorderChildren(parent, orderedChildrenGuids) {
+  let db = yield PlacesUtils.promiseWrappedConnection();
+
+  return db.executeTransaction(function* () {
+    // Select all of the direct children for the given parent.
+    let children = yield fetchBookmarksByParent({ parentGuid: parent.guid });
+    if (!children.length)
+      return;
 
-      // Reorder the children array according to the specified order, provided
-      // GUIDs come first, others are appended in somehow random order.
-      children.sort((a, b) => {
-        let i = orderedChildrenGuids.indexOf(a.guid);
-        let j = orderedChildrenGuids.indexOf(b.guid);
-        // This works provided fetchBookmarksByParent returns sorted children.
-        return (i == -1 && j == -1) ? 0 :
-                 (i != -1 && j != -1 && i < j) || (i != -1 && j == -1) ? -1 : 1;
-       });
+    // Reorder the children array according to the specified order, provided
+    // GUIDs come first, others are appended in somehow random order.
+    children.sort((a, b) => {
+      let i = orderedChildrenGuids.indexOf(a.guid);
+      let j = orderedChildrenGuids.indexOf(b.guid);
+      // This works provided fetchBookmarksByParent returns sorted children.
+      return (i == -1 && j == -1) ? 0 :
+               (i != -1 && j != -1 && i < j) || (i != -1 && j == -1) ? -1 : 1;
+     });
 
-      // Update the bookmarks position now.  If any unknown guid have been
-      // inserted meanwhile, its position will be set to -position, and we'll
-      // handle it later.
-      // To do the update in a single step, we build a VALUES (guid, position)
-      // table.  We then use count() in the sorting table to avoid skipping values
-      // when no more existing GUIDs have been provided.
-      let valuesTable = children.map((child, i) => `("${child.guid}", ${i})`)
-                                .join();
-      yield db.execute(
-        `WITH sorting(g, p) AS (
-           VALUES ${valuesTable}
-         )
-         UPDATE moz_bookmarks SET position = (
-           SELECT CASE count(a.g) WHEN 0 THEN -position
-                                  ELSE count(a.g) - 1
-                  END
-           FROM sorting a
-           JOIN sorting b ON b.p <= a.p
-           WHERE a.g = guid
-             AND parent = :parentId
-        )`, { parentId: parent._id});
+    // Update the bookmarks position now.  If any unknown guid have been
+    // inserted meanwhile, its position will be set to -position, and we'll
+    // handle it later.
+    // To do the update in a single step, we build a VALUES (guid, position)
+    // table.  We then use count() in the sorting table to avoid skipping values
+    // when no more existing GUIDs have been provided.
+    let valuesTable = children.map((child, i) => `("${child.guid}", ${i})`)
+                              .join();
+    yield db.execute(
+      `WITH sorting(g, p) AS (
+         VALUES ${valuesTable}
+       )
+       UPDATE moz_bookmarks SET position = (
+         SELECT CASE count(a.g) WHEN 0 THEN -position
+                                ELSE count(a.g) - 1
+                END
+         FROM sorting a
+         JOIN sorting b ON b.p <= a.p
+         WHERE a.g = guid
+           AND parent = :parentId
+      )`, { parentId: parent._id});
 
-      // Update position of items that could have been inserted in the meanwhile.
-      // Since this can happen rarely and it's only done for schema coherence
-      // resonds, we won't notify about these changes.
-      yield db.executeCached(
-        `CREATE TEMP TRIGGER moz_bookmarks_reorder_trigger
-           AFTER UPDATE OF position ON moz_bookmarks
-           WHEN NEW.position = -1
-         BEGIN
-           UPDATE moz_bookmarks
-           SET position = (SELECT MAX(position) FROM moz_bookmarks
-                           WHERE parent = NEW.parent) +
-                          (SELECT count(*) FROM moz_bookmarks
-                           WHERE parent = NEW.parent
-                             AND position BETWEEN OLD.position AND -1)
-           WHERE guid = NEW.guid;
-         END
-        `);
+    // Update position of items that could have been inserted in the meanwhile.
+    // Since this can happen rarely and it's only done for schema coherence
+    // resonds, we won't notify about these changes.
+    yield db.executeCached(
+      `CREATE TEMP TRIGGER moz_bookmarks_reorder_trigger
+         AFTER UPDATE OF position ON moz_bookmarks
+         WHEN NEW.position = -1
+       BEGIN
+         UPDATE moz_bookmarks
+         SET position = (SELECT MAX(position) FROM moz_bookmarks
+                         WHERE parent = NEW.parent) +
+                        (SELECT count(*) FROM moz_bookmarks
+                         WHERE parent = NEW.parent
+                           AND position BETWEEN OLD.position AND -1)
+         WHERE guid = NEW.guid;
+       END
+      `);
 
-      yield db.executeCached(
-        `UPDATE moz_bookmarks SET position = -1 WHERE position < 0`);
+    yield db.executeCached(
+      `UPDATE moz_bookmarks SET position = -1 WHERE position < 0`);
 
-      yield db.executeCached(`DROP TRIGGER moz_bookmarks_reorder_trigger`);
+    yield db.executeCached(`DROP TRIGGER moz_bookmarks_reorder_trigger`);
 
-      return children;
-    }.bind(this))
-  );
+    return children;
+  }.bind(this));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Helpers.
 
 /**
  * Merges objects into a new object, included non-enumerable properties.
  *
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -215,16 +215,53 @@ SetJournalMode(nsCOMPtr<mozIStorageConne
     }
     // This is an unknown journal.
     MOZ_ASSERT(true);
   }
 
   return JOURNAL_DELETE;
 }
 
+class ConnectionCloseCallback final : public mozIStorageCompletionCallback {
+  bool mDone;
+
+  ~ConnectionCloseCallback() {}
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+  ConnectionCloseCallback();
+};
+
+NS_IMETHODIMP
+ConnectionCloseCallback::Complete(nsresult, nsISupports*)
+{
+  mDone = true;
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  MOZ_ASSERT(os);
+  if (!os)
+    return NS_OK;
+  DebugOnly<nsresult> rv = os->NotifyObservers(nullptr,
+                                               TOPIC_PLACES_CONNECTION_CLOSED,
+                                               nullptr);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  return NS_OK;
+}
+
+ConnectionCloseCallback::ConnectionCloseCallback()
+  : mDone(false)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+NS_IMPL_ISUPPORTS(
+  ConnectionCloseCallback
+, mozIStorageCompletionCallback
+)
+
 nsresult
 CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
            const nsCString& aRootName, const nsCString& aGuid,
            const nsXPIDLString& titleString)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The position of the new item in its folder.
@@ -287,383 +324,52 @@ CreateRoot(nsCOMPtr<mozIStorageConnectio
     ++itemPosition;
 
   return NS_OK;
 }
 
 
 } // Anonymous namespace
 
-/**
- * An AsyncShutdown blocker in charge of shutting down places
- */
-class DatabaseShutdown final:
-    public nsIAsyncShutdownBlocker,
-    public nsIAsyncShutdownCompletionCallback,
-    public mozIStorageCompletionCallback
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIASYNCSHUTDOWNBLOCKER
-  NS_DECL_NSIASYNCSHUTDOWNCOMPLETIONCALLBACK
-  NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
-
-  explicit DatabaseShutdown(Database* aDatabase);
-
-  already_AddRefed<nsIAsyncShutdownClient> GetClient();
-
-  /**
-   * `true` if we have not started shutdown, i.e.  if
-   * `BlockShutdown()` hasn't been called yet, false otherwise.
-   */
-  bool IsStarted() const {
-    return mIsStarted;
-  }
-
-private:
-  nsCOMPtr<nsIAsyncShutdownBarrier> mBarrier;
-  nsCOMPtr<nsIAsyncShutdownClient> mParentClient;
-
-  // The owning database.
-  // The cycle is broken in method Complete(), once the connection
-  // has been closed by mozStorage.
-  nsRefPtr<Database> mDatabase;
-
-  // The current state, used both internally and for
-  // forensics/debugging purposes.
-  enum State {
-    NOT_STARTED,
-
-    // Execution of `BlockShutdown` in progress
-    // a. `BlockShutdown` is starting.
-    RECEIVED_BLOCK_SHUTDOWN,
-    // b. `BlockShutdown` is complete, waiting for clients.
-    CALLED_WAIT_CLIENTS,
-
-    // Execution of `Done` in progress
-    // a. `Done` is starting.
-    RECEIVED_DONE,
-    // b. We have notified observers that Places will close connection.
-    NOTIFIED_OBSERVERS_PLACES_WILL_CLOSE_CONNECTION,
-    // c. Execution of `Done` is complete, waiting for mozStorage shutdown.
-    CALLED_STORAGESHUTDOWN,
-
-    // Execution of `Complete` in progress
-    // a. `Complete` is starting.
-    RECEIVED_STORAGESHUTDOWN_COMPLETE,
-    // b. We have notified observers that Places as closed connection.
-    NOTIFIED_OBSERVERS_PLACES_CONNECTION_CLOSED,
-  };
-  State mState;
-  bool mIsStarted;
-
-  // As tests may resurrect a dead `Database`, we use a counter to
-  // give the instances of `DatabaseShutdown` unique names.
-  uint16_t mCounter;
-  static uint16_t sCounter;
-
-  ~DatabaseShutdown() {}
-};
-uint16_t DatabaseShutdown::sCounter = 0;
-
-DatabaseShutdown::DatabaseShutdown(Database* aDatabase)
-  : mDatabase(aDatabase)
-  , mState(NOT_STARTED)
-  , mIsStarted(false)
-  , mCounter(sCounter++)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
-  MOZ_ASSERT(asyncShutdownSvc);
-
-  if (asyncShutdownSvc) {
-    DebugOnly<nsresult> rv = asyncShutdownSvc->MakeBarrier(
-      NS_LITERAL_STRING("Places Database shutdown"),
-      getter_AddRefs(mBarrier)
-    );
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-  }
-}
-
-already_AddRefed<nsIAsyncShutdownClient>
-DatabaseShutdown::GetClient()
-{
-  nsCOMPtr<nsIAsyncShutdownClient> client;
-  if (mBarrier) {
-    DebugOnly<nsresult> rv = mBarrier->GetClient(getter_AddRefs(client));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-  }
-  return client.forget();
-}
-
-// nsIAsyncShutdownBlocker::GetName
-NS_IMETHODIMP
-DatabaseShutdown::GetName(nsAString& aName)
-{
-  if (mCounter > 0) {
-    // During tests, we can end up with the Database singleton being resurrected.
-    // Make sure that each instance of DatabaseShutdown has a unique name.
-    nsPrintfCString name("Places DatabaseShutdown: Blocking profile-before-change (%x)", this);
-    aName = NS_ConvertUTF8toUTF16(name);
-  } else {
-    aName = NS_LITERAL_STRING("Places DatabaseShutdown: Blocking profile-before-change");
-  }
-  return NS_OK;
-}
-
-// nsIAsyncShutdownBlocker::GetState
-NS_IMETHODIMP DatabaseShutdown::GetState(nsIPropertyBag** aState)
-{
-  nsresult rv;
-  nsCOMPtr<nsIWritablePropertyBag2> bag =
-    do_CreateInstance("@mozilla.org/hash-property-bag;1", &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  // Put `mState` in field `progress`
-  nsCOMPtr<nsIWritableVariant> progress =
-    do_CreateInstance(NS_VARIANT_CONTRACTID, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  rv = progress->SetAsUint8(mState);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  rv = bag->SetPropertyAsInterface(NS_LITERAL_STRING("progress"), progress);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  // Put `mBarrier`'s state in field `barrier`, if possible
-  if (!mBarrier) {
-    return NS_OK;
-  }
-  nsCOMPtr<nsIPropertyBag> barrierState;
-  rv = mBarrier->GetState(getter_AddRefs(barrierState));
-  if (NS_FAILED(rv)) {
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIWritableVariant> barrier =
-    do_CreateInstance(NS_VARIANT_CONTRACTID, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  rv = barrier->SetAsInterface(NS_GET_IID(nsIPropertyBag), barrierState);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  rv = bag->SetPropertyAsInterface(NS_LITERAL_STRING("Barrier"), barrier);
-  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-
-  return NS_OK;
-}
-
-
-// nsIAsyncShutdownBlocker::BlockShutdown
-//
-// Step 1 in shutdown, called during profile-before-change.
-// As a `nsIAsyncShutdownBarrier`, we now need to wait until all clients
-// of `this` barrier have completed their own shutdown.
-//
-// See `Done()` for step 2.
-NS_IMETHODIMP
-DatabaseShutdown::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
-{
-  mParentClient = aParentClient;
-  mState = RECEIVED_BLOCK_SHUTDOWN;
-  mIsStarted = true;
-
-  if (NS_WARN_IF(!mBarrier)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  // Wait until all clients have removed their blockers, then proceed
-  // with own shutdown.
-  DebugOnly<nsresult> rv = mBarrier->Wait(this);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  mState = CALLED_WAIT_CLIENTS;
-  return NS_OK;
-}
-
-// nsIAsyncShutdownCompletionCallback::Done
-//
-// Step 2 in shutdown, called once all clients have removed their blockers.
-// We may now check sanity, inform observers, and close the database handler.
-//
-// See `Complete()` for step 3.
-NS_IMETHODIMP
-DatabaseShutdown::Done()
-{
-  mState = RECEIVED_DONE;
-
-  // Fire internal shutdown notifications.
-  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-  MOZ_ASSERT(os);
-  if (os) {
-    (void)os->NotifyObservers(nullptr, TOPIC_PLACES_WILL_CLOSE_CONNECTION, nullptr);
-  }
-  mState = NOTIFIED_OBSERVERS_PLACES_WILL_CLOSE_CONNECTION;
-
-  // At this stage, any use of this database is forbidden. Get rid of
-  // `gDatabase`. Note, however, that the database could be
-  // resurrected.  This can happen in particular during tests.
-  MOZ_ASSERT(Database::gDatabase == nullptr || Database::gDatabase == mDatabase);
-  Database::gDatabase = nullptr;
-
-  mDatabase->Shutdown();
-  mState = CALLED_STORAGESHUTDOWN;
-  return NS_OK;
-}
-
-
-// mozIStorageCompletionCallback::Complete
-//
-// Step 3 (and last step) of shutdown
-//
-// Called once the connection has been closed by mozStorage.
-// Inform observers of TOPIC_PLACES_CONNECTION_CLOSED.
-//
-NS_IMETHODIMP
-DatabaseShutdown::Complete(nsresult, nsISupports*)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mState = RECEIVED_STORAGESHUTDOWN_COMPLETE;
-  mDatabase = nullptr;
-
-  nsresult rv;
-  if (mParentClient) {
-    // mParentClient may be nullptr in tests
-    rv = mParentClient->RemoveBlocker(this);
-    if (NS_WARN_IF(NS_FAILED(rv))) return rv;
-  }
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  MOZ_ASSERT(os);
-  if (os) {
-    rv = os->NotifyObservers(nullptr,
-                             TOPIC_PLACES_CONNECTION_CLOSED,
-                             nullptr);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-  }
-  mState = NOTIFIED_OBSERVERS_PLACES_CONNECTION_CLOSED;
-
-  if (NS_WARN_IF(!mBarrier)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  nsCOMPtr<nsIAsyncShutdownBarrier> barrier = mBarrier.forget();
-  nsCOMPtr<nsIAsyncShutdownClient> parentClient = mParentClient.forget();
-  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-  MOZ_ASSERT(mainThread);
-
-  NS_ProxyRelease(mainThread, barrier);
-  NS_ProxyRelease(mainThread, parentClient);
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(
-  DatabaseShutdown
-, nsIAsyncShutdownBlocker
-, nsIAsyncShutdownCompletionCallback
-, mozIStorageCompletionCallback
-)
-
 ////////////////////////////////////////////////////////////////////////////////
 //// Database
 
 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
 
 NS_IMPL_ISUPPORTS(Database
 , nsIObserver
 , nsISupportsWeakReference
 )
 
 Database::Database()
   : mMainThreadStatements(mMainConn)
   , mMainThreadAsyncStatements(mMainConn)
   , mAsyncThreadStatements(mMainConn)
   , mDBPageSize(0)
   , mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK)
+  , mShuttingDown(false)
   , mClosed(false)
-  , mConnectionShutdown(new DatabaseShutdown(this))
 {
   MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content,
              "Cannot instantiate Places in the content process");
   // Attempting to create two instances of the service?
   MOZ_ASSERT(!gDatabase);
   gDatabase = this;
-
-  // Prepare async shutdown
-  nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase();
-  MOZ_ASSERT(shutdownPhase);
-
-  if (shutdownPhase) {
-    DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
-      static_cast<nsIAsyncShutdownBlocker*>(mConnectionShutdown.get()),
-      NS_LITERAL_STRING(__FILE__),
-      __LINE__,
-      NS_LITERAL_STRING(""));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-  }
-}
-
-already_AddRefed<nsIAsyncShutdownClient>
-Database::GetShutdownPhase()
-{
-  nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
-  MOZ_ASSERT(asyncShutdownSvc);
-  if (NS_WARN_IF(!asyncShutdownSvc)) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
-  DebugOnly<nsresult> rv = asyncShutdownSvc->
-    GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-  return shutdownPhase.forget();
 }
 
 Database::~Database()
 {
-}
+  // Check to make sure it's us, in case somebody wrongly creates an extra
+  // instance of this singleton class.
+  MOZ_ASSERT(gDatabase == this);
 
-bool
-Database::IsShutdownStarted() const
-{
-  if (!mConnectionShutdown) {
-    // We have already broken the cycle between `this` and `mConnectionShutdown`.
-    return true;
-  }
-  return mConnectionShutdown->IsStarted();
-}
-
-already_AddRefed<mozIStorageAsyncStatement>
-Database::GetAsyncStatement(const nsACString& aQuery) const
-{
-  if (IsShutdownStarted()) {
-    return nullptr;
+  // Remove the static reference to the service.
+  if (gDatabase == this) {
+    gDatabase = nullptr;
   }
-  MOZ_ASSERT(NS_IsMainThread());
-  return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
-}
-
-already_AddRefed<mozIStorageStatement>
-Database::GetStatement(const nsACString& aQuery) const
-{
-  if (IsShutdownStarted()) {
-    return nullptr;
-  }
-  if (NS_IsMainThread()) {
-    return mMainThreadStatements.GetCachedStatement(aQuery);
-  }
-  return mAsyncThreadStatements.GetCachedStatement(aQuery);
-}
-
-already_AddRefed<nsIAsyncShutdownClient>
-Database::GetConnectionShutdown()
-{
-  MOZ_ASSERT(mConnectionShutdown);
-
-  return mConnectionShutdown->GetClient();
 }
 
 nsresult
 Database::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<mozIStorageService> storage =
@@ -727,16 +433,17 @@ Database::Init()
     new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE);
   rv = NS_DispatchToMainThread(completeEvent);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Finally observe profile shutdown notifications.
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
     (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
+    (void)os->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, true);
   }
 
   return NS_OK;
 }
 
 nsresult
 Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
                            bool* aNewDatabaseCreated)
@@ -1895,113 +1602,51 @@ Database::MigrateV28Up() {
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   return NS_OK;
 }
 
 void
 Database::Shutdown()
 {
-
-  // As the last step in the shutdown path, finalize the database handle.
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mShuttingDown);
   MOZ_ASSERT(!mClosed);
 
-  // Break cycle
-  nsCOMPtr<mozIStorageCompletionCallback> closeListener = mConnectionShutdown.forget();
-
-  if (!mMainConn) {
-    // The connection has never been initialized. Just mark it
-    // as closed.
-    mClosed = true;
-    (void)closeListener->Complete(NS_OK, nullptr);
-    return;
-  }
-
-#ifdef DEBUG
-  { // Sanity check for missing guids.
-    bool haveNullGuids = false;
-    nsCOMPtr<mozIStorageStatement> stmt;
-
-    nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-      "SELECT 1 "
-      "FROM moz_places "
-      "WHERE guid IS NULL "
-    ), getter_AddRefs(stmt));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&haveNullGuids);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!haveNullGuids && "Found a page without a GUID!");
-
-    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-      "SELECT 1 "
-      "FROM moz_bookmarks "
-      "WHERE guid IS NULL "
-    ), getter_AddRefs(stmt));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&haveNullGuids);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!haveNullGuids && "Found a bookmark without a GUID!");
-
-    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-      "SELECT 1 "
-      "FROM moz_favicons "
-      "WHERE guid IS NULL "
-    ), getter_AddRefs(stmt));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&haveNullGuids);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!haveNullGuids && "Found a favicon without a GUID!");
-  }
-
-  { // Sanity check for unrounded dateAdded and lastModified values (bug
-    // 1107308).
-    bool hasUnroundedDates = false;
-    nsCOMPtr<mozIStorageStatement> stmt;
-
-    nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-        "SELECT 1 "
-        "FROM moz_bookmarks "
-        "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
-      ), getter_AddRefs(stmt));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    rv = stmt->ExecuteStep(&hasUnroundedDates);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    MOZ_ASSERT(!hasUnroundedDates && "Found unrounded dates!");
-  }
-#endif
+  mShuttingDown = true;
 
   mMainThreadStatements.FinalizeStatements();
   mMainThreadAsyncStatements.FinalizeStatements();
 
   nsRefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
     new FinalizeStatementCacheProxy<mozIStorageStatement>(
-          mAsyncThreadStatements,
-          NS_ISUPPORTS_CAST(nsIObserver*, this)
+          mAsyncThreadStatements, NS_ISUPPORTS_CAST(nsIObserver*, this)
         );
   DispatchToAsyncThread(event);
 
   mClosed = true;
 
+  nsRefPtr<ConnectionCloseCallback> closeListener =
+    new ConnectionCloseCallback();
   (void)mMainConn->AsyncClose(closeListener);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
 Database::Observe(nsISupports *aSubject,
                   const char *aTopic,
                   const char16_t *aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0 ||
-      strcmp(aTopic, TOPIC_SIMULATE_PLACES_MUST_CLOSE_1) == 0) {
+ 
+  if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
     // Tests simulating shutdown may cause multiple notifications.
-    if (IsShutdownStarted()) {
+    if (mShuttingDown) {
       return NS_OK;
     }
 
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     NS_ENSURE_STATE(os);
 
     // If shutdown happens in the same mainthread loop as init, observers could
     // handle the places-init-complete notification after xpcom-shutdown, when
@@ -2018,30 +1663,84 @@ Database::Observe(nsISupports *aSubject,
           nsCOMPtr<nsIObserver> observer = do_QueryInterface(supports);
           (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nullptr);
         }
       }
     }
 
     // Notify all Places users that we are about to shutdown.
     (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr);
-  } else if (strcmp(aTopic, TOPIC_SIMULATE_PLACES_MUST_CLOSE_2) == 0) {
+  }
+
+  else if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
     // Tests simulating shutdown may cause re-entrance.
-    if (IsShutdownStarted()) {
+    if (mShuttingDown) {
       return NS_OK;
     }
 
-    // Since we are going through shutdown of Database,
-    // we don't need to block actual shutdown anymore.
-    nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase();
-    if (shutdownPhase) {
-      shutdownPhase->RemoveBlocker(mConnectionShutdown.get());
+    // Fire internal shutdown notifications.
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    if (os) {
+      (void)os->NotifyObservers(nullptr, TOPIC_PLACES_WILL_CLOSE_CONNECTION, nullptr);
     }
 
-    return mConnectionShutdown->BlockShutdown(nullptr);
+#ifdef DEBUG
+    { // Sanity check for missing guids.
+      bool haveNullGuids = false;
+      nsCOMPtr<mozIStorageStatement> stmt;
+
+      nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+        "SELECT 1 "
+        "FROM moz_places "
+        "WHERE guid IS NULL "
+      ), getter_AddRefs(stmt));
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->ExecuteStep(&haveNullGuids);
+      NS_ENSURE_SUCCESS(rv, rv);
+      MOZ_ASSERT(!haveNullGuids && "Found a page without a GUID!");
+
+      rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+        "SELECT 1 "
+        "FROM moz_bookmarks "
+        "WHERE guid IS NULL "
+      ), getter_AddRefs(stmt));
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->ExecuteStep(&haveNullGuids);
+      NS_ENSURE_SUCCESS(rv, rv);
+      MOZ_ASSERT(!haveNullGuids && "Found a bookmark without a GUID!");
+
+      rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+        "SELECT 1 "
+        "FROM moz_favicons "
+        "WHERE guid IS NULL "
+      ), getter_AddRefs(stmt));
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->ExecuteStep(&haveNullGuids);
+      NS_ENSURE_SUCCESS(rv, rv);
+      MOZ_ASSERT(!haveNullGuids && "Found a favicon without a GUID!");
+    }
+
+    { // Sanity check for unrounded dateAdded and lastModified values (bug
+      // 1107308).
+      bool hasUnroundedDates = false;
+      nsCOMPtr<mozIStorageStatement> stmt;
+
+      nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+        "SELECT 1 "
+        "FROM moz_bookmarks "
+        "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
+      ), getter_AddRefs(stmt));
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->ExecuteStep(&hasUnroundedDates);
+      NS_ENSURE_SUCCESS(rv, rv);
+      MOZ_ASSERT(!hasUnroundedDates && "Found unrounded dates!");
+    }
+#endif
+
+    // As the last step in the shutdown path, finalize the database handle.
+    Shutdown();
   }
+
   return NS_OK;
 }
 
-
-
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -4,17 +4,16 @@
 
 #ifndef mozilla_places_Database_h_
 #define mozilla_places_Database_h_
 
 #include "MainThreadUtils.h"
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIObserver.h"
-#include "nsIAsyncShutdown.h"
 #include "mozilla/storage.h"
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/Attributes.h"
 #include "nsIEventTarget.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
 #define DATABASE_SCHEMA_VERSION 28
@@ -22,36 +21,33 @@
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // Fired when initialization fails due to a locked database.
 #define TOPIC_DATABASE_LOCKED "places-database-locked"
 // This topic is received when the profile is about to be lost.  Places does
 // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
 // Any shutdown work that requires the Places APIs should happen here.
 #define TOPIC_PROFILE_CHANGE_TEARDOWN "profile-change-teardown"
+// This topic is received just before the profile is lost.  Places begins
+// shutting down the connection and notifies TOPIC_PLACES_WILL_CLOSE_CONNECTION
+// to all listeners.  Only critical database cleanups should happen here,
+// some APIs may bail out already.
+#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
 // Fired when Places is shutting down.  Any code should stop accessing Places
 // APIs after this notification.  If you need to listen for Places shutdown
 // you should only use this notification, next ones are intended only for
 // internal Places use.
 #define TOPIC_PLACES_SHUTDOWN "places-shutdown"
 // For Internal use only.  Fired when connection is about to be closed, only
 // cleanup tasks should run at this stage, nothing should be added to the
 // database, nor APIs should be called.
 #define TOPIC_PLACES_WILL_CLOSE_CONNECTION "places-will-close-connection"
 // Fired when the connection has gone, nothing will work from now on.
 #define TOPIC_PLACES_CONNECTION_CLOSED "places-connection-closed"
 
-// Simulate profile-before-change. This topic may only be used by
-// calling `observe` directly on the database. Used for testing only.
-#define TOPIC_SIMULATE_PLACES_MUST_CLOSE_1 "test-simulate-places-shutdown-phase-1"
-
-// Simulate profile-before-change. This topic may only be used by
-// calling `observe` directly on the database. Used for testing only.
-#define TOPIC_SIMULATE_PLACES_MUST_CLOSE_2 "test-simulate-places-shutdown-phase-2"
-
 class nsIRunnable;
 
 namespace mozilla {
 namespace places {
 
 enum JournalMode {
   // Default SQLite journal mode.
   JOURNAL_DELETE = 0
@@ -59,18 +55,16 @@ enum JournalMode {
   // We fallback to this mode when WAL is unavailable.
 , JOURNAL_TRUNCATE
   // Unsafe in case of crashes on database swap or low memory.
 , JOURNAL_MEMORY
   // Can reduce number of fsyncs.  We try to use this mode by default.
 , JOURNAL_WAL
 };
 
-class DatabaseShutdown;
-
 class Database final : public nsIObserver
                      , public nsSupportsWeakReference
 {
   typedef mozilla::storage::StatementCache<mozIStorageStatement> StatementCache;
   typedef mozilla::storage::StatementCache<mozIStorageAsyncStatement> AsyncStatementCache;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -80,19 +74,20 @@ public:
 
   /**
    * Initializes the database connection and the schema.
    * In case of corruption the database is copied to a backup file and replaced.
    */
   nsresult Init();
 
   /**
-   * The AsyncShutdown client used by clients of this API to be informed of shutdown.
+   * Finalizes the cached statements and closes the database connection.
+   * A TOPIC_PLACES_CONNECTION_CLOSED notification is fired when done.
    */
-  already_AddRefed<nsIAsyncShutdownClient> GetConnectionShutdown();
+  void Shutdown();
 
   /**
    * Getter to use when instantiating the class.
    *
    * @return Singleton instance of this class.
    */
   static already_AddRefed<Database> GetDatabase()
   {
@@ -161,17 +156,27 @@ public:
    * Gets a cached synchronous statement.
    *
    * @param aQuery
    *        nsCString of SQL query.
    * @return The cached statement.
    * @note Always null check the result.
    * @note Always use a scoper to reset the statement.
    */
-  already_AddRefed<mozIStorageStatement>  GetStatement(const nsACString& aQuery) const;
+  already_AddRefed<mozIStorageStatement>
+  GetStatement(const nsACString& aQuery) const
+  {
+    if (mShuttingDown) {
+      return nullptr;
+    }
+    if (NS_IsMainThread()) {
+      return mMainThreadStatements.GetCachedStatement(aQuery);
+    }
+    return mAsyncThreadStatements.GetCachedStatement(aQuery);
+  }
 
   /**
    * Gets a cached asynchronous statement.
    *
    * @param aQuery
    *        SQL query literal.
    * @return The cached statement.
    * @note Always null check the result.
@@ -189,28 +194,28 @@ public:
    * Gets a cached asynchronous statement.
    *
    * @param aQuery
    *        nsCString of SQL query.
    * @return The cached statement.
    * @note Always null check the result.
    * @note AsyncStatements are automatically reset on execution.
    */
-  already_AddRefed<mozIStorageAsyncStatement> GetAsyncStatement(const nsACString& aQuery) const;
+  already_AddRefed<mozIStorageAsyncStatement>
+  GetAsyncStatement(const nsACString& aQuery) const
+  {
+    if (mShuttingDown) {
+      return nullptr;
+    }
+    MOZ_ASSERT(NS_IsMainThread());
+    return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
+  }
 
 protected:
   /**
-   * Finalizes the cached statements and closes the database connection.
-   * A TOPIC_PLACES_CONNECTION_CLOSED notification is fired when done.
-   */
-  void Shutdown();
-
-  bool IsShutdownStarted() const;
-
-  /**
    * Initializes the database file.  If the database does not exist or is
    * corrupt, a new one is created.  In case of corruption it also creates a
    * backup copy of the database.
    *
    * @param aStorage
    *        mozStorage service instance.
    * @param aNewDatabaseCreated
    *        whether a new database file has been created.
@@ -243,17 +248,17 @@ protected:
 
   /**
    * Initializes additionale SQLite functions, defined in SQLFunctions.h
    */
   nsresult InitFunctions();
 
   /**
    * Initializes triggers defined in nsPlacesTriggers.h
-   */
+   */  
   nsresult InitTempTriggers();
 
   /**
    * Helpers used by schema upgrades.
    */
   nsresult MigrateV13Up();
   nsresult MigrateV14Up();
   nsresult MigrateV15Up();
@@ -268,18 +273,16 @@ protected:
   nsresult MigrateV24Up();
   nsresult MigrateV25Up();
   nsresult MigrateV26Up();
   nsresult MigrateV27Up();
   nsresult MigrateV28Up();
 
   nsresult UpdateBookmarkRootTitles();
 
-  friend class DatabaseShutdown;
-
 private:
   ~Database();
 
   /**
    * Singleton getter, invoked by class instantiation.
    */
   static already_AddRefed<Database> GetSingleton();
 
@@ -288,30 +291,16 @@ private:
   nsCOMPtr<mozIStorageConnection> mMainConn;
 
   mutable StatementCache mMainThreadStatements;
   mutable AsyncStatementCache mMainThreadAsyncStatements;
   mutable StatementCache mAsyncThreadStatements;
 
   int32_t mDBPageSize;
   uint16_t mDatabaseStatus;
+  bool mShuttingDown;
   bool mClosed;
-
-  /**
-   * Determine at which shutdown phase we need to start shutting down
-   * the Database.
-   */
-  already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase();
-
-  /**
-   * A companion object in charge of shutting down the mozStorage
-   * connection once all clients have disconnected.
-   *
-   * Cycles between `this` and `mConnectionShutdown` are broken
-   * in `Shutdown()`.
-   */
-  nsRefPtr<DatabaseShutdown> mConnectionShutdown;
 };
 
 } // namespace places
 } // namespace mozilla
 
 #endif // mozilla_places_Database_h_
--- a/toolkit/components/places/History.jsm
+++ b/toolkit/components/places/History.jsm
@@ -86,16 +86,64 @@ Cu.importGlobalProperties(["URL"]);
  * to yield time to the main thread every so often to avoid janking.
  * These constants determine the maximal number of notifications we
  * may emit before we yield.
  */
 const NOTIFICATION_CHUNK_SIZE = 300;
 const ONRESULT_CHUNK_SIZE = 300;
 
 /**
+ * Private shutdown barrier blocked by ongoing operations.
+ */
+XPCOMUtils.defineLazyGetter(this, "operationsBarrier", () =>
+  new AsyncShutdown.Barrier("History.jsm: wait until all connections are closed")
+);
+
+/**
+ * Shared connection
+ */
+ XPCOMUtils.defineLazyGetter(this, "DBConnPromised", () =>
+  Task.spawn(function*() {
+    let db = yield PlacesUtils.promiseWrappedConnection();
+    try {
+      Sqlite.shutdown.addBlocker(
+        "Places History.jsm: Closing database wrapper",
+        Task.async(function*() {
+          yield operationsBarrier.wait();
+          gIsClosed = true;
+          yield db.close();
+        }),
+        () => ({
+          fetchState: () => ({
+            isClosed: gIsClosed,
+            operations: operationsBarrier.state,
+          })
+        })
+      );
+    } catch (ex) {
+      // It's too late to block shutdown of Sqlite, so close the connection
+      // immediately.
+      db.close();
+      throw ex;
+    }
+    return db;
+  })
+);
+
+/**
+ * `true` once this module has been shutdown.
+ */
+let gIsClosed = false;
+function ensureModuleIsOpen() {
+  if (gIsClosed) {
+    throw new Error("History.jsm has been shutdown");
+  }
+}
+
+/**
  * Sends a bookmarks notification through the given observers.
  *
  * @param observers
  *        array of nsINavBookmarkObserver objects.
  * @param notification
  *        the notification name.
  * @param args
  *        array of arguments to pass to the notification.
@@ -209,16 +257,18 @@ this.History = Object.freeze({
    * @resolve (bool)
    *      `true` if at least one page was removed, `false` otherwise.
    * @throws (TypeError)
    *       If `pages` has an unexpected type or if a string provided
    *       is neither a valid GUID nor a valid URI or if `pages`
    *       is an empty array.
    */
   remove: function (pages, onResult = null) {
+    ensureModuleIsOpen();
+
     // Normalize and type-check arguments
     if (Array.isArray(pages)) {
       if (pages.length == 0) {
         throw new TypeError("Expected at least one page");
       }
     } else {
       pages = [pages];
     }
@@ -239,18 +289,39 @@ this.History = Object.freeze({
 
     // At this stage, we know that either `guids` is not-empty
     // or `urls` is not-empty.
 
     if (onResult && typeof onResult != "function") {
       throw new TypeError("Invalid function: " + onResult);
     }
 
-    return PlacesUtils.withConnectionWrapper("History.jsm: remove",
-      db => remove(db, normalizedPages, onResult));
+    return Task.spawn(function*() {
+      let promise = remove(normalizedPages, onResult);
+
+      operationsBarrier.client.addBlocker(
+        "History.remove",
+        promise,
+        {
+          // In case of crash, we do not want to upload information on
+          // which urls are being cleared, for privacy reasons. GUIDs
+          // are safe wrt privacy, but useless.
+          fetchState: () => ({
+            guids: guids.length,
+            urls: normalizedPages.urls.map(u => u.protocol),
+          })
+        });
+
+      try {
+        return (yield promise);
+      } finally {
+        // Cleanup the barrier.
+        operationsBarrier.client.removeBlocker(promise);
+      }
+    });
   },
 
   /**
    * Remove visits matching specific characteristics.
    *
    * Any change may be observed through nsINavHistoryObserver.
    *
    * @param filter: (object)
@@ -273,16 +344,18 @@ this.History = Object.freeze({
    * @resolve (bool)
    *      `true` if at least one visit was removed, `false`
    *      otherwise.
    * @throws (TypeError)
    *      If `filter` does not have the expected type, in
    *      particular if the `object` is empty.
    */
   removeVisitsByFilter: function(filter, onResult = null) {
+    ensureModuleIsOpen();
+
     if (!filter || typeof filter != "object") {
       throw new TypeError("Expected a filter");
     }
 
     let hasBeginDate = "beginDate" in filter;
     let hasEndDate = "endDate" in filter;
     if (hasBeginDate) {
       ensureDate(filter.beginDate);
@@ -296,19 +369,31 @@ this.History = Object.freeze({
     if (!hasBeginDate && !hasEndDate) {
       throw new TypeError("Expected a non-empty filter");
     }
 
     if (onResult && typeof onResult != "function") {
       throw new TypeError("Invalid function: " + onResult);
     }
 
-    return PlacesUtils.withConnectionWrapper("History.jsm: removeVisitsByFilter",
-      db => removeVisitsByFilter(db, filter, onResult)
-    );
+    return Task.spawn(function*() {
+      let promise = removeVisitsByFilter(filter, onResult);
+
+      operationsBarrier.client.addBlocker(
+        "History.removeVisitsByFilter",
+        promise
+      );
+
+      try {
+        return (yield promise);
+      } finally {
+        // Cleanup the barrier.
+        operationsBarrier.client.removeBlocker(promise);
+      }
+    });
   },
 
   /**
    * Determine if a page has been visited.
    *
    * @param pages: (URL or nsIURI)
    *      The full URI of the page.
    *            or (string)
@@ -328,19 +413,29 @@ this.History = Object.freeze({
 
   /**
    * Clear all history.
    *
    * @return (Promise)
    *      A promise resolved once the operation is complete.
    */
   clear() {
-    return PlacesUtils.withConnectionWrapper("History.jsm: clear",
-      clear
-    );
+    ensureModuleIsOpen();
+
+    return Task.spawn(function* () {
+      let promise = clear();
+      operationsBarrier.client.addBlocker("History.clear", promise);
+
+      try {
+        return (yield promise);
+      } finally {
+        // Cleanup the barrier.
+        operationsBarrier.client.removeBlocker(promise);
+      }
+    });
   },
 
   /**
    * Possible values for the `transition` property of `VisitInfo`
    * objects.
    */
 
   /**
@@ -457,17 +552,19 @@ let invalidateFrecencies = Task.async(fu
     `UPDATE moz_places
      SET hidden = 0
      WHERE id in (${ ids })
      AND frecency <> 0`
   );
 });
 
 // Inner implementation of History.clear().
-let clear = Task.async(function* (db) {
+let clear = Task.async(function* () {
+  let db = yield DBConnPromised;
+
   // Remove all history.
   yield db.execute("DELETE FROM moz_historyvisits");
 
   // Clear the registered embed visits.
   PlacesUtils.history.clearEmbedVisits();
 
   // Expiration will take care of orphans.
   let observers = PlacesUtils.history.getObservers();
@@ -606,17 +703,19 @@ let notifyOnResult = Task.async(function
       // Every few notifications, yield time back to the main
       // thread to avoid jank.
       yield Promise.resolve();
     }
   }
 });
 
 // Inner implementation of History.removeVisitsByFilter.
-let removeVisitsByFilter = Task.async(function*(db, filter, onResult = null) {
+let removeVisitsByFilter = Task.async(function*(filter, onResult = null) {
+  let db = yield DBConnPromised;
+
   // 1. Determine visits that took place during the interval.  Note
   // that the database uses microseconds, while JS uses milliseconds,
   // so we need to *1000 one way and /1000 the other way.
   let dates = {
     conditions: [],
     args: {},
   };
   if ("beginDate" in filter) {
@@ -693,17 +792,18 @@ let removeVisitsByFilter = Task.async(fu
     PlacesUtils.history.clearEmbedVisits();
   }
 
   return visitsToRemove.length != 0;
 });
 
 
 // Inner implementation of History.remove.
-let remove = Task.async(function*(db, {guids, urls}, onResult = null) {
+let remove = Task.async(function*({guids, urls}, onResult = null) {
+  let db = yield DBConnPromised;
   // 1. Find out what needs to be removed
   let query =
     `SELECT id, url, guid, foreign_count, title, frecency FROM moz_places
      WHERE guid IN (${ sqlList(guids) })
         OR url  IN (${ sqlList(urls)  })
      `;
 
   let onResultData = onResult ? [] : null;
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -44,18 +44,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                   "resource://gre/modules/Deprecated.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Bookmarks",
                                   "resource://gre/modules/Bookmarks.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "History",
                                   "resource://gre/modules/History.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
-                                  "resource://gre/modules/AsyncShutdown.jsm");
 
 // The minimum amount of transactions before starting a batch. Usually we do
 // do incremental updates, a batch will cause views to completely
 // refresh instead.
 const MIN_TRANSACTIONS_FOR_BATCH = 5;
 
 // On Mac OSX, the transferable system converts "\r\n" to "\n\n", where
 // we really just want "\n". On other platforms, the transferable system
@@ -1172,259 +1170,31 @@ this.PlacesUtils = {
       root.containerOpen = false;
       if (!didSuppressNotifications)
         result.suppressNotifications = false;
     }
     return urls;
   },
 
   /**
-   * Serializes the given node (and all its descendents) as JSON
-   * and writes the serialization to the given output stream.
-   *
-   * @param   aNode
-   *          An nsINavHistoryResultNode
-   * @param   aStream
-   *          An nsIOutputStream. NOTE: it only uses the write(str, len)
-   *          method of nsIOutputStream. The caller is responsible for
-   *          closing the stream.
-   */
-  _serializeNodeAsJSONToOutputStream: function (aNode, aStream) {
-    function addGenericProperties(aPlacesNode, aJSNode) {
-      aJSNode.title = aPlacesNode.title;
-      aJSNode.id = aPlacesNode.itemId;
-      let guid = aPlacesNode.bookmarkGuid;
-      if (guid) {
-        aJSNode.itemGuid = guid;
-        var parent = aPlacesNode.parent;
-        if (parent)
-          aJSNode.parent = parent.itemId;
-
-        var dateAdded = aPlacesNode.dateAdded;
-        if (dateAdded)
-          aJSNode.dateAdded = dateAdded;
-        var lastModified = aPlacesNode.lastModified;
-        if (lastModified)
-          aJSNode.lastModified = lastModified;
-
-        // XXX need a hasAnnos api
-        var annos = [];
-        try {
-          annos = PlacesUtils.getAnnotationsForItem(aJSNode.id).filter(function(anno) {
-            // XXX should whitelist this instead, w/ a pref for
-            // backup/restore of non-whitelisted annos
-            // XXX causes JSON encoding errors, so utf-8 encode
-            //anno.value = unescape(encodeURIComponent(anno.value));
-            if (anno.name == PlacesUtils.LMANNO_FEEDURI)
-              aJSNode.livemark = 1;
-            return true;
-          });
-        } catch(ex) {}
-        if (annos.length != 0)
-          aJSNode.annos = annos;
-      }
-      // XXXdietrich - store annos for non-bookmark items
-    }
-
-    function addURIProperties(aPlacesNode, aJSNode) {
-      aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
-      aJSNode.uri = aPlacesNode.uri;
-      if (aJSNode.id && aJSNode.id != -1) {
-        // harvest bookmark-specific properties
-        var keyword = PlacesUtils.bookmarks.getKeywordForBookmark(aJSNode.id);
-        if (keyword)
-          aJSNode.keyword = keyword;
-      }
-
-      if (aPlacesNode.tags)
-        aJSNode.tags = aPlacesNode.tags;
-
-      // last character-set
-      var uri = PlacesUtils._uri(aPlacesNode.uri);
-      try {
-        var lastCharset = PlacesUtils.annotations.getPageAnnotation(
-                            uri, PlacesUtils.CHARSET_ANNO);
-        aJSNode.charset = lastCharset;
-      } catch (e) {}
-    }
-
-    function addSeparatorProperties(aPlacesNode, aJSNode) {
-      aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
-    }
-
-    function addContainerProperties(aPlacesNode, aJSNode) {
-      var concreteId = PlacesUtils.getConcreteItemId(aPlacesNode);
-      if (concreteId != -1) {
-        // This is a bookmark or a tag container.
-        if (PlacesUtils.nodeIsQuery(aPlacesNode) ||
-            concreteId != aPlacesNode.itemId) {
-          aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
-          aJSNode.uri = aPlacesNode.uri;
-          // folder shortcut
-          aJSNode.concreteId = concreteId;
-        }
-        else { // Bookmark folder or a shortcut we should convert to folder.
-          aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
-
-          // Mark root folders.
-          if (aJSNode.id == PlacesUtils.placesRootId)
-            aJSNode.root = "placesRoot";
-          else if (aJSNode.id == PlacesUtils.bookmarksMenuFolderId)
-            aJSNode.root = "bookmarksMenuFolder";
-          else if (aJSNode.id == PlacesUtils.tagsFolderId)
-            aJSNode.root = "tagsFolder";
-          else if (aJSNode.id == PlacesUtils.unfiledBookmarksFolderId)
-            aJSNode.root = "unfiledBookmarksFolder";
-          else if (aJSNode.id == PlacesUtils.toolbarFolderId)
-            aJSNode.root = "toolbarFolder";
-        }
-      }
-      else {
-        // This is a grouped container query, generated on the fly.
-        aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
-        aJSNode.uri = aPlacesNode.uri;
-      }
-    }
-
-    function appendConvertedComplexNode(aNode, aSourceNode, aArray) {
-      var repr = {};
-
-      for (let [name, value] in Iterator(aNode))
-        repr[name] = value;
-
-      // write child nodes
-      var children = repr.children = [];
-      if (!aNode.livemark) {
-        asContainer(aSourceNode);
-        var wasOpen = aSourceNode.containerOpen;
-        if (!wasOpen)
-          aSourceNode.containerOpen = true;
-        var cc = aSourceNode.childCount;
-        for (var i = 0; i < cc; ++i) {
-          var childNode = aSourceNode.getChild(i);
-          appendConvertedNode(aSourceNode.getChild(i), i, children);
-        }
-        if (!wasOpen)
-          aSourceNode.containerOpen = false;
-      }
-
-      aArray.push(repr);
-      return true;
-    }
-
-    function appendConvertedNode(bNode, aIndex, aArray) {
-      var node = {};
-
-      // set index in order received
-      // XXX handy shortcut, but are there cases where we don't want
-      // to export using the sorting provided by the query?
-      if (aIndex)
-        node.index = aIndex;
-
-      addGenericProperties(bNode, node);
-
-      var parent = bNode.parent;
-      var grandParent = parent ? parent.parent : null;
-      if (grandParent)
-        node.grandParentId = grandParent.itemId;
-
-      if (PlacesUtils.nodeIsURI(bNode)) {
-        // Tag root accept only folder nodes
-        if (parent && parent.itemId == PlacesUtils.tagsFolderId)
-          return false;
-
-        // Check for url validity, since we can't halt while writing a backup.
-        // This will throw if we try to serialize an invalid url and it does
-        // not make sense saving a wrong or corrupt uri node.
-        try {
-          PlacesUtils._uri(bNode.uri);
-        } catch (ex) {
-          return false;
-        }
-
-        addURIProperties(bNode, node);
-      }
-      else if (PlacesUtils.nodeIsContainer(bNode)) {
-        // Tag containers accept only uri nodes
-        if (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId)
-          return false;
-
-        addContainerProperties(bNode, node);
-      }
-      else if (PlacesUtils.nodeIsSeparator(bNode)) {
-        // Tag root accept only folder nodes
-        // Tag containers accept only uri nodes
-        if ((parent && parent.itemId == PlacesUtils.tagsFolderId) ||
-            (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId))
-          return false;
-
-        addSeparatorProperties(bNode, node);
-      }
-
-      if (!node.feedURI && node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
-        return appendConvertedComplexNode(node, bNode, aArray);
-
-      aArray.push(node);
-      return true;
-    }
-
-    // serialize to stream
-    var array = [];
-    if (appendConvertedNode(aNode, null, array)) {
-      var json = JSON.stringify(array[0]);
-      aStream.write(json, json.length);
-    }
-    else {
-      throw Cr.NS_ERROR_UNEXPECTED;
-    }
-  },
-
-  /**
    * Gets the shared Sqlite.jsm readonly connection to the Places database.
    * This is intended to be used mostly internally, and by other Places modules.
    * Outside the Places component, it should be used only as a last resort.
    * Keep in mind the Places DB schema is by no means frozen or even stable.
    * Your custom queries can - and will - break overtime.
    */
   promiseDBConnection: () => gAsyncDBConnPromised,
 
   /**
-   * Perform a read/write operation on the Places database.
-   *
    * Gets a Sqlite.jsm wrapped connection to the Places database.
    * This is intended to be used mostly internally, and by other Places modules.
    * Keep in mind the Places DB schema is by no means frozen or even stable.
    * Your custom queries can - and will - break overtime.
-   *
-   * As all operations on the Places database are asynchronous, if shutdown
-   * is initiated while an operation is pending, this could cause dataloss.
-   * Using `withConnectionWrapper` ensures that shutdown waits until all
-   * operations are complete before proceeding.
-   *
-   * Example:
-   * yield withConnectionWrapper("Bookmarks: Remove a bookmark", Task.async(function*(db) {
-   *    // Proceed with the db, asynchronously.
-   *    // Shutdown will not interrupt operations that take place here.
-   * }));
-   *
-   * @param {string} name The name of the operation. Used for debugging, logging
-   *   and crash reporting.
-   * @param {function(db)} task A function that takes as argument a Sqlite.jsm
-   *   connection and returns a Promise. Shutdown is guaranteed to not interrupt
-   *   execution of `task`.
    */
-  withConnectionWrapper: (name, task) => {
-    if (!name) {
-      throw new TypeError("Expecting a user-readable name");
-    }
-    return Task.spawn(function*() {
-      let db = yield gAsyncDBWrapperPromised;
-      return db.executeBeforeShutdown(name, task);
-    });
-  },
+  promiseWrappedConnection: () => gAsyncDBWrapperPromised,
 
   /**
    * Given a uri returns list of itemIds associated to it.
    *
    * @param aURI
    *        nsIURI or spec of the page.
    * @param aCallback
    *        Function to be called when done.
@@ -2204,183 +1974,182 @@ let Keywords = {
     if (!("url" in keywordEntry))
       throw new Error("undefined is not a valid URL");
     let { keyword, url } = keywordEntry;
     keyword = keyword.trim().toLowerCase();
     let postData = keywordEntry.postData || null;
     // This also checks href for validity
     url = new URL(url);
 
-    return PlacesUtils.withConnectionWrapper("Keywords.insert",  Task.async(function*(db) {
-        let cache = yield gKeywordsCachePromise;
-
-        // Trying to set the same keyword is a no-op.
-        let oldEntry = cache.get(keyword);
-        if (oldEntry && oldEntry.url.href == url.href &&
-                        oldEntry.postData == keywordEntry.postData) {
-          return;
-        }
-
-        // A keyword can only be associated to a single page.
-        // If another page is using the new keyword, we must update the keyword
-        // entry.
-        // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
-        // trigger.
-        if (oldEntry) {
-          yield db.executeCached(
-            `UPDATE moz_keywords
-             SET place_id = (SELECT id FROM moz_places WHERE url = :url),
-                 post_data = :post_data
-             WHERE keyword = :keyword
-            `, { url: url.href, keyword: keyword, post_data: postData });
-          yield notifyKeywordChange(oldEntry.url.href, "");
-        } else {
-          // An entry for the given page could be missing, in such a case we need to
-          // create it.
-          yield db.executeCached(
-            `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid)
-             VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
-            `, { url: url.href, rev_host: PlacesUtils.getReversedHost(url),
-                 frecency: url.protocol == "place:" ? 0 : -1 });
-          yield db.executeCached(
-            `INSERT INTO moz_keywords (keyword, place_id, post_data)
-             VALUES (:keyword, (SELECT id FROM moz_places WHERE url = :url), :post_data)
-            `, { url: url.href, keyword: keyword, post_data: postData });
-        }
-
-        cache.set(keyword, { keyword, url, postData });
-
-        // In any case, notify about the new keyword.
-        yield notifyKeywordChange(url.href, keyword);
-      }.bind(this))
-    );
+    return Task.spawn(function* () {
+      let cache = yield gKeywordsCachePromise;
+
+      // Trying to set the same keyword is a no-op.
+      let oldEntry = cache.get(keyword);
+      if (oldEntry && oldEntry.url.href == url.href &&
+                      oldEntry.postData == keywordEntry.postData) {
+        return;
+      }
+
+      // A keyword can only be associated to a single page.
+      // If another page is using the new keyword, we must update the keyword
+      // entry.
+      // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
+      // trigger.
+      let db = yield PlacesUtils.promiseWrappedConnection();
+      if (oldEntry) {
+        yield db.executeCached(
+          `UPDATE moz_keywords
+           SET place_id = (SELECT id FROM moz_places WHERE url = :url),
+               post_data = :post_data
+           WHERE keyword = :keyword
+          `, { url: url.href, keyword: keyword, post_data: postData });
+        yield notifyKeywordChange(oldEntry.url.href, "");
+      } else {
+        // An entry for the given page could be missing, in such a case we need to
+        // create it.
+        yield db.executeCached(
+          `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid)
+           VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
+          `, { url: url.href, rev_host: PlacesUtils.getReversedHost(url),
+               frecency: url.protocol == "place:" ? 0 : -1 });
+        yield db.executeCached(
+          `INSERT INTO moz_keywords (keyword, place_id, post_data)
+           VALUES (:keyword, (SELECT id FROM moz_places WHERE url = :url), :post_data)
+          `, { url: url.href, keyword: keyword, post_data: postData });
+      }
+
+      cache.set(keyword, { keyword, url, postData });
+
+      // In any case, notify about the new keyword.
+      yield notifyKeywordChange(url.href, keyword);
+    }.bind(this));
   },
 
   /**
    * Removes a keyword.
    *
    * @param keyword
    *        The keyword to remove.
    * @return {Promise}
    * @resolves when the removal is complete.
    */
   remove(keyword) {
     if (!keyword || typeof(keyword) != "string")
       throw new Error("Invalid keyword");
     keyword = keyword.trim().toLowerCase();
-    return PlacesUtils.withConnectionWrapper("Keywords.remove",  Task.async(function*(db) {
+    return Task.spawn(function* () {
       let cache = yield gKeywordsCachePromise;
       if (!cache.has(keyword))
         return;
       let { url } = cache.get(keyword);
       cache.delete(keyword);
 
+      let db = yield PlacesUtils.promiseWrappedConnection();
       yield db.execute(`DELETE FROM moz_keywords WHERE keyword = :keyword`,
                        { keyword });
 
       // Notify bookmarks about the removal.
       yield notifyKeywordChange(url.href, "");
-    }.bind(this))) ;
+    }.bind(this));
   }
 };
 
 // Set by the keywords API to distinguish notifications fired by the old API.
 // Once the old API will be gone, we can remove this and stop observing.
 let gIgnoreKeywordNotifications = false;
 
-XPCOMUtils.defineLazyGetter(this, "gKeywordsCachePromise", () =>
-  PlacesUtils.withConnectionWrapper("PlacesUtils: gKeywordsCachePromise",
-    Task.async(function*(db) {
-      let cache = new Map();
-      let rows = yield db.execute(
-        `SELECT keyword, url, post_data
-         FROM moz_keywords k
-         JOIN moz_places h ON h.id = k.place_id
-        `);
-      for (let row of rows) {
-        let keyword = row.getResultByName("keyword");
-        let entry = { keyword,
-                      url: new URL(row.getResultByName("url")),
-                      postData: row.getResultByName("post_data") };
-        cache.set(keyword, entry);
-      }
-
-      // Helper to get a keyword from an href.
-      function keywordsForHref(href) {
-        let keywords = [];
-        for (let [ key, val ] of cache) {
-          if (val.url.href == href)
-            keywords.push(key);
+XPCOMUtils.defineLazyGetter(this, "gKeywordsCachePromise", Task.async(function* () {
+  let cache = new Map();
+  let db = yield PlacesUtils.promiseWrappedConnection();
+  let rows = yield db.execute(
+    `SELECT keyword, url, post_data
+     FROM moz_keywords k
+     JOIN moz_places h ON h.id = k.place_id
+    `);
+  for (let row of rows) {
+    let keyword = row.getResultByName("keyword");
+    let entry = { keyword,
+                  url: new URL(row.getResultByName("url")),
+                  postData: row.getResultByName("post_data") };
+    cache.set(keyword, entry);
+  }
+
+  // Helper to get a keyword from an href.
+  function keywordsForHref(href) {
+    let keywords = [];
+    for (let [ key, val ] of cache) {
+      if (val.url.href == href)
+        keywords.push(key);
+    }
+    return keywords;
+  }
+
+  // Start observing changes to bookmarks. For now we are going to keep that
+  // relation for backwards compatibility reasons, but mostly because we are
+  // lacking a UI to manage keywords directly.
+  let observer = {
+    QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
+    onBeginUpdateBatch() {},
+    onEndUpdateBatch() {},
+    onItemAdded() {},
+    onItemVisited() {},
+    onItemMoved() {},
+
+    onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
+      if (itemType != PlacesUtils.bookmarks.TYPE_BOOKMARK)
+        return;
+
+      let keywords = keywordsForHref(uri.spec);
+      // This uri has no keywords associated, so there's nothing to do.
+      if (keywords.length == 0)
+        return;
+
+      Task.spawn(function* () {
+        // If the uri is not bookmarked anymore, we can remove this keyword.
+        let bookmark = yield PlacesUtils.bookmarks.fetch({ url: uri });
+        if (!bookmark) {
+          for (let keyword of keywords) {
+            yield PlacesUtils.keywords.remove(keyword);
+          }
         }
-        return keywords;
-      }
-
-      // Start observing changes to bookmarks. For now we are going to keep that
-      // relation for backwards compatibility reasons, but mostly because we are
-      // lacking a UI to manage keywords directly.
-      let observer = {
-        QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
-        onBeginUpdateBatch() {},
-        onEndUpdateBatch() {},
-        onItemAdded() {},
-        onItemVisited() {},
-        onItemMoved() {},
-
-        onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
-          if (itemType != PlacesUtils.bookmarks.TYPE_BOOKMARK)
-            return;
-
-          let keywords = keywordsForHref(uri.spec);
-          // This uri has no keywords associated, so there's nothing to do.
-          if (keywords.length == 0)
-            return;
-
-          Task.spawn(function* () {
-            // If the uri is not bookmarked anymore, we can remove this keyword.
-            let bookmark = yield PlacesUtils.bookmarks.fetch({ url: uri });
-            if (!bookmark) {
-              for (let keyword of keywords) {
-                yield PlacesUtils.keywords.remove(keyword);
-              }
-            }
-          }).catch(Cu.reportError);
-        },
-
-        onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid) {
-          if (gIgnoreKeywordNotifications ||
-              prop != "keyword")
-            return;
-
-          Task.spawn(function* () {
-            let bookmark = yield PlacesUtils.bookmarks.fetch(guid);
-            // By this time the bookmark could have gone, there's nothing we can do.
-            if (!bookmark)
-              return;
-
-            if (val.length == 0) {
-              // We are removing a keyword.
-              let keywords = keywordsForHref(bookmark.url.href)
-              for (let keyword of keywords) {
-                cache.delete(keyword);
-              }
-            } else {
-              // We are adding a new keyword.
-              cache.set(val, { keyword: val, url: bookmark.url });
-            }
-          }).catch(Cu.reportError);
+      }).catch(Cu.reportError);
+    },
+
+    onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid) {
+      if (gIgnoreKeywordNotifications ||
+          prop != "keyword")
+        return;
+
+      Task.spawn(function* () {
+        let bookmark = yield PlacesUtils.bookmarks.fetch(guid);
+        // By this time the bookmark could have gone, there's nothing we can do.
+        if (!bookmark)
+          return;
+
+        if (val.length == 0) {
+          // We are removing a keyword.
+          let keywords = keywordsForHref(bookmark.url.href)
+          for (let keyword of keywords) {
+            cache.delete(keyword);
+          }
+        } else {
+          // We are adding a new keyword.
+          cache.set(val, { keyword: val, url: bookmark.url });
         }
-      };
-
-      PlacesUtils.bookmarks.addObserver(observer, false);
-      PlacesUtils.registerShutdownFunction(() => {
-        PlacesUtils.bookmarks.removeObserver(observer);
-      });
-      return cache;
-    })
-));
+      }).catch(Cu.reportError);
+    }
+  };
+
+  PlacesUtils.bookmarks.addObserver(observer, false);
+  PlacesUtils.registerShutdownFunction(() => {
+    PlacesUtils.bookmarks.removeObserver(observer);
+  });
+  return cache;
+}));
 
 // Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
 // itemIds will be deprecated in favour of GUIDs, which play much better
 // with multiple undo/redo operations.  Because these GUIDs are already stored,
 // and because we don't want to revise the transactions API once more when this
 // happens, transactions are set to work with GUIDs exclusively, in the sense
 // that they may never expose itemIds, nor do they accept them as input.
 // More importantly, transactions which add or remove items guarantee to
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -292,17 +292,17 @@ nsNavHistory::Init()
   mDB = Database::GetDatabase();
   NS_ENSURE_STATE(mDB);
 
   /*****************************************************************************
    *** IMPORTANT NOTICE!
    ***
    *** Nothing after these add observer calls should return anything but NS_OK.
    *** If a failure code is returned, this nsNavHistory object will be held onto
-   *** by the observer service and the preference service.
+   *** by the observer service and the preference service. 
    ****************************************************************************/
 
   // Observe preferences changes.
   Preferences::AddWeakObservers(this, kObservedPrefs);
 
   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
   if (obsSvc) {
     (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
@@ -769,17 +769,17 @@ nsNavHistory::GetUpdateRequirements(cons
     if (! query->Domain().IsVoid())
       domainBasedItems = true;
   }
 
   if (aOptions->ResultType() ==
       nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
     return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
 
-  // Whenever there is a maximum number of results,
+  // Whenever there is a maximum number of results, 
   // and we are not a bookmark query we must requery. This
   // is because we can't generally know if any given addition/change causes
   // the item to be in the top N items in the database.
   if (aOptions->MaxResults() > 0)
     return QUERYUPDATE_COMPLEX;
 
   if (aQueries.Count() == 1 && domainBasedItems)
     return QUERYUPDATE_HOST;
@@ -1271,17 +1271,17 @@ static
 bool IsOptimizableHistoryQuery(const nsCOMArray<nsNavHistoryQuery>& aQueries,
                                  nsNavHistoryQueryOptions *aOptions,
                                  uint16_t aSortMode)
 {
   if (aQueries.Count() != 1)
     return false;
 
   nsNavHistoryQuery *aQuery = aQueries[0];
-
+ 
   if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
     return false;
 
   if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI)
     return false;
 
   if (aOptions->SortingMode() != aSortMode)
     return false;
@@ -1293,51 +1293,51 @@ bool IsOptimizableHistoryQuery(const nsC
     return false;
 
   if (aOptions->IncludeHidden())
     return false;
 
   if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1)
     return false;
 
-  if (aQuery->BeginTime() || aQuery->BeginTimeReference())
+  if (aQuery->BeginTime() || aQuery->BeginTimeReference()) 
     return false;
 
-  if (aQuery->EndTime() || aQuery->EndTimeReference())
+  if (aQuery->EndTime() || aQuery->EndTimeReference()) 
     return false;
 
-  if (!aQuery->SearchTerms().IsEmpty())
+  if (!aQuery->SearchTerms().IsEmpty()) 
     return false;
 
-  if (aQuery->OnlyBookmarked())
+  if (aQuery->OnlyBookmarked()) 
     return false;
 
   if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty())
     return false;
 
-  if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty())
+  if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty()) 
     return false;
 
-  if (aQuery->UriIsPrefix() || aQuery->Uri())
+  if (aQuery->UriIsPrefix() || aQuery->Uri()) 
     return false;
 
   if (aQuery->Folders().Length() > 0)
     return false;
 
   if (aQuery->Tags().Length() > 0)
     return false;
 
   if (aQuery->Transitions().Length() > 0)
     return false;
 
   return true;
 }
 
 static
-bool NeedToFilterResultSet(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+bool NeedToFilterResultSet(const nsCOMArray<nsNavHistoryQuery>& aQueries, 
                              nsNavHistoryQueryOptions *aOptions)
 {
   uint16_t resultType = aOptions->ResultType();
   return resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS;
 }
 
 // ** Helper class for ConstructQueryString **/
 
@@ -1385,18 +1385,18 @@ private:
   nsCString mQueryString;
   nsCString mGroupBy;
   bool mHasDateColumns;
   bool mSkipOrderBy;
   nsNavHistory::StringHash& mAddParams;
 };
 
 PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
-    const nsCString& aConditions,
-    nsNavHistoryQueryOptions* aOptions,
+    const nsCString& aConditions, 
+    nsNavHistoryQueryOptions* aOptions, 
     bool aUseLimit,
     nsNavHistory::StringHash& aAddParams,
     bool aHasSearchTerms)
 : mConditions(aConditions)
 , mUseLimit(aUseLimit)
 , mHasSearchTerms(aHasSearchTerms)
 , mResultType(aOptions->ResultType())
 , mQueryType(aOptions->QueryType())
@@ -1850,17 +1850,17 @@ PlacesSQLQueryBuilder::SelectAsSite()
 nsresult
 PlacesSQLQueryBuilder::SelectAsTag()
 {
   nsNavHistory *history = nsNavHistory::GetHistoryService();
   NS_ENSURE_STATE(history);
 
   // This allows sorting by date fields what is not possible with
   // other history queries.
-  mHasDateColumns = true;
+  mHasDateColumns = true; 
 
   mQueryString = nsPrintfCString(
     "SELECT null, 'place:folder=' || id || '&queryType=%d&type=%ld', "
            "title, null, null, null, null, null, dateAdded, "
            "lastModified, null, null, null "
     "FROM moz_bookmarks "
     "WHERE parent = %lld",
     nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS,
@@ -1900,17 +1900,17 @@ PlacesSQLQueryBuilder::Where()
     additionalVisitsConditions.AppendLiteral("LIMIT 1)");
   }
 
   mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}",
                                 additionalVisitsConditions.get());
   mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}",
                                 additionalPlacesConditions.get());
 
-  // If we used WHERE already, we inject the conditions
+  // If we used WHERE already, we inject the conditions 
   // in place of {ADDITIONAL_CONDITIONS}
   if (mQueryString.Find("{ADDITIONAL_CONDITIONS}", 0) != kNotFound) {
     nsAutoCString innerCondition;
     // If we have condition AND it
     if (!mConditions.IsEmpty()) {
       innerCondition = " AND (";
       innerCondition += mConditions;
       innerCondition += ")";
@@ -2051,25 +2051,25 @@ PlacesSQLQueryBuilder::Limit()
     mQueryString.Append(' ');
   }
   return NS_OK;
 }
 
 nsresult
 nsNavHistory::ConstructQueryString(
     const nsCOMArray<nsNavHistoryQuery>& aQueries,
-    nsNavHistoryQueryOptions* aOptions,
-    nsCString& queryString,
+    nsNavHistoryQueryOptions* aOptions, 
+    nsCString& queryString, 
     bool& aParamsPresent,
     nsNavHistory::StringHash& aAddParams)
 {
   // For information about visit_type see nsINavHistoryService.idl.
   // visitType == 0 is undefined (see bug #375777 for details).
-  // Some sites, especially Javascript-heavy ones, load things in frames to
-  // display them, resulting in a lot of these entries. This is the reason
+  // Some sites, especially Javascript-heavy ones, load things in frames to 
+  // display them, resulting in a lot of these entries. This is the reason 
   // why such visits are filtered out.
   nsresult rv;
   aParamsPresent = false;
 
   int32_t sortingMode = aOptions->SortingMode();
   NS_ASSERTION(sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE &&
                sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING,
                "Invalid sortingMode found while building query!");
@@ -2146,17 +2146,17 @@ nsNavHistory::ConstructQueryString(
                                            useLimitClause, aAddParams,
                                            hasSearchTerms);
   rv = queryStringBuilder.GetQueryString(queryString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-PLDHashOperator BindAdditionalParameter(nsNavHistory::StringHash::KeyType aParamName,
+PLDHashOperator BindAdditionalParameter(nsNavHistory::StringHash::KeyType aParamName, 
                                         nsCString aParamValue,
                                         void* aStatement)
 {
   mozIStorageStatement* stmt = static_cast<mozIStorageStatement*>(aStatement);
 
   nsresult rv = stmt->BindUTF8StringByName(aParamName, aParamValue);
   if (NS_FAILED(rv))
     return PL_DHASH_STOP;
@@ -2228,17 +2228,17 @@ nsNavHistory::GetQueryResults(nsNavHisto
     nsCOMArray<nsNavHistoryResultNode> toplevel;
     rv = ResultsAsList(statement, aOptions, &toplevel);
     NS_ENSURE_SUCCESS(rv, rv);
 
     FilterResultSet(aResultNode, toplevel, aResults, aQueries, aOptions);
   } else {
     rv = ResultsAsList(statement, aOptions, aResults);
     NS_ENSURE_SUCCESS(rv, rv);
-  }
+  } 
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
@@ -2976,27 +2976,16 @@ nsNavHistory::GetDBConnection(mozIStorag
   NS_ENSURE_ARG_POINTER(_DBConnection);
   nsRefPtr<mozIStorageConnection> connection = mDB->MainConn();
   connection.forget(_DBConnection);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNavHistory::GetShutdownClient(nsIAsyncShutdownClient **_shutdownClient)
-{
-  NS_ENSURE_ARG_POINTER(_shutdownClient);
-  nsRefPtr<nsIAsyncShutdownClient> client = mDB->GetConnectionShutdown();
-  MOZ_ASSERT(client);
-  client.forget(_shutdownClient);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries,
                                         uint32_t aQueryCount,
                                         nsINavHistoryQueryOptions* aOptions,
                                         mozIStorageStatementCallback* aCallback,
                                         mozIStoragePendingStatement** _stmt)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
   NS_ENSURE_ARG(aQueries);
@@ -3083,20 +3072,19 @@ nsNavHistory::NotifyOnPageExpired(nsIURI
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
 nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
                     const char16_t *aData)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
   if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 ||
-      strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0 ||
-      strcmp(aTopic, TOPIC_SIMULATE_PLACES_MUST_CLOSE_1) == 0 ||
-      strcmp(aTopic, TOPIC_SIMULATE_PLACES_MUST_CLOSE_2) == 0) {
+      strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0) {
     // These notifications are used by tests to simulate a Places shutdown.
     // They should just be forwarded to the Database handle.
     mDB->Observe(aSubject, aTopic, aData);
   }
 
   else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
       // Don't even try to notify observers from this point on, the category
       // cache would init services that could try to use our APIs.
@@ -3226,20 +3214,20 @@ nsNavHistory::DecayFrecency()
   return NS_OK;
 }
 
 
 // Query stuff *****************************************************************
 
 // Helper class for QueryToSelectClause
 //
-// This class helps to build part of the WHERE clause. It supports
-// multiple queries by appending the query index to the parameter name.
+// This class helps to build part of the WHERE clause. It supports 
+// multiple queries by appending the query index to the parameter name. 
 // For the query with index 0 the parameter name is not altered what
-// allows using this parameter in other situations (see SelectAsSite).
+// allows using this parameter in other situations (see SelectAsSite). 
 
 class ConditionBuilder
 {
 public:
 
   explicit ConditionBuilder(int32_t aQueryIndex): mQueryIndex(aQueryIndex)
   { }
 
@@ -3266,17 +3254,17 @@ public:
       mClause.Append(aParam);
     else
       mClause += nsPrintfCString("%s%d", aParam, mQueryIndex);
 
     mClause.Append(' ');
     return *this;
   }
 
-  void GetClauseString(nsCString& aResult)
+  void GetClauseString(nsCString& aResult) 
   {
     aResult = mClause;
   }
 
 private:
 
   int32_t mQueryIndex;
   nsCString mClause;
@@ -3301,17 +3289,17 @@ nsNavHistory::QueryToSelectClause(nsNavH
 
   ConditionBuilder clause(aQueryIndex);
 
   if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) ||
     (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) {
     clause.Condition("EXISTS (SELECT 1 FROM moz_historyvisits "
                               "WHERE place_id = h.id");
     // begin time
-    if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt)
+    if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) 
       clause.Condition("visit_date >=").Param(":begin_time");
     // end time
     if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)
       clause.Condition("visit_date <=").Param(":end_time");
     clause.Str(" LIMIT 1)");
   }
 
   // search terms
@@ -3332,17 +3320,17 @@ nsNavHistory::QueryToSelectClause(nsNavH
   }
 
   // min and max visit count
   if (aQuery->MinVisits() >= 0)
     clause.Condition("h.visit_count >=").Param(":min_visits");
 
   if (aQuery->MaxVisits() >= 0)
     clause.Condition("h.visit_count <=").Param(":max_visits");
-
+  
   // only bookmarked, has no affect on bookmarks-only queries
   if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
       aQuery->OnlyBookmarked())
     clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ")
           .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get())
           .Str("AND b.fk = h.id)");
 
   // domain
@@ -3636,17 +3624,17 @@ nsNavHistory::ResultsAsList(mozIStorageS
 }
 
 const int64_t UNDEFINED_URN_VALUE = -1;
 
 // Create a urn (like
 // urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29)
 // to be used to persist the open state of this container
 nsresult
-CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode,
+CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode, 
                        int64_t aValue, const nsCString& aTitle, nsCString& aURN)
 {
   nsAutoCString uri;
   nsresult rv = aResultNode->GetUri(uri);
   NS_ENSURE_SUCCESS(rv, rv);
 
   aURN.AssignLiteral("urn:places-persist:");
   aURN.Append(uri);
@@ -3665,35 +3653,35 @@ CreatePlacesPersistURN(nsNavHistoryQuery
 
   return NS_OK;
 }
 
 int64_t
 nsNavHistory::GetTagsFolder()
 {
   // cache our tags folder
-  // note, we can't do this in nsNavHistory::Init(),
+  // note, we can't do this in nsNavHistory::Init(), 
   // as getting the bookmarks service would initialize it.
   if (mTagsFolder == -1) {
     nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
     NS_ENSURE_TRUE(bookmarks, -1);
-
+    
     nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder);
     NS_ENSURE_SUCCESS(rv, -1);
   }
   return mTagsFolder;
 }
 
 // nsNavHistory::FilterResultSet
 //
 // This does some post-query-execution filtering:
 //   - searching on title, url and tags
 //   - limit count
 //
-// Note:  changes to filtering in FilterResultSet()
+// Note:  changes to filtering in FilterResultSet() 
 // may require changes to NeedToFilterResultSet()
 
 nsresult
 nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode,
                               const nsCOMArray<nsNavHistoryResultNode>& aSet,
                               nsCOMArray<nsNavHistoryResultNode>* aFiltered,
                               const nsCOMArray<nsNavHistoryQuery>& aQueries,
                               nsNavHistoryQueryOptions *aOptions)
@@ -4040,17 +4028,17 @@ nsNavHistory::QueryRowToResult(int64_t i
         // At this point the node is set up like a regular folder node. Here
         // we make the necessary change to make it a folder shortcut.
         resultNode->GetAsFolder()->mTargetFolderItemId = targetFolderId;
         resultNode->mItemId = itemId;
         nsAutoCString targetFolderGuid(resultNode->GetAsFolder()->mBookmarkGuid);
         resultNode->mBookmarkGuid = aBookmarkGuid;
         resultNode->GetAsFolder()->mTargetFolderGuid = targetFolderGuid;
 
-        // Use the query item title, unless it's void (in that case use the
+        // Use the query item title, unless it's void (in that case use the 
         // concrete folder title).
         if (!aTitle.IsVoid()) {
           resultNode->mTitle = aTitle;
         }
       }
     }
     else {
       // This is a regular query.
--- a/toolkit/components/places/nsPIPlacesDatabase.idl
+++ b/toolkit/components/places/nsPIPlacesDatabase.idl
@@ -6,24 +6,23 @@
 
 #include "nsISupports.idl"
 
 interface mozIStorageConnection;
 interface nsINavHistoryQuery;
 interface nsINavHistoryQueryOptions;
 interface mozIStorageStatementCallback;
 interface mozIStoragePendingStatement;
-interface nsIAsyncShutdownClient;
 
 /**
  * This is a private interface used by Places components to get access to the
  * database.  If outside consumers wish to use this, they should only read from
  * the database so they do not break any internal invariants.
  */
-[scriptable, uuid(366ee63e-a413-477d-9ad6-8d6863e89401)]
+[scriptable, uuid(6eb7ed3d-13ca-450b-b370-15c75e2f3dab)]
 interface nsPIPlacesDatabase : nsISupports
 {
   /**
    * The database connection used by Places.
    */
   readonly attribute mozIStorageConnection DBConnection;
 
   /**
@@ -38,15 +37,9 @@ interface nsPIPlacesDatabase : nsISuppor
    *       are absolutely sure that the returned results are fine for
    *       your use-case.
    */
   mozIStoragePendingStatement asyncExecuteLegacyQueries(
     [array, size_is(aQueryCount)] in nsINavHistoryQuery aQueries,
     in unsigned long aQueryCount,
     in nsINavHistoryQueryOptions aOptions,
     in mozIStorageStatementCallback aCallback);
-
-  /**
-   * Hook for clients who need to perform actions during/by the end of
-   * the shutdown of the database.
-   */
-  readonly attribute nsIAsyncShutdownClient shutdownClient;
 };
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -3,17 +3,16 @@
 this.EXPORTED_SYMBOLS = [
   "PlacesTestUtils",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
 
 this.PlacesTestUtils = Object.freeze({
   /**
    * Asynchronously adds visits to a page.
@@ -28,18 +27,18 @@ this.PlacesTestUtils = Object.freeze({
    *            [optional] visitDate: visit date in microseconds from the epoch
    *            [optional] referrer: nsIURI of the referrer for this visit
    *          }
    *
    * @return {Promise}
    * @resolves When all visits have been added successfully.
    * @rejects JavaScript exception.
    */
-  addVisits: Task.async(function*(placeInfo) {
-    let promise = new Promise((resolve, reject) => {
+  addVisits(placeInfo) {
+    return new Promise((resolve, reject) => {
       let places = [];
       if (placeInfo instanceof Ci.nsIURI) {
         places.push({ uri: placeInfo });
       }
       else if (Array.isArray(placeInfo)) {
         places = places.concat(placeInfo);
       } else {
         places.push(placeInfo)
@@ -69,18 +68,17 @@ this.PlacesTestUtils = Object.freeze({
           },
           handleResult: function () {},
           handleCompletion: function UP_handleCompletion() {
             resolve();
           }
         }
       );
     });
-    return (yield promise);
-  }),
+  },
 
   /**
    * Clear all history.
    *
    * @return {Promise}
    * @resolves When history was cleared successfully.
    * @rejects JavaScript exception.
    */
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -362,30 +362,22 @@ function promiseTopicObserved(aTopic)
       resolve([aSubject, aData]);
     }, aTopic, false);
   });
 }
 
 /**
  * Simulates a Places shutdown.
  */
-let shutdownPlaces = function() {
-  do_print("shutdownPlaces: starting");
-  let promise = new Promise(resolve => {
-    Services.obs.addObserver(resolve, "places-connection-closed", false);
-  });
+function shutdownPlaces(aKeepAliveConnection)
+{
   let hs = PlacesUtils.history.QueryInterface(Ci.nsIObserver);
-  hs.observe(null, "test-simulate-places-shutdown-phase-1", null);
-  do_print("shutdownPlaces: sent test-simulate-places-shutdown-phase-1");
-  hs.observe(null, "test-simulate-places-shutdown-phase-2", null);
-  do_print("shutdownPlaces: sent test-simulate-places-shutdown-phase-2");
-  return promise.then(() => {
-    do_print("shutdownPlaces: complete");
-  });
-};
+  hs.observe(null, "profile-change-teardown", null);
+  hs.observe(null, "profile-before-change", null);
+}
 
 const FILENAME_BOOKMARKS_HTML = "bookmarks.html";
 const FILENAME_BOOKMARKS_JSON = "bookmarks-" +
   (PlacesBackups.toISODateString(new Date())) + ".json";
 
 /**
  * Creates a bookmarks.html file in the profile folder from a given source file.
  *
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -216,17 +216,17 @@ add_task(function* test_remove_many() {
     onPageChanged: function(aURI) {
       Assert.ok(false, "Unexpected call to onPageChanged " + aURI.spec);
     },
     onFrecencyChanged: function(aURI) {
       let origin = pages.find(x =>  x.uri.spec == aURI.spec);
       Assert.ok(origin);
       Assert.ok(origin.hasBookmark, "Observing onFrecencyChanged on a page with a bookmark");
       origin.onFrecencyChangedCalled = true;
-      // We do not make sure that `origin.onFrecencyChangedCalled` is `false`, as
+      // We do not make sure that `origin.onFrecencyChangedCalled` is `false`, as 
     },
     onManyFrecenciesChanged: function() {
       Assert.ok(false, "Observing onManyFrecenciesChanges, this is most likely correct but not covered by this test");
     },
     onDeleteURI: function(aURI) {
       let origin = pages.find(x => x.uri.spec == aURI.spec);
       Assert.ok(origin);
       Assert.ok(!origin.hasBookmark, "Observing onDeleteURI on a page without a bookmark");
--- a/toolkit/components/places/tests/unit/test_PlacesUtils_invalidateCachedGuidFor.js
+++ b/toolkit/components/places/tests/unit/test_PlacesUtils_invalidateCachedGuidFor.js
@@ -5,20 +5,19 @@ add_task(function* () {
   let id = yield PlacesUtils.promiseItemId(bm.guid);
   Assert.equal((yield PlacesUtils.promiseItemGuid(id)), bm.guid);
 
   // Ensure invalidating a non-existent itemId doesn't throw.
   PlacesUtils.invalidateCachedGuidFor(null);
   PlacesUtils.invalidateCachedGuidFor(9999);
 
   do_print("Change the GUID.");
-  yield PlacesUtils.withConnectionWrapper("test", Task.async(function*(db) {
-    yield db.execute("UPDATE moz_bookmarks SET guid = :guid WHERE id = :id",
-                     { guid: "123456789012", id});
-  }));
+  let db = yield PlacesUtils.promiseWrappedConnection();
+  yield db.execute("UPDATE moz_bookmarks SET guid = :guid WHERE id = :id",
+                   { guid: "123456789012", id});
   // The cache should still point to the wrong id.
   Assert.equal((yield PlacesUtils.promiseItemGuid(id)), bm.guid);
 
   do_print("Invalidate the cache.");
   PlacesUtils.invalidateCachedGuidFor(id);
   Assert.equal((yield PlacesUtils.promiseItemGuid(id)), "123456789012");
   Assert.equal((yield PlacesUtils.promiseItemId("123456789012")), id);
   yield Assert.rejects(PlacesUtils.promiseItemId(bm.guid), /no item found for the given GUID/);
--- a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js
@@ -26,20 +26,19 @@ add_task(function* test_corrupt_file() {
   yield database_check();
 });
 
 add_task(function* test_corrupt_database() {
   // Create corruption in the database, then export.
   let corruptBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
                                                              url: "http://test.mozilla.org",
                                                              title: "We love belugas" });
-  let db = yield PlacesUtils.withConnectionWrapper("test", Task.async(function*(db) {
-    yield db.execute("UPDATE moz_bookmarks SET fk = NULL WHERE guid = :guid",
-                     { guid: corruptBookmark.guid });
-  }));
+  let db = yield PlacesUtils.promiseWrappedConnection();
+  yield db.execute("UPDATE moz_bookmarks SET fk = NULL WHERE guid = :guid",
+                   { guid: corruptBookmark.guid });
 
   let bookmarksFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.exported.html");
   if ((yield OS.File.exists(bookmarksFile)))
     yield OS.File.remove(bookmarksFile);
   yield BookmarkHTMLUtils.exportToFile(bookmarksFile);
 
   // Import again and check for correctness.
   yield PlacesUtils.bookmarks.eraseEverything();
--- a/toolkit/components/places/tests/unit/test_history_notifications.js
+++ b/toolkit/components/places/tests/unit/test_history_notifications.js
@@ -46,33 +46,30 @@ function run_test() {
   do_check_true(db.exists());
 
   // We need an exclusive lock on the db
   dbConn.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
   // Exclusive locking is lazy applied, we need to make a write to activate it
   dbConn.executeSimpleSQL("PRAGMA USER_VERSION = 1");
 
   // Try to create history service while the db is locked
-  Assert.throws(() => Cc["@mozilla.org/browser/nav-history-service;1"].
-    getService(Ci.nsINavHistoryService),
-    /NS_ERROR_XPC_GS_RETURNED_FAILURE/);
+  try {
+    var hs1 = Cc["@mozilla.org/browser/nav-history-service;1"].
+              getService(Ci.nsINavHistoryService);
+    do_throw("Creating an instance of history service on a locked db should throw");
+  } catch (ex) {}
 
   // Close our connection and try to cleanup the file (could fail on Windows)
   dbConn.close();
   if (db.exists()) {
     try {
       db.remove(false);
     } catch(e) { dump("Unable to remove dummy places.sqlite"); }
   }
 
-  // Make sure that the incorrectly opened service is closed before
-  // we make another attempt. Otherwise, there will be a conflict between
-  // the two services (and an assertion failure).
-  yield shutdownPlaces();
-
   // Create history service correctly
   try {
     var hs2 = Cc["@mozilla.org/browser/nav-history-service;1"].
               getService(Ci.nsINavHistoryService);
   } catch (ex) {
     do_throw("Creating an instance of history service on a not locked db should not throw");
   }
 }
--- a/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js
+++ b/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js
@@ -437,17 +437,17 @@ add_task(function* test_getLivemark_id_s
   do_check_eq(livemark.index, bookmark.index);
 });
 
 add_task(function* test_getLivemark_removeItem_contention() {
   // do not yield.
   PlacesUtils.livemarks.addLivemark({ title: "test"
                                     , parentGuid: PlacesUtils.bookmarks.unfiledGuid
                                     , feedURI: FEED_URI
-                                  }).catch(() => {/* swallow errors*/});
+                                    });
   yield PlacesUtils.bookmarks.eraseEverything();
   let livemark = yield PlacesUtils.livemarks.addLivemark(
     { title: "test"
     , parentGuid: PlacesUtils.bookmarks.unfiledGuid
     , feedURI: FEED_URI
     });
 
   livemark = yield PlacesUtils.livemarks.getLivemark({ guid: livemark.guid });
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
@@ -13,72 +13,62 @@ XPCOMUtils.defineLazyGetter(this, "Sanit
 });
 
 /**
  * These tests ensure that the thumbnail storage is working as intended.
  * Newly captured thumbnails should be saved as files and they should as well
  * be removed when the user sanitizes their history.
  */
 function runTests() {
-  yield Task.spawn(function*() {
-    dontExpireThumbnailURLs([URL, URL_COPY]);
+  dontExpireThumbnailURLs([URL, URL_COPY]);
 
-    yield promiseClearHistory();
-    yield promiseAddVisitsAndRepopulateNewTabLinks(URL);
-    yield promiseCreateThumbnail();
+  yield clearHistory();
+  yield addVisitsAndRepopulateNewTabLinks(URL, next);
+  yield createThumbnail();
 
-    // Make sure Storage.copy() updates an existing file.
-    yield PageThumbsStorage.copy(URL, URL_COPY);
-    let copy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
-    let mtime = copy.lastModifiedTime -= 60;
+  // Make sure Storage.copy() updates an existing file.
+  yield PageThumbsStorage.copy(URL, URL_COPY);
+  let copy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+  let mtime = copy.lastModifiedTime -= 60;
 
-    yield PageThumbsStorage.copy(URL, URL_COPY);
-    isnot(new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY)).lastModifiedTime, mtime,
-          "thumbnail file was updated");
-
-    let file = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL));
-    let fileCopy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+  yield PageThumbsStorage.copy(URL, URL_COPY);
+  isnot(new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY)).lastModifiedTime, mtime,
+        "thumbnail file was updated");
 
-    // Clear the browser history. Retry until the files are gone because Windows
-    // locks them sometimes.
-    info("Clearing history");
-    while (file.exists() || fileCopy.exists()) {
-      yield promiseClearHistory();
-    }
-    info("History is clear");
+  let file = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL));
+  let fileCopy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+
+  // Clear the browser history. Retry until the files are gone because Windows
+  // locks them sometimes.
+  while (file.exists() || fileCopy.exists()) {
+    yield clearHistory();
+  }
 
-    info("Repopulating");
-    yield promiseAddVisitsAndRepopulateNewTabLinks(URL);
-    yield promiseCreateThumbnail();
+  yield addVisitsAndRepopulateNewTabLinks(URL, next);
+  yield createThumbnail();
 
-    info("Clearing the last 10 minutes of browsing history");
-    // Clear the last 10 minutes of browsing history.
-    yield promiseClearHistory(true);
+  // Clear the last 10 minutes of browsing history.
+  yield clearHistory(true);
 
-    info("Attempt to clear file");
-    // Retry until the file is gone because Windows locks it sometimes.
-    yield promiseClearFile(file, URL);    
-
-    info("Done");
-  });
+  // Retry until the file is gone because Windows locks it sometimes.
+  clearFile(file, URL);
 }
 
-let promiseClearFile = Task.async(function*(aFile, aURL) {
-  if (!aFile.exists()) {
-    return;
+function clearFile(aFile, aURL) {
+  if (aFile.exists()) {
+    // Re-add our URL to the history so that history observer's onDeleteURI()
+    // is called again.
+    PlacesTestUtils.addVisits(makeURI(aURL)).then(() => {
+      // Try again...
+      clearHistory(true, () => clearFile(aFile, aURL));
+    });
   }
-  // Re-add our URL to the history so that history observer's onDeleteURI()
-  // is called again.
-  yield PlacesTestUtils.addVisits(makeURI(aURL));
-  yield promiseClearHistory(true);
-  // Then retry.
-  return clearFile(aFile, aURL);
-});
+}
 
-function promiseClearHistory(aUseRange) {
+function clearHistory(aUseRange, aCallback = next) {
   let s = new Sanitizer();
   s.prefDomain = "privacy.cpd.";
 
   let prefs = gPrefService.getBranch(s.prefDomain);
   prefs.setBoolPref("history", true);
   prefs.setBoolPref("downloads", false);
   prefs.setBoolPref("cache", false);
   prefs.setBoolPref("cookies", false);
@@ -89,24 +79,23 @@ function promiseClearHistory(aUseRange) 
   prefs.setBoolPref("siteSettings", false);
 
   if (aUseRange) {
     let usec = Date.now() * 1000;
     s.range = [usec - 10 * 60 * 1000 * 1000, usec];
     s.ignoreTimespan = false;
   }
 
-  return s.sanitize().then(() => {
-    s.range = null;
-    s.ignoreTimespan = true;
-  });
+  s.sanitize();
+  s.range = null;
+  s.ignoreTimespan = true;
+
+  executeSoon(aCallback);
 }
 
-function promiseCreateThumbnail() {
-  return new Promise(resolve => {
-    addTab(URL, function () {
-      whenFileExists(URL, function () {
-        gBrowser.removeTab(gBrowser.selectedTab);
-        resolve();
-      });
+function createThumbnail() {
+  addTab(URL, function () {
+    whenFileExists(URL, function () {
+      gBrowser.removeTab(gBrowser.selectedTab);
+      next();
     });
   });
 }
--- a/toolkit/components/thumbnails/test/head.js
+++ b/toolkit/components/thumbnails/test/head.js
@@ -205,19 +205,16 @@ function removeThumbnail(aURL) {
  * Calls addVisits, and then forces the newtab module to repopulate its links.
  * See addVisits for parameter descriptions.
  */
 function addVisitsAndRepopulateNewTabLinks(aPlaceInfo, aCallback) {
   PlacesTestUtils.addVisits(makeURI(aPlaceInfo)).then(() => {
     NewTabUtils.links.populateCache(aCallback, true);
   });
 }
-function promiseAddVisitsAndRepopulateNewTabLinks(aPlaceInfo) {
-  return new Promise(resolve => addVisitsAndRepopulateNewTabLinks(aPlaceInfo, resolve));
-}
 
 /**
  * Calls a given callback when the thumbnail for a given URL has been found
  * on disk. Keeps trying until the thumbnail has been created.
  *
  * @param aURL The URL of the thumbnail's page.
  * @param [optional] aCallback
  *        Function to be invoked on completion.
--- a/toolkit/modules/Sqlite.jsm
+++ b/toolkit/modules/Sqlite.jsm
@@ -214,19 +214,16 @@ function ConnectionData(connection, iden
 
   // A map from statement index to mozIStoragePendingStatement, to allow for
   // canceling prior to finalizing the mozIStorageStatements.
   this._pendingStatements = new Map();
 
   // Increments for each executed statement for the life of the connection.
   this._statementCounter = 0;
 
-  // Increments whenever we request a unique operation id.
-  this._operationsCounter = 0;
-
   this._hasInProgressTransaction = false;
   // Manages a chain of transactions promises, so that new transactions
   // always happen in queue to the previous ones.  It never rejects.
   this._transactionQueue = Promise.resolve();
 
   this._idleShrinkMS = options.shrinkMemoryOnConnectionIdleMS;
   if (this._idleShrinkMS) {
     this._idleShrinkTimer = Cc["@mozilla.org/timer;1"]
@@ -235,20 +232,16 @@ function ConnectionData(connection, iden
     // shrinking now would not do anything.
   }
 
   // Deferred whose promise is resolved when the connection closing procedure
   // is complete.
   this._deferredClose = PromiseUtils.defer();
   this._closeRequested = false;
 
-  // An AsyncShutdown barrier used to make sure that we wait until clients
-  // are done before shutting down the connection.
-  this._barrier = new AsyncShutdown.Barrier(`${this._identifier}: waiting for clients`);
-
   Barriers.connections.client.addBlocker(
     this._identifier + ": waiting for shutdown",
     this._deferredClose.promise,
     () =>  ({
       identifier: this._identifier,
       isCloseRequested: this._closeRequested,
       hasDbConn: !!this._dbConn,
       hasInProgressTransaction: this._hasInProgressTransaction,
@@ -266,157 +259,57 @@ function ConnectionData(connection, iden
  * connections on garbage collection.
  *
  * Key: _identifier of ConnectionData
  * Value: ConnectionData object
  */
 ConnectionData.byId = new Map();
 
 ConnectionData.prototype = Object.freeze({
-  /**
-   * Run a task, ensuring that its execution will not be interrupted by shutdown.
-   *
-   * As the operations of this module are asynchronous, a sequence of operations,
-   * or even an individual operation, can still be pending when the process shuts
-   * down. If any of this operations is a write, this can cause data loss, simply
-   * because the write has not been completed (or even started) by shutdown.
-   *
-   * To avoid this risk, clients are encouraged to use `executeBeforeShutdown` for
-   * any write operation, as follows:
-   *
-   * myConnection.executeBeforeShutdown("Bookmarks: Removing a bookmark",
-   *   Task.async(function*(db) {
-   *     // The connection will not be closed and shutdown will not proceed
-   *     // until this task has completed.
-   *
-   *     // `db` exposes the same API as `myConnection` but provides additional
-   *     // logging support to help debug hard-to-catch shutdown timeouts.
-   *
-   *     yield db.execute(...);
-   * }));
-   *
-   * @param {string} name A human-readable name for the ongoing operation, used
-   *  for logging and debugging purposes.
-   * @param {function(db)} task A function that takes as argument a Sqlite.jsm
-   *  db and returns a Promise.
-   */
-  executeBeforeShutdown: function(name, task) {
-    if (!name) {
-      throw new TypeError("Expected a human-readable name as first argument");
-    }
-    if (typeof task != "function") {
-      throw new TypeError("Expected a function as second argument");
-    }
-    if (this._closeRequested) {
-      throw new Error(`${this._identifier}: cannot execute operation ${name}, the connection is already closing`);
-    }
-
-    // Status, used for AsyncShutdown crash reports.
-    let status = {
-      // The latest command started by `task`, either as a
-      // sql string, or as one of "<not started>" or "<closing>".
-      command: "<not started>",
-
-      // `true` if `command` was started but not completed yet.
-      isPending: false,
-    };
-
-    // An object with the same API as `this` but with
-    // additional logging. To keep logging simple, we
-    // assume that `task` is not running several queries
-    // concurrently.
-    let loggedDb = Object.create(this, {
-      execute: {
-        value: Task.async(function*(sql, ...rest) {
-          status.isPending = true;
-          status.command = sql;
-          try {
-            return (yield this.execute(sql, ...rest));
-          } finally {
-            status.isPending = false;
-          }
-        }.bind(this))
-      },
-      close: {
-        value: Task.async(function*() {
-          status.isPending = false;
-          status.command = "<close>";
-          try {
-            return (yield this.close());
-          } finally {
-            status.isPending = false;
-          }
-        }.bind(this))
-      },
-      executeCached: {
-        value: Task.async(function*(sql, ...rest) {
-          status.isPending = false;
-          status.command = sql;
-          try {
-            return (yield this.executeCached(sql, ...rest));
-          } finally {
-            status.isPending = false;
-          }
-        }.bind(this))
-      },
-    });
-
-    let promiseResult = task(loggedDb);
-    if (!promiseResult || typeof promiseResult != "object" || !("then" in promiseResult)) {
-      throw new TypeError("Expected a Promise");
-    }
-    let key = `${this._identifier}: ${name} (${this._getOperationId()})`;
-    let promiseComplete = promiseResult.catch(() => {});
-    this._barrier.client.addBlocker(key, promiseComplete, {
-      fetchState: () => status
-    });
-
-    return Task.spawn(function*() {
-      try {
-        return (yield promiseResult);
-      } finally {
-        this._barrier.client.removeBlocker(key, promiseComplete)
-      }
-    }.bind(this));
-  },
   close: function () {
     this._closeRequested = true;
 
     if (!this._dbConn) {
       return this._deferredClose.promise;
     }
 
     this._log.debug("Request to close connection.");
     this._clearIdleShrinkTimer();
 
-    return this._barrier.wait().then(() => {
-      if (!this._dbConn) {
-        return;
-      }
+    // We need to take extra care with transactions during shutdown.
+    //
+    // If we don't have a transaction in progress, we can proceed with shutdown
+    // immediately.
+    if (!this._hasInProgressTransaction) {
       return this._finalize();
-    });
+    }
+
+    // If instead we do have a transaction in progress, it might be rollback-ed
+    // automaticall by closing the connection.  Regardless, we wait for its
+    // completion, next enqueued transactions will be rejected.
+    this._log.warn("Transaction in progress at time of close. Rolling back.");
+
+    return this._transactionQueue.then(() => this._finalize());
   },
 
   clone: function (readOnly=false) {
     this.ensureOpen();
 
     this._log.debug("Request to clone connection.");
 
     let options = {
       connection: this._dbConn,
       readOnly: readOnly,
     };
     if (this._idleShrinkMS)
       options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;
 
     return cloneStorageConnection(options);
   },
-  _getOperationId: function() {
-    return this._operationsCounter++;
-  },
+
   _finalize: function () {
     this._log.debug("Finalizing connection.");
     // Cancel any pending statements.
     for (let [k, statement] of this._pendingStatements) {
       statement.cancel();
     }
     this._pendingStatements.clear();
 
@@ -631,20 +524,16 @@ ConnectionData.prototype = Object.freeze
         setTimeout(() => reject(new Error("Transaction timeout, most likely caused by unresolved pending work.")),
                    TRANSACTIONS_QUEUE_TIMEOUT_MS);
       });
       return Promise.race([transactionPromise, timeoutPromise]);
     });
     // Atomically update the queue before anyone else has a chance to enqueue
     // further transactions.
     this._transactionQueue = promise.catch(ex => { console.error(ex) });
-
-    // Make sure that we do not shutdown the connection during a transaction.
-    this._barrier.client.addBlocker(`Transaction (${this._getOperationId()})`,
-      this._transactionQueue);
     return promise;
   },
 
   shrinkMemory: function () {
     this._log.info("Shrinking memory usage.");
     let onShrunk = this._clearIdleShrinkTimer.bind(this);
     return this.execute("PRAGMA shrink_memory").then(onShrunk, onShrunk);
   },
@@ -907,18 +796,18 @@ function openConnection(options) {
     let dbOptions = null;
     if (!sharedMemoryCache) {
       dbOptions = Cc["@mozilla.org/hash-property-bag;1"].
         createInstance(Ci.nsIWritablePropertyBag);
       dbOptions.setProperty("shared", false);
     }
     Services.storage.openAsyncDatabase(file, dbOptions, (status, connection) => {
       if (!connection) {
-        log.warn(`Could not open connection to ${path}: ${status}`);
-        reject(new Error(`Could not open connection to ${path}: ${status}`));
+        log.warn("Could not open connection: " + status);
+        reject(new Error("Could not open connection: " + status));
         return;
       }
       log.info("Connection opened");
       try {
         resolve(
           new OpenedConnection(connection.QueryInterface(Ci.mozIStorageAsyncConnection),
                               identifier, openedOptions));
       } catch (ex) {
@@ -1201,20 +1090,16 @@ OpenedConnection.prototype = Object.free
    *        the original connection.
    *
    * @return Promise<OpenedConnection>
    */
   clone: function (readOnly=false) {
     return this._connectionData.clone(readOnly);
   },
 
-  executeBeforeShutdown: function(name, task) {
-    return this._connectionData.executeBeforeShutdown(name, task);
-  },
-
   /**
    * Execute a SQL statement and cache the underlying statement object.
    *
    * This function executes a SQL statement and also caches the underlying
    * derived statement object so subsequent executions are faster and use
    * less resources.
    *
    * This function optionally binds parameters to the statement as well as