Bug 1340828 - Switch Fennec's session store over to SessionHistory.jsm. r?sebastian draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sat, 18 Feb 2017 23:35:48 +0100
changeset 486578 5415213d7f4cbc238e20cc5bad1e1c3393e1f6bf
parent 486577 f37430b6297a0b38e99f80da2dafe0eafd754da9
child 546289 2063bda787fbf20a22bff3a2d29b421e1bfb2a4e
push id46029
push usermozilla@buttercookie.de
push dateSat, 18 Feb 2017 22:38:44 +0000
reviewerssebastian
bugs1340828
milestone54.0a1
Bug 1340828 - 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 anything handled in part 2, 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 will 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();
@@ -676,44 +667,28 @@ SessionStore.prototype = {
       return;
     }
 
     // Ignore a transient "about:blank"
     if (!aBrowser.canGoBack && aBrowser.currentURI.spec == "about:blank") {
       return;
     }
 
-    let history = aBrowser.sessionHistory;
-
-    // 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--;
-        }
-        continue;
-      }
-      let entry = this._serializeHistoryEntry(historyEntry);
-      entries.push(entry);
-    }
-    let data = { entries: entries, index: index };
+    // Serialize the tab session history data.
+    let data = SessionHistory.collect(aBrowser.docShell);
 
     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 {
@@ -1192,290 +1167,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;
-      }
-    }
-
-    // The field aEntry.owner_b64 got renamed to aEntry.triggeringPricipal_b64 in
-    // Bug 1286472. To remain backward compatible we still have to support that
-    // field for a few cycles before we can remove it within Bug 1289785.
-    if (aEntry.owner_b64) {
-      aEntry.triggeringPricipal_b64 = aEntry.owner_b64;
-      delete aEntry.owner_b64;
-    }
-
-    // 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,
@@ -1513,60 +1214,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 create new entries 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 restoration.
     try {
-      aHistory.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
+      history.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.
   */