Backed out changesets 24c800ff4936 and 5e4e054f3c00 (bug 867143) for mochitest orange.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 19 Jul 2013 12:00:29 -0400
changeset 139259 65f5580b94770141da6f1bd9ca5dec07f51e3a63
parent 139258 3f3a0ed44893e8f3d87399551dd04216825ba9da
child 139260 8190b78ed33cf145b8a3843a29469c9484d98e13
push id24983
push userryanvm@gmail.com
push dateSat, 20 Jul 2013 00:51:06 +0000
treeherdermozilla-central@6030c759a502 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs867143
milestone25.0a1
backs out24c800ff49361bc4f5175c52a617088c2d936aa6
5e4e054f3c0088f444a33a1bc9e6747c403b3b03
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changesets 24c800ff4936 and 5e4e054f3c00 (bug 867143) for mochitest orange.
browser/components/sessionstore/content/content-sessionStore.js
browser/components/sessionstore/src/SessionStore.jsm
browser/components/sessionstore/test/Makefile.in
browser/components/sessionstore/test/browser_sessionStorage.js
browser/components/sessionstore/test/head.js
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -8,51 +8,33 @@ function debug(msg) {
 
 /**
  * Listens for and handles content events that we need for the
  * session store service to be notified of state changes in content.
  */
 let EventListener = {
 
   DOM_EVENTS: [
-    "pageshow", "change", "input", "MozStorageChanged"
+    "pageshow", "change", "input"
   ],
 
   init: function () {
     this.DOM_EVENTS.forEach(e => addEventListener(e, this, true));
   },
 
   handleEvent: function (event) {
     switch (event.type) {
       case "pageshow":
         if (event.persisted)
           sendAsyncMessage("SessionStore:pageshow");
         break;
       case "input":
       case "change":
         sendAsyncMessage("SessionStore:input");
         break;
-      case "MozStorageChanged":
-        {
-          let isSessionStorage = true;
-          // We are only interested in sessionStorage events
-          try {
-            if (event.storageArea != content.sessionStorage) {
-              isSessionStorage = false;
-            }
-          } catch (ex) {
-            // This page does not even have sessionStorage
-            // (this is typically the case of about: pages)
-            isSessionStorage = false;
-          }
-          if (isSessionStorage) {
-            sendAsyncMessage("SessionStore:MozStorageChanged");
-          }
-          break;
-        }
       default:
         debug("received unknown event '" + event.type + "'");
         break;
     }
   }
 };
 
 EventListener.init();
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -53,21 +53,17 @@ const MESSAGES = [
   // The content script tells us that its form data (or that of one of its
   // subframes) might have changed. This can be the contents or values of
   // standard form fields or of ContentEditables.
   "SessionStore:input",
 
   // 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 has received a MozStorageChanged event dealing
-  // with a change in the contents of the sessionStorage.
-  "SessionStore:MozStorageChanged"
+  "SessionStore:pageshow"
 ];
 
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
@@ -119,26 +115,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
   "resource:///modules/sessionstore/_SessionFile.jsm");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
