Bug 407110 - sessionstore should use native json (r=dietrich)
☠☠ backed out by 5b1425e11037 ☠ ☠
authorzeniko@gmail.com
Wed, 05 Nov 2008 15:52:45 -0800
changeset 21362 10c21d116e5dc358a9a11f38d50f79f429a87055
parent 21361 eef8801a25bd0e4e8abdcde6f086217a1bef7502
child 21363 4df50933e7cb75bb212cfaaf6c19a765a1b5f4d0
child 21411 5b1425e110372a469590cb5e2eeb641b321f7bc0
push id3507
push userdietrich@mozilla.com
push dateWed, 05 Nov 2008 23:56:06 +0000
treeherdermozilla-central@10c21d116e5d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdietrich
bugs407110
milestone1.9.1b2pre
Bug 407110 - sessionstore should use native json (r=dietrich)
browser/components/sessionstore/src/nsSessionStore.js
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -95,18 +95,16 @@ const WINDOW_HIDEABLE_FEATURES = [
 docShell capabilities to (re)store
 Restored in restoreHistory()
 eg: browser.docShell["allow" + aCapability] = false;
 */
 const CAPABILITIES = [
   "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images"
 ];
 
-// module for JSON conversion (needed for the nsISessionStore API)
-Cu.import("resource://gre/modules/JSON.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function debug(aMsg) {
   aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
                                      .logStringMessage(aMsg);
 }
 
@@ -577,23 +575,16 @@ SessionStoreService.prototype = {
     
     for (var i = 0; i < tabpanels.childNodes.length; i++) {
       this.onTabRemove(aWindow, tabpanels.childNodes[i], true);
     }
     
     // cache the window state until the window is completely gone
     aWindow.__SS_dyingCache = winData;
     
-    // reset the _tab property to avoid keeping the tab's XUL element alive
-    // longer than we need it
-    var tabCount = aWindow.__SS_dyingCache.tabs.length;
-    for (var t = 0; t < tabCount; t++) {
-      delete aWindow.__SS_dyingCache.tabs[t]._tab;
-    }
-    
     delete aWindow.__SSi;
   },
 
   /**
    * set up listeners for a new tab
    * @param aWindow
    *        Window reference
    * @param aPanel
@@ -657,20 +648,16 @@ SessionStoreService.prototype = {
     if (maxTabsUndo == 0) {
       return;
     }
     
     // make sure that the tab related data is up-to-date
     var tabState = this._collectTabData(aTab);
     this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
 
-    // reset the _tab property to avoid keeping the tab's XUL element alive
-    // longer than we need it
-    delete tabState._tab;
-    
     // store closed-tab data for undo
     if (tabState.entries.length > 0) {
       let tabTitle = aTab.label;
       let tabbrowser = aWindow.gBrowser;
       // replace "Loading..." with the document title (with minimal side-effects)
       if (tabTitle == tabbrowser.mStringBundle.getString("tabs.loading")) {
         tabbrowser.setTabTitle(aTab);
         [tabTitle, aTab.label] = [aTab.label, tabTitle];
@@ -810,34 +797,31 @@ SessionStoreService.prototype = {
     return this._toJSONString(tabState);
   },
 
   setTabState: function sss_setTabState(aTab, aState) {
     var tabState = this._safeEval("(" + aState + ")");
     if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     
-    tabState._tab = aTab;
-    
     var window = aTab.ownerDocument.defaultView;
-    this.restoreHistoryPrecursor(window, [tabState], 0, 0, 0);
+    this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
   },
 
   duplicateTab: function sss_duplicateTab(aWindow, aTab) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
         !aWindow.getBrowser)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     
     var tabState = this._collectTabData(aTab, true);
     var sourceWindow = aTab.ownerDocument.defaultView;
     this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
     
     var newTab = aWindow.getBrowser().addTab();
-    tabState._tab = newTab;
-    this.restoreHistoryPrecursor(aWindow, [tabState], 0, 0, 0);
+    this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0);
     
     return newTab;
   },
 
   getClosedTabCount: function sss_getClosedTabCount(aWindow) {
     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
       return aWindow.__SS_dyingCache._closedTabs.length;
     if (!aWindow.__SSi)
@@ -868,23 +852,23 @@ SessionStoreService.prototype = {
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     
     // fetch the data of closed tab, while removing it from the array
     let closedTab = closedTabs.splice(aIndex, 1).shift();
     let closedTabState = closedTab.state;
 
     // create a new tab
     let browser = aWindow.gBrowser;
-    let tab = closedTabState._tab = browser.addTab();
+    let tab = browser.addTab();
       
     // restore the tab's position
     browser.moveTabTo(tab, closedTab.pos);
 
     // restore tab content
-    this.restoreHistoryPrecursor(aWindow, [closedTabState], 1, 0, 0);
+    this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1, 0, 0);
 
     // focus the tab's content area
     let content = browser.getBrowserForTab(tab).contentWindow;
     aWindow.setTimeout(function() { content.focus(); }, 0);
     
     return tab;
   },
 
@@ -977,17 +961,17 @@ SessionStoreService.prototype = {
    */
   _collectTabData: function sss_collectTabData(aTab, aFullData) {
     var tabData = { entries: [] };
     var browser = aTab.linkedBrowser;
     
     if (!browser || !browser.currentURI)
       // can happen when calling this function right after .addTab()
       return tabData;
-    else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tab)
+    else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tabStillLoading)
       // use the data to be restored when the tab hasn't been completely loaded
       return browser.parentNode.__SS_data;
     
     var history = null;
     try {
       history = browser.sessionHistory;
     }
     catch (ex) { } // this could happen if we catch a tab during (de)initialization
