Bug 944694 - Part 2: Update SessionStore to use the new docshellID format, r=mikedeboer
authorMichael Layzell <michael@thelayzells.com>
Wed, 16 Nov 2016 16:43:20 -0500
changeset 323236 66686afdff45b44fe457b1d78c8a963f169a5a0e
parent 323235 048cb0a9f401b13f4787c77d5d5117938762f018
child 323237 fb4d2febca8d74ffbcf653f3e49f8598b876a0a9
push id21
push usermaklebus@msu.edu
push dateThu, 01 Dec 2016 06:22:08 +0000
reviewersmikedeboer
bugs944694
milestone53.0a1
Bug 944694 - Part 2: Update SessionStore to use the new docshellID format, r=mikedeboer MozReview-Commit-ID: 29te1YC9FNo
browser/components/sessionstore/SessionHistory.jsm
browser/components/sessionstore/test/browser.ini
browser/components/sessionstore/test/browser_docshell_uuid_consistency.js
mobile/android/components/SessionStore.js
--- a/browser/components/sessionstore/SessionHistory.jsm
+++ b/browser/components/sessionstore/SessionHistory.jsm
@@ -10,16 +10,18 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
+  "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
 
 function debug(msg) {
   Services.console.logStringMessage("SessionHistory: " + msg);
 }
 
 /**
  * The external API exported by this module.
  */
@@ -37,16 +39,21 @@ this.SessionHistory = Object.freeze({
   }
 });
 
 /**
  * The internal API for the SessionHistory module.
  */
 var SessionHistoryInternal = {
   /**
+   * Mapping from legacy docshellIDs to docshellUUIDs.
+   */
+  _docshellUUIDMap: new Map(),
+
+  /**
    * Returns whether the given docShell's session history is empty.
    *
    * @param docShell
    *        The docShell that owns the session history.
    */
   isEmpty: function (docShell) {
     let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
     let history = webNavigation.sessionHistory;
@@ -127,17 +134,17 @@ var SessionHistoryInternal = {
     let cacheKey = shEntry.cacheKey;
     if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
         cacheKey.data != 0) {
       // XXXbz would be better to have cache keys implement
       // nsISerializable or something.
       entry.cacheKey = cacheKey.data;
     }
     entry.ID = shEntry.ID;
-    entry.docshellID = shEntry.docshellID;
+    entry.docshellUUID = shEntry.docshellID.toString();
 
     // We will include the property only if it's truthy to save a couple of
     // bytes when the resulting object is stringified and saved to disk.
     if (shEntry.referrerURI) {
       entry.referrer = shEntry.referrerURI.spec;
       entry.referrerPolicy = shEntry.referrerPolicy;
     }
 
@@ -323,18 +330,32 @@ var SessionHistoryInternal = {
       if (!id) {
         for (id = Date.now(); id in idMap.used; id++);
         idMap[entry.ID] = id;
         idMap.used[id] = true;
       }
       shEntry.ID = id;
     }
 
-    if (entry.docshellID)
-      shEntry.docshellID = entry.docshellID;
+    // If we have the legacy docshellID on our entry, upgrade it to a
+    // docshellUUID by going through the mapping.
+    if (entry.docshellID) {
+      if (!this._docshellUUIDMap.has(entry.docshellID)) {
+        // Convert the nsID to a string so that the docshellUUID property
+        // is correctly stored as a string.
+        this._docshellUUIDMap.set(entry.docshellID,
+                                  uuidGenerator.generateUUID().toString());
+      }
+      entry.docshellUUID = this._docshellUUIDMap.get(entry.docshellID);
+      delete entry.docshellID;
+    }
+
+    if (entry.docshellUUID) {
+      shEntry.docshellID = Components.ID(entry.docshellUUID);
+    }
 
     if (entry.structuredCloneState && entry.structuredCloneVersion) {
       shEntry.stateData =
         Cc["@mozilla.org/docshell/structured-clone-container;1"].
         createInstance(Ci.nsIStructuredCloneContainer);
 
       shEntry.stateData.initFromBase64(entry.structuredCloneState,
                                        entry.structuredCloneVersion);
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -232,8 +232,9 @@ run-if = e10s
 [browser_remoteness_flip_on_restore.js]
 run-if = e10s
 [browser_background_tab_crash.js]
 run-if = e10s && crashreporter
 
 # Disabled on debug for frequent intermittent failures:
 [browser_undoCloseById.js]
 skip-if = debug
+[browser_docshell_uuid_consistency.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_docshell_uuid_consistency.js
@@ -0,0 +1,65 @@
+// First test - open a tab and duplicate it, using session restore to restore the history into the new tab.
+add_task(function* duplicateTab () {
+  const TEST_URL = "data:text/html,foo";
+  let tab = gBrowser.addTab(TEST_URL);
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+  yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+    let docshell = content.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsIWebNavigation)
+                                 .QueryInterface(Ci.nsIDocShell);
+    let shEntry = docshell.sessionHistory.getEntryAtIndex(0, false);
+    is(shEntry.docshellID.toString(), docshell.historyID.toString());
+  });
+
+  let tab2 = gBrowser.duplicateTab(tab);
+  yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+
+  yield ContentTask.spawn(tab2.linkedBrowser, null, function() {
+    let docshell = content.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsIWebNavigation)
+                                 .QueryInterface(Ci.nsIDocShell);
+    let shEntry = docshell.sessionHistory.getEntryAtIndex(0, false);
+    is(shEntry.docshellID.toString(), docshell.historyID.toString());
+  });
+
+  yield BrowserTestUtils.removeTab(tab);
+  yield BrowserTestUtils.removeTab(tab2);
+});
+
+// Second test - open a tab and navigate across processes, which triggers sessionrestore to persist history.
+add_task(function* contentToChromeNavigate() {
+  const TEST_URL = "data:text/html,foo";
+  let tab = gBrowser.addTab(TEST_URL);
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+  yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+    let docshell = content.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsIWebNavigation)
+                                 .QueryInterface(Ci.nsIDocShell);
+    let sh = docshell.sessionHistory;
+    is(sh.count, 1);
+    is(sh.getEntryAtIndex(0, false).docshellID.toString(), docshell.historyID.toString());
+  });
+
+  // Force the browser to navigate to the chrome process.
+  yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+    const CHROME_URL = "about:config";
+    let webnav = content.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIWebNavigation);
+    webnav.loadURI(CHROME_URL, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+  });
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+  // Check to be sure that we're in the chrome process.
+  let docShell = tab.linkedBrowser.frameLoader.docShell;
+
+  // 'cause we're in the chrome process, we can just directly poke at the shistory.
+  let sh = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
+
+  is(sh.count, 2);
+  is(sh.getEntryAtIndex(0, false).docshellID.toString(), docShell.historyID.toString());
+  is(sh.getEntryAtIndex(1, false).docshellID.toString(), docShell.historyID.toString());
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -18,16 +18,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition", "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/AndroidLog.jsm", "AndroidLog");
 XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences", "resource://gre/modules/SharedPreferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils", "resource://gre/modules/sessionstore/Utils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "serializationHelper",
                                    "@mozilla.org/network/serialization-helper;1",
                                    "nsISerializationHelper");
+XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 let loggingEnabled = false;
 
 function log(a) {
@@ -90,16 +93,19 @@ SessionStore.prototype = {
   // Whether or not to send notifications for changes to the closed tabs.
   _notifyClosedTabs: false,
 
   // If we're simultaneously closing both a tab and Firefox, we don't want
   // to bother reloading the newly selected tab if it is zombified.
   // The Java UI will tell us which tab to watch out for.
   _keepAsZombieTabId: -1,
 
+  // Mapping from legacy docshellIDs to docshellUUIDs.
+  _docshellUUIDMap: new Map(),
+
   init: function ss_init() {
     loggingEnabled = Services.prefs.getBoolPref("browser.sessionstore.debug_logging");
 
     // Get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
     this._sessionFilePrevious = this._sessionFile.clone();
     this._sessionFileTemp = this._sessionFile.clone();
@@ -1158,17 +1164,17 @@ SessionStore.prototype = {
     }
 
     let cacheKey = aEntry.cacheKey;
     if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0) {
       entry.cacheKey = cacheKey.data;
     }
 
     entry.ID = aEntry.ID;
-    entry.docshellID = aEntry.docshellID;
+    entry.docshellUUID = aEntry.docshellID.number;
 
     if (aEntry.referrerURI) {
       entry.referrer = aEntry.referrerURI.spec;
     }
 
     if (aEntry.originalURI) {
       entry.originalURI = aEntry.originalURI.spec;
     }
@@ -1293,18 +1299,31 @@ SessionStore.prototype = {
       if (!id) {
         for (id = Date.now(); id in aIdMap.used; id++);
         aIdMap[aEntry.ID] = id;
         aIdMap.used[id] = true;
       }
       shEntry.ID = id;
     }
 
+    // If we have the legacy docshellID on our aEntry, upgrade it to a
+    // docshellUUID by going through the mapping.
     if (aEntry.docshellID) {
-      shEntry.docshellID = aEntry.docshellID;
+      if (!this._docshellUUIDMap.has(aEntry.docshellID)) {
+        // Get the `.number` property out of the nsID such that the docshellUUID
+        // property is correctly stored as a string.
+        this._docshellUUIDMap.set(aEntry.docshellID,
+                                  uuidGenerator.generateUUID().number);
+      }
+      aEntry.docshellUUID = this._docshellUUIDMap.get(aEntry.docshellID);
+      delete aEntry.docshellID;
+    }
+
+    if (aEntry.docshellUUID) {
+      shEntry.docshellID = Components.ID(aEntry.docshellUUID);
     }
 
     if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
       shEntry.stateData =
         Cc["@mozilla.org/docshell/structured-clone-container;1"].
         createInstance(Ci.nsIStructuredCloneContainer);
 
       shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion);