Bug 960903 - Broadcast session history data r=yoric
authorTim Taubert <ttaubert@mozilla.com>
Mon, 20 Jan 2014 17:37:41 +0100
changeset 165118 c99d792be115612fa33f955fb1ce6634fdf9c1b4
parent 165117 aebbede0c432251227095ceb6b0c690d637a930b
child 165119 4c85a2007510f24b938b124bb9fd35bbf14b8f4d
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersyoric
bugs960903
milestone29.0a1
Bug 960903 - Broadcast session history data r=yoric
browser/app/profile/firefox.js
browser/components/sessionstore/content/content-sessionStore.js
browser/components/sessionstore/src/Messenger.jsm
browser/components/sessionstore/src/SessionSaver.jsm
browser/components/sessionstore/src/SessionStore.jsm
browser/components/sessionstore/src/TabState.jsm
browser/components/sessionstore/src/TabStateCache.jsm
browser/components/sessionstore/src/Utils.jsm
browser/components/sessionstore/src/moz.build
toolkit/components/telemetry/Histograms.json
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -866,18 +866,16 @@ pref("browser.sessionstore.restore_hidde
 // If restore_on_demand is set, pinned tabs are restored on startup by default.
 // When set to true, this pref overrides that behavior, and pinned tabs will only
 // be restored when they are focused.
 pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
 // The version at which we performed the latest upgrade backup
 pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
-// Enable asynchronous data collection by default.
-pref("browser.sessionstore.async", true);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
 
 // the (maximum) number of the recent visits to sample
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -71,74 +71,57 @@ function isSessionStorageEvent(event) {
 /**
  * Listens for and handles content events that we need for the
  * session store service to be notified of state changes in content.
  */
 let EventListener = {
 
   init: function () {
     addEventListener("load", this, true);
-    addEventListener("pageshow", this, true);
   },
 
   handleEvent: function (event) {
-    switch (event.type) {
-      case "load":
-        // Ignore load events from subframes.
-        if (event.target == content.document) {
-          // If we're in the process of restoring, this load may signal
-          // the end of the restoration.
-          let epoch = gContentRestore.getRestoreEpoch();
-          if (epoch) {
-            // Restore the form data and scroll position.
-            gContentRestore.restoreDocument();
+    // Ignore load events from subframes.
+    if (event.target != content.document) {
+      return;
+    }
 
-            // Ask SessionStore.jsm to trigger SSTabRestored.
-            sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
-          }
+    // If we're in the process of restoring, this load may signal
+    // the end of the restoration.
+    let epoch = gContentRestore.getRestoreEpoch();
+    if (epoch) {
+      // Restore the form data and scroll position.
+      gContentRestore.restoreDocument();
 
-          // Send a load message for all loads so we can invalidate the TabStateCache.
-          sendAsyncMessage("SessionStore:load");
-        }
-        break;
-      case "pageshow":
-        if (event.persisted && event.target == content.document)
-          sendAsyncMessage("SessionStore:pageshow");
-        break;
-      default:
-        debug("received unknown event '" + event.type + "'");
-        break;
+      // Ask SessionStore.jsm to trigger SSTabRestored.
+      sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
     }
+
+    // Send a load message for all loads.
+    sendAsyncMessage("SessionStore:load");
   }
 };
 
 /**
  * Listens for and handles messages sent by the session store service.
  */
 let MessageListener = {
 
   MESSAGES: [
-    "SessionStore:collectSessionHistory",
-
     "SessionStore:restoreHistory",
     "SessionStore:restoreTabContent",
     "SessionStore:resetRestore",
   ],
 
   init: function () {
     this.MESSAGES.forEach(m => addMessageListener(m, this));
   },
 
   receiveMessage: function ({name, data}) {
-    let id = data ? data.id : 0;
     switch (name) {
-      case "SessionStore:collectSessionHistory":
-        let history = SessionHistory.collect(docShell);
-        sendAsyncMessage(name, {id: id, data: history});
-        break;
       case "SessionStore:restoreHistory":
         let reloadCallback = () => {
           // Inform SessionStore.jsm about the reload. It will send
           // restoreTabContent in response.
           sendAsyncMessage("SessionStore:reloadPendingTab", {epoch: data.epoch});
         };
         gContentRestore.restoreHistory(data.epoch, data.tabData, reloadCallback);
 
@@ -175,36 +158,32 @@ let MessageListener = {
       default:
         debug("received unknown message '" + name + "'");
         break;
     }
   }
 };
 
 /**
- * If session data must be collected synchronously, we do it via
- * method calls to this object (rather than via messages to
- * MessageListener). When using multiple processes, these methods run
- * in the content process, but the parent synchronously waits on them
- * using cross-process object wrappers. Without multiple processes, we
- * still use this code for synchronous collection.
+ * On initialization, this handler gets sent to the parent process as a CPOW.
+ * The parent will use it only to flush pending data from the frame script
+ * when needed, i.e. when closing a tab, closing a window, shutting down, etc.
+ *
+ * This will hopefully not be needed in the future once we have async APIs for
+ * closing windows and tabs.
  */
 let SyncHandler = {
   init: function () {
     // Send this object as a CPOW to chrome. In single-process mode,
     // the synchronous send ensures that the handler object is
     // available in SessionStore.jsm immediately upon loading
     // content-sessionStore.js.
     sendSyncMessage("SessionStore:setupSyncHandler", {}, {handler: this});
   },
 
-  collectSessionHistory: function (includePrivateData) {
-    return SessionHistory.collect(docShell);
-  },
-
   /**
    * This function is used to make the tab process flush all data that
    * hasn't been sent to the parent process, yet.
    *
    * @param id (int)
    *        A unique id that represents the last message received by the chrome
    *        process before flushing. We will use this to determine data that
    *        would be lost when data has been sent asynchronously shortly
@@ -220,31 +199,61 @@ let SyncHandler = {
    * This function is used to simulate certain situations where race conditions
    * can occur by sending data shortly before flushing synchronously.
    */
   flushAsync: function () {
     MessageQueue.flushAsync();
   }
 };
 
-let ProgressListener = {
-  init: function() {
-    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIWebProgress);
-    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+/**
+ * Listens for changes to the session history. Whenever the user navigates
+ * we will collect URLs and everything belonging to session history.
+ *
+ * Causes a SessionStore:update message to be sent that contains the current
+ * session history.
+ *
+ * Example:
+ *   {entries: [{url: "about:mozilla", ...}, ...], index: 1}
+ */
+let SessionHistoryListener = {
+  init: function () {
+    gFrameTree.addObserver(this);
+    addEventListener("hashchange", this, true);
+    Services.obs.addObserver(this, "browser:purge-session-history", true);
   },
-  onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
-    // We are changing page, so time to invalidate the state of the tab
-    sendAsyncMessage("SessionStore:loadStart");
+
+  observe: function () {
+    // We need to use setTimeout() here because we listen for
+    // "browser:purge-session-history". When that is fired all observers are
+    // expected to purge their data. We can't expect to be called *after* the
+    // observer in browser.xml that clears session history so we need to wait
+    // a tick before actually collecting data.
+    setTimeout(() => this.collect(), 0);
+  },
+
+  handleEvent: function () {
+    this.collect();
   },
-  onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {},
-  onProgressChange: function() {},
-  onStatusChange: function() {},
-  onSecurityChange: function() {},
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+
+  collect: function () {
+    if (docShell) {
+      MessageQueue.push("history", () => SessionHistory.collect(docShell));
+    }
+  },
+
+  onFrameTreeCollected: function () {
+    this.collect();
+  },
+
+  onFrameTreeReset: function () {
+    this.collect();
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
 /**
  * Listens for scroll position changes. Whenever the user scrolls the top-most
  * frame we update the scroll position and will restore it when requested.
  *
  * Causes a SessionStore:update message to be sent that contains the current
@@ -365,19 +374,17 @@ let PageStyleListener = {
   },
 
   onFrameTreeCollected: function () {
     MessageQueue.push("pageStyle", () => this.collect());
   },
 
   onFrameTreeReset: function () {
     MessageQueue.push("pageStyle", () => null);
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+  }
 };
 
 /**
  * Listens for changes to docShell capabilities. Whenever a new load is started
  * we need to re-check the list of capabilities and send message when it has
  * changed.
  *
  * Causes a SessionStore:update message to be sent that contains the currently
@@ -451,19 +458,17 @@ let SessionStorageListener = {
   },
 
   onFrameTreeCollected: function () {
     this.collect();
   },
 
   onFrameTreeReset: function () {
     this.collect();
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+  }
 };
 
 /**
  * Listen for changes to the privacy status of the tab.
  * By definition, tabs start in non-private mode.
  *
  * Causes a SessionStore:update message to be sent for
  * field "isPrivate". This message contains
@@ -652,18 +657,18 @@ let MessageQueue = {
     this.send();
   }
 };
 
 EventListener.init();
 MessageListener.init();
 FormDataListener.init();
 SyncHandler.init();
-ProgressListener.init();
 PageStyleListener.init();
+SessionHistoryListener.init();
 SessionStorageListener.init();
 ScrollPositionListener.init();
 DocShellCapabilitiesListener.init();
 PrivacyListener.init();
 
 addEventListener("unload", () => {
   // Remove all registered nsIObservers.
   PageStyleListener.uninit();
deleted file mode 100644
--- a/browser/components/sessionstore/src/Messenger.jsm
+++ /dev/null
@@ -1,71 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
-* License, v. 2.0. If a copy of the MPL was not distributed with this file,
-* You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Messenger"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Promise.jsm", this);
-Cu.import("resource://gre/modules/Timer.jsm", this);
-
-/**
- * The external API exported by this module.
- */
-this.Messenger = Object.freeze({
-  send: function (tab, type, options = {}) {
-    return MessengerInternal.send(tab, type, options);
-  }
-});
-
-/**
- * A module that handles communication between the main and content processes.
- */
-let MessengerInternal = {
-  // The id of the last message we sent. This is used to assign a unique ID to
-  // every message we send to handle multiple responses from the same browser.
-  _latestMessageID: 0,
-
-  /**
-   * Sends a message to the given tab and waits for a response.
-   *
-   * @param tab
-   *        tabbrowser tab
-   * @param type
-   *        {string} the type of the message
-   * @param options (optional)
-   *        {timeout: int} to set the timeout in milliseconds
-   * @return {Promise} A promise that will resolve to the response message or
-   *                   be reject when timing out.
-   */
-  send: function (tab, type, options = {}) {
-    let browser = tab.linkedBrowser;
-    let mm = browser.messageManager;
-    let deferred = Promise.defer();
-    let id = ++this._latestMessageID;
-    let timeout;
-
-    function onMessage({data: {id: mid, data}}) {
-      if (mid == id) {
-        mm.removeMessageListener(type, onMessage);
-        clearTimeout(timeout);
-        deferred.resolve(data);
-      }
-    }
-
-    mm.addMessageListener(type, onMessage);
-    mm.sendAsyncMessage(type, {id: id});
-
-    function onTimeout() {
-      mm.removeMessageListener(type, onMessage);
-      deferred.reject(new Error("Timed out while waiting for a " + type + " " +
-                                "response message."));
-    }
-
-    let delay = (options && options.timeout) || 5000;
-    timeout = setTimeout(onTimeout, delay);
-    return deferred.promise;
-  }
-};
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -229,32 +229,18 @@ let SessionSaverInternal = {
    * Saves the current session state. Collects data asynchronously and calls
    * _saveState() to collect data again (with a cache hit rate of hopefully
    * 100%) and write to disk afterwards.
    */
   _saveStateAsync: function () {
     // Allow scheduling delayed saves again.
     this._timeoutID = null;
 
-    // Check whether asynchronous data collection is disabled.
-    if (!Services.prefs.getBoolPref("browser.sessionstore.async")) {
-      this._saveState();
-      return;
-    }
-
-    // Update the last save time to make sure we wait at least another interval
-    // length until we call _saveStateAsync() again.
-    this.updateLastSaveTime();
-
-    // Save state synchronously after all tab caches have been filled. The data
-    // for the tab caches is collected asynchronously. We will reuse this
-    // cached data if the tab hasn't been invalidated in the meantime. In that
-    // case we will just fall back to synchronous data collection for single
-    // tabs.
-    SessionStore.fillTabCachesAsynchronously().then(() => this._saveState());
+    // Write to disk.
+    this._saveState();
   },
 
   /**
    * Write the given state object to disk.
    */
   _writeState: function (state) {
     stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
     let data = JSON.stringify(state);
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -44,34 +44,25 @@ const WINDOW_ATTRIBUTES = ["width", "hei
 
 // Hideable window features to (re)store
 // Restored in restoreWindowFeatures()
 const WINDOW_HIDEABLE_FEATURES = [
   "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
 ];
 
 const MESSAGES = [
-  // The content script has received a pageshow event. This happens when a
-  // page is loaded from bfcache without any network activity, i.e. when
-  // clicking the back or forward button.
-  "SessionStore:pageshow",
-
-  // The content script tells us that a new page just started loading in a
-  // browser.
-  "SessionStore:loadStart",
-
   // The content script gives us a reference to an object that performs
   // synchronous collection of session data.
   "SessionStore:setupSyncHandler",
 
   // The content script sends us data that has been invalidated and needs to
   // be saved to disk.
   "SessionStore:update",
 
-  // A "load" event happened. Invalidate the TabStateCache.
+  // A "load" event happened.
   "SessionStore:load",
 
   // The restoreHistory code has run. This is a good time to run SSTabRestoring.
   "SessionStore:restoreHistoryComplete",
 
   // The load for the restoring tab has begun. We update the URL bar at this
   // time; if we did it before, the load would overwrite it.
   "SessionStore:restoreTabContentStarted",
@@ -90,21 +81,16 @@ const MESSAGES = [
 ];
 
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
-// Browser events observed.
-const BROWSER_EVENTS = [
-  "SwapDocShells", "UserTypedValueChanged"
-];
-
 // The number of milliseconds in a day
 const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
@@ -116,24 +102,23 @@ XPCOMUtils.defineLazyServiceGetter(this,
   "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
 XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
   "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
   "@mozilla.org/base/telemetry;1", "nsITelemetry");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+  "resource:///modules/RecentWindow.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
   "resource:///modules/sessionstore/GlobalState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
-  "resource:///modules/sessionstore/Messenger.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
-  "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   "resource:///modules/devtools/scratchpad-manager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
   "resource:///modules/sessionstore/SessionSaver.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   "resource:///modules/sessionstore/SessionCookies.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   "resource:///modules/sessionstore/SessionFile.jsm");
@@ -283,20 +268,16 @@ this.SessionStore = {
   restoreLastSession: function ss_restoreLastSession() {
     SessionStoreInternal.restoreLastSession();
   },
 
   getCurrentState: function (aUpdateAll) {
     return SessionStoreInternal.getCurrentState(aUpdateAll);
   },
 
-  fillTabCachesAsynchronously: function () {
-    return SessionStoreInternal.fillTabCachesAsynchronously();
-  },
-
   /**
    * Backstage pass to implementation details, used for testing purpose.
    * Controlled by preference "browser.sessionstore.testmode".
    */
   get _internal() {
     if (Services.prefs.getBoolPref("browser.sessionstore.debug")) {
       return SessionStoreInternal;
     }
@@ -604,32 +585,25 @@ let SessionStoreInternal = {
    * This method handles incoming messages sent by the session store content
    * script and thus enables communication with OOP tabs.
    */
   receiveMessage: function ssi_receiveMessage(aMessage) {
     var browser = aMessage.target;
     var win = browser.ownerDocument.defaultView;
 
     switch (aMessage.name) {
-      case "SessionStore:pageshow":
-        this.onTabLoad(win, browser);
-        break;
-      case "SessionStore:loadStart":
-        TabStateCache.delete(browser);
-        break;
       case "SessionStore:setupSyncHandler":
         TabState.setSyncHandler(browser, aMessage.objects.handler);
         break;
       case "SessionStore:update":
         this.recordTelemetry(aMessage.data.telemetry);
         TabState.update(browser, aMessage.data);
         this.saveStateDelayed(win);
         break;
       case "SessionStore:load":
-        TabStateCache.delete(browser);
         this.onTabLoad(win, browser);
         break;
       case "SessionStore:restoreHistoryComplete":
         if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
           // Notify the tabbrowser that the tab chrome has been restored.
           let tab = this._getTabForBrowser(browser);
           let tabData = browser.__SS_data;
 
@@ -739,26 +713,16 @@ let SessionStoreInternal = {
     let browser;
     switch (aEvent.type) {
       case "SwapDocShells":
         browser = aEvent.currentTarget;
         let otherBrowser = aEvent.detail;
         TabState.onBrowserContentsSwapped(browser, otherBrowser);
         TabStateCache.onBrowserContentsSwapped(browser, otherBrowser);
         break;
-      case "UserTypedValueChanged":
-        browser = aEvent.currentTarget;
-        if (browser.userTypedValue) {
-          TabStateCache.updateField(browser, "userTypedValue", browser.userTypedValue);
-          TabStateCache.updateField(browser, "userTypedClear", browser.userTypedClear);
-        } else {
-          TabStateCache.removeField(browser, "userTypedValue");
-          TabStateCache.removeField(browser, "userTypedClear");
-        }
-        break;
       case "TabOpen":
         this.onTabAdd(win, aEvent.originalTarget);
         break;
       case "TabClose":
         // aEvent.detail determines if the tab was closed by moving to a different window
         if (!aEvent.detail)
           this.onTabClose(win, aEvent.originalTarget);
         this.onTabRemove(win, aEvent.originalTarget);
@@ -768,23 +732,17 @@ let SessionStoreInternal = {
         break;
       case "TabShow":
         this.onTabShow(win, aEvent.originalTarget);
         break;
       case "TabHide":
         this.onTabHide(win, aEvent.originalTarget);
         break;
       case "TabPinned":
-        // If possible, update cached data without having to invalidate it
-        TabStateCache.updateField(aEvent.originalTarget, "pinned", true);
-        this.saveStateDelayed(win);
-        break;
       case "TabUnpinned":
-        // If possible, update cached data without having to invalidate it
-        TabStateCache.updateField(aEvent.originalTarget, "pinned", false);
         this.saveStateDelayed(win);
         break;
     }
     this._clearRestoringWindows();
   },
 
   /**
    * Generate a unique window identifier
@@ -1217,17 +1175,16 @@ let SessionStoreInternal = {
     // session data on disk as this notification fires after the
     // quit-application notification so the browser is about to exit.
     if (this._loadState == STATE_QUITTING)
       return;
     LastSession.clear();
     let openWindows = {};
     this._forEachBrowserWindow(function(aWindow) {
       Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
-        TabStateCache.delete(aTab);
         delete aTab.linkedBrowser.__SS_data;
         if (aTab.linkedBrowser.__SS_restoreState)
           this._resetTabRestoringState(aTab);
       }, this);
       openWindows[aWindow.__SSi] = true;
     });
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
@@ -1336,17 +1293,17 @@ let SessionStoreInternal = {
    *        Window reference
    * @param aTab
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
     let browser = aTab.linkedBrowser;
-    BROWSER_EVENTS.forEach(msg => browser.addEventListener(msg, this, true));
+    browser.addEventListener("SwapDocShells", this, true);
 
     let mm = browser.messageManager;
     MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
 
     // Load the frame script after registering listeners.
     mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", false);
 
     if (!aNoNotification) {
@@ -1362,17 +1319,17 @@ let SessionStoreInternal = {
    *        Window reference
    * @param aTab
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
     let browser = aTab.linkedBrowser;
-    BROWSER_EVENTS.forEach(msg => browser.removeEventListener(msg, this, true));
+    browser.removeEventListener("SwapDocShells", this, true);
 
     let mm = browser.messageManager;
     MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
 
     delete browser.__SS_data;
 
     // If this tab was in the middle of restoring or still needs to be restored,
     // we need to reset that state. If the tab was restoring, we will attempt to
@@ -1407,17 +1364,17 @@ let SessionStoreInternal = {
     if (this._max_tabs_undo == 0) {
       return;
     }
 
     // Flush all data queued in the content script before the tab is gone.
     TabState.flush(aTab.linkedBrowser);
 
     // Get the latest data for this tab (generally, from the cache)
-    let tabState = TabState.collectSync(aTab);
+    let tabState = TabState.collect(aTab);
 
     // Don't save private tabs
     let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
     if (!isPrivateWindow && tabState.isPrivate) {
       return;
     }
 
     // store closed-tab data for undo
@@ -1443,27 +1400,23 @@ let SessionStoreInternal = {
    * When a tab loads, invalidate its cached state, trigger async save.
    *
    * @param aWindow
    *        Window reference
    * @param aBrowser
    *        Browser reference
    */
   onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) {
-    // react on "load" and solitary "pageshow" events (the first "pageshow"
-    // following "load" is too late for deleting the data caches)
     // It's possible to get a load event after calling stop on a browser (when
     // overwriting tabs). We want to return early if the tab hasn't been restored yet.
     if (aBrowser.__SS_restoreState &&
         aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       return;
     }
 
-    TabStateCache.delete(aBrowser);
-
     delete aBrowser.__SS_data;
     this.saveStateDelayed(aWindow);
 
     // attempt to update the current URL we send in a crash report
     this._updateCrashReportURL(aWindow);
   },
 
   /**
@@ -1494,47 +1447,39 @@ let SessionStoreInternal = {
         aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       TabRestoreQueue.hiddenToVisible(aTab);
 
       // let's kick off tab restoration again to ensure this tab gets restored
       // with "restore_hidden_tabs" == false (now that it has become visible)
       this.restoreNextTab();
     }
 
-    // If possible, update cached data without having to invalidate it
-    TabStateCache.updateField(aTab, "hidden", false);
-
     // Default delay of 2 seconds gives enough time to catch multiple TabShow
     // events due to changing groups in Panorama.
     this.saveStateDelayed(aWindow);
   },
 
   onTabHide: function ssi_onTabHide(aWindow, aTab) {
     // If the tab hasn't been restored yet, move it into the right bucket
     if (aTab.linkedBrowser.__SS_restoreState &&
         aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       TabRestoreQueue.visibleToHidden(aTab);
     }
 
-    // If possible, update cached data without having to invalidate it
-    TabStateCache.updateField(aTab, "hidden", true);
-
     // Default delay of 2 seconds gives enough time to catch multiple TabHide
     // events due to changing groups in Panorama.
     this.saveStateDelayed(aWindow);
   },
 
   onGatherTelemetry: function() {
     // On the first gather-telemetry notification of the session,
     // gather telemetry data.
     Services.obs.removeObserver(this, "gather-telemetry");
-    this.fillTabCachesAsynchronously().then(function() {
-      let stateString = SessionStore.getBrowserState();
-      return SessionFile.gatherTelemetry(stateString);
-    });
+    let stateString = SessionStore.getBrowserState();
+    return SessionFile.gatherTelemetry(stateString);
   },
 
   /* ........ nsISessionStore API .............. */
 
   getBrowserState: function ssi_getBrowserState() {
     let state = this.getCurrentState();
 
     // Don't include the last session state in getBrowserState().
@@ -1618,17 +1563,17 @@ let SessionStoreInternal = {
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab.ownerDocument) {
       throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aTab.ownerDocument.defaultView.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    let tabState = TabState.collectSync(aTab);
+    let tabState = TabState.collect(aTab);
 
     return this._toJSONString(tabState);
   },
 
   setTabState: function ssi_setTabState(aTab, aState) {
     // Remove the tab state from the cache.
     // Note that we cannot simply replace the contents of the cache
     // as |aState| can be an incomplete state that will be completed
@@ -1651,17 +1596,16 @@ let SessionStoreInternal = {
     if (!("__SSi" in window)) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     if (aTab.linkedBrowser.__SS_restoreState) {
       this._resetTabRestoringState(aTab);
     }
 
-    TabStateCache.delete(aTab);
     this._setWindowStateBusy(window);
     this.restoreTabs(window, [aTab], [tabState], 0);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
     if (!aTab.ownerDocument) {
       throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
     }
@@ -1856,17 +1800,16 @@ let SessionStoreInternal = {
       saveTo = aTab.linkedBrowser.__SS_data.extData;
     }
     else {
       aTab.__SS_extdata = {};
       saveTo = aTab.__SS_extdata;
     }
 
     saveTo[aKey] = aStringValue;
-    TabStateCache.updateField(aTab, "extData", saveTo);
     this.saveStateDelayed(aTab.ownerDocument.defaultView);
   },
 
   deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
     // We want to make sure that if data is accessed early, we attempt to delete
     // that data from __SS_data as well. Otherwise we'll throw in cases where
     // data can be set or read.
     let deleteFrom;
@@ -1874,25 +1817,16 @@ let SessionStoreInternal = {
       deleteFrom = aTab.__SS_extdata;
     }
     else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
       deleteFrom = aTab.linkedBrowser.__SS_data.extData;
     }
 
     if (deleteFrom && aKey in deleteFrom) {
       delete deleteFrom[aKey];
-
-      // Keep the extData object only if it is not empty, to save
-      // a little disk space when serializing the tab state later.
-      if (Object.keys(deleteFrom).length) {
-        TabStateCache.updateField(aTab, "extData", deleteFrom);
-      } else {
-        TabStateCache.removeField(aTab, "extData");
-      }
-
       this.saveStateDelayed(aTab.ownerDocument.defaultView);
     }
   },
 
   getGlobalValue: function ssi_getGlobalValue(aKey) {
     return this._globalState.get(aKey);
   },
 
@@ -1903,17 +1837,16 @@ let SessionStoreInternal = {
 
   deleteGlobalValue: function ssi_deleteGlobalValue(aKey) {
     this._globalState.delete(aKey);
     this.saveStateDelayed();
   },
 
   persistTabAttribute: function ssi_persistTabAttribute(aName) {
     if (TabAttributes.persist(aName)) {
-      TabStateCache.clear();
       this.saveStateDelayed();
     }
   },
 
   /**
    * Restores the session state stored in LastSession. This will attempt
    * to merge data into the current session. If a window was opened at startup
    * with pinned tab(s), then the remaining data from the previous session for
@@ -2081,78 +2014,16 @@ let SessionStoreInternal = {
       for (let i = removableTabs.length - 1; i >= 0; i--) {
         tabbrowser.removeTab(removableTabs.pop(), { animate: false });
       }
     }
 
     return [true, canOverwriteTabs];
   },
 
-  /* ........ Async Data Collection .............. */
-
-  /**
-   * Kicks off asynchronous data collection for all tabs that do not have any
-   * cached data. The returned promise will only notify that the tab collection
-   * has been finished without resolving to any data. The tab collection for a
-   * a few or all tabs might have failed or timed out. By calling
-   * fillTabCachesAsynchronously() and waiting for the promise to be resolved
-   * before calling getCurrentState(), callers ensure that most of the data
-   * should have been collected asynchronously, without blocking the main
-   * thread.
-   *
-   * @return {Promise} the promise that is fulfilled when the tab data is ready
-   */
-  fillTabCachesAsynchronously: function () {
-    let countdown = 0;
-    let deferred = Promise.defer();
-    let activeWindow = this._getMostRecentBrowserWindow();
-
-    // The callback that will be called when a promise has been resolved
-    // successfully, i.e. the tab data has been collected.
-    function done() {
-      if (--countdown === 0) {
-        deferred.resolve();
-      }
-    }
-
-    // The callback that will be called when a promise is rejected, i.e. we
-    // we couldn't collect the tab data because of a script error or a timeout.
-    function fail(reason) {
-      debug("Failed collecting tab data asynchronously: " + reason);
-      done();
-    }
-
-    this._forEachBrowserWindow(win => {
-      if (!this._isWindowLoaded(win)) {
-        // Bail out if the window hasn't even loaded, yet.
-        return;
-      }
-
-      if (!DirtyWindows.has(win) && win != activeWindow) {
-        // Bail out if the window is not dirty and inactive.
-        return;
-      }
-
-      for (let tab of win.gBrowser.tabs) {
-        if (!tab.closing && !TabStateCache.has(tab)) {
-          countdown++;
-          TabState.collect(tab).then(done, fail);
-        }
-      }
-    });
-
-    // If no dirty tabs were found, return a resolved
-    // promise because there is nothing to do here.
-    if (countdown == 0) {
-      return Promise.resolve();
-    }
-
-    return deferred.promise;
-  },
-
   /* ........ Saving Functionality .............. */
 
   /**
    * Store window dimensions, visibility, sidebar
    * @param aWindow
    *        Window reference
    */
   _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
@@ -2318,17 +2189,17 @@ let SessionStoreInternal = {
 
     let tabbrowser = aWindow.gBrowser;
     let tabs = tabbrowser.tabs;
     let winData = this._windows[aWindow.__SSi];
     let tabsData = winData.tabs = [];
 
     // update the internal state data for this window
     for (let tab of tabs) {
-      tabsData.push(TabState.collectSync(tab));
+      tabsData.push(TabState.collect(tab));
     }
     winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
 
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
     // or leaving PB mode.
     if (aWindow.__SS_lastSessionWindowID)
@@ -2698,20 +2569,16 @@ let SessionStoreInternal = {
       else
         tabbrowser.showTab(tab);
 
       if ("attributes" in tabData) {
         // Ensure that we persist tab attributes restored from previous sessions.
         Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
       }
 
-      // Any data that's in the process of being collected for this tab will be
-      // out of date now that we're restoring it.
-      TabState.dropPendingCollections(browser);
-
       if (!tabData.entries) {
         tabData.entries = [];
       }
       if (tabData.extData) {
         tab.__SS_extdata = {};
         for (let key in tabData.extData)
          tab.__SS_extdata[key] = tabData.extData[key];
       } else {
@@ -2740,17 +2607,18 @@ let SessionStoreInternal = {
       // keep the data around to prevent dataloss in case
       // a tab gets closed before it's been properly restored
       browser.__SS_data = tabData;
       browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
       browser.setAttribute("pending", "true");
       tab.setAttribute("pending", "true");
 
       // Update the persistent tab state cache with |tabData| information.
-      TabStateCache.updatePersistent(browser, {
+      TabStateCache.update(browser, {
+        history: {entries: tabData.entries, index: tabData.index},
         scroll: tabData.scroll || null,
         storage: tabData.storage || null,
         formdata: tabData.formdata || null,
         disallow: tabData.disallow || null,
         pageStyle: tabData.pageStyle || null
       });
 
       // In electrolysis, we may need to change the browser's remote
--- a/browser/components/sessionstore/src/TabState.jsm
+++ b/browser/components/sessionstore/src/TabState.jsm
@@ -9,18 +9,16 @@ this.EXPORTED_SYMBOLS = ["TabState"];
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
-  "resource:///modules/sessionstore/Messenger.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   "resource:///modules/sessionstore/TabAttributes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource:///modules/sessionstore/Utils.jsm");
@@ -48,34 +46,22 @@ this.TabState = Object.freeze({
   flushWindow: function (window) {
     TabStateInternal.flushWindow(window);
   },
 
   collect: function (tab) {
     return TabStateInternal.collect(tab);
   },
 
-  collectSync: function (tab) {
-    return TabStateInternal.collectSync(tab);
-  },
-
   clone: function (tab) {
     return TabStateInternal.clone(tab);
-  },
-
-  dropPendingCollections: function (browser) {
-    TabStateInternal.dropPendingCollections(browser);
   }
 });
 
 let TabStateInternal = {
-  // A map (xul:browser -> promise) that keeps track of tabs and
-  // their promises when collecting tab data asynchronously.
-  _pendingCollections: new WeakMap(),
-
   // A map (xul:browser -> handler) that maps a tab to the
   // synchronous collection handler object for that tab.
   // See SyncHandler in content-sessionStore.js.
   _syncHandlers: new WeakMap(),
 
   // A map (xul:browser -> int) that maps a browser to the
   // last "SessionStore:update" message ID we received for it.
   _latestMessageID: new WeakMap(),
@@ -92,17 +78,17 @@ let TabStateInternal = {
    * Processes a data update sent by the content script.
    */
   update: function (browser, {id, data}) {
     // Only ever process messages that have an ID higher than the last one we
     // saw. This ensures we don't use stale data that has already been received
     // synchronously.
     if (id > this._latestMessageID.get(browser)) {
       this._latestMessageID.set(browser, id);
-      TabStateCache.updatePersistent(browser, data);
+      TabStateCache.update(browser, data);
     }
   },
 
   /**
    * Flushes all data currently queued in the given browser's content script.
    */
   flush: function (browser) {
     if (this._syncHandlers.has(browser)) {
@@ -122,338 +108,61 @@ let TabStateInternal = {
 
   /**
    * When a docshell swap happens, a xul:browser element will be
    * associated with a different content-sessionStore.js script
    * global. In this case, the sync handler for the element needs to
    * be swapped just like the docshell.
    */
   onBrowserContentsSwapped: function (browser, otherBrowser) {
-    // Data collected while docShells have been swapped should not go into
-    // the TabStateCache. Collections will most probably time out but we want
-    // to make sure.
-    this.dropPendingCollections(browser);
-    this.dropPendingCollections(otherBrowser);
-
     // Swap data stored per-browser.
     [this._syncHandlers, this._latestMessageID]
       .forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
   },
 
   /**
-   * Collect data related to a single tab, asynchronously.
-   *
-   * @param tab
-   *        tabbrowser tab
-   *
-   * @returns {Promise} A promise that will resolve to a TabData instance.
-   */
-  collect: function (tab) {
-    if (!tab) {
-      throw new TypeError("Expecting a tab");
-    }
-
-    // Don't collect if we don't need to.
-    if (TabStateCache.has(tab)) {
-      return Promise.resolve(TabStateCache.get(tab));
-    }
-
-    // If the tab was recently added, or if it's being restored, we
-    // just collect basic data about it and skip the cache.
-    if (!this._tabNeedsExtraCollection(tab)) {
-      let tabData = this._collectBaseTabData(tab);
-      return Promise.resolve(tabData);
-    }
-
-    let browser = tab.linkedBrowser;
-
-    let promise = Task.spawn(function task() {
-      // Collect session history data asynchronously.
-      let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
-
-      // The tab could have been closed while waiting for a response.
-      if (!tab.linkedBrowser) {
-        return;
-      }
-
-      // Collect basic tab data, without session history and storage.
-      let tabData = this._collectBaseTabData(tab);
-
-      // Apply collected data.
-      tabData.entries = history.entries;
-      if ("index" in history) {
-        tabData.index = history.index;
-      }
-
-      // If we're still the latest async collection for the given tab and
-      // the cache hasn't been filled by collect() in the meantime, let's
-      // fill the cache with the data we received.
-      if (this._pendingCollections.get(browser) == promise) {
-        TabStateCache.set(tab, tabData);
-        this._pendingCollections.delete(browser);
-      }
-
-      // Copy data from the persistent cache. We need to create an explicit
-      // copy of the |tabData| object so that the properties injected by
-      // |_copyFromPersistentCache| don't end up in the non-persistent cache.
-      // The persistent cache does not store "null" values, so any values that
-      // have been cleared by the frame script would not be overriden by
-      // |_copyFromPersistentCache|. These two caches are only an interim
-      // solution and the non-persistent one will go away soon.
-      tabData = Utils.copy(tabData);
-      this._copyFromPersistentCache(tab, tabData);
-
-      throw new Task.Result(tabData);
-    }.bind(this));
-
-    // Save the current promise as the latest asynchronous collection that is
-    // running. This will be used to check whether the collected data is still
-    // valid and will be used to fill the tab state cache.
-    this._pendingCollections.set(browser, promise);
-
-    return promise;
-  },
-
-  /**
    * Collect data related to a single tab, synchronously.
    *
    * @param tab
    *        tabbrowser tab
    *
    * @returns {TabData} An object with the data for this tab.  If the
    * tab has not been invalidated since the last call to
-   * collectSync(aTab), the same object is returned.
+   * collect(aTab), the same object is returned.
    */
-  collectSync: function (tab) {
-    if (!tab) {
-      throw new TypeError("Expecting a tab");
-    }
-    if (TabStateCache.has(tab)) {
-      // Copy data from the persistent cache. We need to create an explicit
-      // copy of the |tabData| object so that the properties injected by
-      // |_copyFromPersistentCache| don't end up in the non-persistent cache.
-      // The persistent cache does not store "null" values, so any values that
-      // have been cleared by the frame script would not be overriden by
-      // |_copyFromPersistentCache|. These two caches are only an interim
-      // solution and the non-persistent one will go away soon.
-      let tabData = Utils.copy(TabStateCache.get(tab));
-      this._copyFromPersistentCache(tab, tabData);
-      return tabData;
-    }
-
-    let tabData = this._collectSyncUncached(tab);
-
-    if (this._tabCachingAllowed(tab)) {
-      TabStateCache.set(tab, tabData);
-    }
-
-    // Copy data from the persistent cache. We need to create an explicit
-    // copy of the |tabData| object so that the properties injected by
-    // |_copyFromPersistentCache| don't end up in the non-persistent cache.
-    // The persistent cache does not store "null" values, so any values that
-    // have been cleared by the frame script would not be overriden by
-    // |_copyFromPersistentCache|. These two caches are only an interim
-    // solution and the non-persistent one will go away soon.
-    tabData = Utils.copy(tabData);
-    this._copyFromPersistentCache(tab, tabData);
-
-    // Prevent all running asynchronous collections from filling the cache.
-    // Every asynchronous data collection started before a collectSync() call
-    // can't expect to retrieve different data than the sync call. That's why
-    // we just fill the cache with the data collected from the sync call and
-    // discard any data collected asynchronously.
-    this.dropPendingCollections(tab.linkedBrowser);
-
-    return tabData;
-  },
-
-  /**
-   * Drop any pending calls to TabState.collect. These calls will
-   * continue to run, but they won't store their results in the
-   * TabStateCache.
-   *
-   * @param browser
-   *        xul:browser
-   */
-  dropPendingCollections: function (browser) {
-    this._pendingCollections.delete(browser);
+  collect: function (tab) {
+    return this._collectBaseTabData(tab);
   },
 
   /**
    * Collect data related to a single tab, including private data.
    * Use with caution.
    *
    * @param tab
    *        tabbrowser tab
    *
    * @returns {object} An object with the data for this tab. This data is never
    *                   cached, it will always be read from the tab and thus be
    *                   up-to-date.
    */
   clone: function (tab) {
-    let options = {includePrivateData: true};
-    let tabData = this._collectSyncUncached(tab, options);
-
-    // Copy data from the persistent cache.
-    this._copyFromPersistentCache(tab, tabData, options);
-
-    return tabData;
-  },
-
-  /**
-   * Synchronously collect all session data for a tab. The
-   * TabStateCache is not consulted, and the resulting data is not put
-   * in the cache.
-   */
-  _collectSyncUncached: function (tab, options = {}) {
-    // Collect basic tab data, without session history and storage.
-    let tabData = this._collectBaseTabData(tab);
-
-    // If we don't need any other data, return what we have.
-    if (!this._tabNeedsExtraCollection(tab)) {
-      return tabData;
-    }
-
-    // In multiprocess Firefox, there is a small window of time after
-    // tab creation when we haven't received a sync handler object. In
-    // this case the tab shouldn't have any history or cookie data, so we
-    // just return the base data already collected.
-    if (!this._syncHandlers.has(tab.linkedBrowser)) {
-      return tabData;
-    }
-
-    let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
-
-    let includePrivateData = options && options.includePrivateData;
-
-    let history;
-    try {
-      history = syncHandler.collectSessionHistory(includePrivateData);
-    } catch (e) {
-      // This may happen if the tab has crashed.
-      console.error(e);
-      return tabData;
-    }
-
-    tabData.entries = history.entries;
-    if ("index" in history) {
-      tabData.index = history.index;
-    }
-
-    return tabData;
-  },
-
-  /**
-   * Copy tab data for the given |tab| from the persistent cache to |tabData|.
-   *
-   * @param tab (xul:tab)
-   *        The tab belonging to the given |tabData| object.
-   * @param tabData (object)
-   *        The tab data belonging to the given |tab|.
-   * @param options (object)
-   *        {includePrivateData: true} to always include private data
-   */
-  _copyFromPersistentCache: function (tab, tabData, options = {}) {
-    let data = TabStateCache.getPersistent(tab.linkedBrowser);
-    if (!data) {
-      return;
-    }
-
-    // The caller may explicitly request to omit privacy checks.
-    let includePrivateData = options && options.includePrivateData;
-
-    for (let key of Object.keys(data)) {
-      let value = data[key];
-
-      // Filter sensitive data according to the current privacy level.
-      if (!includePrivateData) {
-        if (key === "storage") {
-          value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
-        } else if (key === "formdata") {
-          value = PrivacyFilter.filterFormData(value, tab.pinned);
-        }
-      }
-
-      if (value) {
-        tabData[key] = value;
-      }
-    }
-  },
-
-  /*
-   * Returns true if the xul:tab element is newly added (i.e., if it's
-   * showing about:blank with no history).
-   */
-  _tabIsNew: function (tab) {
-    let browser = tab.linkedBrowser;
-    return (!browser || !browser.currentURI);
-  },
-
-  /*
-   * Returns true if the xul:tab element is in the process of being
-   * restored.
-   */
-  _tabIsRestoring: function (tab) {
-    return !!tab.linkedBrowser.__SS_data;
-  },
-
-  /**
-   * This function returns true if we need to collect history, page
-   * style, and text and scroll data from the tab. Normally we do. The
-   * cases when we don't are:
-   * 1. the tab is about:blank with no history, or
-   * 2. the tab is waiting to be restored.
-   *
-   * @param tab   A xul:tab element.
-   * @returns     True if the tab is in the process of being restored.
-   */
-  _tabNeedsExtraCollection: function (tab) {
-    if (this._tabIsNew(tab)) {
-      // Tab is about:blank with no history.
-      return false;
-    }
-
-    if (this._tabIsRestoring(tab)) {
-      // Tab is waiting to be restored.
-      return false;
-    }
-
-    // Otherwise we need the extra data.
-    return true;
-  },
-
-  /*
-   * Returns true if we should cache the tabData for the given the
-   * xul:tab element.
-   */
-  _tabCachingAllowed: function (tab) {
-    if (this._tabIsNew(tab)) {
-      // No point in caching data for newly created tabs.
-      return false;
-    }
-
-    if (this._tabIsRestoring(tab)) {
-      // If the tab is being restored, we just return the data being
-      // restored. This data may be incomplete (if supplied by
-      // setBrowserState, for example), so we don't want to cache it.
-      return false;
-    }
-
-    return true;
+    return this._collectBaseTabData(tab, {includePrivateData: true});
   },
 
   /**
    * Collects basic tab data for a given tab.
    *
    * @param tab
    *        tabbrowser tab
+   * @param options (object)
+   *        {includePrivateData: true} to always include private data
    *
    * @returns {object} An object with the basic data for this tab.
    */
-  _collectBaseTabData: function (tab) {
+  _collectBaseTabData: function (tab, options) {
     let tabData = {entries: [], lastAccessed: tab.lastAccessed };
     let browser = tab.linkedBrowser;
 
     if (!browser || !browser.currentURI) {
       // can happen when calling this function right after .addTab()
       return tabData;
     }
     if (browser.__SS_data) {
@@ -502,11 +211,58 @@ let TabStateInternal = {
     let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
     tabData.image = tabbrowser.getIcon(tab);
 
     if (tab.__SS_extdata)
       tabData.extData = tab.__SS_extdata;
     else if (tabData.extData)
       delete tabData.extData;
 
+    // Copy data from the tab state cache only if the tab has fully finished
+    // restoring. We don't want to overwrite data contained in __SS_data.
+    this._copyFromCache(tab, tabData, options);
+
     return tabData;
+  },
+
+  /**
+   * Copy tab data for the given |tab| from the cache to |tabData|.
+   *
+   * @param tab (xul:tab)
+   *        The tab belonging to the given |tabData| object.
+   * @param tabData (object)
+   *        The tab data belonging to the given |tab|.
+   * @param options (object)
+   *        {includePrivateData: true} to always include private data
+   */
+  _copyFromCache: function (tab, tabData, options = {}) {
+    let data = TabStateCache.get(tab.linkedBrowser);
+    if (!data) {
+      return;
+    }
+
+    // The caller may explicitly request to omit privacy checks.
+    let includePrivateData = options && options.includePrivateData;
+
+    for (let key of Object.keys(data)) {
+      let value = data[key];
+
+      // Filter sensitive data according to the current privacy level.
+      if (!includePrivateData) {
+        if (key === "storage") {
+          value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
+        } else if (key === "formdata") {
+          value = PrivacyFilter.filterFormData(value, tab.pinned);
+        }
+      }
+
+      if (key === "history") {
+        tabData.entries = value.entries;
+
+        if (value.hasOwnProperty("index")) {
+          tabData.index = value.index;
+        }
+      } else if (value) {
+        tabData[key] = value;
+      }
+    }
   }
 };
--- a/browser/components/sessionstore/src/TabStateCache.jsm
+++ b/browser/components/sessionstore/src/TabStateCache.jsm
@@ -20,364 +20,95 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  * to tab data (as objects).
  *
  * Note that we should never cache private data, as:
  * - that data is used very seldom by SessionStore;
  * - caching private data in addition to public data is memory consuming.
  */
 this.TabStateCache = Object.freeze({
   /**
-   * Tells whether an entry is in the cache.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * @return {bool} Whether there's a cached entry for the given tab.
-   */
-  has: function (aTab) {
-    return TabStateCacheInternal.has(aTab);
-  },
-
-  /**
-   * Add or replace an entry in the cache.
-   *
-   * @param {XULElement} aTab The key, which may be either a tab
-   * or the corresponding browser. The binding will disappear
-   * if the tab/browser is destroyed.
-   * @param {*} aValue The data associated to |aTab|.
-   */
-  set: function(aTab, aValue) {
-    return TabStateCacheInternal.set(aTab, aValue);
-  },
-
-  /**
-   * Return the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * @return {*|undefined} The data if available, |undefined|
-   * otherwise.
-   */
-  get: function(aKey) {
-    return TabStateCacheInternal.get(aKey);
-  },
-
-  /**
-   * Delete the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * Noop of there is no tab data associated with the tab.
-   */
-  delete: function(aKey) {
-    return TabStateCacheInternal.delete(aKey);
-  },
-
-  /**
-   * Delete all tab data.
-   */
-  clear: function() {
-    return TabStateCacheInternal.clear();
-  },
-
-  /**
-   * Update in place a piece of data.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to update.
-   * @param {*} aValue The new value to place in the field.
-   */
-  updateField: function(aKey, aField, aValue) {
-    return TabStateCacheInternal.updateField(aKey, aField, aValue);
-  },
-
-  /**
-   * Remove a given field from a cached tab state.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to remove.
-   */
-  removeField: function(aKey, aField) {
-    return TabStateCacheInternal.removeField(aKey, aField);
-  },
-
-  /**
    * Swap cached data for two given browsers.
    *
    * @param {xul:browser} browser
    *        The first of the two browsers that swapped docShells.
    * @param {xul:browser} otherBrowser
    *        The second of the two browsers that swapped docShells.
    */
   onBrowserContentsSwapped: function(browser, otherBrowser) {
     TabStateCacheInternal.onBrowserContentsSwapped(browser, otherBrowser);
   },
 
   /**
-   * Retrieves persistently cached data for a given |browser|.
+   * Retrieves cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser to retrieve cached data for.
    * @return (object)
-   *         The persistently cached data stored for the given |browser|.
+   *         The cached data stored for the given |browser|.
    */
-  getPersistent: function (browser) {
-    return TabStateCacheInternal.getPersistent(browser);
+  get: function (browser) {
+    return TabStateCacheInternal.get(browser);
   },
 
   /**
-   * Updates persistently cached data for a given |browser|. This data is
-   * persistently in the sense that we never clear it, it will always be
-   * overwritten.
+   * Updates cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser belonging to the given tab data.
    * @param newData (object)
    *        The new data to be stored for the given |browser|.
    */
-  updatePersistent: function (browser, newData) {
-    TabStateCacheInternal.updatePersistent(browser, newData);
-  },
-
-  /**
-   * Total number of cache hits during the session.
-   */
-  get hits() {
-    return TabStateCacheTelemetry.hits;
-  },
-
-  /**
-   * Total number of cache misses during the session.
-   */
-  get misses() {
-    return TabStateCacheTelemetry.misses;
-  },
-
-  /**
-   * Total number of cache clears during the session.
-   */
-  get clears() {
-    return TabStateCacheTelemetry.clears;
-  },
+  update: function (browser, newData) {
+    TabStateCacheInternal.update(browser, newData);
+  }
 });
 
 let TabStateCacheInternal = {
   _data: new WeakMap(),
-  _persistentData: new WeakMap(),
-
-  /**
-   * Tells whether an entry is in the cache.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * @return {bool} Whether there's a cached entry for the given tab.
-   */
-  has: function (aTab) {
-    let key = this._normalizeToBrowser(aTab);
-    return this._data.has(key);
-  },
-
-  /**
-   * Add or replace an entry in the cache.
-   *
-   * @param {XULElement} aTab The key, which may be either a tab
-   * or the corresponding browser. The binding will disappear
-   * if the tab/browser is destroyed.
-   * @param {*} aValue The data associated to |aTab|.
-   */
-  set: function(aTab, aValue) {
-    let key = this._normalizeToBrowser(aTab);
-    this._data.set(key, aValue);
-  },
-
-  /**
-   * Return the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * @return {*|undefined} The data if available, |undefined|
-   * otherwise.
-   */
-  get: function(aKey) {
-    let key = this._normalizeToBrowser(aKey);
-    let result = this._data.get(key);
-    TabStateCacheTelemetry.recordAccess(!!result);
-    return result;
-  },
-
-  /**
-   * Delete the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * Noop of there is no tab data associated with the tab.
-   */
-  delete: function(aKey) {
-    let key = this._normalizeToBrowser(aKey);
-    this._data.delete(key);
-  },
-
-  /**
-   * Delete all tab data.
-   */
-  clear: function() {
-    TabStateCacheTelemetry.recordClear();
-    this._data.clear();
-  },
-
-  /**
-   * Update in place a piece of data.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to update.
-   * @param {*} aValue The new value to place in the field.
-   */
-  updateField: function(aKey, aField, aValue) {
-    let key = this._normalizeToBrowser(aKey);
-    let data = this._data.get(key);
-    if (data) {
-      data[aField] = aValue;
-    }
-  },
-
-  /**
-   * Remove a given field from a cached tab state.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to remove.
-   */
-  removeField: function(aKey, aField) {
-    let key = this._normalizeToBrowser(aKey);
-    let data = this._data.get(key);
-    if (data && aField in data) {
-      delete data[aField];
-    }
-  },
 
   /**
    * Swap cached data for two given browsers.
    *
    * @param {xul:browser} browser
    *        The first of the two browsers that swapped docShells.
    * @param {xul:browser} otherBrowser
    *        The second of the two browsers that swapped docShells.
    */
   onBrowserContentsSwapped: function(browser, otherBrowser) {
     // Swap data stored per-browser.
-    [this._data, this._persistentData]
-      .forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
+    Utils.swapMapEntries(this._data, browser, otherBrowser);
   },
 
   /**
-   * Retrieves persistently cached data for a given |browser|.
+   * Retrieves cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser to retrieve cached data for.
    * @return (object)
-   *         The persistently cached data stored for the given |browser|.
+   *         The cached data stored for the given |browser|.
    */
-  getPersistent: function (browser) {
-    return this._persistentData.get(browser);
+  get: function (browser) {
+    return this._data.get(browser);
   },
 
   /**
-   * Updates persistently cached data for a given |browser|. This data is
-   * persistent in the sense that we never clear it, it will always be
-   * overwritten.
+   * Updates cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser belonging to the given tab data.
    * @param newData (object)
    *        The new data to be stored for the given |browser|.
    */
-  updatePersistent: function (browser, newData) {
-    let data = this._persistentData.get(browser) || {};
+  update: function (browser, newData) {
+    let data = this._data.get(browser) || {};
 
     for (let key of Object.keys(newData)) {
       let value = newData[key];
       if (value === null) {
         delete data[key];
       } else {
         data[key] = value;
       }
     }
 
-    this._persistentData.set(browser, data);
-  },
-
-  _normalizeToBrowser: function(aKey) {
-    let nodeName = aKey.localName;
-    if (nodeName == "tab") {
-      return aKey.linkedBrowser;
-    }
-    if (nodeName == "browser") {
-      return aKey;
-    }
-    throw new TypeError("Key is neither a tab nor a browser: " + nodeName);
+    this._data.set(browser, data);
   }
 };
-
-let TabStateCacheTelemetry = {
-  // Total number of hits during the session
-  hits: 0,
-  // Total number of misses during the session
-  misses: 0,
-  // Total number of clears during the session
-  clears: 0,
-  // |true| once we have been initialized
-  _initialized: false,
-
-  /**
-   * Record a cache access.
-   *
-   * @param {boolean} isHit If |true|, the access was a hit, otherwise
-   * a miss.
-   */
-  recordAccess: function(isHit) {
-    this._init();
-    if (isHit) {
-      ++this.hits;
-    } else {
-      ++this.misses;
-    }
-  },
-
-  /**
-   * Record a cache clear
-   */
-  recordClear: function() {
-    this._init();
-    ++this.clears;
-  },
-
-  /**
-   * Initialize the telemetry.
-   */
-  _init: function() {
-    if (this._initialized) {
-      // Avoid double initialization
-      return;
-    }
-    this._initialized = true;
-    Services.obs.addObserver(this, "profile-before-change", false);
-  },
-
-  observe: function() {
-    Services.obs.removeObserver(this, "profile-before-change");
-
-    // Record hit/miss rate
-    let accesses = this.hits + this.misses;
-    if (accesses == 0) {
-      return;
-    }
-
-    this._fillHistogram("HIT_RATE", this.hits, accesses);
-    this._fillHistogram("CLEAR_RATIO", this.clears, accesses);
-  },
-
-  _fillHistogram: function(suffix, positive, total) {
-    let PREFIX = "FX_SESSION_RESTORE_TABSTATECACHE_";
-    let histo = Services.telemetry.getHistogramById(PREFIX + suffix);
-    let rate = Math.floor( ( positive * 100 ) / total );
-    histo.add(rate);
-  }
-};
--- a/browser/components/sessionstore/src/Utils.jsm
+++ b/browser/components/sessionstore/src/Utils.jsm
@@ -59,21 +59,10 @@ this.Utils = Object.freeze({
     if (map.has(otherKey)) {
       let otherValue = map.get(otherKey);
       map.set(key, otherValue);
       map.set(otherKey, value);
     } else {
       map.set(otherKey, value);
       map.delete(key);
     }
-  },
-
-  // Copies all properties of a given object to a new one and returns it.
-  copy: function (from) {
-    let to = {};
-
-    for (let key of Object.keys(from)) {
-      to[key] = from[key];
-    }
-
-    return to;
   }
 });
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -13,17 +13,16 @@ EXTRA_COMPONENTS += [
 JS_MODULES_PATH = 'modules/sessionstore'
 
 EXTRA_JS_MODULES = [
     'ContentRestore.jsm',
     'DocShellCapabilities.jsm',
     'FormData.jsm',
     'FrameTree.jsm',
     'GlobalState.jsm',
-    'Messenger.jsm',
     'PageStyle.jsm',
     'PrivacyFilter.jsm',
     'PrivacyLevel.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'ScrollPosition.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionHistory.jsm',
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3364,28 +3364,16 @@
   },
   "FX_SESSION_RESTORE_SESSION_LENGTH": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "365",
     "n_buckets": 15,
     "description": "Session restore: Days elapsed since the session was first started"
   },
-  "FX_SESSION_RESTORE_TABSTATECACHE_HIT_RATE": {
-    "expires_in_version": "never",
-    "kind": "enumerated",
-    "n_values": 101,
-    "description": "Session restore: Percentage of tab state cache hits in all tab state cache accesses"
-  },
-  "FX_SESSION_RESTORE_TABSTATECACHE_CLEAR_RATIO": {
-    "expires_in_version": "never",
-    "kind": "enumerated",
-    "n_values": 101,
-    "description": "Session restore: Number of times the tab state cache has been cleared during a session divided by number of total accesses during the session (percentage)"
-  },
   "FX_SESSION_RESTORE_EXTRACTING_STATISTICS_DURATION_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "3000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Session restore: Duration of the off main thread statistics extraction mechanism (ms)"
   },