@@ -1215,17 +1199,18 @@ SessionStoreService.prototype = {
    * @param aWindow
    *        Window reference
    */
   _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
     var browsers = aWindow.getBrowser().browsers;
     for (var i = 0; i < browsers.length; i++) {
       try {
         var tabData = this._windows[aWindow.__SSi].tabs[i];
-        if (browsers[i].parentNode.__SS_data && browsers[i].parentNode.__SS_data._tab)
+        if (browsers[i].parentNode.__SS_data &&
+            browsers[i].parentNode.__SS_data._tabStillLoading)
           continue; // ignore incompletely initialized tabs
         this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
       }
       catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
     }
   },
 
   /**
@@ -1616,22 +1601,23 @@ SessionStoreService.prototype = {
     else if (root._firstTabs && !aOverwriteTabs && winData.tabs.length == 1 &&
              (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
       winData.tabs = [];
     }
     
     var tabbrowser = aWindow.getBrowser();
     var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
     var newTabCount = winData.tabs.length;
+    let tabs = [];
     
     for (var t = 0; t < newTabCount; t++) {
-      winData.tabs[t]._tab = t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab();
+      tabs.push(t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab());
       // when resuming at startup: add additionally requested pages to the end
       if (!aOverwriteTabs && root._firstTabs) {
-        tabbrowser.moveTabTo(winData.tabs[t]._tab, t);
+        tabbrowser.moveTabTo(tabs[t], t);
       }
     }
 
     // when overwriting tabs, remove all superflous ones
     for (t = openTabCount - 1; t >= newTabCount; t--) {
       tabbrowser.removeTab(tabbrowser.mTabs[t]);
     }
     
@@ -1648,124 +1634,132 @@ SessionStoreService.prototype = {
       for (var key in winData.extData) {
         this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
       }
     }
     if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) {
       this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs;
     }
     
-    this.restoreHistoryPrecursor(aWindow, winData.tabs, (aOverwriteTabs ?
-      (parseInt(winData.selected) || 1) : 0), 0, 0);
+    this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs,
+      (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0);
 
     this._notifyIfAllWindowsRestored();
   },
 
   /**
    * Manage history restoration for a window
+   * @param aWindow
+   *        Window to restore the tabs into
    * @param aTabs
+   *        Array of tab references
+   * @param aTabData
    *        Array of tab data
-   * @param aCurrentTabs
-   *        Array of tab references
    * @param aSelectTab
    *        Index of selected tab
    * @param aIx
    *        Index of the next tab to check readyness for
    * @param aCount
    *        Counter for number of times delaying b/c browser or history aren't ready
    */
-  restoreHistoryPrecursor: function sss_restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount) {
+  restoreHistoryPrecursor:
+    function sss_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount) {
     var tabbrowser = aWindow.getBrowser();
     
     // 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]._tab).webNavigation.sessionHistory) {
+        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, aSelectTab, aIx, aCount + 1);
+            self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount + 1);
           }
           aWindow.setTimeout(restoreHistoryFunc, 100, this);
           return;
         }
       }
     }
     
     // mark the tabs as loading
     for (t = 0; t < aTabs.length; t++) {
-      var tab = aTabs[t]._tab;
+      var tab = aTabs[t];
       var browser = tabbrowser.getBrowserForTab(tab);
       
-      if (!aTabs[t].entries || aTabs[t].entries.length == 0) {
+      aTabData[t]._tabStillLoading = true;
+      if (!aTabData[t].entries || aTabData[t].entries.length == 0) {
         // make sure to blank out this tab's content
         // (just purging the tab's history won't be enough)
         browser.contentDocument.location = "about:blank";
         continue;
       }
       
       browser.stop(); // in case about:blank isn't done yet
       
       tab.setAttribute("busy", "true");
       tabbrowser.updateIcon(tab);
       tabbrowser.setTabTitleLoading(tab);
       
       // wall-paper fix for bug 439675: make sure that the URL to be loaded
       // is always visible in the address bar
-      let activeIndex = (aTabs[t].index || aTabs[t].entries.length) - 1;
-      let activePageData = aTabs[t].entries[activeIndex] || null;
+      let activeIndex = (aTabData[t].index || aTabData[t].entries.length) - 1;
+      let activePageData = aTabData[t].entries[activeIndex] || null;
       browser.userTypedValue = activePageData ? activePageData.url || null : null;
       
       // keep the data around to prevent dataloss in case
       // a tab gets closed before it's been properly restored
-      browser.parentNode.__SS_data = aTabs[t];
+      browser.parentNode.__SS_data = aTabData[t];
     }
     
     // make sure to restore the selected tab first (if any)
     if (aSelectTab-- && aTabs[aSelectTab]) {
         aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
-        tabbrowser.selectedTab = aTabs[0]._tab;
+        aTabData.unshift(aTabData.splice(aSelectTab, 1)[0]);
+        tabbrowser.selectedTab = aTabs[0];
     }
 
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
       delete this._statesToRestore[aWindow.__SS_restoreID];
       delete aWindow.__SS_restoreID;
     }
     
     // helper hash for ensuring unique frame IDs
     var idMap = { used: {} };
