Bug 1310771 - Part 3: Add support to SessionStore for recording history for GroupedSHistories, r=mikedeboer
authorMichael Layzell <michael@thelayzells.com>
Wed, 09 Nov 2016 14:52:19 -0500
changeset 370759 03a2935834730946245667e944bbeff337d7eaae
parent 370758 04f861d4b2fe0b5b7c46b285be6767ef67b52113
child 370760 5fa643642a67be8401eb96e52ef170ab3946ed31
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1310771
milestone53.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 1310771 - Part 3: Add support to SessionStore for recording history for GroupedSHistories, r=mikedeboer MozReview-Commit-ID: Ffq7h3zRUm3
browser/components/sessionstore/SessionHistory.jsm
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/TabStateCache.jsm
browser/components/sessionstore/content/content-sessionStore.js
--- a/browser/components/sessionstore/SessionHistory.jsm
+++ b/browser/components/sessionstore/SessionHistory.jsm
@@ -25,18 +25,18 @@ function debug(msg) {
 /**
  * The external API exported by this module.
  */
 this.SessionHistory = Object.freeze({
   isEmpty: function (docShell) {
     return SessionHistoryInternal.isEmpty(docShell);
   },
 
-  collect: function (docShell) {
-    return SessionHistoryInternal.collect(docShell);
+  collect: function (docShell, aFromIdx = -1) {
+    return SessionHistoryInternal.collect(docShell, aFromIdx);
   },
 
   restore: function (docShell, tabData) {
     SessionHistoryInternal.restore(docShell, tabData);
   }
 });
 
 /**
@@ -64,21 +64,25 @@ var SessionHistoryInternal = {
     return uri == "about:blank" && history.count == 0;
   },
 
   /**
    * Collects session history data for a given docShell.
    *
    * @param docShell
    *        The docShell that owns the session history.
+   * @param aFromIdx
+   *        The starting local index to collect the history from.
+   * @return An object reprereseting a partial global history update.
    */
-  collect: function (docShell) {
+  collect: function (docShell, aFromIdx = -1) {
     let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
     let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
     let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+    let ihistory = history.QueryInterface(Ci.nsISHistory);
 
     let data = {entries: [], userContextId: loadContext.originAttributes.userContextId };
 
     if (history && history.count > 0) {
       // Loop over the transaction linked list directly so we can get the
       // persist property for each transaction.
       for (let txn = history.rootTransaction; txn; txn = txn.next) {
         let entry = this.serializeEntry(txn.sHEntry);
@@ -102,16 +106,33 @@ var SessionHistoryInternal = {
       // record it. For about:blank we explicitly want an empty array without
       // an 'index' property to denote that there are no history entries.
       if (uri != "about:blank" || (body && body.hasChildNodes())) {
         data.entries.push({ url: uri });
         data.index = 1;
       }
     }
 
+    // Check if we should discard some of the entries which didn't change
+    if (aFromIdx > -1) {
+      data.entries.splice(0, aFromIdx + 1);
+    }
+
+    // Transform the entries from local to global index space.
+    data.index += ihistory.globalIndexOffset;
+    data.fromIdx = aFromIdx + ihistory.globalIndexOffset;
+
+    // If we are not the most recent partialSHistory in our groupedSHistory, we
+    // need to make certain that we don't replace the entries from the following
+    // SHistories - so we replace only the number of entries which our SHistory
+    // takes up.
+    if (ihistory.globalIndexOffset + ihistory.count < ihistory.globalCount) {
+      data.toIdx = data.fromIdx + ihistory.count;
+    }
+
     return data;
   },
 
   /**
    * Get an object that is a serialized representation of a History entry.
    *
    * @param shEntry
    *        nsISHEntry instance
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -966,16 +966,28 @@ var SessionStoreInternal = {
         if (target.namespaceURI == NS_XUL &&
             target.localName == "browser" &&
             target.frameLoader &&
             target.permanentKey) {
           this._lastKnownFrameLoader.set(target.permanentKey, target.frameLoader);
           this.resetEpoch(target);
         }
         break;
+      case "BrowserWillChangeProcess":
+        let promise = TabStateFlusher.flush(target);
+        target.frameLoader.addProcessChangeBlockingPromise(promise);
+        break;
+      case "BrowserChangedProcess":
+        let newEpoch = 1 + Math.max(this.getCurrentEpoch(target),
+                                    this.getCurrentEpoch(aEvent.otherBrowser));
+        this.setCurrentEpoch(target, newEpoch);
+        target.messageManager.sendAsyncMessage("SessionStore:becomeActiveProcess", {
+          epoch: newEpoch
+        });
+        break;
       default:
         throw new Error(`unhandled event ${aEvent.type}?`);
     }
     this._clearRestoringWindows();
   },
 
   /**
    * Generate a unique window identifier
@@ -1032,16 +1044,18 @@ var SessionStoreInternal = {
     }
     // notification of tab add/remove/selection/show/hide
     TAB_EVENTS.forEach(function(aEvent) {
       tabbrowser.tabContainer.addEventListener(aEvent, this, true);
     }, this);
 
     // Keep track of a browser's latest frameLoader.
     aWindow.gBrowser.addEventListener("XULFrameLoaderCreated", this);
+    aWindow.gBrowser.addEventListener("BrowserChangedProcess", this);
+    aWindow.gBrowser.addEventListener("BrowserWillChangeProcess", this);
   },
 
   /**
    * Initializes a given window.
    *
    * Windows are registered as soon as they are created but we need to wait for
    * the session file to load, and the initial window's delayed startup to
    * finish before initializing a window, i.e. restoring data into it.
@@ -1286,16 +1300,18 @@ var SessionStoreInternal = {
 
     let browsers = Array.from(tabbrowser.browsers);
 
     TAB_EVENTS.forEach(function(aEvent) {
       tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
     }, this);
 
     aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
+    aWindow.gBrowser.removeEventListener("BrowserChangedProcess", this);
+    aWindow.gBrowser.removeEventListener("BrowserWillChangeProcess", this);
 
     let winData = this._windows[aWindow.__SSi];
 
     // Collect window data only when *not* closed during shutdown.
     if (RunState.isRunning) {
       // Grab the most recent window data. The tab data will be updated
       // once we finish flushing all of the messages from the tabs.
       let tabMap = this._collectWindowData(aWindow);
@@ -4408,16 +4424,24 @@ var SessionStoreInternal = {
    */
   startNextEpoch(browser) {
     let next = this.getCurrentEpoch(browser) + 1;
     this._browserEpochs.set(browser.permanentKey, next);
     return next;
   },
 
   /**
+   * Manually set the epoch to a given value.
+   */
+  setCurrentEpoch(aBrowser, aEpoch) {
+    this._browserEpochs.set(aBrowser.permanentKey, aEpoch);
+    return aEpoch;
+  },
+
+  /**
    * Returns the current epoch for the given <browser>. If we haven't assigned
    * a new epoch this will default to zero for new tabs.
    */
   getCurrentEpoch(browser) {
     return this._browserEpochs.get(browser.permanentKey) || 0;
   },
 
   /**
--- a/browser/components/sessionstore/TabStateCache.jsm
+++ b/browser/components/sessionstore/TabStateCache.jsm
@@ -108,25 +108,29 @@ var TabStateCacheInternal = {
   updatePartialHistoryChange: function (data, change) {
     const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
 
     if (!data.history) {
       data.history = { entries: [] };
     }
 
     let history = data.history;
+    let toIdx = history.entries.length;
+    if ("toIdx" in change) {
+      toIdx = Math.min(toIdx, change.toIdx + 1);
+    }
+
     for (let key of Object.keys(change)) {
       if (key == "entries") {
         if (change.fromIdx != kLastIndex) {
-          history.entries.splice(change.fromIdx + 1);
-          while (change.entries.length) {
-            history.entries.push(change.entries.shift());
-          }
+          let start = change.fromIdx + 1;
+          history.entries.splice.apply(
+            history.entries, [start, toIdx - start].concat(change.entries));
         }
-      } else if (key != "fromIndex") {
+      } else if (key != "fromIdx" && key != "toIdx") {
         history[key] = change[key];
       }
     }
   },
 
   /**
    * Updates cached data for a given |tab| or associated |browser|.
    *
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -109,16 +109,17 @@ var EventListener = {
  */
 var MessageListener = {
 
   MESSAGES: [
     "SessionStore:restoreHistory",
     "SessionStore:restoreTabContent",
     "SessionStore:resetRestore",
     "SessionStore:flush",
+    "SessionStore:becomeActiveProcess",
   ],
 
   init: function () {
     this.MESSAGES.forEach(m => addMessageListener(m, this));
   },
 
   receiveMessage: function ({name, data}) {
     // The docShell might be gone. Don't process messages,
@@ -143,16 +144,28 @@ var MessageListener = {
         this.restoreTabContent(data);
         break;
       case "SessionStore:resetRestore":
         gContentRestore.resetRestore();
         break;
       case "SessionStore:flush":
         this.flush(data);
         break;
+      case "SessionStore:becomeActiveProcess":
+        let shistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
+        // Check if we are at the end of the current session history, if we are,
+        // it is safe for us to collect and transmit our session history, so
+        // transmit all of it. Otherwise, we only want to transmit our index changes,
+        // so collect from kLastIndex.
+        if (shistory.globalCount - shistory.globalIndexOffset == shistory.count) {
+          SessionHistoryListener.collect();
+        } else {
+          SessionHistoryListener.collectFrom(kLastIndex);
+        }
+        break;
       default:
         debug("received unknown message '" + name + "'");
         break;
     }
   },
 
   restoreHistory({epoch, tabData, loadArguments, isRemotenessUpdate}) {
     gContentRestore.restoreHistory(tabData, loadArguments, {
@@ -250,19 +263,22 @@ var SessionHistoryListener = {
   uninit: function () {
     let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
     if (sessionHistory) {
       sessionHistory.removeSHistoryListener(this);
     }
   },
 
   collect: function () {
-    this._fromIdx = kNoIndex;
+    // We want to send down a historychange even for full collects in case our
+    // session history is a partial session history, in which case we don't have
+    // enough information for a full update. collectFrom(-1) tells the collect
+    // function to collect all data avaliable in this process.
     if (docShell) {
-      MessageQueue.push("history", () => SessionHistory.collect(docShell));
+      this.collectFrom(-1);
     }
   },
 
   _fromIdx: kNoIndex,
 
   // History can grow relatively big with the nested elements, so if we don't have to, we
   // don't want to send the entire history all the time. For a simple optimization
   // we keep track of the smallest index from after any change has occured and we just send
@@ -281,25 +297,17 @@ var SessionHistoryListener = {
     }
 
     this._fromIdx = idx;
     MessageQueue.push("historychange", () => {
       if (this._fromIdx === kNoIndex) {
         return null;
       }
 
-      let history = SessionHistory.collect(docShell);
-      if (kLastIndex == idx) {
-        history.entries = [];
-      } else {
-        history.entries.splice(0, this._fromIdx + 1);
-      }
-
-      history.fromIdx = this._fromIdx;
-
+      let history = SessionHistory.collect(docShell, this._fromIdx);
       this._fromIdx = kNoIndex;
       return history;
     });
   },
 
   handleEvent(event) {
     this.collect();
   },