Bug 630398 - Session store doesn't save session data quickly enough [r=mbrubeck]
authorMark Finkle <mfinkle@mozilla.com>
Tue, 26 Apr 2011 15:39:17 -0400
changeset 68613 0063bd47e5bae49c4d69aae4d3621f0f53c3dbc3
parent 68612 eb730526b7ca7e63efd9c548e8f246396b27fe1d
child 68614 cbe3defeccb7298fcc0c846b877fcc99146d7674
push id19683
push usermfinkle@mozilla.com
push dateTue, 26 Apr 2011 19:39:49 +0000
treeherdermozilla-central@cc24b61149ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs630398
milestone6.0a1
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
Bug 630398 - Session store doesn't save session data quickly enough [r=mbrubeck]
mobile/components/SessionStore.js
--- a/mobile/components/SessionStore.js
+++ b/mobile/components/SessionStore.js
@@ -156,17 +156,17 @@ SessionStore.prototype = {
           self.onWindowOpen(window);
           window.removeEventListener("load", arguments.callee, false);
         }, false);
         break;
       case "domwindowclosed": // catch closed windows
         this.onWindowClose(aSubject);
         break;
       case "browser-lastwindow-close-granted":
-        // Force and open timer to save state
+        // If a save has been queued, kill the timer and save state now
         if (this._saveTimer) {
           this._saveTimer.cancel();
           this._saveTimer = null;
           this.saveState();
         }
 
         // Freeze the data at what we've got (ignoring closing windows)
         this._loadState = STATE_QUITTING;
@@ -204,17 +204,17 @@ SessionStore.prototype = {
 
         observerService.removeObserver(this, "domwindowopened");
         observerService.removeObserver(this, "domwindowclosed");
         observerService.removeObserver(this, "browser-lastwindow-close-granted");
         observerService.removeObserver(this, "quit-application-requested");
         observerService.removeObserver(this, "quit-application-granted");
         observerService.removeObserver(this, "quit-application");
 
-        // Make sure to break our cycle with the save timer
+        // If a save has been queued, kill the timer and save state now
         if (this._saveTimer) {
           this._saveTimer.cancel();
           this._saveTimer = null;
           this.saveState();
         }
         break;
       case "browser:purge-session-history": // catch sanitization 
         this._clearDisk();
@@ -225,19 +225,18 @@ SessionStore.prototype = {
         if (this._loadState == STATE_QUITTING)
           return;
 
         // Clear all data about closed tabs
         for (let [ssid, win] in Iterator(this._windows))
           win.closedTabs = [];
 
         if (this._loadState == STATE_RUNNING) {
-          // The next delayed save request should execute immediately
-          this._lastSaveTime -= this._interval;
-          this.saveStateDelayed();
+          // Save the purged state immediately
+          this.saveStateNow();
         }
         break;
       case "timer-callback":
         // Timer call back for delayed saving
         this._saveTimer = null;
         this.saveState();
         break;
     }
@@ -330,24 +329,26 @@ SessionStore.prototype = {
     let tabs = aWindow.Browser.tabs;
     for (let i = 0; i < tabs.length; i++)
       this.onTabRemove(aWindow, tabs[i].browser, true);
 
     delete aWindow.__SSID;
   },
 
   onTabAdd: function ss_onTabAdd(aWindow, aBrowser, aNoNotification) {
+    aBrowser.messageManager.addMessageListener("DOMContentLoaded", this);
     aBrowser.messageManager.addMessageListener("pageshow", this);
 
     if (!aNoNotification)
       this.saveStateDelayed();
     this._updateCrashReportURL(aWindow);
   },
 
   onTabRemove: function ss_onTabRemove(aWindow, aBrowser, aNoNotification) {
+    aBrowser.messageManager.removeMessageListener("DOMContentLoaded", this);
     aBrowser.messageManager.removeMessageListener("pageshow", this);
 
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore)
       return;
 
     delete aBrowser.__SS_data;
 
@@ -375,17 +376,19 @@ SessionStore.prototype = {
   onTabLoad: function ss_onTabLoad(aWindow, aBrowser, aMessage) {
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore)
       return;
 
     delete aBrowser.__SS_data;
     this._collectTabData(aBrowser);
 
-    this.saveStateDelayed();
+    // Save out the state as quickly as possible
+    if (aMessage.name == "pageshow")
+      this.saveStateNow();
     this._updateCrashReportURL(aWindow);
   },
 
   onTabSelect: function ss_onTabSelect(aWindow, aBrowser) {
     if (this._loadState != STATE_RUNNING)
       return;
 
     let index = aWindow.Elements.browsers.selectedIndex;
@@ -416,16 +419,25 @@ SessionStore.prototype = {
         this._saveTimer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
       }
       else {
         this.saveState();
       }
     }
   },
 
