Bug 1437382 - Part 9 - Track number of outstanding "private tabs only" saveState calls. r?esawin draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Mon, 12 Feb 2018 22:15:35 +0100
changeset 761292 13ce5e430bd77130f3385994edeba91a959f5662
parent 761291 80841a51f5bc95a82debfe0dce5941ea75f3e65e
child 761293 ce36990aed01026cfea48381ff93094a1573d4e8
push id100929
push usermozilla@buttercookie.de
push dateWed, 28 Feb 2018 22:01:37 +0000
reviewersesawin
bugs1437382
milestone60.0a1
Bug 1437382 - Part 9 - Track number of outstanding "private tabs only" saveState calls. r?esawin Private tabs are saved in memory only by sending them to GeckoApp, so to speed up processing (compare part 3), we want to avoid writing out the full session store file for normal tabs as well if all outstanding saveState(Delayed) calls concerned private tabs only. To that effect, we slightly change the semantics of our pendingWrites counter and now increment it each time saveStateDelayed is called, even if the save timer is already running. This is because if e.g. a private tab update started the timer and then another saveStateDelayed call happens for a non-private tab, we need to change our plans and write the normal session store file after all as well. Tracking every saveStateDelayed call allows us to do this. Because writeFile only cares about the fact whether additional pending writes were queued while it was executing asynchronously or not, but not about the absolute amount of pending writes (if no additional writes were queued, the count is simply reset to 0), incrementing the pending writes count even when the timer is already running causes no ill effects. MozReview-Commit-ID: AjhIp8bpyf
mobile/android/components/SessionStore.js
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -62,16 +62,17 @@ SessionStore.prototype = {
 
   _windows: {},
   _lastSaveTime: 0,
   _lastBackupTime: 0,
   _interval: 10000,
   _backupInterval: 120000, // 2 minutes
   _maxTabsUndo: 5,
   _pendingWrite: 0,
+  _pendingWritePrivateOnly: 0,
   _scrollSavePending: null,
   _writeInProgress: false,
 
   // We only want to start doing backups if we've successfully
   // written the session data at least once.
   _sessionDataIsGood: false,
 
   // The index where the most recently closed tab was in the tabs array
@@ -346,17 +347,18 @@ SessionStore.prototype = {
       case "browser:purge-session-tabs":
       case "browser:purge-session-history": // catch sanitization
         this._purgeHistory(aTopic);
         break;
       case "timer-callback":
         if (this._loadState == STATE_RUNNING) {
           // Timer call back for delayed saving
           this._saveTimer = null;
-          log("timer-callback, pendingWrite = " + this._pendingWrite);
+          log("timer-callback, pendingWrite = " + this._pendingWritePrivateOnly +
+              "/" + this._pendingWrite);
           if (this._pendingWrite) {
             this._saveState(true);
           }
         }
         break;
       case "Session:NotifyLocationChange": {
         let browser = aSubject;
 
@@ -965,38 +967,47 @@ SessionStore.prototype = {
       Ci.nsIDOMWindowUtils).getContentViewerSize(width, height);
 
     displaySize.width = width.value;
     displaySize.height = height.value;
 
     return displaySize;
   },
 
-  saveStateDelayed: function ss_saveStateDelayed() {
+  saveStateDelayed: function ss_saveStateDelayed(aPrivateTabsOnly = false) {
+    this._pendingWrite++;
+    if (aPrivateTabsOnly) {
+      this._pendingWritePrivateOnly++;
+    }
+    log("incrementing _pendingWrite to " + this._pendingWritePrivateOnly +
+        "/" + this._pendingWrite);
     if (!this._saveTimer) {
       // Interval until the next disk operation is allowed
       let currentDelay = this._lastSaveTime + this._interval - Date.now();
 
       // If we have to wait, set a timer, otherwise saveState directly
       let delay = Math.max(currentDelay, MINIMUM_SAVE_DELAY);
       if (delay > 0) {
-        this._pendingWrite++;
         this._createTimer(delay);
       } else {
         log("saveStateDelayed() no delay");
         this.saveState();
       }
     } else {
       log("saveStateDelayed() timer already running, taking no action");
     }
   },
 
-  saveState: function ss_saveState() {
+  saveState: function ss_saveState(aPrivateTabsOnly = false) {
     this._pendingWrite++;
-    log("saveState(), incrementing _pendingWrite to " + this._pendingWrite);
+    if (aPrivateTabsOnly) {
+      this._pendingWritePrivateOnly++;
+    }
+    log("saveState(), incrementing _pendingWrite to " + this._pendingWritePrivateOnly +
+        "/" + this._pendingWrite);
     this._saveState(true);
   },
 
   /**
    * Immediately and synchronously writes any pending state to disk.
    *
    * @return True if data was written, false if no pending file writes were present.
    */
@@ -1007,18 +1018,17 @@ SessionStore.prototype = {
       return true;
     }
     return false;
   },
 
   _createTimer: function ss_createTimer(aDelay) {
     this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
-    log("saveTimer delay = " + aDelay +
-        ", incrementing _pendingWrite to " + this._pendingWrite);
+    log("saveTimer delay = " + aDelay);
   },
 
   _killTimer: function ss_killTimer() {
     if (this._saveTimer) {
       this._saveTimer.cancel();
       this._saveTimer = null;
       log("killed queued saveTimer");
     }
@@ -1081,16 +1091,24 @@ SessionStore.prototype = {
     let window = Services.wm.getMostRecentWindow("navigator:browser");
     if (window) { // can be null if we're restarting
       window.WindowEventDispatcher.sendRequest({
         type: "PrivateBrowsing:Data",
         session: (privateData.windows.length > 0 && privateData.windows[0].tabs.length > 0) ? JSON.stringify(privateData) : null
       });
     }
 
+    // If all queued writes were for private tabs only, we can stop here.
+    if (this._pendingWrite === this._pendingWritePrivateOnly) {
+      this._pendingWrite = 0;
+      this._pendingWritePrivateOnly = 0;
+      this._lastSaveTime = Date.now();
+      return;
+    }
+
     // Write only non-private data to disk
     if (normalData.windows[0] && normalData.windows[0].tabs) {
       log("_saveState() writing normal data, " +
            normalData.windows[0].tabs.length + " tabs in window[0]");
     } else {
       log("_saveState() writing empty normal data");
     }
     this._writeFile(this._sessionFile, this._sessionFileTemp, normalData, aAsync);
@@ -1194,16 +1212,17 @@ SessionStore.prototype = {
     this._write(aFile, aFileTemp, buffer, aAsync).then(() => {
       let stopWriteMs = Cu.now();
 
       // Make sure this._pendingWrite is the same value it was before we
       // fired off the async write. If the count is different, another write
       // is pending, so we shouldn't reset this._pendingWrite yet.
       if (pendingWrite === this._pendingWrite) {
         this._pendingWrite = 0;
+        this._pendingWritePrivateOnly = 0;
         this._writeInProgress = false;
       }
 
       log("_writeFile() _write() returned, _pendingWrite = " + this._pendingWrite);
 
       // We don't use a stopwatch here since the calls are async and stopwatches can only manage
       // a single timer per histogram.
       Services.telemetry.getHistogramById("FX_SESSION_RESTORE_WRITE_FILE_MS").add(Math.round(stopWriteMs - startWriteMs));