Bug 930269 - Create SessionHistory.restore (r=smacleod)

This commit is contained in:
Bill McCloskey 2013-11-12 15:02:53 -08:00
parent d930fb07c8
commit c936cdc7dd
2 changed files with 171 additions and 145 deletions

View File

@ -15,6 +15,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
function debug(msg) {
Services.console.logStringMessage("SessionHistory: " + msg);
@ -38,6 +40,10 @@ XPCOMUtils.defineLazyGetter(this, "gPostData", function () {
this.SessionHistory = Object.freeze({
collect: function (docShell, includePrivateData) {
return SessionHistoryInternal.collect(docShell, includePrivateData);
},
restore: function (docShell, tabData) {
SessionHistoryInternal.restore(docShell, tabData);
}
});
@ -274,5 +280,166 @@ let SessionHistoryInternal = {
// is guaranteed to handle all chars in strings, including embedded
// nulls.
return btoa(String.fromCharCode.apply(null, ownerBytes));
}
},
/**
* Restores session history data for a given docShell.
*
* @param docShell
* The docShell that owns the session history.
* @param tabData
* The tabdata including all history entries.
*/
restore: function (docShell, tabData) {
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
let history = webNavigation.sessionHistory;
if (history.count > 0) {
history.PurgeHistory(history.count);
}
history.QueryInterface(Ci.nsISHistoryInternal);
let idMap = { used: {} };
let docIdentMap = {};
for (let i = 0; i < tabData.entries.length; i++) {
//XXXzpao Wallpaper patch for bug 514751
if (!tabData.entries[i].url)
continue;
history.addEntry(this.deserializeEntry(tabData.entries[i],
idMap, docIdentMap), true);
}
},
/**
* Expands serialized history data into a session-history-entry instance.
*
* @param entry
* Object containing serialized history data for a URL
* @param idMap
* Hash for ensuring unique frame IDs
* @param docIdentMap
* Hash to ensure reuse of BFCache entries
* @returns nsISHEntry
*/
deserializeEntry: function (entry, idMap, docIdentMap) {
var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
createInstance(Ci.nsISHEntry);
shEntry.setURI(Utils.makeURI(entry.url));
shEntry.setTitle(entry.title || entry.url);
if (entry.subframe)
shEntry.setIsSubFrame(entry.subframe || false);
shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
if (entry.contentType)
shEntry.contentType = entry.contentType;
if (entry.referrer)
shEntry.referrerURI = Utils.makeURI(entry.referrer);
if (entry.isSrcdocEntry)
shEntry.srcdocData = entry.srcdocData;
if (entry.cacheKey) {
var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
createInstance(Ci.nsISupportsPRUint32);
cacheKey.data = entry.cacheKey;
shEntry.cacheKey = cacheKey;
}
if (entry.ID) {
// get a new unique ID for this frame (since the one from the last
// start might already be in use)
var id = idMap[entry.ID] || 0;
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 (entry.structuredCloneState && entry.structuredCloneVersion) {
shEntry.stateData =
Cc["@mozilla.org/docshell/structured-clone-container;1"].
createInstance(Ci.nsIStructuredCloneContainer);
shEntry.stateData.initFromBase64(entry.structuredCloneState,
entry.structuredCloneVersion);
}
if (entry.scroll) {
var scrollPos = (entry.scroll || "0,0").split(",");
scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
}
if (entry.postdata_b64) {
var postdata = atob(entry.postdata_b64);
var stream = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
stream.setData(postdata, postdata.length);
shEntry.postData = stream;
}
let childDocIdents = {};
if (entry.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 = docIdentMap[entry.docIdentifier];
if (!matchingEntry) {
matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
docIdentMap[entry.docIdentifier] = matchingEntry;
}
else {
shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
childDocIdents = matchingEntry.childDocIdents;
}
}
if (entry.owner_b64) {
var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
var binaryData = atob(entry.owner_b64);
ownerInput.setData(binaryData, binaryData.length);
var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIObjectInputStream);
binaryStream.setInputStream(ownerInput);
try { // Catch possible deserialization exceptions
shEntry.owner = binaryStream.readObject(true);
} catch (ex) { debug(ex); }
}
if (entry.children && shEntry instanceof Ci.nsISHContainer) {
for (var i = 0; i < entry.children.length; i++) {
//XXXzpao Wallpaper patch for bug 514751
if (!entry.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.deserializeEntry(entry.children[i], idMap,
childDocIdents), i);
}
}
return shEntry;
},
};

View File

@ -117,6 +117,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TextAndScrollData",
"resource:///modules/sessionstore/TextAndScrollData.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
"resource:///modules/sessionstore/SessionHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabState",
@ -2663,23 +2665,10 @@ let SessionStoreInternal = {
let browser = tab.linkedBrowser;
let history = browser.webNavigation.sessionHistory;
if (history.count > 0) {
history.PurgeHistory(history.count);
}
history.QueryInterface(Ci.nsISHistoryInternal);
browser.__SS_shistoryListener = new SessionStoreSHistoryListener(tab);
history.addSHistoryListener(browser.__SS_shistoryListener);
let idMap = { used: {} };
let docIdentMap = {};
for (var i = 0; i < tabData.entries.length; i++) {
//XXXzpao Wallpaper patch for bug 514751
if (!tabData.entries[i].url)
continue;
history.addEntry(this._deserializeHistoryEntry(tabData.entries[i],
idMap, docIdentMap), true);
}
SessionHistory.restore(browser.docShell, tabData);
// make sure to reset the capabilities and attributes, in case this tab gets reused
let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
@ -2831,136 +2820,6 @@ let SessionStoreInternal = {
}
},
/**
* expands serialized history data into a session-history-entry instance
* @param aEntry
* Object containing serialized history data for a URL
* @param aIdMap
* Hash for ensuring unique frame IDs
* @returns nsISHEntry
*/
_deserializeHistoryEntry:
function ssi_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
createInstance(Ci.nsISHEntry);
shEntry.setURI(Utils.makeURI(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 = Utils.makeURI(aEntry.referrer);
if (aEntry.isSrcdocEntry)
shEntry.srcdocData = aEntry.srcdocData;
if (aEntry.cacheKey) {
var 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)
var 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 (aEntry.docshellID)
shEntry.docshellID = aEntry.docshellID;
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.scroll) {
var scrollPos = (aEntry.scroll || "0,0").split(",");
scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
}
if (aEntry.postdata_b64) {
var postdata = atob(aEntry.postdata_b64);
var stream = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
stream.setData(postdata, postdata.length);
shEntry.postData = stream;
}
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;
}
}
if (aEntry.owner_b64) {
var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
var binaryData = atob(aEntry.owner_b64);
ownerInput.setData(binaryData, binaryData.length);
var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIObjectInputStream);
binaryStream.setInputStream(ownerInput);
try { // Catch possible deserialization exceptions
shEntry.owner = binaryStream.readObject(true);
} catch (ex) { debug(ex); }
}
if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
for (var i = 0; i < aEntry.children.length; i++) {
//XXXzpao Wallpaper patch for bug 514751
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;
},
/**
* Accumulates a list of frames that need to be restored for the
* given browser element. A frame is only restored if its current