Bug 1340828 - Part 3 - Switch Fennec's session store over to SessionHistory.jsm. r=sebastian
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 18 Feb 2017 23:35:48 +0100
changeset 346102 a9c30ad75f435f3e8f98df919d466c498bfe998d
parent 346101 bcea389b60b1c65f9b020a624234e01b92392a13
child 346103 adc6bebaff1d64fcdded2fb562c5cc0d804ba929
push id31459
push usercbook@mozilla.com
push dateTue, 07 Mar 2017 14:05:14 +0000
treeherdermozilla-central@1fb56ba248d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1340828
milestone54.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 1340828 - Part 3 - Switch Fennec's session store over to SessionHistory.jsm. r=sebastian Fennec's session store code was forked from Desktop a few years ago. Since then, the Desktop session store code has been refactored and modularised, which allows us to pull in again the bits the are compatible with our session store architecture and don't require any mobile-specific handling. The handling of session history is such a case - with the exception of the "wyciwyg" filtering, any differences between SessionHistory.jsm and our equivalent in sessionstore.js seems to stem from Desktop bugfixes that were never ported over to our copy of the code. Switching Fennec's session store over to use SessionHistory.jsm will prevent those sort of errors from occurring again in the future and hopefully simplify the maintenance of the session history code going forward. MozReview-Commit-ID: Hm0TWTwtPsI
mobile/android/components/SessionStore.js
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -16,23 +16,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher", "resource://gre/modules/Messaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel", "resource://gre/modules/sessionstore/PrivacyLevel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormData", "resource://gre/modules/FormData.jsm");
 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");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory", "resource://gre/modules/sessionstore/SessionHistory.jsm");
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 let loggingEnabled = false;
 
 function log(a) {
@@ -93,19 +87,16 @@ 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();
@@ -703,44 +694,48 @@ SessionStore.prototype = {
       return;
     }
 
     // Ignore a transient "about:blank"
     if (!aBrowser.canGoBack && aBrowser.currentURI.spec == "about:blank") {
       return;
     }
 
-    let history = aBrowser.sessionHistory;
+    // Serialize the tab's session history data.
+    let data = SessionHistory.collect(aBrowser.docShell);
+    if (!data.index) {
+      // Bail out if we couldn't collect even basic fallback data.
+      return;
+    }
 
-    // Serialize the tab data
-    let entries = [];
-    let index = history.index + 1;
-    for (let i = 0; i < history.count; i++) {
-      let historyEntry = history.getEntryAtIndex(i, false);
-      // Don't try to restore wyciwyg URLs
-      if (historyEntry.URI.schemeIs("wyciwyg")) {
-        // Adjust the index to account for skipped history entries
-        if (i <= history.index) {
-          index--;
+    // Filter out any top level "wyciwyg" entries that might have come through.
+    // Once we can figure out a GroupedSHistory-compatible way of doing this,
+    // we should move this into SessionHistory.jsm (see bug 1340874).
+    let historyIndex = data.index - 1;
+    for (let i = 0; i < data.entries.length; i++) {
+      if (data.entries[i].url.startsWith("wyciwyg")) {
+        // Adjust the index to account for skipped history entries.
+        if (i <= historyIndex) {
+          data.index--;
+          historyIndex--;
         }
-        continue;
+        data.entries.splice(i, 1);
+        i--;
       }
-      let entry = this._serializeHistoryEntry(historyEntry);
-      entries.push(entry);
     }
-    let data = { entries: entries, index: index };
 
     let formdata;
     let scrolldata;
     if (aBrowser.__SS_data) {
       formdata = aBrowser.__SS_data.formdata;
       scrolldata = aBrowser.__SS_data.scrolldata;
     }
     delete aBrowser.__SS_data;
 
+    // Collect the rest of the tab data and merge it with the history collected above.
     this._collectTabData(aWindow, aBrowser, data);
     if (aBrowser.__SS_restoreDataOnLoad || aBrowser.__SS_restoreDataOnPageshow) {
       // If the tab has been freshly restored and the "load" or "pageshow"
       // events haven't yet fired, we need to preserve any form data and
       // scroll positions that might have been present.
       aBrowser.__SS_data.formdata = formdata;
       aBrowser.__SS_data.scrolldata = scrolldata;
     } else {
@@ -1068,18 +1063,16 @@ SessionStore.prototype = {
   },
 
   _collectTabData: function ss__collectTabData(aWindow, aBrowser, aHistory) {
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore) {
       return;
     }
 
-    aHistory = aHistory || { entries: [{ url: aBrowser.currentURI.spec, title: aBrowser.contentTitle }], index: 1 };
-
     let tabData = {};
     let tab = aWindow.BrowserApp.getTabForBrowser(aBrowser);
     tabData.entries = aHistory.entries;
     tabData.index = aHistory.index;
     tabData.attributes = { image: aBrowser.mIconURL };
     tabData.desktopMode = tab.desktopMode;
     tabData.isPrivate = aBrowser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
     tabData.tabId = tab.id;
@@ -1219,282 +1212,16 @@ SessionStore.prototype = {
     } catch (ex) {
       // don't make noise when crashreporter is built but not enabled
       if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
         Cu.reportError("SessionStore:" + ex);
       }
     }
   },
 
-  /**
-   * Determines whether a given session history entry has been added dynamically.
-   */
-  isDynamic: function(aEntry) {
-    // aEntry.isDynamicallyAdded() is true for dynamically added
-    // <iframe> and <frameset>, but also for <html> (the root of the
-    // document) so we use aEntry.parent to ensure that we're not looking
-    // at the root of the document
-    return aEntry.parent && aEntry.isDynamicallyAdded();
-  },
-
-  /**
-  * Get an object that is a serialized representation of a History entry.
-  */
-  _serializeHistoryEntry: function _serializeHistoryEntry(aEntry) {
-    let entry = { url: aEntry.URI.spec };
-
-    if (aEntry.title && aEntry.title != entry.url) {
-      entry.title = aEntry.title;
-    }
-
-    if (!(aEntry instanceof Ci.nsISHEntry)) {
-      return entry;
-    }
-
-    let cacheKey = aEntry.cacheKey;
-    if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0) {
-      entry.cacheKey = cacheKey.data;
-    }
-
-    entry.ID = aEntry.ID;
-    entry.docshellUUID = aEntry.docshellID.number;
-
-    if (aEntry.referrerURI) {
-      entry.referrer = aEntry.referrerURI.spec;
-    }
-
-    if (aEntry.originalURI) {
-      entry.originalURI = aEntry.originalURI.spec;
-    }
-
-    if (aEntry.loadReplace) {
-      entry.loadReplace = aEntry.loadReplace;
-    }
-
-    if (aEntry.contentType) {
-      entry.contentType = aEntry.contentType;
-    }
-
-    if (aEntry.scrollRestorationIsManual) {
-      entry.scrollRestorationIsManual = true;
-    } else {
-      let x = {}, y = {};
-      aEntry.getScrollPosition(x, y);
-      if (x.value != 0 || y.value != 0) {
-        entry.scroll = x.value + "," + y.value;
-      }
-    }
-
-    // Collect triggeringPrincipal data for the current history entry.
-    // Please note that before Bug 1297338 there was no concept of a
-    // principalToInherit. To remain backward/forward compatible we
-    // serialize the principalToInherit as triggeringPrincipal_b64.
-    // Once principalToInherit is well established (within FF55)
-    // we can update this code, remove triggeringPrincipal_b64 and
-    // just keep triggeringPrincipal_base64 as well as
-    // principalToInherit_base64;  see Bug 1301666.
-    if (aEntry.principalToInherit) {
-      try {
-        let principalToInherit = Utils.serializePrincipal(aEntry.principalToInherit);
-        if (principalToInherit) {
-          entry.triggeringPrincipal_b64 = principalToInherit;
-          entry.principalToInherit_base64 = principalToInherit;
-        }
-      } catch (e) {
-        dump(e);
-      }
-    }
-
-    if (aEntry.triggeringPrincipal) {
-      try {
-        let triggeringPrincipal = Utils.serializePrincipal(aEntry.triggeringPrincipal);
-        if (triggeringPrincipal) {
-          entry.triggeringPrincipal_base64 = triggeringPrincipal;
-        }
-      } catch (e) {
-        dump(e);
-      }
-    }
-
-    entry.docIdentifier = aEntry.BFCacheEntry.ID;
-
-    if (aEntry.stateData != null) {
-      entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
-      entry.structuredCloneVersion = aEntry.stateData.formatVersion;
-    }
-
-    if (!(aEntry instanceof Ci.nsISHContainer)) {
-      return entry;
-    }
-
-    if (aEntry.childCount > 0) {
-      let children = [];
-      for (let i = 0; i < aEntry.childCount; i++) {
-        let child = aEntry.GetChildAt(i);
-
-        if (child && !this.isDynamic(child)) {
-          // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
-          if (child.URI.schemeIs("wyciwyg")) {
-            children = [];
-            break;
-          }
-          children.push(this._serializeHistoryEntry(child));
-        }
-      }
-
-      if (children.length) {
-        entry.children = children;
-      }
-    }
-
-    return entry;
-  },
-
-  _deserializeHistoryEntry: function _deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
-    let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry);
-
-    shEntry.setURI(Services.io.newURI(aEntry.url));
-    shEntry.setTitle(aEntry.title || aEntry.url);
-    if (aEntry.subframe) {
-      shEntry.setIsSubFrame(aEntry.subframe || false);
-    }
-    shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
-    if (aEntry.contentType) {
-      shEntry.contentType = aEntry.contentType;
-    }
-    if (aEntry.referrer) {
-      shEntry.referrerURI = Services.io.newURI(aEntry.referrer);
-    }
-
-    if (aEntry.originalURI) {
-      shEntry.originalURI =  Services.io.newURI(aEntry.originalURI);
-    }
-
-    if (aEntry.loadReplace) {
-      shEntry.loadReplace = aEntry.loadReplace;
-    }
-
-    if (aEntry.cacheKey) {
-      let cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32);
-      cacheKey.data = aEntry.cacheKey;
-      shEntry.cacheKey = cacheKey;
-    }
-
-    if (aEntry.ID) {
-      // get a new unique ID for this frame (since the one from the last
-      // start might already be in use)
-      let id = aIdMap[aEntry.ID] || 0;
-      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) {
-      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);
-    }
-
-    if (aEntry.scrollRestorationIsManual) {
-      shEntry.scrollRestorationIsManual = true;
-    } else if (aEntry.scroll) {
-      let scrollPos = aEntry.scroll.split(",");
-      scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
-      shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
-    }
-
-    let childDocIdents = {};
-    if (aEntry.docIdentifier) {
-      // If we have a serialized document identifier, try to find an SHEntry
-      // which matches that doc identifier and adopt that SHEntry's
-      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
-      // for the document identifier.
-      let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
-      if (!matchingEntry) {
-        matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
-        aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
-      } else {
-        shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
-        childDocIdents = matchingEntry.childDocIdents;
-      }
-    }
-
-    // Before introducing the concept of principalToInherit we only had
-    // a triggeringPrincipal within every entry which basically is the
-    // equivalent of the new principalToInherit. To avoid compatibility
-    // issues, we first check if the entry has entries for
-    // triggeringPrincipal_base64 and principalToInherit_base64. If not
-    // we fall back to using the principalToInherit (which is stored
-    // as triggeringPrincipal_b64) as the triggeringPrincipal and
-    // the principalToInherit.
-    // FF55 will remove the triggeringPrincipal_b64, see Bug 1301666.
-    if (aEntry.triggeringPrincipal_base64 || aEntry.principalToInherit_base64) {
-      if (aEntry.triggeringPrincipal_base64) {
-        shEntry.triggeringPrincipal =
-          Utils.deserializePrincipal(aEntry.triggeringPrincipal_base64);
-      }
-      if (aEntry.principalToInherit_base64) {
-        shEntry.principalToInherit =
-          Utils.deserializePrincipal(aEntry.principalToInherit_base64);
-      }
-    } else if (aEntry.triggeringPrincipal_b64) {
-      shEntry.triggeringPrincipal = Utils.deserializePrincipal(aEntry.triggeringPrincipal_b64);
-      shEntry.principalToInherit = shEntry.triggeringPrincipal;
-    }
-
-    if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
-      for (let i = 0; i < aEntry.children.length; i++) {
-        if (!aEntry.children[i].url) {
-          continue;
-        }
-
-        // We're getting sessionrestore.js files with a cycle in the
-        // doc-identifier graph, likely due to bug 698656.  (That is, we have
-        // an entry where doc identifier A is an ancestor of doc identifier B,
-        // and another entry where doc identifier B is an ancestor of A.)
-        //
-        // If we were to respect these doc identifiers, we'd create a cycle in
-        // the SHEntries themselves, which causes the docshell to loop forever
-        // when it looks for the root SHEntry.
-        //
-        // So as a hack to fix this, we restrict the scope of a doc identifier
-        // to be a node's siblings and cousins, and pass childDocIdents, not
-        // aDocIdents, to _deserializeHistoryEntry.  That is, we say that two
-        // SHEntries with the same doc identifier have the same document iff
-        // they have the same parent or their parents have the same document.
-
-        shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i);
-      }
-    }
-
-    return shEntry;
-  },
-
   // This function iterates through a list of urls opening a new tab for each.
   _openTabs: function ss_openTabs(aData) {
     let window = Services.wm.getMostRecentWindow("navigator:browser");
     for (let i = 0; i < aData.urls.length; i++) {
       let url = aData.urls[i];
       let params = {
         selected: (i == aData.urls.length - 1),
         isPrivate: false,
@@ -1532,60 +1259,43 @@ SessionStore.prototype = {
   */
   _restoreTab: function ss_restoreTab(aTabData, aBrowser) {
     // aTabData shouldn't be empty here, but if it is,
     // _restoreHistory() will crash otherwise.
     if (!aTabData || aTabData.entries.length == 0) {
       Cu.reportError("SessionStore.js: Error trying to restore tab with empty tabdata");
       return;
     }
-    this._restoreHistory(aTabData, aBrowser.sessionHistory);
+    this._restoreHistory(aBrowser.docShell, aTabData);
 
     // Various bits of state can only be restored if page loading has progressed far enough:
     // The MobileViewportManager needs to be told as early as possible about
     // our desired zoom level so it can take it into account during the
     // initial document resolution calculation.
     aBrowser.__SS_restoreDataOnLocationChange = true;
     // Restoring saved form data requires the input fields to be available,
     // so we have to wait for the content to load.
     aBrowser.__SS_restoreDataOnLoad = true;
     // Restoring the scroll position depends on the document resolution having been set,
     // which is only guaranteed to have happened *after* we receive the load event.
     aBrowser.__SS_restoreDataOnPageshow = true;
   },
 
   /**
-  * Takes serialized history data and create news entries into the given
+  * A thin wrapper around SessionHistory.jsm's restore function, which
+  * takes serialized history data and restores it into the given
   * nsISessionHistory object.
   */
-  _restoreHistory: function ss_restoreHistory(aTabData, aHistory) {
-    if (aHistory.count > 0) {
-      aHistory.PurgeHistory(aHistory.count);
-    }
-    aHistory.QueryInterface(Ci.nsISHistoryInternal);
-
-    // Helper hashes for ensuring unique frame IDs and unique document
-    // identifiers.
-    let idMap = { used: {} };
-    let docIdentMap = {};
+  _restoreHistory: function ss_restoreHistory(aDocShell, aTabData) {
+    let history = SessionHistory.restore(aDocShell, aTabData);
 
-    for (let i = 0; i < aTabData.entries.length; i++) {
-      if (!aTabData.entries[i].url) {
-        continue;
-      }
-      aHistory.addEntry(this._deserializeHistoryEntry(aTabData.entries[i], idMap, docIdentMap), true);
-    }
-
-    // We need to force set the active history item and cause it to reload since
-    // we stop the load above
-    let activeIndex = (aTabData.index || aTabData.entries.length) - 1;
-    aHistory.getEntryAtIndex(activeIndex, true);
-
+    // SessionHistory.jsm will have force set the active history item,
+    // but we still need to reload it in order to finish the process.
     try {
-      aHistory.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
+      history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
     } catch (e) {
       // This will throw if the current entry is an error page.
     }
   },
 
   /**
   * Takes serialized form text data and restores it into the given browser.
   */