Backed out changeset 101c149b3287 (bug 867143) for browser-chrome failures
authorEd Morley <emorley@mozilla.com>
Wed, 24 Jul 2013 13:32:33 +0100
changeset 152069 8a3b8fe776426822278b565db53087b9ef731ba8
parent 152068 3352acd5e5504f305a52b64bef472619e1473eb3
child 152070 e772c5b069fc77b8f7fce8d7411b8e3b6e7e4b78
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs867143
milestone25.0a1
backs out101c149b32870cb1edf32e89c36224b666426469
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 changeset 101c149b3287 (bug 867143) for browser-chrome failures
browser/components/sessionstore/content/content-sessionStore.js
browser/components/sessionstore/src/SessionStore.jsm
--- 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");
     });
 
@@ -622,53 +607,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) {
@@ -677,20 +656,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();
   },
 
@@ -705,17 +680,16 @@ let SessionStoreInternal = {
 
     var win = aEvent.currentTarget.ownerDocument.defaultView;
     switch (aEvent.type) {
       case "load":
         // If __SS_restore_data is set, then we need to restore the document
         // (form data, scrolling, etc.). This will only happen when a tab is
         // first restored.
         let browser = aEvent.currentTarget;
-        TabStateCache.delete(browser);
         if (browser.__SS_restore_data)
           this.restoreDocument(win, browser, aEvent);
         this.onTabLoad(win, browser);
         break;
       case "TabOpen":
         this.onTabAdd(win, aEvent.originalTarget);
         break;
       case "TabClose":
@@ -729,26 +703,21 @@ 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.update(aEvent.originalTarget, "pinned", true);
+      case "TabUnpinned":
         this.saveStateDelayed(win);
         break;
-      case "TabUnpinned":
-        // If possible, update cached data without having to invalidate it
-        TabStateCache.update(aEvent.originalTarget, "pinned", false);
-        this.saveStateDelayed(win);
-        break;
-    }
+    }
+
     this._clearRestoringWindows();
   },
 
   /**
    * If it's the first window load since app start...
    * - determine if we're reloading after a crash or a forced-restart
    * - restore window state
    * - restart downloads
@@ -1108,17 +1077,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;
@@ -1332,18 +1300,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({
@@ -1354,35 +1323,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);
   },
@@ -1393,18 +1359,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
    */
@@ -1431,34 +1395,28 @@ 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.update(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.update(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);
   },
 
   /* ........ nsISessionStore API .............. */
 
   getBrowserState: function ssi_getBrowserState() {
@@ -1524,63 +1482,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();
@@ -1740,33 +1677,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;
@@ -1775,36 +1710,33 @@ 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)) {
-      TabStateCache.clear();
       this.saveStateDelayed();
     }
   },
 
   /**
    * Restores the session state stored in _lastSessionState. 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
@@ -1968,66 +1900,47 @@ 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));
-    if (this._updateTextAndScrollDataForTab(aTab, tabData)) {
-      TabStateCache.set(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;
-    if (!browser || !browser.currentURI) {
+  _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;
-    }
-    if (browser.__SS_data && browser.__SS_tabStillLoading) {
+    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)
         tabData.pinned = true;
       else
         delete tabData.pinned;
       tabData.hidden = aTab.hidden;
 
@@ -2047,26 +1960,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
@@ -2082,17 +1995,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;
     }
 
@@ -2131,39 +2044,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) {
@@ -2204,26 +2117,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);
         }
       }
@@ -2274,113 +2187,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)
-   * @return false if data should not be cached because the tab
-   *        has not been fully initialized yet.
+   * @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.currentURI
-        || (browser.__SS_data && browser.__SS_tabStillLoading)) {
-      return false;
-    }
-
-    let tabIndex = (aTabData.index || aTabData.entries.length) - 1;
+    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 false;
-    }
-
-    let selectedPageStyle = browser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
-                            this._getSelectedPageStyle(browser.contentWindow);
+    if (!aTabData.entries[tabIndex])
+      return;
+
+    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: {}
       };
-      return true;
   },
 
   /**
    * 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"]);
         }
@@ -2494,16 +2413,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
@@ -2730,39 +2671,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;
 
@@ -2886,23 +2808,20 @@ let SessionStoreInternal = {
       winData.tabs[0].hidden = false;
       tabbrowser.showTab(tabs[0]);
     }
 
     // If overwriting tabs, we want to reset each tab's "restoring" state. Since
     // we're overwriting those tabs, they should no longer be restoring. The
     // tabs will be rebuilt and marked if they need to be restored after loading
     // state (in restoreHistoryPrecursor).
-    // We also want to invalidate any cached information on the tab state.
     if (aOverwriteTabs) {
       for (let i = 0; i < tabbrowser.tabs.length; i++) {
-        let tab = tabbrowser.tabs[i];
-        TabStateCache.delete(tab);
         if (tabbrowser.browsers[i].__SS_restoreState)
-          this._resetTabRestoringState(tab);
+          this._resetTabRestoringState(tabbrowser.tabs[i]);
       }
     }
 
     // We want to set up a counter on the window that indicates how many tabs
     // in this window are unrestored. This will be used in restoreNextTab to
     // determine if gRestoreTabsProgressListener should be removed from the window.
     // If we aren't overwriting existing tabs, then we want to add to the existing
     // count in case there are still tabs restoring.
@@ -3054,33 +2973,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
@@ -3206,16 +3124,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);
 
@@ -3770,27 +3689,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();
       }
     }
@@ -4052,17 +3971,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 ||
@@ -4875,35 +4793,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;
   }
@@ -4916,111 +4820,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);
-  },
-
-  /**
-   * Delete all tab data.
-   */
-  clear: function() {
-    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.
-   */
-  update: function(aKey, aField, aValue) {
-    let key = this._normalizeToBrowser(aKey);
-    let data = this._data.get(key);
-    if (data) {
-      data[aField] = aValue;
-    }
-  },
-
-  _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);
-  }
-};