-/**
- * |true| if we are in debug mode, |false| otherwise.
- * Debug mode is controlled by preference browser.sessionstore.debug
- */
-let gDebuggingEnabled = false;
 function debug(aMsg) {
-  if (gDebuggingEnabled) {
-    aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
-    Services.console.logStringMessage(aMsg);
-  }
+  aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
+  Services.console.logStringMessage(aMsg);
 }
 
 this.SessionStore = {
   get promiseInitialized() {
     return SessionStoreInternal.promiseInitialized.promise;
   },
 
   get canRestoreLastSession() {
@@ -543,23 +532,19 @@ let SessionStoreInternal = {
   _initEncoding : function ssi_initEncoding() {
     // The (UTF-8) encoder used to write to files.
     XPCOMUtils.defineLazyGetter(this, "_writeFileEncoder", function () {
       return new TextEncoder();
     });
   },
 
   _initPrefs : function() {
-    this._prefBranch = Services.prefs.getBranch("browser.");
-
-    gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
-
-    Services.prefs.addObserver("browser.sessionstore.debug", () => {
-      gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
-    }, false);
+    XPCOMUtils.defineLazyGetter(this, "_prefBranch", function () {
+      return Services.prefs.getBranch("browser.");
+    });
 
     // minimal interval between two save operations (in milliseconds)
     XPCOMUtils.defineLazyGetter(this, "_interval", function () {
       // used often, so caching/observing instead of fetching on-demand
       this._prefBranch.addObserver("sessionstore.interval", this, true);
       return this._prefBranch.getIntPref("sessionstore.interval");
     });
 
@@ -631,53 +616,47 @@ let SessionStoreInternal = {
 
   /**
    * Handle notifications
    */
   observe: function ssi_observe(aSubject, aTopic, aData) {
     if (this._disabledForMultiProcess)
       return;
 
-    try {
-      switch (aTopic) {
-        case "domwindowopened": // catch new windows
-          this.onOpen(aSubject);
-          break;
-        case "domwindowclosed": // catch closed windows
-          this.onClose(aSubject);
-          break;
-        case "quit-application-requested":
-          this.onQuitApplicationRequested();
-          break;
-        case "quit-application-granted":
-          this.onQuitApplicationGranted();
-          break;
-        case "browser-lastwindow-close-granted":
-          this.onLastWindowCloseGranted();
-          break;
-        case "quit-application":
-          this.onQuitApplication(aData);
-          break;
-        case "browser:purge-session-history": // catch sanitization
-          this.onPurgeSessionHistory();
-          break;
-        case "browser:purge-domain-data":
-          this.onPurgeDomainData(aData);
-          break;
-        case "nsPref:changed": // catch pref changes
-          this.onPrefChange(aData);
-          break;
-        case "timer-callback": // timer call back for delayed saving
-          this.onTimerCallback();
-          break;
-      }
-    } catch (ex) {
-      debug("Uncaught error during observe");
-      debug(ex);
-      debug(ex.stack);
+    switch (aTopic) {
+      case "domwindowopened": // catch new windows
+        this.onOpen(aSubject);
+        break;
+      case "domwindowclosed": // catch closed windows
+        this.onClose(aSubject);
+        break;
+      case "quit-application-requested":
+        this.onQuitApplicationRequested();
+        break;
+      case "quit-application-granted":
+        this.onQuitApplicationGranted();
+        break;
+      case "browser-lastwindow-close-granted":
+        this.onLastWindowCloseGranted();
+        break;
+      case "quit-application":
+        this.onQuitApplication(aData);
+        break;
+      case "browser:purge-session-history": // catch sanitization
+        this.onPurgeSessionHistory();
+        break;
+      case "browser:purge-domain-data":
+        this.onPurgeDomainData(aData);
+        break;
+      case "nsPref:changed": // catch pref changes
+        this.onPrefChange(aData);
+        break;
+      case "timer-callback": // timer call back for delayed saving
+        this.onTimerCallback();
+        break;
     }
   },
 
   /**
    * This method handles incoming messages sent by the session store content
    * script and thus enables communication with OOP tabs.
    */
   receiveMessage: function ssi_receiveMessage(aMessage) {
@@ -686,20 +665,16 @@ let SessionStoreInternal = {
 
     switch (aMessage.name) {
       case "SessionStore:pageshow":
         this.onTabLoad(win, browser);
         break;
       case "SessionStore:input":
         this.onTabInput(win, browser);
         break;
-      case "SessionStore:MozStorageChanged":
-        TabStateCache.delete(browser);
-        this.saveStateDelayed(win);
-        break;
       default:
         debug("received unknown message '" + aMessage.name + "'");
         break;
     }
 
     this._clearRestoringWindows();
   },
 
@@ -1111,17 +1086,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;
     this._lastSessionState = null;
     let openWindows = {};
     this._forEachBrowserWindow(function(aWindow) {
       Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
-        TabStateCache.delete(aTab);
         delete aTab.linkedBrowser.__SS_data;
         delete aTab.linkedBrowser.__SS_tabStillLoading;
         delete aTab.linkedBrowser.__SS_formDataSaved;
         delete aTab.linkedBrowser.__SS_hostSchemeData;
         if (aTab.linkedBrowser.__SS_restoreState)
           this._resetTabRestoringState(aTab);
       });
       openWindows[aWindow.__SSi] = true;
@@ -1335,18 +1309,19 @@ let SessionStoreInternal = {
     event.initEvent("SSTabClosing", true, false);
     aTab.dispatchEvent(event);
 
     // don't update our internal state if we don't have to
     if (this._max_tabs_undo == 0) {
       return;
     }
 
-    // Get the latest data for this tab (generally, from the cache)
-    let tabState = this._collectTabData(aTab);
+    // make sure that the tab related data is up-to-date
+    var tabState = this._collectTabData(aTab);
+    this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
 
     // store closed-tab data for undo
     if (this._shouldSaveTabState(tabState)) {
       let tabTitle = aTab.label;
       let tabbrowser = aWindow.gBrowser;
       tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
 
       this._windows[aWindow.__SSi]._closedTabs.unshift({
@@ -1357,35 +1332,32 @@ let SessionStoreInternal = {
       });
       var length = this._windows[aWindow.__SSi]._closedTabs.length;
       if (length > this._max_tabs_undo)
         this._windows[aWindow.__SSi]._closedTabs.splice(this._max_tabs_undo, length - this._max_tabs_undo);
     }
   },
 
   /**
-   * When a tab loads, invalidate its cached state, trigger async save.
-   *
+   * When a tab loads, save state.
    * @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;
     delete aBrowser.__SS_tabStillLoading;
     delete aBrowser.__SS_formDataSaved;
     this.saveStateDelayed(aWindow);
 
     // attempt to update the current URL we send in a crash report
     this._updateCrashReportURL(aWindow);
   },
@@ -1396,18 +1368,16 @@ let SessionStoreInternal = {
    *        Window reference
    * @param aBrowser
    *        Browser reference
    */
   onTabInput: function ssi_onTabInput(aWindow, aBrowser) {
     // deleting __SS_formDataSaved will cause us to recollect form data
     delete aBrowser.__SS_formDataSaved;
 
-    TabStateCache.delete(aBrowser);
-
     this.saveStateDelayed(aWindow, 3000);
   },
 
   /**
    * When a tab is selected, save session data
    * @param aWindow
    *        Window reference
    */
@@ -1521,63 +1491,42 @@ let SessionStoreInternal = {
 
     this.restoreWindow(aWindow, aState, aOverwrite);
   },
 
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
-    let tabState = this._collectTabData(aTab);
+    var tabState = this._collectTabData(aTab);
+
+    var window = aTab.ownerDocument.defaultView;
+    this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
 
     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
-    // by |restoreHistoryPrecursor|.
-    let tabState = JSON.parse(aState);
-    if (!tabState) {
-      debug("Empty state argument");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-    }
-    if (typeof tabState != "object") {
-      debug("State argument does not represent an object");
+    var tabState = JSON.parse(aState);
+    if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-    }
-    if (!("entries" in tabState)) {
-      debug("State argument must contain field 'entries'");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-    }
-    if (!aTab.ownerDocument) {
-      debug("Tab argument must have an owner document");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    let window = aTab.ownerDocument.defaultView;
-    if (!("__SSi" in window)) {
-      debug("Default view of ownerDocument must have a unique identifier");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    TabStateCache.delete(aTab);
+
+    var window = aTab.ownerDocument.defaultView;
     this._setWindowStateBusy(window);
     this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
         !aWindow.getBrowser)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
-    // Duplicate the tab state
-    let tabState = this._cloneFullTabData(aTab);
-
+    var tabState = this._collectTabData(aTab, true);
+    var sourceWindow = aTab.ownerDocument.defaultView;
+    this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
     tabState.index += aDelta;
     tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
     tabState.pinned = false;
 
     this._setWindowStateBusy(aWindow);
     let newTab = aTab == aWindow.gBrowser.selectedTab ?
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
       aWindow.gBrowser.addTab();
@@ -1737,33 +1686,31 @@ let SessionStoreInternal = {
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     }
   },
 
   deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
     if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
         this._windows[aWindow.__SSi].extData[aKey])
       delete this._windows[aWindow.__SSi].extData[aKey];
-    this.saveStateDelayed(aWindow);
   },
 
   getTabValue: function ssi_getTabValue(aTab, aKey) {
     let data = {};
     if (aTab.__SS_extdata) {
       data = aTab.__SS_extdata;
     }
     else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
       // If the tab hasn't been fully restored, get the data from the to-be-restored data
       data = aTab.linkedBrowser.__SS_data.extData;
     }
     return data[aKey] || "";
   },
 
   setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
-    TabStateCache.delete(aTab);
     // If the tab hasn't been restored, then set the data there, otherwise we
     // could lose newly added data.
     let saveTo;
     if (aTab.__SS_extdata) {
       saveTo = aTab.__SS_extdata;
     }
     else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
       saveTo = aTab.linkedBrowser.__SS_data.extData;
@@ -1772,31 +1719,29 @@ let SessionStoreInternal = {
       aTab.__SS_extdata = {};
       saveTo = aTab.__SS_extdata;
     }
     saveTo[aKey] = aStringValue;
     this.saveStateDelayed(aTab.ownerDocument.defaultView);
   },
 
   deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
-    TabStateCache.delete(aTab);
     // 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;
     if (aTab.__SS_extdata) {
       deleteFrom = aTab.__SS_extdata;
     }
     else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
       deleteFrom = aTab.linkedBrowser.__SS_data.extData;
     }
 
     if (deleteFrom && deleteFrom[aKey])
       delete deleteFrom[aKey];
-    this.saveStateDelayed(aTab.ownerDocument.defaultView);
   },
 
   persistTabAttribute: function ssi_persistTabAttribute(aName) {
     if (TabAttributes.persist(aName)) {
       this.saveStateDelayed();
     }
   },
 
@@ -1964,61 +1909,42 @@ let SessionStoreInternal = {
     }
 
     return [true, canOverwriteTabs];
   },
 
   /* ........ Saving Functionality .............. */
 
   /**
-   * Collect data related to a single tab
-   *
-   * @param aTab
-   *        tabbrowser tab
-   *
-   * @returns {TabData} An object with the data for this tab.  If the
-   * tab has not been invalidated since the last call to
-   * _collectTabData(aTab), the same object is returned.
+   * Store all session data for a window
+   * @param aWindow
+   *        Window reference
    */
-  _collectTabData: function ssi_collectTabData(aTab) {
-    if (!aTab) {
-      throw new TypeError("Expecting a tab");
-    }
-    let tabData;
-    if ((tabData = TabStateCache.get(aTab))) {
-      return tabData;
-    }
-    tabData = new TabData(this._collectBaseTabData(aTab));
-    TabStateCache.set(aTab, tabData);
-
-    this._updateTextAndScrollDataForTab(aTab, tabData);
-    return tabData;
+  _saveWindowHistory: function ssi_saveWindowHistory(aWindow) {
+    var tabbrowser = aWindow.gBrowser;
+    var tabs = tabbrowser.tabs;
+    var tabsData = this._windows[aWindow.__SSi].tabs = [];
+
+    for (var i = 0; i < tabs.length; i++)
+      tabsData.push(this._collectTabData(tabs[i]));
+
+    this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
   },
 
   /**
-   * Collect data related to a single tab, including private data.
-   * Use with caution.
-   *
+   * Collect data related to a single tab
    * @param aTab
    *        tabbrowser tab
-   *
-   * @returns {object} An object with the data for this tab. This object
-   * is recomputed at every call.
+   * @param aFullData
+   *        always return privacy sensitive data (use with care)
+   * @returns object
    */
-  _cloneFullTabData: function ssi_cloneFullTabData(aTab) {
-    let options = { includePrivateData: true };
-    let tabData = this._collectBaseTabData(aTab, options);
-    this._updateTextAndScrollDataForTab(aTab, tabData, options);
-    return tabData;
-  },
-
-  _collectBaseTabData: function ssi_collectBaseTabData(aTab, aOptions = null) {
-    let includePrivateData = aOptions && aOptions.includePrivateData;
-    let tabData = {entries: [], lastAccessed: aTab.lastAccessed };
-    let browser = aTab.linkedBrowser;
+  _collectTabData: function ssi_collectTabData(aTab, aFullData) {
+    var tabData = { entries: [], lastAccessed: aTab.lastAccessed };
+    var browser = aTab.linkedBrowser;
 
     if (!browser || !browser.currentURI)
       // can happen when calling this function right after .addTab()
       return tabData;
     else if (browser.__SS_data && browser.__SS_tabStillLoading) {
       // use the data to be restored when the tab hasn't been completely loaded
       tabData = browser.__SS_data;
       if (aTab.pinned)
@@ -2043,26 +1969,26 @@ let SessionStoreInternal = {
     }
     catch (ex) { } // this could happen if we catch a tab during (de)initialization
 
     // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
     //           data even when we shouldn't (e.g. Back, different anchor)
     if (history && browser.__SS_data &&
         browser.__SS_data.entries[history.index] &&
         browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
-        history.index < this._sessionhistory_max_entries - 1 && !includePrivateData) {
+        history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
       tabData = browser.__SS_data;
       tabData.index = history.index + 1;
     }
     else if (history && history.count > 0) {
       browser.__SS_hostSchemeData = [];
       try {
         for (var j = 0; j < history.count; j++) {
           let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
-                                                  includePrivateData, aTab.pinned, browser.__SS_hostSchemeData);
+                                                  aFullData, aTab.pinned, browser.__SS_hostSchemeData);
           tabData.entries.push(entry);
         }
         // If we make it through the for loop, then we're ok and we should clear
         // any indicator of brokenness.
         delete aTab.__SS_broken_history;
       }
       catch (ex) {
         // In some cases, getEntryAtIndex will throw. This seems to be due to
@@ -2078,17 +2004,17 @@ let SessionStoreInternal = {
           NS_ASSERT(false, "SessionStore failed gathering complete history " +
                            "for the focused window/tab. See bug 669196.");
           aTab.__SS_broken_history = true;
         }
       }
       tabData.index = history.index + 1;
 
       // make sure not to cache privacy sensitive data which shouldn't get out
-      if (!includePrivateData)
+      if (!aFullData)
         browser.__SS_data = tabData;
     }
     else if (browser.currentURI.spec != "about:blank" ||
              browser.contentDocument.body.hasChildNodes()) {
       tabData.entries[0] = { url: browser.currentURI.spec };
       tabData.index = 1;
     }
 
@@ -2127,39 +2053,39 @@ let SessionStoreInternal = {
     tabData.image = tabbrowser.getIcon(aTab);
 
     if (aTab.__SS_extdata)
       tabData.extData = aTab.__SS_extdata;
     else if (tabData.extData)
       delete tabData.extData;
 
     if (history && browser.docShell instanceof Ci.nsIDocShell) {
-      let storageData = SessionStorage.serialize(browser.docShell, includePrivateData)
+      let storageData = SessionStorage.serialize(browser.docShell, aFullData)
       if (Object.keys(storageData).length)
         tabData.storage = storageData;
     }
 
     return tabData;
   },
 
   /**
    * Get an object that is a serialized representation of a History entry
    * Used for data storage
    * @param aEntry
    *        nsISHEntry instance
-   * @param aIncludePrivateData
+   * @param aFullData
    *        always return privacy sensitive data (use with care)
    * @param aIsPinned
    *        the tab is pinned and should be treated differently for privacy
    * @param aHostSchemeData
    *        an array of objects with host & scheme keys
    * @returns object
    */
   _serializeHistoryEntry:
-    function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned, aHostSchemeData) {
+    function ssi_serializeHistoryEntry(aEntry, aFullData, aIsPinned, aHostSchemeData) {
     var entry = { url: aEntry.URI.spec };
 
     try {
       // throwing is expensive, we know that about: pages will throw
       if (entry.url.indexOf("about:") != 0)
         aHostSchemeData.push({ host: aEntry.URI.host, scheme: aEntry.URI.scheme });
     }
     catch (ex) {
@@ -2200,26 +2126,26 @@ let SessionStoreInternal = {
 
     var x = {}, y = {};
     aEntry.getScrollPosition(x, y);
     if (x.value != 0 || y.value != 0)
       entry.scroll = x.value + "," + y.value;
 
     try {
       var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
-      if (aEntry.postData && (aIncludePrivateData || prefPostdata &&
+      if (aEntry.postData && (aFullData || prefPostdata &&
             this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
         aEntry.postData.QueryInterface(Ci.nsISeekableStream).
                         seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
         var stream = Cc["@mozilla.org/binaryinputstream;1"].
                      createInstance(Ci.nsIBinaryInputStream);
         stream.setInputStream(aEntry.postData);
         var postBytes = stream.readByteArray(stream.available());
         var postdata = String.fromCharCode.apply(null, postBytes);
-        if (aIncludePrivateData || prefPostdata == -1 ||
+        if (aFullData || prefPostdata == -1 ||
             postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
               prefPostdata) {
           // We can stop doing base64 encoding once our serialization into JSON
           // is guaranteed to handle all chars in strings, including embedded
           // nulls.
           entry.postdata_b64 = btoa(postdata);
         }
       }
@@ -2270,107 +2196,119 @@ let SessionStoreInternal = {
 
         if (child) {
           // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
           if (child.URI.schemeIs("wyciwyg")) {
             children = [];
             break;
           }
 
-          children.push(this._serializeHistoryEntry(child, aIncludePrivateData,
+          children.push(this._serializeHistoryEntry(child, aFullData,
                                                     aIsPinned, aHostSchemeData));
         }
       }
 
       if (children.length)
         entry.children = children;
     }
 
     return entry;
   },
 
   /**
-   * Go through all frames and store the current scroll positions
+   * go through all tabs and store the current scroll positions
    * and innerHTML content of WYSIWYG editors
-   *
-   * @param aTab
-   *        tabbrowser tab
+   * @param aWindow
+   *        Window reference
+   */
+  _updateTextAndScrollData: function ssi_updateTextAndScrollData(aWindow) {
+    var browsers = aWindow.gBrowser.browsers;
+    this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
+      try {
+        this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
+      }
+      catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
+    }, this);
+  },
+
+  /**
+   * go through all frames and store the current scroll positions
+   * and innerHTML content of WYSIWYG editors
+   * @param aWindow
+   *        Window reference
+   * @param aBrowser
+   *        single browser reference
    * @param aTabData
    *        tabData object to add the information to
-   * @param options
-   *        An optional object that may contain the following field:
-   *        - includePrivateData: always return privacy sensitive data
-   *          (use with care)
+   * @param aFullData
+   *        always return privacy sensitive data (use with care)
    */
   _updateTextAndScrollDataForTab:
-    function ssi_updateTextAndScrollDataForTab(aTab, aTabData, aOptions = null) {
-    let includePrivateData = aOptions && aOptions.includePrivateData;
-    let window = aTab.ownerDocument.defaultView;
-    let browser = aTab.linkedBrowser;
+    function ssi_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
     // we shouldn't update data for incompletely initialized tabs
-    if (browser.__SS_data && browser.__SS_tabStillLoading)
+    if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading)
       return;
 
     var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
     // entry data needn't exist for tabs just initialized with an incomplete session state
     if (!aTabData.entries[tabIndex])
       return;
 
-    let selectedPageStyle = browser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
-                            this._getSelectedPageStyle(browser.contentWindow);
+    let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
+                            this._getSelectedPageStyle(aBrowser.contentWindow);
     if (selectedPageStyle)
       aTabData.pageStyle = selectedPageStyle;
     else if (aTabData.pageStyle)
       delete aTabData.pageStyle;
 
-    this._updateTextAndScrollDataForFrame(window, browser.contentWindow,
+    this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
                                           aTabData.entries[tabIndex],
-                                          !browser.__SS_formDataSaved, includePrivateData,
+                                          !aBrowser.__SS_formDataSaved, aFullData,
                                           !!aTabData.pinned);
-    browser.__SS_formDataSaved = true;
-    if (browser.currentURI.spec == "about:config")
+    aBrowser.__SS_formDataSaved = true;
+    if (aBrowser.currentURI.spec == "about:config")
       aTabData.entries[tabIndex].formdata = {
         id: {
-          "textbox": browser.contentDocument.getElementById("textbox").value
+          "textbox": aBrowser.contentDocument.getElementById("textbox").value
         },
         xpath: {}
       };
   },
 
   /**
    * go through all subframes and store all form data, the current
    * scroll positions and innerHTML content of WYSIWYG editors
    * @param aWindow
    *        Window reference
    * @param aContent
    *        frame reference
    * @param aData
    *        part of a tabData object to add the information to
    * @param aUpdateFormData
    *        update all form data for this tab
-   * @param aIncludePrivateData
+   * @param aFullData
    *        always return privacy sensitive data (use with care)
    * @param aIsPinned
    *        the tab is pinned and should be treated differently for privacy
    */
   _updateTextAndScrollDataForFrame:
     function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
-                                                 aUpdateFormData, aIncludePrivateData, aIsPinned) {
+                                                 aUpdateFormData, aFullData, aIsPinned) {
     for (var i = 0; i < aContent.frames.length; i++) {
       if (aData.children && aData.children[i])
         this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
                                               aData.children[i], aUpdateFormData,
-                                              aIncludePrivateData, aIsPinned);
+                                              aFullData, aIsPinned);
     }
     var isHTTPS = this._getURIFromString((aContent.parent || aContent).
                                          document.location.href).schemeIs("https");
     let topURL = aContent.top.document.location.href;
     let isAboutSR = topURL == "about:sessionrestore" || topURL == "about:welcomeback";
-    if (aIncludePrivateData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
-      if (aIncludePrivateData || aUpdateFormData) {
+    if (aFullData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
+      if (aFullData || aUpdateFormData) {
         let formData = DocumentUtils.getFormData(aContent.document);
 
         // We want to avoid saving data for about:sessionrestore as a string.
         // Since it's stored in the form as stringified JSON, stringifying further
         // causes an explosion of escape characters. cf. bug 467409
         if (formData && isAboutSR) {
           formData.id["sessionData"] = JSON.parse(formData.id["sessionData"]);
         }
@@ -2484,16 +2422,38 @@ let SessionStoreInternal = {
       aHosts[aHost] = aIsPinned;
     }
     else if (aScheme == "file") {
       aHosts[aHost] = true;
     }
   },
 
   /**
+   * store all hosts for a URL
+   * @param aWindow
+   *        Window reference
+   */
+  _updateCookieHosts: function ssi_updateCookieHosts(aWindow) {
+    var hosts = this._internalWindows[aWindow.__SSi].hosts = {};
+
+    // Since _updateCookiesHosts is only ever called for open windows during a
+    // session, we can call into _extractHostsForCookiesFromHostScheme directly
+    // using data that is attached to each browser.
+    for (let i = 0; i < aWindow.gBrowser.tabs.length; i++) {
+      let tab = aWindow.gBrowser.tabs[i];
+      let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
+      for (let j = 0; j < hostSchemeData.length; j++) {
+        this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
+                                                   hostSchemeData[j].scheme,
+                                                   hosts, true, tab.pinned);
+      }
+    }
+  },
+
+  /**
    * Serialize cookie data
    * @param aWindows
    *        JS object containing window data references
    *        { id: winData, etc. }
    */
   _updateCookies: function ssi_updateCookies(aWindows) {
     function addCookieToHash(aHash, aHost, aPath, aName, aCookie) {
       // lazily build up a 3-dimensional hash, with
@@ -2720,39 +2680,20 @@ let SessionStoreInternal = {
 
     return { windows: [winData] };
   },
 
   _collectWindowData: function ssi_collectWindowData(aWindow) {
     if (!this._isWindowLoaded(aWindow))
       return;
 
-    let tabbrowser = aWindow.gBrowser;
-    let tabs = tabbrowser.tabs;
-    let winData = this._windows[aWindow.__SSi];
-    let tabsData = winData.tabs = [];
-    let hosts = this._internalWindows[aWindow.__SSi].hosts = {};
-
     // update the internal state data for this window
-    for (let tab of tabs) {
-      tabsData.push(this._collectTabData(tab));
-
-      // Since we are only ever called for open
-      // windows during a session, we can call into
-      // _extractHostsForCookiesFromHostScheme directly using data
-      // that is attached to each browser.
-      let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
-      for (let j = 0; j < hostSchemeData.length; j++) {
-        this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
-                                                   hostSchemeData[j].scheme,
-                                                   hosts, true, tab.pinned);
-      }
-    }
-    winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
-
+    this._saveWindowHistory(aWindow);
+    this._updateTextAndScrollData(aWindow);
+    this._updateCookieHosts(aWindow);
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
     // or leaving PB mode.
     if (aWindow.__SS_lastSessionWindowID)
       this._windows[aWindow.__SSi].__lastSessionWindowID =
         aWindow.__SS_lastSessionWindowID;
 
@@ -3041,33 +2982,32 @@ let SessionStoreInternal = {
    *        Counter for number of times delaying b/c browser or history aren't ready
    * @param aRestoreImmediately
    *        Flag to indicate whether the given set of tabs aTabs should be
    *        restored/loaded immediately even if restore_on_demand = true
    */
   restoreHistoryPrecursor:
     function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
                                          aIx, aCount, aRestoreImmediately = false) {
-
     var tabbrowser = aWindow.gBrowser;
 
     // make sure that all browsers and their histories are available
     // - if one's not, resume this check in 100ms (repeat at most 10 times)
     for (var t = aIx; t < aTabs.length; t++) {
       try {
         if (!tabbrowser.getBrowserForTab(aTabs[t]).webNavigation.sessionHistory) {
           throw new Error();
         }
       }
       catch (ex) { // in case browser or history aren't ready yet
         if (aCount < 10) {
           var restoreHistoryFunc = function(self) {
             self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
                                          aIx, aCount + 1, aRestoreImmediately);
-          };
+          }
           aWindow.setTimeout(restoreHistoryFunc, 100, this);
           return;
         }
       }
     }
 
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
@@ -3193,16 +3133,17 @@ let SessionStoreInternal = {
       // At this point we're essentially ready for consumers to read/write data
       // via the sessionstore API so we'll send the SSWindowStateReady event.
       this._setWindowStateReady(aWindow);
       return; // no more tabs to restore
     }
 
     var tab = aTabs.shift();
     var tabData = aTabData.shift();
+
     var browser = aWindow.gBrowser.getBrowserForTab(tab);
     var history = browser.webNavigation.sessionHistory;
 
     if (history.count > 0) {
       history.PurgeHistory(history.count);
     }
     history.QueryInterface(Ci.nsISHistoryInternal);
 
@@ -3757,27 +3698,27 @@ let SessionStoreInternal = {
   /**
    * save state delayed by N ms
    * marks window as dirty (i.e. data update can't be skipped)
    * @param aWindow
    *        Window reference
    * @param aDelay
    *        Milliseconds to delay
    */
-  saveStateDelayed: function ssi_saveStateDelayed(aWindow = null, aDelay = 2000) {
+  saveStateDelayed: function ssi_saveStateDelayed(aWindow, aDelay) {
     if (aWindow) {
       this._dirtyWindows[aWindow.__SSi] = true;
     }
 
     if (!this._saveTimer) {
       // interval until the next disk operation is allowed
       var minimalDelay = this._lastSaveTime + this._interval - Date.now();
 
       // if we have to wait, set a timer, otherwise saveState directly
-      aDelay = Math.max(minimalDelay, aDelay);
+      aDelay = Math.max(minimalDelay, aDelay || 2000);
       if (aDelay > 0) {
         this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
         this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
       }
       else {
         this.saveState();
       }
     }
@@ -4039,17 +3980,16 @@ let SessionStoreInternal = {
    */
   _getTabForBrowser: function ssi_getTabForBrowser(aBrowser) {
     let window = aBrowser.ownerDocument.defaultView;
     for (let i = 0; i < window.gBrowser.tabs.length; i++) {
       let tab = window.gBrowser.tabs[i];
       if (tab.linkedBrowser == aBrowser)
         return tab;
     }
-    return undefined;
   },
 
   /**
    * Whether or not to resume session, if not recovering from a crash.
    * @returns bool
    */
   _doResumeSession: function ssi_doResumeSession() {
     return this._prefBranch.getIntPref("startup.page") == 3 ||
@@ -4862,35 +4802,21 @@ function SessionStoreSHistoryListener(aT
   this.tab = aTab;
 }
 SessionStoreSHistoryListener.prototype = {
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsISHistoryListener,
     Ci.nsISupportsWeakReference
   ]),
   browser: null,
-// The following events (with the exception of OnHistoryPurge)
-// accompany either a "load" or a "pageshow" which will in turn cause
-// invalidations.
-  OnHistoryNewEntry: function(aNewURI) {
-
-  },
-  OnHistoryGoBack: function(aBackURI) {
-    return true;
-  },
-  OnHistoryGoForward: function(aForwardURI) {
-    return true;
-  },
-  OnHistoryGotoIndex: function(aIndex, aGotoURI) {
-    return true;
-  },
-  OnHistoryPurge: function(aNumEntries) {
-    TabStateCache.delete(this.tab);
-    return true;
-  },
+  OnHistoryNewEntry: function(aNewURI) { },
+  OnHistoryGoBack: function(aBackURI) { return true; },
+  OnHistoryGoForward: function(aForwardURI) { return true; },
+  OnHistoryGotoIndex: function(aIndex, aGotoURI) { return true; },
+  OnHistoryPurge: function(aNumEntries) { return true; },
   OnHistoryReload: function(aReloadURI, aReloadFlags) {
     // On reload, we want to make sure that session history loads the right
     // URI. In order to do that, we will juet call restoreTab. That will remove
     // the history listener and load the right URI.
     SessionStoreInternal.restoreTab(this.tab);
     // Returning false will stop the load that docshell is attempting.
     return false;
   }
@@ -4903,88 +4829,9 @@ String.prototype.hasRootDomain = functio
     return false;
 
   if (this == aDomain)
     return true;
 
   let prevChar = this[index - 1];
   return (index == (this.length - aDomain.length)) &&
          (prevChar == "." || prevChar == "/");
-};
-
-function TabData(obj = null) {
-  if (obj) {
-    if (obj instanceof TabData) {
-      // FIXME: Can we get rid of this?
-      return obj;
-    }
-    for (let [key, value] in Iterator(obj)) {
-      this[key] = value;
-    }
-  }
-  return this;
 }
-
-/**
- * A cache for tabs data.
- *
- * This cache implements a weak map from tabs (as XUL elements)
- * to tab data (as instances of TabData).
- *
- * 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.
- */
-let TabStateCache = {
-  _data: new WeakMap(),
-
-  /**
-   * 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 {TabData} aValue The data associated to |aTab|.
-   */
-  set: function(aTab, aValue) {
-    let key = this._normalizeToBrowser(aTab);
-    if (!(aValue instanceof TabData)) {
-      throw new TypeError("Attempting to cache a non TabData");
-    }
-    this._data.set(key, aValue);
-  },
-
-  /**
-   * Return the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * @return {TabData|undefined} The data if available, |undefined|
-   * otherwise.
-   */
-  get: function(aKey) {
-    let key = this._normalizeToBrowser(aKey);
-    return this._data.get(key);
-  },
-
-  /**
-   * 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);
-  },
-
-  _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);
-  }
-};
--- a/browser/components/sessionstore/test/Makefile.in
+++ b/browser/components/sessionstore/test/Makefile.in
@@ -22,17 +22,16 @@ MOCHITEST_BROWSER_FILES = \
 	browser_dying_cache.js \
 	browser_form_restore_events.js \
 	browser_form_restore_events_sample.html \
 	browser_formdata_format.js \
 	browser_formdata_format_sample.html \
 	browser_input.js \
 	browser_input_sample.html \
 	browser_pageshow.js \
-	browser_sessionStorage.js \
         browser_upgrade_backup.js \
 	browser_windowRestore_perwindowpb.js \
 	browser_248970_b_perwindowpb.js \
 	browser_248970_b_sample.html \
 	browser_339445.js \
 	browser_339445_sample.html \
 	browser_345898.js \
 	browser_346337.js \
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_sessionStorage.js
+++ /dev/null
@@ -1,89 +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/. */
-
-let Scope = {};
-Cu.import("resource://gre/modules/Task.jsm", Scope);
-Cu.import("resource://gre/modules/Promise.jsm", Scope);
-let {Task, Promise} = Scope;
-
-function promiseBrowserLoaded(aBrowser) {
-  let deferred = Promise.defer();
-  whenBrowserLoaded(aBrowser, () => deferred.resolve());
-  return deferred.promise;
-}
-
-function forceWriteState() {
-  let deferred = Promise.defer();
-  const PREF = "browser.sessionstore.interval";
-  const TOPIC = "sessionstore-state-write";
-
-  Services.obs.addObserver(function observe() {
-    Services.obs.removeObserver(observe, TOPIC);
-    Services.prefs.clearUserPref(PREF);
-    deferred.resolve();
-  }, TOPIC, false);
-
-  Services.prefs.setIntPref(PREF, 0);
-  return deferred.promise;
-}
-
-function waitForStorageChange(aTab) {
-  let deferred = Promise.defer();
-  waitForContentMessage(aTab.linkedBrowser,
-    "sessionstore:MozStorageChanged",
-    200,
-    ((x) => deferred.resolve(x)));
-  return deferred.promise;
-}
-
-function test() {
-
-  waitForExplicitFinish();
-
-  let tab;
-  Task.spawn(function() {
-    try {
-      tab = gBrowser.addTab("http://example.com");
-      // about:home supports sessionStorage and localStorage
-
-      let win = tab.linkedBrowser.contentWindow;
-
-      // Flush loading and next save, call getBrowserState()
-      // a few times to ensure that everything is cached.
-      yield promiseBrowserLoaded(tab.linkedBrowser);
-      yield forceWriteState();
-      info("Calling getBrowserState() to populate cache");
-      ss.getBrowserState();
-
-      info("Change sessionStorage, ensure that state is saved");
-      win.sessionStorage["SESSION_STORAGE_KEY"] = "SESSION_STORAGE_VALUE";
-      yield waitForStorageChange(tab);
-      yield forceWriteState();
-
-      let state = ss.getBrowserState();
-      ok(state.indexOf("SESSION_STORAGE_KEY") != -1, "Key appears in state");
-      ok(state.indexOf("SESSION_STORAGE_VALUE") != -1, "Value appears in state");
-
-
-      info("Change localStorage, ensure that state is not saved");
-      win.localStorage["LOCAL_STORAGE_KEY"] = "LOCAL_STORAGE_VALUE";
-      yield waitForStorageChange(tab);
-      yield forceWriteState();
-
-      state = ss.getBrowserState();
-      ok(state.indexOf("LOCAL_STORAGE_KEY") == -1, "Key does not appear in state");
-      ok(state.indexOf("LOCAL_STORAGE_VALUE") == -1, "Value does not appear in state");
-    } catch (ex) {
-      ok(false, ex);
-      info(ex.stack);
-    } finally {
-      // clean up
-      if (tab) {
-        gBrowser.removeTab(tab);
-      }
-
-      executeSoon(finish);
-    }
-  });
-}
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -151,100 +151,53 @@ function waitForTabState(aTab, aState, a
   registerCleanupFunction(function() {
     if (listening) {
       aTab.removeEventListener("SSTabRestored", onSSTabRestored, false);
     }
   });
   ss.setTabState(aTab, JSON.stringify(aState));
 }
 
-/**
- * Wait for a content -> chrome message.
- */
-function waitForContentMessage(aBrowser, aTopic, aTimeout, aCallback) {
-  let mm = aBrowser.messageManager;
+// waitForSaveState waits for a state write but not necessarily for the state to
+// turn dirty.
+function waitForSaveState(aSaveStateCallback) {
   let observing = false;
+  let topic = "sessionstore-state-write";
+
+  let sessionSaveTimeout = 1000 +
+    Services.prefs.getIntPref("browser.sessionstore.interval");
+
   function removeObserver() {
     if (!observing)
       return;
-    mm.removeMessageListener(aTopic, observer);
+    Services.obs.removeObserver(observer, topic);
     observing = false;
   }
 
   let timeout = setTimeout(function () {
     removeObserver();
-    aCallback(false);
-  }, aTimeout);
+    aSaveStateCallback();
+  }, sessionSaveTimeout);
 
   function observer(aSubject, aTopic, aData) {
     removeObserver();
     timeout = clearTimeout(timeout);
-    executeSoon(() => aCallback(true));
+    executeSoon(aSaveStateCallback);
   }
 
   registerCleanupFunction(function() {
     removeObserver();
     if (timeout) {
       clearTimeout(timeout);
     }
   });
 
   observing = true;
-  mm.addMessageListener(aTopic, observer);
-}
-
-function waitForTopic(aTopic, aTimeout, aCallback) {
-  let observing = false;
-  function removeObserver() {
-    if (!observing)
-      return;
-    Services.obs.removeObserver(observer, aTopic);
-    observing = false;
-  }
-
-  let timeout = setTimeout(function () {
-    removeObserver();
-    aCallback(false);
-  }, aTimeout);
-
-  function observer(aSubject, aTopic, aData) {
-    removeObserver();
-    timeout = clearTimeout(timeout);
-    executeSoon(() => aCallback(true));
-  }
-
-  registerCleanupFunction(function() {
-    removeObserver();
-    if (timeout) {
-      clearTimeout(timeout);
-    }
-  });
-
-  observing = true;
-  Services.obs.addObserver(observer, aTopic, false);
-}
-
-/**
- * Wait until session restore has finished collecting its data and is
- * getting ready to write that data ("sessionstore-state-write").
- *
- * This function is meant to be called immediately after the code
- * that will trigger the saving.
- *
- * Note that this does not wait for the disk write to be complete.
- *
- * @param {function} aCallback If sessionstore-state-write is sent
- * within buffering interval + 100 ms, the callback is passed |true|,
- * otherwise, it is passed |false|.
- */
-function waitForSaveState(aCallback) {
-  let timeout = 100 +
-    Services.prefs.getIntPref("browser.sessionstore.interval");
-  return waitForTopic("sessionstore-state-write", timeout, aCallback);
-}
+  Services.obs.addObserver(observer, topic, false);
+};
 
 function whenBrowserLoaded(aBrowser, aCallback = next) {
   aBrowser.addEventListener("load", function onLoad() {
     aBrowser.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
   }, true);
 }