+  saveStateNow: function ss_saveStateNow() {
+    // Kill any queued timer and save immediately
+    if (this._saveTimer) {
+      this._saveTimer.cancel();
+      this._saveTimer = null;
+    }
+    this.saveState();
+  },
+
   saveState: function ss_saveState() {
     let data = this._getCurrentState();
     this._writeFile(this._sessionFile, JSON.stringify(data));
 
     this._lastSaveTime = Date.now();
   },
 
   _getCurrentState: function ss_getCurrentState() {
@@ -507,37 +519,16 @@ SessionStore.prototype = {
     let istream = converter.convertToInputStream(aData);
     NetUtil.asyncCopy(istream, ostream, function(rc) {
       if (Components.isSuccessCode(rc)) {
         Services.obs.notifyObservers(null, "sessionstore-state-write-complete", "");
       }
     });
   },
 
-  _readFile: function ss_readFile(aFile) {
-    try {
-      let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
-      stream.init(aFile, 0x01, 0, 0);
-      let cvstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
-
-      let fileSize = stream.available();
-      cvstream.init(stream, "UTF-8", fileSize, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
-
-      let data = {};
-      cvstream.readString(fileSize, data);
-      let content = data.value;
-      cvstream.close();
-
-      return content.replace(/\r\n?/g, "\n");
-    } catch (ex) {
-      Cu.reportError(ex);
-    }
-    return null;
-  },
-
   _updateCrashReportURL: function ss_updateCrashReportURL(aWindow) {
 #ifdef MOZ_CRASH_REPORTER
     try {
       let currentURI = aWindow.Browser.selectedBrowser.currentURI.clone();
       // if the current URI contains a username/password, remove it
       try {
         currentURI.userPass = "";
       }
@@ -643,55 +634,87 @@ SessionStore.prototype = {
   restoreLastSession: function ss_restoreLastSession() {
     // The previous session data has already been renamed to the backup file
     let dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
     let session = dirService.get("ProfD", Ci.nsILocalFile);
     session.append("sessionstore.bak");
     if (!session.exists())
       return;
 
-    let data = JSON.parse(this._readFile(session));
-    if (!data || data.windows.length == 0) {
+    function notifyObservers() {
       Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
-      return;
     }
 
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
-
-    let selected = data.windows[0].selected;
-    let tabs = data.windows[0].tabs;
-    for (let i=0; i<tabs.length; i++) {
-      let tabData = tabs[i];
+    try {
+      let channel = NetUtil.newChannel(session);
+      channel.contentType = "application/json";
+      NetUtil.asyncFetch(channel, function(aStream, aResult) {
+        if (!Components.isSuccessCode(aResult)) {
+          Cu.reportError("SessionStore: Could not read from sessionstore.bak file");
+          notifyObservers();
+          return;
+        }
 
-      // Add a tab, but don't load the URL until we need to
-      let params = { getAttention: false, delayLoad: true };
-      if (i + 1 == selected)
-        params.delayLoad = false;
+        // Read session state file into a string and let observers modify the state before it's being used
+        let state = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+        state.data = NetUtil.readInputStreamToString(aStream, aStream.available()) || "";
+        aStream.close();
+
+        Services.obs.notifyObservers(state, "sessionstore-state-read", "");
 
-      // We must have selected tabs as soon as possible, so we let all tabs be selected
-      // until we get the real selected tab. Then we stop selecting tabs. The end result
-      // is that the right tab is selected, but we also don't get a bunch of errors
-      let bringToFront = (i + 1 <= selected);
-      let tab = window.Browser.addTab(tabData.entries[0].url, bringToFront, null, params);
+        let data = null;
+        try {
+          data = JSON.parse(state.data);
+        } catch (ex) {
+          Cu.reportError("SessionStore: Could not parse JSON: " + ex);
+        }
+
+        if (!data || data.windows.length == 0) {
+          notifyObservers();
+          return;
+        }
 
-      // Recreate the thumbnail if we are delay loading the tab
-      if (tabData.extData && params.delayLoad) {
-          let canvas = tab.chromeTab.thumbnail;
-          canvas.setAttribute("restored", "true");
-
-          let image = new window.Image();
-          image.onload = function() {
-            if (canvas)
-              canvas.getContext("2d").drawImage(image, 0, 0);
-          };
-          image.src = tabData.extData.thumbnail;
-      }
-
-      tab.browser.__SS_data = tabData;
-      tab.browser.__SS_extdata = tabData.extData;
-      tab.browser.__SS_restore = params.delayLoad;
+        let window = Services.wm.getMostRecentWindow("navigator:browser");
+    
+        let selected = data.windows[0].selected;
+        let tabs = data.windows[0].tabs;
+        for (let i=0; i<tabs.length; i++) {
+          let tabData = tabs[i];
+    
+          // Add a tab, but don't load the URL until we need to
+          let params = { getAttention: false, delayLoad: true };
+          if (i + 1 == selected)
+            params.delayLoad = false;
+    
+          // We must have selected tabs as soon as possible, so we let all tabs be selected
+          // until we get the real selected tab. Then we stop selecting tabs. The end result
+          // is that the right tab is selected, but we also don't get a bunch of errors
+          let bringToFront = (i + 1 <= selected);
+          let tab = window.Browser.addTab(tabData.entries[0].url, bringToFront, null, params);
+    
+          // Recreate the thumbnail if we are delay loading the tab
+          if (tabData.extData && params.delayLoad) {
+              let canvas = tab.chromeTab.thumbnail;
+              canvas.setAttribute("restored", "true");
+    
+              let image = new window.Image();
+              image.onload = function() {
+                if (canvas)
+                  canvas.getContext("2d").drawImage(image, 0, 0);
+              };
+              image.src = tabData.extData.thumbnail;
+          }
+    
+          tab.browser.__SS_data = tabData;
+          tab.browser.__SS_extdata = tabData.extData;
+          tab.browser.__SS_restore = params.delayLoad;
+        }
+    
+        notifyObservers();
+      });
+    } catch (ex) {
+      Cu.reportError("SessionStore: Could not read from sessionstore.bak file: " + ex);
+      notifyObservers();
     }
-
-    Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
   }
 };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStore]);