-    this.restoreHistory(aWindow, aTabs, idMap);
+    this.restoreHistory(aWindow, aTabs, aTabData, idMap);
   },
 
   /**
    * Restory history for a window
    * @param aWindow
    *        Window reference
    * @param aTabs
+   *        Array of tab references
+   * @param aTabData
    *        Array of tab data
    * @param aIdMap
    *        Hash for ensuring unique frame IDs
    */
-  restoreHistory: function sss_restoreHistory(aWindow, aTabs, aIdMap) {
+  restoreHistory: function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap) {
     var _this = this;
-    while (aTabs.length > 0 && (!aTabs[0]._tab || !aTabs[0]._tab.parentNode)) {
+    while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) {
       aTabs.shift(); // this tab got removed before being completely restored
+      aTabData.shift();
     }
     if (aTabs.length == 0) {
       return; // no more tabs to restore
     }
     
-    var tabData = aTabs.shift();
+    var tab = aTabs.shift();
+    var tabData = aTabData.shift();
 
-    var tab = tabData._tab;
     var browser = aWindow.getBrowser().getBrowserForTab(tab);
     var history = browser.webNavigation.sessionHistory;
     
     if (history.count > 0) {
       history.PurgeHistory(history.count);
     }
     history.QueryInterface(Ci.nsISHistoryInternal);
     
@@ -1826,17 +1820,17 @@ SessionStoreService.prototype = {
       browser.__SS_restore_data = tabData.entries[activeIndex] || {};
       browser.__SS_restore_text = tabData.text || "";
       browser.__SS_restore_pageStyle = tabData.pageStyle || "";
       browser.__SS_restore_tab = tab;
       browser.__SS_restore = this.restoreDocument_proxy;
       browser.addEventListener("load", browser.__SS_restore, true);
     }
     
-    aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aIdMap); }, 0);
+    aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap); }, 0);
   },
 
   /**
    * expands serialized history data into a session-history-entry instance
    * @param aEntry
    *        Object containing serialized history data for a URL
    * @param aIdMap
    *        Hash for ensuring unique frame IDs
@@ -2219,17 +2213,18 @@ SessionStoreService.prototype = {
   },
 
   /**
    * write a state object to disk
    */
   _saveStateObject: function sss_saveStateObject(aStateObj) {
     var stateString = Cc["@mozilla.org/supports-string;1"].
                         createInstance(Ci.nsISupportsString);
-    stateString.data = aStateObj.toSource();
+    // parentheses are for backwards compatibility with Firefox 2.0 and 3.0
+    stateString.data = "(" + this._toJSONString(aStateObj) + ")";
 
     var observerService = Cc["@mozilla.org/observer-service;1"].
                           getService(Ci.nsIObserverService);
     observerService.notifyObservers(stateString, "sessionstore-state-write", "");
 
     // don't touch the file if an observer has deleted all state data
     if (stateString.data)
       this._writeFile(this._sessionFile, stateString.data);
@@ -2464,30 +2459,25 @@ SessionStoreService.prototype = {
   _safeEval: function sss_safeEval(aStr) {
     return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank"));
   },
 
   /**
    * Converts a JavaScript object into a JSON string
    * (see http://www.json.org/ for more information).
    *
-   * The inverse operation consists of eval("(" + JSON_string + ")");
-   * and should be provably safe.
+   * The inverse operation consists of JSON.parse(JSON_string).
    *
    * @param aJSObject is the object to be converted
-   * @return the object's JSON representation
+   * @returns the object's JSON representation
    */
   _toJSONString: function sss_toJSONString(aJSObject) {
-    let str = JSONModule.toString(aJSObject, ["_tab", "_hosts", "_formDataSaved"] /* keys to drop */);
-    
-    // sanity check - so that API consumers can just eval this string
-    if (!JSONModule.isMostlyHarmless(str))
-      throw new Error("JSON conversion failed unexpectedly!");
-    
-    return str;
+    // XXXzeniko drop the following keys used only for internal bookkeeping:
+    //           _tabStillLoading, _hosts, _formDataSaved
+    return JSON.stringify(aJSObject);
   },
 
   _notifyIfAllWindowsRestored: function sss_notifyIfAllWindowsRestored() {
     if (this._restoreCount) {
       this._restoreCount--;
       if (this._restoreCount == 0) {
         // This was the last window restored at startup, notify observers.
         var observerService = Cc["@mozilla.org/observer-service;1"].