Backout c0a84f517f4f (bug 824433) for xpcshell tests crashes on Linux
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 04 Feb 2014 16:18:38 +0100
changeset 177720 f2480a45ad45269923c4e8feba577136cfacb3af
parent 177719 5b3dd105258e854964272afe93f24949eed596e4
child 177721 cb2e9f9a09f76a6b08afa13c4b9020f809be2741
push id5439
push userffxbld
push dateMon, 17 Mar 2014 23:08:15 +0000
treeherdermozilla-aurora@c0befb3c8038 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs824433
milestone30.0a1
backs outc0a84f517f4f44ed056afd9e63282f5572a45649
Backout c0a84f517f4f (bug 824433) for xpcshell tests crashes on Linux
browser/app/profile/firefox.js
browser/components/nsBrowserGlue.js
browser/components/places/content/places.js
browser/components/places/tests/unit/test_browserGlue_shutdown.js
browser/components/places/tests/unit/test_clearHistory_shutdown.js
browser/components/places/tests/unit/xpcshell.ini
toolkit/components/places/BookmarkJSONUtils.jsm
toolkit/components/places/PlacesBackups.jsm
toolkit/components/telemetry/Histograms.json
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -474,17 +474,17 @@ pref("browser.ctrlTab.recentlyUsedLimit"
 // If true, at shutdown the bookmarks in your menu and toolbar will
 // be exported as HTML to the bookmarks.html file.
 pref("browser.bookmarks.autoExportHTML",          false);
 
 // The maximum number of daily bookmark backups to 
 // keep in {PROFILEDIR}/bookmarkbackups. Special values:
 // -1: unlimited
 //  0: no backups created (and deletes all existing backups)
-pref("browser.bookmarks.max_backups",             15);
+pref("browser.bookmarks.max_backups",             10);
 
 // Scripts & Windows prefs
 pref("dom.disable_open_during_load",              true);
 pref("javascript.options.showInConsole",          true);
 #ifdef DEBUG
 pref("general.warnOnAboutConfig",                 false);
 #endif
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -77,30 +77,26 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
-                                  "resource:///modules/AsyncShutdown.jsm");
-
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
-// Seconds of idle before trying to create a bookmarks backup.
-const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 10 * 60;
-// Minimum interval between backups.  We try to not create more than one backup
-// per interval.
-const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
-// Maximum interval between backups.  If the last backup is older than these
-// days we will try to create a new one more aggressively.
-const BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS = 5;
+// We try to backup bookmarks at idle times, to avoid doing that at shutdown.
+// Number of idle seconds before trying to backup bookmarks.  10 minutes.
+const BOOKMARKS_BACKUP_IDLE_TIME = 10 * 60;
+// Minimum interval in milliseconds between backups.
+const BOOKMARKS_BACKUP_INTERVAL = 86400 * 1000;
+// Maximum number of backups to create.  Old ones will be purged.
+const BOOKMARKS_BACKUP_MAX_BACKUPS = 10;
 
 // Factory object
 const BrowserGlueServiceFactory = {
   _instance: null,
   createInstance: function BGSF_createInstance(outer, iid) {
     if (outer != null)
       throw Components.results.NS_ERROR_NO_AGGREGATION;
     return this._instance == null ?
@@ -133,16 +129,17 @@ function BrowserGlue() {
 #ifndef XP_MACOSX
 # OS X has the concept of zero-window sessions and therefore ignores the
 # browser-lastwindow-close-* topics.
 #define OBSERVE_LASTWINDOW_CLOSE_TOPICS 1
 #endif
 
 BrowserGlue.prototype = {
   _saveSession: false,
+  _isIdleObserver: false,
   _isPlacesInitObserver: false,
   _isPlacesLockedObserver: false,
   _isPlacesShutdownObserver: false,
   _isPlacesDatabaseLocked: false,
   _migrationImportsDefaultBookmarks: false,
 
   _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
     if (!this._saveSession && !aForce)
@@ -260,17 +257,18 @@ BrowserGlue.prototype = {
         if (this._isPlacesShutdownObserver) {
           Services.obs.removeObserver(this, "places-shutdown");
           this._isPlacesShutdownObserver = false;
         }
         // places-shutdown is fired when the profile is about to disappear.
         this._onPlacesShutdown();
         break;
       case "idle":
-        this._backupBookmarks();
+        if (this._idleService.idleTime > BOOKMARKS_BACKUP_IDLE_TIME * 1000)
+          this._backupBookmarks();
         break;
       case "distribution-customization-complete":
         Services.obs.removeObserver(this, "distribution-customization-complete");
         // Customization has finished, we don't need the customizer anymore.
         delete this._distributionCustomizer;
         break;
       case "browser-glue-test": // used by tests
         if (data == "post-update-notification") {
@@ -419,20 +417,18 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "browser-lastwindow-close-requested");
     os.removeObserver(this, "browser-lastwindow-close-granted");
 #endif
 #ifdef MOZ_SERVICES_SYNC
     os.removeObserver(this, "weave:service:ready");
     os.removeObserver(this, "weave:engine:clients:display-uri");
 #endif
     os.removeObserver(this, "session-save");
-    if (this._bookmarksBackupIdleTime) {
-      this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
-      delete this._bookmarksBackupIdleTime;
-    }
+    if (this._isIdleObserver)
+      this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
     if (this._isPlacesInitObserver)
       os.removeObserver(this, "places-init-complete");
     if (this._isPlacesLockedObserver)
       os.removeObserver(this, "places-database-locked");
     if (this._isPlacesShutdownObserver)
       os.removeObserver(this, "places-shutdown");
     os.removeObserver(this, "handle-xul-text-link");
     os.removeObserver(this, "profile-before-change");
@@ -1063,17 +1059,17 @@ BrowserGlue.prototype = {
           importBookmarks = true;
         }
       } catch(ex) {}
 
       // If the user did not require to restore default bookmarks, or import
       // from bookmarks.html, we will try to restore from JSON
       if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) {
         // get latest JSON backup
-        var bookmarksBackupFile = yield PlacesBackups.getMostRecentBackup("json");
+        var bookmarksBackupFile = yield PlacesBackups.getMostRecent("json");
         if (bookmarksBackupFile) {
           // restore from JSON backup
           yield BookmarkJSONUtils.importFromFile(bookmarksBackupFile, true);
           importBookmarks = false;
         }
         else {
           // We have created a new database but we don't have any backup available
           importBookmarks = true;
@@ -1161,94 +1157,100 @@ BrowserGlue.prototype = {
         if (importBookmarksHTML)
           Services.prefs.setBoolPref("browser.places.importBookmarksHTML", false);
         if (restoreDefaultBookmarks)
           Services.prefs.setBoolPref("browser.bookmarks.restore_default_bookmarks",
                                      false);
       }
 
       // Initialize bookmark archiving on idle.
-      if (!this._bookmarksBackupIdleTime) {
-        this._bookmarksBackupIdleTime = BOOKMARKS_BACKUP_IDLE_TIME_SEC;
-
-        // If there is no backup, or the last bookmarks backup is too old, use
-        // a more aggressive idle observer.
-        let lastBackupFile = yield PlacesBackups.getMostRecentBackup();
-        if (!lastBackupFile) {
-            this._bookmarksBackupIdleTime /= 2;
-        }
-        else {
-          let lastBackupTime = PlacesBackups.getDateForFile(lastBackupFile);
-          let profileLastUse = Services.appinfo.replacedLockTime || Date.now();
-
-          // If there is a backup after the last profile usage date it's fine,
-          // regardless its age.  Otherwise check how old is the last
-          // available backup compared to that session.
-          if (profileLastUse > lastBackupTime) {
-            let backupAge = Math.round((profileLastUse - lastBackupTime) / 86400000);
-            // Report the age of the last available backup.
-            try {
-              Services.telemetry
-                      .getHistogramById("PLACES_BACKUPS_DAYSFROMLAST")
-                      .add(backupAge);
-            } catch (ex) {
-              Components.utils.reportError("Unable to report telemetry.");
-            }
-
-            if (backupAge > BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS)
-              this._bookmarksBackupIdleTime /= 2;
-          }
-        }
-        this._idleService.addIdleObserver(this, this._bookmarksBackupIdleTime);
+      // Once a day, either on idle or shutdown, bookmarks are backed up.
+      if (!this._isIdleObserver) {
+        this._idleService.addIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
+        this._isIdleObserver = true;
       }
 
       Services.obs.notifyObservers(null, "places-browser-init-complete", "");
     }.bind(this));
   },
 
   /**
    * Places shut-down tasks
+   * - back up bookmarks if needed.
+   * - export bookmarks as HTML, if so configured.
    * - finalize components depending on Places.
-   * - export bookmarks as HTML, if so configured.
    */
   _onPlacesShutdown: function BG__onPlacesShutdown() {
     this._sanitizer.onShutdown();
     PageThumbs.uninit();
 
-    if (this._bookmarksBackupIdleTime) {
-      this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
-      delete this._bookmarksBackupIdleTime;
+    if (this._isIdleObserver) {
+      this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
+      this._isIdleObserver = false;
     }
 
-    // Support legacy bookmarks.html format for apps that depend on that format.
-    try {
-      if (Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML")) {
-        // places-shutdown happens at profile-change-teardown, so here we
-        // can safely add a profile-before-change blocker.
-        AsyncShutdown.profileBeforeChange.addBlocker(
-          "Places: bookmarks.html",
-          () => BookmarkHTMLUtils.exportToFile(Services.dirsvc.get("BMarks", Ci.nsIFile))
-                                 .then(null, Cu.reportError)
-        );
+    let waitingForBackupToComplete = true;
+    this._backupBookmarks().then(
+      function onSuccess() {
+        waitingForBackupToComplete = false;
+      },
+      function onFailure() {
+        Cu.reportError("Unable to backup bookmarks.");
+        waitingForBackupToComplete = false;
       }
-    } catch (ex) {} // Do not export.
+    );
+
+    // Backup bookmarks to bookmarks.html to support apps that depend
+    // on the legacy format.
+    let waitingForHTMLExportToComplete = false;
+    // If this fails to get the preference value, we don't export.
+    if (Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML")) {
+      // Exceptionally, since this is a non-default setting and HTML format is
+      // discouraged in favor of the JSON backups, we spin the event loop on
+      // shutdown, to wait for the export to finish.  We cannot safely spin
+      // the event loop on shutdown until we include a watchdog to prevent
+      // potential hangs (bug 518683).  The asynchronous shutdown operations
+      // will then be handled by a shutdown service (bug 435058).
+      waitingForHTMLExportToComplete = true;
+      BookmarkHTMLUtils.exportToFile(Services.dirsvc.get("BMarks", Ci.nsIFile)).then(
+        function onSuccess() {
+          waitingForHTMLExportToComplete = false;
+        },
+        function onFailure() {
+          Cu.reportError("Unable to auto export html.");
+          waitingForHTMLExportToComplete = false;
+        }
+      );
+    }
+
+    // The events loop should spin at least once because waitingForBackupToComplete
+    // is true before checking whether backup should be made.
+    let thread = Services.tm.currentThread;
+    while (waitingForBackupToComplete || waitingForHTMLExportToComplete) {
+      thread.processNextEvent(true);
+    }
   },
 
   /**
-   * If a backup for today doesn't exist, this creates one.
+   * Backup bookmarks.
    */
   _backupBookmarks: function BG__backupBookmarks() {
     return Task.spawn(function() {
       let lastBackupFile = yield PlacesBackups.getMostRecentBackup();
       // Should backup bookmarks if there are no backups or the maximum
       // interval between backups elapsed.
       if (!lastBackupFile ||
-          new Date() - PlacesBackups.getDateForFile(lastBackupFile) > BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS * 86400000) {
-        let maxBackups = Services.prefs.getIntPref("browser.bookmarks.max_backups");
-        yield PlacesBackups.create(maxBackups);
+          new Date() - PlacesBackups.getDateForFile(lastBackupFile) > BOOKMARKS_BACKUP_INTERVAL) {
+        let maxBackups = BOOKMARKS_BACKUP_MAX_BACKUPS;
+        try {
+          maxBackups = Services.prefs.getIntPref("browser.bookmarks.max_backups");
+        }
+        catch(ex) { /* Use default. */ }
+
+        yield PlacesBackups.create(maxBackups); // Don't force creation.
       }
     });
   },
 
   /**
    * Show the notificationBox for a locked places database.
    */
   _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() {
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -535,18 +535,17 @@ var PlacesOrganizer = {
    */
   backupBookmarks: function PO_backupBookmarks() {
     let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
                  getService(Ci.nsIProperties);
     let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     let fpCallback = function fpCallback_done(aResult) {
       if (aResult != Ci.nsIFilePicker.returnCancel) {
-        // There is no OS.File version of the filepicker yet (Bug 937812).
-        PlacesBackups.saveBookmarksToJSONFile(fp.file.path);
+        PlacesBackups.saveBookmarksToJSONFile(fp.file);
       }
     };
 
     fp.init(window, PlacesUIUtils.getString("bookmarksBackupTitle"),
             Ci.nsIFilePicker.modeSave);
     fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
                     PlacesUIUtils.getString("bookmarksRestoreFilterExtension"));
     fp.defaultString = PlacesBackups.getFilenameForDate();
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_shutdown.js
@@ -0,0 +1,153 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue is correctly exporting based on preferences values,
+ * and creating bookmarks backup if one does not exist for today.
+ */
+
+// Initialize nsBrowserGlue after Places.
+let bg = Cc["@mozilla.org/browser/browserglue;1"].
+         getService(Ci.nsIBrowserGlue);
+
+// Initialize Places through Bookmarks Service.
+let bs = PlacesUtils.bookmarks;
+
+// Get other services.
+let ps = Services.prefs;
+let os = Services.obs;
+
+const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML";
+
+let tests = [];
+
+//------------------------------------------------------------------------------
+
+tests.push({
+  description: "Export to bookmarks.html if autoExportHTML is true.",
+  exec: function() {
+    remove_all_JSON_backups();
+
+    // Sanity check: we should have bookmarks on the toolbar.
+    do_check_true(bs.getIdForItemAt(bs.toolbarFolder, 0) > 0);
+
+    // Set preferences.
+    ps.setBoolPref(PREF_AUTO_EXPORT_HTML, true);
+
+    // Force nsBrowserGlue::_shutdownPlaces().
+    bg.QueryInterface(Ci.nsIObserver).observe(null,
+                                              PlacesUtils.TOPIC_SHUTDOWN,
+                                              null);
+
+    // Check bookmarks.html has been created.
+    check_bookmarks_html();
+    // Check JSON backup has been created.
+    check_JSON_backup(true);
+
+    // Check preferences have not been reverted.
+    do_check_true(ps.getBoolPref(PREF_AUTO_EXPORT_HTML));
+    // Reset preferences.
+    ps.setBoolPref(PREF_AUTO_EXPORT_HTML, false);
+
+    next_test();
+  }
+});
+
+//------------------------------------------------------------------------------
+
+tests.push({
+  description: "Export to bookmarks.html if autoExportHTML is true and a bookmarks.html exists.",
+  exec: function() {
+    // Sanity check: we should have bookmarks on the toolbar.
+    do_check_true(bs.getIdForItemAt(bs.toolbarFolder, 0) > 0);
+
+    // Set preferences.
+    ps.setBoolPref(PREF_AUTO_EXPORT_HTML, true);
+
+    // Create a bookmarks.html in the profile.
+    let profileBookmarksHTMLFile = create_bookmarks_html("bookmarks.glue.html");
+
+    // set the file's lastModifiedTime to one minute ago and get its size.
+    let lastMod = Date.now() - 60*1000;
+    profileBookmarksHTMLFile.lastModifiedTime = lastMod;
+
+    let fileSize = profileBookmarksHTMLFile.fileSize;
+
+    // Force nsBrowserGlue::_shutdownPlaces().
+    bg.QueryInterface(Ci.nsIObserver).observe(null,
+                                              PlacesUtils.TOPIC_SHUTDOWN,
+                                              null);
+
+    // Check a new bookmarks.html has been created.
+    let profileBookmarksHTMLFile = check_bookmarks_html();
+    do_check_true(profileBookmarksHTMLFile.lastModifiedTime > lastMod);
+    do_check_neq(profileBookmarksHTMLFile.fileSize, fileSize);
+
+    // Check preferences have not been reverted.
+    do_check_true(ps.getBoolPref(PREF_AUTO_EXPORT_HTML));
+    // Reset preferences.
+    ps.setBoolPref(PREF_AUTO_EXPORT_HTML, false);
+
+    next_test();
+  }
+});
+
+//------------------------------------------------------------------------------
+
+tests.push({
+  description: "Backup to JSON should be a no-op if a backup for today already exists.",
+  exec: function() {
+    // Sanity check: we should have bookmarks on the toolbar.
+    do_check_true(bs.getIdForItemAt(bs.toolbarFolder, 0) > 0);
+
+    // Create a JSON backup in the profile.
+    let profileBookmarksJSONFile = create_JSON_backup("bookmarks.glue.json");
+    // Get file lastModified and size.
+    let lastMod = profileBookmarksJSONFile.lastModifiedTime;
+    let fileSize = profileBookmarksJSONFile.fileSize;
+
+    // Force nsBrowserGlue::_shutdownPlaces().
+    bg.QueryInterface(Ci.nsIObserver).observe(null,
+                                              PlacesUtils.TOPIC_SHUTDOWN,
+                                              null);
+
+    // Check a new JSON backup has not been created.
+    do_check_true(profileBookmarksJSONFile.exists());
+    do_check_eq(profileBookmarksJSONFile.lastModifiedTime, lastMod);
+    do_check_eq(profileBookmarksJSONFile.fileSize, fileSize);
+
+    do_test_finished();
+  }
+});
+
+//------------------------------------------------------------------------------
+
+var testIndex = 0;
+function next_test() {
+  // Remove bookmarks.html from profile.
+  remove_bookmarks_html();
+
+  // Execute next test.
+  let test = tests.shift();
+  dump("\nTEST " + (++testIndex) + ": " + test.description);
+  test.exec();
+}
+
+function run_test() {
+  do_test_pending();
+
+  // Clean up bookmarks.
+  remove_all_bookmarks();
+
+  // Create some bookmarks.
+  bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://mozilla.org/"),
+                    bs.DEFAULT_INDEX, "bookmark-on-menu");
+  bs.insertBookmark(bs.toolbarFolder, uri("http://mozilla.org/"),
+                    bs.DEFAULT_INDEX, "bookmark-on-toolbar");
+
+  // Kick-off tests.
+  next_test();
+}
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -14,18 +14,18 @@ const URIS = [
 , "http://b.example2.com/"
 , "http://c.example3.com/"
 ];
 
 const TOPIC_CONNECTION_CLOSED = "places-connection-closed";
 
 let EXPECTED_NOTIFICATIONS = [
   "places-shutdown"
+, "places-expiration-finished"
 , "places-will-close-connection"
-, "places-expiration-finished"
 , "places-connection-closed"
 ];
 
 const UNEXPECTED_NOTIFICATIONS = [
   "xpcom-shutdown"
 ];
 
 const URL = "ftp://localhost/clearHistoryOnShutdown/";
--- a/browser/components/places/tests/unit/xpcshell.ini
+++ b/browser/components/places/tests/unit/xpcshell.ini
@@ -11,12 +11,13 @@ support-files =
 [test_421483.js]
 [test_browserGlue_corrupt.js]
 [test_browserGlue_corrupt_nobackup.js]
 [test_browserGlue_corrupt_nobackup_default.js]
 [test_browserGlue_distribution.js]
 [test_browserGlue_migrate.js]
 [test_browserGlue_prefs.js]
 [test_browserGlue_restore.js]
+[test_browserGlue_shutdown.js]
 [test_browserGlue_smartBookmarks.js]
 [test_clearHistory_shutdown.js]
 [test_leftpane_corruption_handling.js]
 [test_PUIU_makeTransaction.js]
--- a/toolkit/components/places/BookmarkJSONUtils.jsm
+++ b/toolkit/components/places/BookmarkJSONUtils.jsm
@@ -4,34 +4,24 @@
 
 this.EXPORTED_SYMBOLS = [ "BookmarkJSONUtils" ];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Sqlite.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
-  "resource://gre/modules/PlacesBackups.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
-                                  "resource://gre/modules/Deprecated.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
-XPCOMUtils.defineLazyGetter(this, "gTextEncoder", () => new TextEncoder());
-XPCOMUtils.defineLazyGetter(this, "localFileCtor",
-  () => Components.Constructor("@mozilla.org/file/local;1",
-                               "nsILocalFile", "initWithPath"));
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 
 this.BookmarkJSONUtils = Object.freeze({
   /**
    * Import bookmarks from a url.
    *
    * @param aURL
    *        url of the bookmark data.
    * @param aReplace
@@ -46,69 +36,43 @@ this.BookmarkJSONUtils = Object.freeze({
     return importer.importFromURL(aURL, aReplace);
   },
 
   /**
    * Restores bookmarks and tags from a JSON file.
    * @note any item annotated with "places/excludeFromBackup" won't be removed
    *       before executing the restore.
    *
-   * @param aFilePath
-   *        OS.File path or nsIFile of bookmarks in JSON format to be restored.
+   * @param aFile
+   *        nsIFile of bookmarks in JSON format to be restored.
    * @param aReplace
    *        Boolean if true, replace existing bookmarks, else merge.
    *
    * @return {Promise}
    * @resolves When the new bookmarks have been created.
    * @rejects JavaScript exception.
    */
-  importFromFile: function BJU_importFromFile(aFilePath, aReplace) {
+  importFromFile: function BJU_importFromFile(aFile, aReplace) {
     let importer = new BookmarkImporter();
-    // TODO (bug 967192): convert to pure OS.File
-    let file = aFilePath instanceof Ci.nsIFile ? aFilePath
-                                               : new localFileCtor(aFilePath);
-    return importer.importFromFile(file, aReplace);
+    return importer.importFromFile(aFile, aReplace);
   },
 
   /**
-   * Serializes bookmarks using JSON, and writes to the supplied file path.
+   * Serializes bookmarks using JSON, and writes to the supplied file.
    *
-   * @param aFilePath
-   *        OS.File path for the "bookmarks.json" file to be created.
+   * @param aLocalFile
+   *        nsIFile for the "bookmarks.json" file to be created.
    *
    * @return {Promise}
-   * @resolves To the exported bookmarks count when the file has been created.
+   * @resolves When the file has been created.
    * @rejects JavaScript exception.
-   * @deprecated passing an nsIFile is deprecated
    */
-  exportToFile: function BJU_exportToFile(aFilePath) {
-    if (aFilePath instanceof Ci.nsIFile) {
-      Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.exportToFile " +
-                         "is deprecated. Please use an OS.File path instead.",
-                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
-      aFilePath = aFilePath.path;
-    }
-    return Task.spawn(function* () {
-      let [bookmarks, count] = yield PlacesBackups.getBookmarksTree();
-      let startTime = Date.now();
-      let jsonString = JSON.stringify(bookmarks);
-      // Report the time taken to convert the tree to JSON.
-      try {
-        Services.telemetry
-                .getHistogramById("PLACES_BACKUPS_TOJSON_MS")
-                .add(Date.now() - startTime);
-      } catch (ex) {
-        Components.utils.reportError("Unable to report telemetry.");
-      }
-
-      yield OS.File.writeAtomic(aFilePath,
-                                jsonString,
-                                { tmpPath: aFilePath + ".tmp" });
-      return count;
-    });
+  exportToFile: function BJU_exportToFile(aLocalFile) {
+    let exporter = new BookmarkExporter();
+    return exporter.exportToFile(aLocalFile);
   },
 
   /**
    * Takes a JSON-serialized node and inserts it into the db.
    *
    * @param aData
    *        The unwrapped data blob of dropped or pasted data.
    * @param aContainer
@@ -393,18 +357,17 @@ BookmarkImporter.prototype = {
                 PlacesUtils.tagging.tagURI(
                   NetUtil.newURI(aChild.uri), [aData.title]);
               } catch (ex) {
                 // Invalid tag child, skip it
               }
             });
             return [folderIdMap, searchIds];
           }
-        } else if (aData.annos &&
-                   aData.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
+        } else if (aData.livemark && aData.annos) {
           // Node is a livemark
           let feedURI = null;
           let siteURI = null;
           aData.annos = aData.annos.filter(function(aAnno) {
             switch (aAnno.name) {
               case PlacesUtils.LMANNO_FEEDURI:
                 feedURI = NetUtil.newURI(aAnno.value);
                 return false;
@@ -454,18 +417,17 @@ BookmarkImporter.prototype = {
         }
         break;
       case PlacesUtils.TYPE_X_MOZ_PLACE:
         id = PlacesUtils.bookmarks.insertBookmark(
                aContainer, NetUtil.newURI(aData.uri), aIndex, aData.title);
         if (aData.keyword)
           PlacesUtils.bookmarks.setKeywordForBookmark(id, aData.keyword);
         if (aData.tags) {
-          // TODO (bug 967196) the tagging service should trim by itself.
-          let tags = aData.tags.split(",").map(tag => tag.trim());
+          let tags = aData.tags.split(", ");
           if (tags.length)
             PlacesUtils.tagging.tagURI(NetUtil.newURI(aData.uri), tags);
         }
         if (aData.charset) {
           PlacesUtils.annotations.setPageAnnotation(
             NetUtil.newURI(aData.uri), PlacesUtils.CHARSET_ANNO, aData.charset,
             0, Ci.nsIAnnotationService.EXPIRE_NEVER);
         }
@@ -535,16 +497,316 @@ function fixupQuery(aQueryURI, aFolderId
   let convert = function(str, p1, offset, s) {
     return "folder=" + aFolderIdMap[p1];
   }
   let stringURI = aQueryURI.spec.replace(/folder=([0-9]+)/g, convert);
 
   return NetUtil.newURI(stringURI);
 }
 
+function BookmarkExporter() {}
+BookmarkExporter.prototype = {
+  exportToFile: function BE_exportToFile(aLocalFile) {
+    return Task.spawn(this._writeToFile(aLocalFile));
+  },
+
+  _converterOut: null,
+
+  _writeToFile: function BE__writeToFile(aLocalFile) {
+    // Create a file that can be accessed by the current user only.
+    let safeFileOut = Cc["@mozilla.org/network/safe-file-output-stream;1"].
+                      createInstance(Ci.nsIFileOutputStream);
+    safeFileOut.init(aLocalFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
+                     FileUtils.MODE_TRUNCATE, parseInt("0600", 8), 0);
+    let nodeCount;
+
+    try {
+      // We need a buffered output stream for performance.  See bug 202477.
+      let bufferedOut = Cc["@mozilla.org/network/buffered-output-stream;1"].
+                        createInstance(Ci.nsIBufferedOutputStream);
+      bufferedOut.init(safeFileOut, 4096);
+      try {
+        // Write bookmarks in UTF-8.
+        this._converterOut = Cc["@mozilla.org/intl/converter-output-stream;1"].
+                             createInstance(Ci.nsIConverterOutputStream);
+        this._converterOut.init(bufferedOut, "utf-8", 0, 0);
+        try {
+          nodeCount = yield this._writeContentToFile();
+
+          // Flush the buffer and retain the target file on success only.
+          bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
+        } finally {
+          this._converterOut.close();
+          this._converterOut = null;
+        }
+      } finally {
+        bufferedOut.close();
+      }
+    } finally {
+      safeFileOut.close();
+    }
+    throw new Task.Result(nodeCount);
+  },
+
+  _writeContentToFile: function BE__writeContentToFile() {
+    return Task.spawn(function() {
+      // Weep over stream interface variance.
+      let streamProxy = {
+        converter: this._converterOut,
+        write: function(aData, aLen) {
+          this.converter.writeString(aData);
+        }
+      };
+
+      // Get list of itemIds that must be excluded from the backup.
+      let excludeItems = PlacesUtils.annotations.getItemsWithAnnotation(
+                           PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
+      // Serialize to JSON and write to stream.
+      let nodeCount = yield BookmarkRow.serializeJSONToOutputStream(streamProxy,
+                                                                    excludeItems);
+      throw new Task.Result(nodeCount);
+    }.bind(this));
+  }
+}
+
+let BookmarkRow = {
+  /**
+   * Serializes the SQL results as JSON with async SQL call and writes the
+   * serialization to the given output stream.
+   *
+   * @param   aStream
+   *          An nsIOutputStream. NOTE: it only uses the write(str, len)
+   *          method of nsIOutputStream. The caller is responsible for
+   *          closing the stream.
+   * @param   aExcludeItems
+   *          An array of item ids that should not be written to the backup.
+   * @return  {Promise}
+   * @resolves the number of serialized uri nodes.
+   */
+  serializeJSONToOutputStream: function(aStream, aExcludeItems) {
+    return Task.spawn(function() {
+      let nodes = [];
+      let nodeCount = 0;
+
+      let dbFilePath = OS.Path.join(OS.Constants.Path.profileDir,
+                                    "places.sqlite");
+      let conn = yield Sqlite.openConnection({ path: dbFilePath,
+                                               sharedMemoryCache: false });
+      try {
+        let rows = yield conn.execute(
+          "SELECT b.id, h.url, b.position, b.title, b.parent, " +
+            "b.type, b.dateAdded, b.lastModified, b.guid, t.parent AS grandParent " +
+          "FROM moz_bookmarks b " +
+          "LEFT JOIN moz_bookmarks t ON t.id = b.parent " +
+          "LEFT JOIN moz_places h ON h.id = b.fk " +
+          "ORDER BY b.parent, b.position, b.id");
+
+        // Create a Map for lookup.
+        let rowMap = new Map();
+        for (let row of rows) {
+          let parent = row.getResultByName("parent");
+          if (rowMap.has(parent)) {
+            let data = rowMap.get(parent);
+            data.children.push(row);
+          } else {
+            rowMap.set(parent, { children: [row] });
+          }
+        }
+
+        let root = rowMap.get(0);
+        if (!root) {
+          throw new Error("Root does not exist.");
+        }
+        let result = yield BookmarkRow._appendConvertedNode(root.children[0],
+                                                            rowMap,
+                                                            nodes,
+                                                            aExcludeItems);
+        if (result.appendedNode) {
+          nodeCount = result.nodeCount;
+          let json = JSON.stringify(nodes[0]);
+          aStream.write(json, json.length);
+        }
+      } catch(e) {
+        Cu.reportError("serializeJSONToOutputStream error " + e);
+      } finally {
+        yield conn.close();
+      }
+      throw new Task.Result(nodeCount);
+    });
+  },
+
+  _appendConvertedNode: function BR__appendConvertedNode(
+    aRow, aRowMap, aNodes, aExcludeItems) {
+    return Task.spawn(function() {
+      let node = {};
+      let nodeCount = 0;
+
+      this._addGenericProperties(aRow, node);
+
+      let parent = aRow.getResultByName("parent");
+      let grandParent = parent ? aRow.getResultByName("grandParent") : null;
+      let type = aRow.getResultByName("type");
+
+      if (type == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
+        // Tag root accept only folder nodes
+        if (parent == PlacesUtils.tagsFolderId)
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
+
+        // 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 {
+          NetUtil.newURI(aRow.getResultByName("url"));
+        } catch (ex) {
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
+        }
+        yield this._addURIProperties(aRow, node);
+        nodeCount++;
+      } else if (type == Ci.nsINavBookmarksService.TYPE_FOLDER) {
+        // Tag containers accept only uri nodes
+        if (grandParent && grandParent == PlacesUtils.tagsFolderId) {
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
+        }
+        this._addContainerProperties(aRow, node);
+      } else if (type == Ci.nsINavBookmarksService.TYPE_SEPARATOR) {
+        // Tag root accept only folder nodes
+        // Tag containers accept only uri nodes
+        if ((parent == PlacesUtils.tagsFolderId) ||
+            (grandParent == PlacesUtils.tagsFolderId)) {
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
+        }
+        this._addSeparatorProperties(aRow, node);
+      }
+
+      if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+        nodeCount += yield this._appendConvertedComplexNode(node,
+                                                            aNodes,
+                                                            aRowMap,
+                                                            aExcludeItems);
+        throw new Task.Result({ appendedNode: true, nodeCount: nodeCount });
+      }
+
+      aNodes.push(node);
+      throw new Task.Result({ appendedNode: true, nodeCount: nodeCount });
+    }.bind(this));
+  },
+
+  _addGenericProperties: function BR__addGenericProperties(aRow, aJSNode) {
+    let title = aRow.getResultByName("title")
+    aJSNode.title = title ? title : "";
+    aJSNode.guid = aRow.getResultByName("guid");
+    aJSNode.id = aRow.getResultByName("id");
+    aJSNode.index = aRow.getResultByName("position");
+    if (aJSNode.id != -1) {
+      let parent = aRow.getResultByName("parent");
+      if (parent)
+        aJSNode.parent = parent;
+      let dateAdded = aRow.getResultByName("dateAdded");;
+      if (dateAdded)
+        aJSNode.dateAdded = dateAdded;
+      let lastModified = aRow.getResultByName("lastModified");
+      if (lastModified)
+        aJSNode.lastModified = lastModified;
+
+      // XXX need a hasAnnos api
+      let 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
+  },
+
+  _addURIProperties: function BR__addURIProperties(aRow, aJSNode) {
+    return Task.spawn(function() {
+      aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
+      aJSNode.uri = aRow.getResultByName("url");
+      if (aJSNode.id && aJSNode.id != -1) {
+        // Harvest bookmark-specific properties
+        let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(aJSNode.id);
+        if (keyword)
+          aJSNode.keyword = keyword;
+      }
+
+      // Last character-set
+      let uri = NetUtil.newURI(aRow.getResultByName("url"));
+      let lastCharset = yield PlacesUtils.getCharsetForURI(uri)
+      if (lastCharset)
+        aJSNode.charset = lastCharset;
+    });
+  },
+
+  _addSeparatorProperties: function BR__addSeparatorProperties(aRow, aJSNode) {
+    aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
+  },
+
+  _addContainerProperties: function BR__addContainerProperties(aRow, aJSNode) {
+    // This is a bookmark or a tag container.
+    // Bookmark folder or a shortcut we should convert to folder.
+    aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
+
+    // Mark root folders
+    let itemId = aRow.getResultByName("id");
+    if (itemId == PlacesUtils.placesRootId)
+      aJSNode.root = "placesRoot";
+    else if (itemId == PlacesUtils.bookmarksMenuFolderId)
+      aJSNode.root = "bookmarksMenuFolder";
+    else if (itemId == PlacesUtils.tagsFolderId)
+      aJSNode.root = "tagsFolder";
+    else if (itemId == PlacesUtils.unfiledBookmarksFolderId)
+      aJSNode.root = "unfiledBookmarksFolder";
+    else if (itemId == PlacesUtils.toolbarFolderId)
+      aJSNode.root = "toolbarFolder";
+  },
+
+  _appendConvertedComplexNode: function BR__appendConvertedComplexNode(
+    aNode, aNodes, aRowMap, aExcludeItems) {
+    return Task.spawn(function() {
+      let repr = {};
+      let nodeCount = 0;
+
+      for (let [name, value] in Iterator(aNode))
+        repr[name] = value;
+      repr.children = [];
+
+      let data = aRowMap.get(aNode.id);
+      if (data) {
+        for (let row of data.children) {
+          let id = row.getResultByName("id");
+          // ignore exclude items
+          if (aExcludeItems && aExcludeItems.indexOf(id) != -1) {
+            continue;
+          }
+          let result = yield this._appendConvertedNode(row,
+                                                       aRowMap,
+                                                       repr.children,
+                                                       aExcludeItems);
+          nodeCount += result.nodeCount;
+        }
+      } else {
+        Cu.reportError("_appendConvertedComplexNode error: Unable to find node");
+      }
+
+      aNodes.push(repr);
+      throw new Task.Result(nodeCount);
+    }.bind(this));
+  }
+}
+
 let BookmarkNode = {
   /**
    * 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
@@ -559,36 +821,36 @@ let BookmarkNode = {
    * @param   aExcludeItems
    *          An array of item ids that should not be written to the backup.
    * @returns Task promise
    * @resolves the number of serialized uri nodes.
    */
   serializeAsJSONToOutputStream: function BN_serializeAsJSONToOutputStream(
     aNode, aStream, aIsUICommand, aResolveShortcuts, aExcludeItems) {
 
-    return Task.spawn(function* () {
+    return Task.spawn(function() {
       // Serialize to stream
       let array = [];
       let result = yield this._appendConvertedNode(aNode, null, array,
                                                    aIsUICommand,
                                                    aResolveShortcuts,
                                                    aExcludeItems);
       if (result.appendedNode) {
-        let jsonString = JSON.stringify(array[0]);
-        aStream.write(jsonString, jsonString.length);
+        let json = JSON.stringify(array[0]);
+        aStream.write(json, json.length);
       } else {
         throw Cr.NS_ERROR_UNEXPECTED;
       }
-      return result.nodeCount;
+      throw new Task.Result(result.nodeCount);
     }.bind(this));
   },
 
   _appendConvertedNode: function BN__appendConvertedNode(
     bNode, aIndex, aArray, aIsUICommand, aResolveShortcuts, aExcludeItems) {
-    return Task.spawn(function* () {
+    return Task.spawn(function() {
       let node = {};
       let nodeCount = 0;
 
       // 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;
@@ -596,58 +858,58 @@ let BookmarkNode = {
       this._addGenericProperties(bNode, node, aResolveShortcuts);
 
       let parent = bNode.parent;
       let grandParent = parent ? parent.parent : null;
 
       if (PlacesUtils.nodeIsURI(bNode)) {
         // Tag root accept only folder nodes
         if (parent && parent.itemId == PlacesUtils.tagsFolderId)
-          return { appendedNode: false, nodeCount: nodeCount };
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
 
         // 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 {
           NetUtil.newURI(bNode.uri);
         } catch (ex) {
-          return { appendedNode: false, nodeCount: nodeCount };
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
         }
 
         yield this._addURIProperties(bNode, node, aIsUICommand);
         nodeCount++;
       } else if (PlacesUtils.nodeIsContainer(bNode)) {
         // Tag containers accept only uri nodes
         if (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId)
-          return { appendedNode: false, nodeCount: nodeCount };
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
 
         this._addContainerProperties(bNode, node, aIsUICommand,
                                      aResolveShortcuts);
       } 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 { appendedNode: false, nodeCount: nodeCount };
+          throw new Task.Result({ appendedNode: false, nodeCount: nodeCount });
 
         this._addSeparatorProperties(bNode, node);
       }
 
       if (!node.feedURI && node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
         nodeCount += yield this._appendConvertedComplexNode(node,
                                                            bNode,
                                                            aArray,
                                                            aIsUICommand,
                                                            aResolveShortcuts,
                                                            aExcludeItems)
-        return { appendedNode: true, nodeCount: nodeCount };
+        throw new Task.Result({ appendedNode: true, nodeCount: nodeCount });
       }
 
       aArray.push(node);
-      return { appendedNode: true, nodeCount: nodeCount };
+      throw new Task.Result({ appendedNode: true, nodeCount: nodeCount });
     }.bind(this));
   },
 
   _addGenericProperties: function BN__addGenericProperties(
     aPlacesNode, aJSNode, aResolveShortcuts) {
     aJSNode.title = aPlacesNode.title;
     aJSNode.id = aPlacesNode.itemId;
     if (aJSNode.id != -1) {
@@ -665,16 +927,18 @@ let BookmarkNode = {
       let 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;
           if (anno.name == PlacesUtils.READ_ONLY_ANNO && aResolveShortcuts) {
             // When copying a read-only node, remove the read-only annotation.
             return false;
           }
           return true;
         });
       } catch(ex) {}
       if (annos.length != 0)
@@ -745,27 +1009,26 @@ let BookmarkNode = {
       aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
       aJSNode.uri = aPlacesNode.uri;
     }
   },
 
   _appendConvertedComplexNode: function BN__appendConvertedComplexNode(
     aNode, aSourceNode, aArray, aIsUICommand, aResolveShortcuts,
     aExcludeItems) {
-    return Task.spawn(function* () {
+    return Task.spawn(function() {
       let repr = {};
       let nodeCount = 0;
 
       for (let [name, value] in Iterator(aNode))
         repr[name] = value;
 
       // Write child nodes
       let children = repr.children = [];
-      if (!aNode.annos ||
-          !aNode.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
+      if (!aNode.livemark) {
         PlacesUtils.asContainer(aSourceNode);
         let wasOpen = aSourceNode.containerOpen;
         if (!wasOpen)
           aSourceNode.containerOpen = true;
         let cc = aSourceNode.childCount;
         for (let i = 0; i < cc; ++i) {
           let childNode = aSourceNode.getChild(i);
           if (aExcludeItems && aExcludeItems.indexOf(childNode.itemId) != -1)
@@ -775,12 +1038,12 @@ let BookmarkNode = {
                                                        aExcludeItems);
           nodeCount += result.nodeCount;
         }
         if (!wasOpen)
           aSourceNode.containerOpen = false;
       }
 
       aArray.push(repr);
-      return nodeCount;
+      throw new Task.Result(nodeCount);
     }.bind(this));
   }
-}
+}
\ No newline at end of file
--- a/toolkit/components/places/PlacesBackups.jsm
+++ b/toolkit/components/places/PlacesBackups.jsm
@@ -12,26 +12,21 @@ const Cc = Components.classes;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Deprecated.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
   "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
-  "resource://gre/modules/Sqlite.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "localFileCtor",
-  () => Components.Constructor("@mozilla.org/file/local;1",
-                               "nsILocalFile", "initWithPath"));
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+  "resource://gre/modules/FileUtils.jsm");
 
 this.PlacesBackups = {
   get _filenamesRegex() {
     // Get the localized backup filename, will be used to clear out
     // old backups with a localized name (bug 445704).
     let localizedFilename =
       PlacesUtils.getFormattedString("bookmarksArchiveFilename", [new Date()]);
     let localizedFilenamePrefix =
@@ -40,104 +35,96 @@ this.PlacesBackups = {
     return this._filenamesRegex =
       new RegExp("^(bookmarks|" + localizedFilenamePrefix + ")-([0-9-]+)(_[0-9]+)*\.(json|html)");
   },
 
   get folder() {
     Deprecated.warning(
       "PlacesBackups.folder is deprecated and will be removed in a future version",
       "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
-    return this._folder;
-  },
 
-  /**
-   * This exists just to avoid spamming deprecate warnings from internal calls
-   * needed to support deprecated methods themselves.
-   */
-  get _folder() {
     let bookmarksBackupDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     bookmarksBackupDir.append(this.profileRelativeFolderPath);
     if (!bookmarksBackupDir.exists()) {
       bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8));
       if (!bookmarksBackupDir.exists())
         throw("Unable to create bookmarks backup folder");
     }
-    delete this._folder;
-    return this._folder = bookmarksBackupDir;
+    delete this.folder;
+    return this.folder = bookmarksBackupDir;
   },
 
   /**
    * Gets backup folder asynchronously.
    * @return {Promise}
    * @resolve the folder (the folder string path).
    */
   getBackupFolder: function PB_getBackupFolder() {
-    return Task.spawn(function* () {
-      if (this._backupFolder) {
-        return this._backupFolder;
+    return Task.spawn(function() {
+      if (this._folder) {
+        throw new Task.Result(this._folder);
       }
       let profileDir = OS.Constants.Path.profileDir;
       let backupsDirPath = OS.Path.join(profileDir, this.profileRelativeFolderPath);
-      yield OS.File.makeDir(backupsDirPath, { ignoreExisting: true });
-      return this._backupFolder = backupsDirPath;
-    }.bind(this));
+      yield OS.File.makeDir(backupsDirPath, { ignoreExisting: true }).then(
+        function onSuccess() {
+          this._folder = backupsDirPath;
+         }.bind(this),
+         function onError() {
+           throw("Unable to create bookmarks backup folder");
+         });
+       throw new Task.Result(this._folder);
+     }.bind(this));
   },
 
   get profileRelativeFolderPath() "bookmarkbackups",
 
   /**
    * Cache current backups in a sorted (by date DESC) array.
    */
   get entries() {
     Deprecated.warning(
       "PlacesBackups.entries is deprecated and will be removed in a future version",
       "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
-    return this._entries;
-  },
 
-  /**
-   * This exists just to avoid spamming deprecate warnings from internal calls
-   * needed to support deprecated methods themselves.
-   */
-  get _entries() {
-    delete this._entries;
-    this._entries = [];
-    let files = this._folder.directoryEntries;
+    delete this.entries;
+    this.entries = [];
+    let files = this.folder.directoryEntries;
     while (files.hasMoreElements()) {
       let entry = files.getNext().QueryInterface(Ci.nsIFile);
       // A valid backup is any file that matches either the localized or
       // not-localized filename (bug 445704).
       let matches = entry.leafName.match(this._filenamesRegex);
       if (!entry.isHidden() && matches) {
         // Remove bogus backups in future dates.
         if (this.getDateForFile(entry) > new Date()) {
           entry.remove(false);
           continue;
         }
-        this._entries.push(entry);
+        this.entries.push(entry);
       }
     }
-    this._entries.sort((a, b) => {
+    this.entries.sort((a, b) => {
       let aDate = this.getDateForFile(a);
       let bDate = this.getDateForFile(b);
       return aDate < bDate ? 1 : aDate > bDate ? -1 : 0;
     });
-    return this._entries;
+    return this.entries;
   },
 
   /**
    * Cache current backups in a sorted (by date DESC) array.
    * @return {Promise}
    * @resolve a sorted array of string paths.
    */
   getBackupFiles: function PB_getBackupFiles() {
-    return Task.spawn(function* () {
-      if (this._backupFiles)
-        return this._backupFiles;
-
+    return Task.spawn(function() {
+      if (this._backupFiles) {
+        throw new Task.Result(this._backupFiles);
+      }
       this._backupFiles = [];
 
       let backupFolderPath = yield this.getBackupFolder();
       let iterator = new OS.File.DirectoryIterator(backupFolderPath);
       yield iterator.forEach(function(aEntry) {
         let matches = aEntry.name.match(this._filenamesRegex);
         if (matches) {
           // Remove bogus backups in future dates.
@@ -146,23 +133,23 @@ this.PlacesBackups = {
             return OS.File.remove(filePath);
           } else {
             this._backupFiles.push(filePath);
           }
         }
       }.bind(this));
       iterator.close();
 
-      this._backupFiles.sort((a, b) => {
+      this._backupFiles.sort(function(a, b) {
         let aDate = this.getDateForFile(a);
         let bDate = this.getDateForFile(b);
         return aDate < bDate ? 1 : aDate > bDate ? -1 : 0;
-      });
+      }.bind(this));
 
-      return this._backupFiles;
+      throw new Task.Result(this._backupFiles);
     }.bind(this));
   },
 
   /**
    * Creates a filename for bookmarks backup files.
    *
    * @param [optional] aDateObj
    *                   Date object used to build the filename.
@@ -202,101 +189,97 @@ this.PlacesBackups = {
    * @returns nsIFile backup file
    */
   getMostRecent: function PB_getMostRecent(aFileExt) {
     Deprecated.warning(
       "PlacesBackups.getMostRecent is deprecated and will be removed in a future version",
       "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
 
     let fileExt = aFileExt || "(json|html)";
-    for (let i = 0; i < this._entries.length; i++) {
+    for (let i = 0; i < this.entries.length; i++) {
       let rx = new RegExp("\." + fileExt + "$");
-      if (this._entries[i].leafName.match(rx))
-        return this._entries[i];
+      if (this.entries[i].leafName.match(rx))
+        return this.entries[i];
     }
     return null;
   },
 
    /**
     * Get the most recent backup file.
     *
     * @param [optional] aFileExt
     *                   Force file extension.  Either "html" or "json".
     *                   Will check for both if not defined.
     * @return {Promise}
     * @result the path to the file.
     */
    getMostRecentBackup: function PB_getMostRecentBackup(aFileExt) {
-     return Task.spawn(function* () {
+     return Task.spawn(function() {
        let fileExt = aFileExt || "(json|html)";
        let entries = yield this.getBackupFiles();
        for (let entry of entries) {
          let rx = new RegExp("\." + fileExt + "$");
          if (OS.Path.basename(entry).match(rx)) {
-           return entry;
+           throw new Task.Result(entry);
          }
        }
-       return null;
+       throw new Task.Result(null);
     }.bind(this));
   },
 
   /**
    * Serializes bookmarks using JSON, and writes to the supplied file.
    * Note: any item that should not be backed up must be annotated with
    *       "places/excludeFromBackup".
    *
-   * @param aFilePath
-   *        OS.File path for the "bookmarks.json" file to be created.
+   * @param aFile
+   *        nsIFile where to save JSON backup.
    * @return {Promise}
    * @resolves the number of serialized uri nodes.
-   * @deprecated passing an nsIFile is deprecated
    */
-  saveBookmarksToJSONFile: function PB_saveBookmarksToJSONFile(aFilePath) {
-    if (aFilePath instanceof Ci.nsIFile) {
-      Deprecated.warning("Passing an nsIFile to PlacesBackups.saveBookmarksToJSONFile " +
-                         "is deprecated. Please use an OS.File path instead.",
-                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
-      aFilePath = aFilePath.path;
-    }
-    return Task.spawn(function* () {
-      let nodeCount = yield BookmarkJSONUtils.exportToFile(aFilePath);
+  saveBookmarksToJSONFile: function PB_saveBookmarksToJSONFile(aFile) {
+    return Task.spawn(function() {
+      let nodeCount = yield BookmarkJSONUtils.exportToFile(aFile);
 
       let backupFolderPath = yield this.getBackupFolder();
-      if (OS.Path.dirname(aFilePath) == backupFolderPath) {
-        // We are creating a backup in the default backups folder,
-        // so just update the internal cache.
-        this._entries.unshift(new localFileCtor(aFilePath));
+      if (aFile.parent.path == backupFolderPath) {
+        // Update internal cache.
+        this.entries.push(aFile);
         if (!this._backupFiles) {
           yield this.getBackupFiles();
         }
-        this._backupFiles.unshift(aFilePath);
+        this._backupFiles.push(aFile.path);
       } else {
         // If we are saving to a folder different than our backups folder, then
         // we also want to copy this new backup to it.
         // This way we ensure the latest valid backup is the same saved by the
         // user.  See bug 424389.
         let name = this.getFilenameForDate();
         let newFilename = this._appendMetaDataToFilename(name,
                                                          { nodeCount: nodeCount });
         let newFilePath = OS.Path.join(backupFolderPath, newFilename);
         let backupFile = yield this._getBackupFileForSameDate(name);
-        if (!backupFile) {
+
+        if (backupFile) {
+          yield OS.File.remove(backupFile, { ignoreAbsent: true });
+        } else {
+          let file = new FileUtils.File(newFilePath);
+
           // Update internal cache if we are not replacing an existing
           // backup file.
-          this._entries.unshift(new localFileCtor(newFilePath));
+          this.entries.push(file);
           if (!this._backupFiles) {
             yield this.getBackupFiles();
           }
-          this._backupFiles.unshift(newFilePath);
+          this._backupFiles.push(file.path);
         }
-
-        yield OS.File.copy(aFilePath, newFilePath);
+        yield OS.File.copy(aFile.path, newFilePath);
       }
 
-      return nodeCount;
+      throw new Task.Result(nodeCount);
     }.bind(this));
   },
 
   /**
    * Creates a dated backup in <profile>/bookmarkbackups.
    * Stores the bookmarks using JSON.
    * Note: any item that should not be backed up must be annotated with
    *       "places/excludeFromBackup".
@@ -304,17 +287,17 @@ this.PlacesBackups = {
    * @param [optional] int aMaxBackups
    *                       The maximum number of backups to keep.
    * @param [optional] bool aForceBackup
    *                        Forces creating a backup even if one was already
    *                        created that day (overwrites).
    * @return {Promise}
    */
   create: function PB_create(aMaxBackups, aForceBackup) {
-    return Task.spawn(function* () {
+    return Task.spawn(function() {
       // Construct the new leafname.
       let newBackupFilename = this.getFilenameForDate();
       let mostRecentBackupFile = yield this.getMostRecentBackup();
 
       if (!aForceBackup) {
         let numberOfBackupsToDelete = 0;
         if (aMaxBackups !== undefined && aMaxBackups > -1) {
           let backupFiles = yield this.getBackupFiles();
@@ -326,17 +309,17 @@ this.PlacesBackups = {
           // the total backups after this operation does not exceed the
           // number specified in the pref.
           if (!mostRecentBackupFile ||
               !this._isFilenameWithSameDate(OS.Path.basename(mostRecentBackupFile),
                                             newBackupFilename))
             numberOfBackupsToDelete++;
 
           while (numberOfBackupsToDelete--) {
-            this._entries.pop();
+            this.entries.pop();
             if (!this._backupFiles) {
               yield this.getBackupFiles();
             }
             let oldestBackup = this._backupFiles.pop();
             yield OS.File.remove(oldestBackup);
           }
         }
 
@@ -353,32 +336,35 @@ this.PlacesBackups = {
         if (aForceBackup) {
           yield OS.File.remove(backupFile, { ignoreAbsent: true });
         } else {
           return;
         }
       }
 
       // Save bookmarks to a backup file.
-      let backupFolder = yield this.getBackupFolder();
-      let newBackupFile = OS.Path.join(backupFolder, newBackupFilename);
+      let backupFolderPath = yield this.getBackupFolder();
+      let backupFolder = new FileUtils.File(backupFolderPath);
+      let newBackupFile = backupFolder.clone();
+      newBackupFile.append(newBackupFilename);
+
       let nodeCount = yield this.saveBookmarksToJSONFile(newBackupFile);
       // Rename the filename with metadata.
       let newFilenameWithMetaData = this._appendMetaDataToFilename(
                                       newBackupFilename,
                                       { nodeCount: nodeCount });
-      let newBackupFileWithMetadata = OS.Path.join(backupFolder, newFilenameWithMetaData);
-      yield OS.File.move(newBackupFile, newBackupFileWithMetadata);
+      newBackupFile.moveTo(backupFolder, newFilenameWithMetaData);
 
       // Update internal cache.
-      let newFileWithMetaData = new localFileCtor(newBackupFileWithMetadata);
-      this._entries.pop();
-      this._entries.unshift(newFileWithMetaData);
+      let newFileWithMetaData = backupFolder.clone();
+      newFileWithMetaData.append(newFilenameWithMetaData);
+      this.entries.pop();
+      this.entries.push(newFileWithMetaData);
       this._backupFiles.pop();
-      this._backupFiles.unshift(newBackupFileWithMetadata);
+      this._backupFiles.push(newFileWithMetaData.path);
     }.bind(this));
   },
 
   _appendMetaDataToFilename:
   function PB__appendMetaDataToFilename(aFilename, aMetaData) {
     let matches = aFilename.match(this._filenamesRegex);
     let newFilename = matches[1] + "-" + matches[2] + "_" +
                       aMetaData.nodeCount + "." + matches[4];
@@ -409,221 +395,27 @@ this.PlacesBackups = {
     let targetMatches = aTargetName.match(this._filenamesRegex);
 
     return (sourceMatches && targetMatches &&
             sourceMatches[1] == targetMatches[1] &&
             sourceMatches[2] == targetMatches[2] &&
             sourceMatches[4] == targetMatches[4]);
     },
 
-  _getBackupFileForSameDate:
-  function PB__getBackupFileForSameDate(aFilename) {
-    return Task.spawn(function* () {
-      let backupFolderPath = yield this.getBackupFolder();
-      let iterator = new OS.File.DirectoryIterator(backupFolderPath);
-      let backupFile;
-
-      yield iterator.forEach(function(aEntry) {
-        if (this._isFilenameWithSameDate(aEntry.name, aFilename)) {
-          backupFile = aEntry.path;
-          return iterator.close();
-        }
-      }.bind(this));
-      yield iterator.close();
-
-      return backupFile;
-    }.bind(this));
-  },
-
-  /**
-   * Gets a bookmarks tree representation usable to create backups in different
-   * file formats.  The root or the tree is PlacesUtils.placesRootId.
-   * Items annotated with PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO and all of their
-   * descendants are excluded.
-   *
-   * @return an object representing a tree with the places root as its root.
-   *         Each bookmark is represented by an object having these properties:
-   *         * id: the item id (make this not enumerable after bug 824502)
-   *         * title: the title
-   *         * guid: unique id
-   *         * parent: item id of the parent folder, not enumerable
-   *         * index: the position in the parent
-   *         * dateAdded: microseconds from the epoch
-   *         * lastModified: microseconds from the epoch
-   *         * type: type of the originating node as defined in PlacesUtils 
-   *         The following properties exist only for a subset of bookmarks:
-   *         * annos: array of annotations
-   *         * uri: url
-   *         * keyword: associated keyword
-   *         * charset: last known charset
-   *         * tags: csv string of tags
-   *         * root: string describing whether this represents a root
-   *         * children: array of child items in a folder
-   */
-  getBookmarksTree: function () {
-    return Task.spawn(function* () {
-      let dbFilePath = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
-      let conn = yield Sqlite.openConnection({ path: dbFilePath,
-                                               sharedMemoryCache: false });
-      let rows = [];
-      try {
-        rows = yield conn.execute(
-          "SELECT b.id, h.url, IFNULL(b.title, '') AS title, b.parent, " +
-                 "b.position AS [index], b.type, b.dateAdded, b.lastModified, b.guid, " +
-                 "( SELECT GROUP_CONCAT(t.title, ',') " +
-                   "FROM moz_bookmarks b2 " +
-                   "JOIN moz_bookmarks t ON t.id = +b2.parent AND t.parent = :tags_folder " +
-                   "WHERE b2.fk = h.id " +
-                 ") AS tags, " +
-                 "EXISTS (SELECT 1 FROM moz_items_annos WHERE item_id = b.id LIMIT 1) AS has_annos, " +
-                 "( SELECT a.content FROM moz_annos a " +
-                   "JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id " +
-                   "WHERE place_id = h.id AND n.name = :charset_anno " +
-                 ") AS charset " +
-          "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.id <> :tags_folder AND b.parent <> :tags_folder AND p.parent <> :tags_folder " +
-          "ORDER BY b.parent, b.position",
-          { tags_folder: PlacesUtils.tagsFolderId,
-            charset_anno: PlacesUtils.CHARSET_ANNO });
-      } catch(e) {
-        Cu.reportError("Unable to query the database " + e);
-      } finally {
-        yield conn.close();
-      }
-
-      let startTime = Date.now();
-      // Create a Map for lookup and recursive building of the tree.
-      let itemsMap = new Map();
-      for (let row of rows) {
-        let id = row.getResultByName("id");
-        try {
-          let bookmark = sqliteRowToBookmarkObject(row);
-          if (itemsMap.has(id)) {
-            // Since children may be added before parents, we should merge with
-            // the existing object.
-            let original = itemsMap.get(id);
-            for (prop in bookmark) {
-              original[prop] = bookmark[prop];
-            }
-            bookmark = original;
-          }
-          else {
-            itemsMap.set(id, bookmark);
-          }
+   _getBackupFileForSameDate:
+   function PB__getBackupFileForSameDate(aFilename) {
+     return Task.spawn(function() {
+       let backupFolderPath = yield this.getBackupFolder();
+       let iterator = new OS.File.DirectoryIterator(backupFolderPath);
+       let backupFile;
 
-          // Append bookmark to its parent.
-          if (!itemsMap.has(bookmark.parent))
-            itemsMap.set(bookmark.parent, {});
-          let parent = itemsMap.get(bookmark.parent);
-          if (!("children" in parent))
-            parent.children = [];
-          parent.children.push(bookmark);
-        } catch (e) {
-          Cu.reportError("Error while reading node " + id + " " + e);
-        }
-      }
-
-      // Handle excluded items, by removing entire subtrees pointed by them.
-      function removeFromMap(id) {
-        // Could have been removed by a previous call, since we can't
-        // predict order of items in EXCLUDE_FROM_BACKUP_ANNO.
-        if (itemsMap.has(id)) {
-          let excludedItem = itemsMap.get(id);
-          if (excludedItem.children) {
-            for (let child of excludedItem.children) {
-              removeFromMap(child.id);
-            }
-          }
-          // Remove the excluded item from its parent's children...
-          let parentItem = itemsMap.get(excludedItem.parent);
-          parentItem.children = parentItem.children.filter(aChild => aChild.id != id);
-          // ...then remove it from the map.
-          itemsMap.delete(id);
-        }
-      }
-
-      for (let id of PlacesUtils.annotations.getItemsWithAnnotation(
-                       PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO)) {
-        removeFromMap(id);
-      }
-
-      // Report the time taken to build the tree. This doesn't take into
-      // account the time spent in the query since that's off the main-thread.
-      try {
-        Services.telemetry
-                .getHistogramById("PLACES_BACKUPS_BOOKMARKSTREE_MS")
-                .add(Date.now() - startTime);
-      } catch (ex) {
-        Components.utils.reportError("Unable to report telemetry.");
-      }
-
-      return [itemsMap.get(PlacesUtils.placesRootId), itemsMap.size];
-    });
-  }
-}
+       yield iterator.forEach(function(aEntry) {
+         if (this._isFilenameWithSameDate(aEntry.name, aFilename)) {
+           backupFile = aEntry.path;
+           return iterator.close();
+         }
+       }.bind(this));
+       yield iterator.close();
 
-/**
- * Helper function to convert a Sqlite.jsm row to a bookmark object
- * representation.
- *
- * @param aRow The Sqlite.jsm result row.
- */
-function sqliteRowToBookmarkObject(aRow) {
-  let bookmark = {};
-  for (let p of [ "id" ,"guid", "title", "index", "dateAdded", "lastModified" ]) {
-    bookmark[p] = aRow.getResultByName(p);
-  }
-  Object.defineProperty(bookmark, "parent",
-                        { value: aRow.getResultByName("parent") });
-
-  let type = aRow.getResultByName("type");
-
-  // Add annotations.
-  if (aRow.getResultByName("has_annos")) {
-    try {
-      bookmark.annos = PlacesUtils.getAnnotationsForItem(bookmark.id);
-    } catch (e) {
-      Cu.reportError("Unexpected error while reading annotations " + e);
-    }
-  }
-
-  switch (type) {
-    case Ci.nsINavBookmarksService.TYPE_BOOKMARK:
-      // TODO: What about shortcuts?
-      bookmark.type = PlacesUtils.TYPE_X_MOZ_PLACE;
-      // This will throw if we try to serialize an invalid url and the node will
-      // just be skipped.
-      bookmark.uri = NetUtil.newURI(aRow.getResultByName("url")).spec;
-      // Keywords are cached, so this should be decently fast.
-      let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(bookmark.id);
-      if (keyword)
-        bookmark.keyword = keyword;
-      let charset = aRow.getResultByName("charset");
-      if (charset)
-        bookmark.charset = charset;
-      let tags = aRow.getResultByName("tags");
-      if (tags)
-        bookmark.tags = tags;
-      break;
-    case Ci.nsINavBookmarksService.TYPE_FOLDER:
-      bookmark.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
-
-      // Mark root folders.
-      if (bookmark.id == PlacesUtils.placesRootId)
-        bookmark.root = "placesRoot";
-      else if (bookmark.id == PlacesUtils.bookmarksMenuFolderId)
-        bookmark.root = "bookmarksMenuFolder";
-      else if (bookmark.id == PlacesUtils.unfiledBookmarksFolderId)
-        bookmark.root = "unfiledBookmarksFolder";
-      else if (bookmark.id == PlacesUtils.toolbarFolderId)
-        bookmark.root = "toolbarFolder";
-      break;
-    case Ci.nsINavBookmarksService.TYPE_SEPARATOR:
-      bookmark.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
-      break;
-    default:
-      Cu.reportError("Unexpected bookmark type");
-      break;
-  }
-  return bookmark;
+       throw new Task.Result(backupFile);
+     }.bind(this));
+   }
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -2839,40 +2839,16 @@
   "PLACES_KEYWORDS_COUNT": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "200",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "PLACES: Number of keywords"
   },
-  "PLACES_BACKUPS_DAYSFROMLAST": {
-    "expires_in_version": "never",
-    "kind": "enumerated",
-    "n_values": 15,
-    "description": "PLACES: Days from last backup"
-  },
-  "PLACES_BACKUPS_BOOKMARKSTREE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "low": 50,
-    "high": 2000,
-    "n_buckets": 10,
-    "extended_statistics_ok": true,
-    "description": "PLACES: Time to build the bookmarks tree"
-  },
-  "PLACES_BACKUPS_TOJSON_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "low": 50,
-    "high": 2000,
-    "n_buckets": 10,
-    "extended_statistics_ok": true,
-    "description": "PLACES: Time to convert and write the backup"
-  },
   "FENNEC_FAVICONS_COUNT": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "2000",
     "n_buckets": 10,
     "cpp_guard": "ANDROID",
     "extended_statistics_ok": true,
     "description": "FENNEC: (Places) Number of favicons stored"