Mypal/application/basilisk/components/sessionstore/SessionHistory.jsm

431 lines
14 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["SessionHistory"];
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");
function debug(msg) {
Services.console.logStringMessage("SessionHistory: " + 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);
},
restore: function (docShell, tabData) {
SessionHistoryInternal.restore(docShell, tabData);
}
});
/**
* The internal API for the SessionHistory module.
*/
var SessionHistoryInternal = {
/**
* 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;
if (!webNavigation.currentURI) {
return true;
}
let uri = webNavigation.currentURI.spec;
return uri == "about:blank" && history.count == 0;
},
/**
* Collects session history data for a given docShell.
*
* @param docShell
* The docShell that owns the session history.
*/
collect: function (docShell) {
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
let data = {entries: []};
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);
entry.persist = txn.persist;
data.entries.push(entry);
}
// Ensure the index isn't out of bounds if an exception was thrown above.
data.index = Math.min(history.index + 1, data.entries.length);
}
// If either the session history isn't available yet or doesn't have any
// valid entries, make sure we at least include the current page.
if (data.entries.length == 0) {
let uri = webNavigation.currentURI.spec;
let body = webNavigation.document.body;
// We landed here because the history is inaccessible or there are no
// history entries. In that case we should at least record the docShell's
// current URL as a single history entry. If the URL is not about:blank
// or it's a blank tab that was modified (like a custom newtab page),
// 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,
triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL
});
data.index = 1;
}
}
return data;
},
/**
* Get an object that is a serialized representation of a History entry.
*
* @param shEntry
* nsISHEntry instance
* @return object
*/
serializeEntry: function (shEntry) {
let entry = { url: shEntry.URI.spec };
// Save some bytes and don't include the title property
// if that's identical to the current entry's URL.
if (shEntry.title && shEntry.title != entry.url) {
entry.title = shEntry.title;
}
if (shEntry.isSubFrame) {
entry.subframe = true;
}
entry.charset = shEntry.URI.originCharset;
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;
// 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;
}
if (shEntry.originalURI) {
entry.originalURI = shEntry.originalURI.spec;
}
if (shEntry.loadReplace) {
entry.loadReplace = shEntry.loadReplace;
}
if (shEntry.srcdocData)
entry.srcdocData = shEntry.srcdocData;
if (shEntry.isSrcdocEntry)
entry.isSrcdocEntry = shEntry.isSrcdocEntry;
if (shEntry.baseURI)
entry.baseURI = shEntry.baseURI.spec;
if (shEntry.contentType)
entry.contentType = shEntry.contentType;
if (shEntry.scrollRestorationIsManual) {
entry.scrollRestorationIsManual = true;
} else {
let x = {}, y = {};
shEntry.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 (shEntry.principalToInherit) {
try {
let principalToInherit = Utils.serializePrincipal(shEntry.principalToInherit);
if (principalToInherit) {
entry.triggeringPrincipal_b64 = principalToInherit;
entry.principalToInherit_base64 = principalToInherit;
}
} catch (e) {
debug(e);
}
}
if (shEntry.triggeringPrincipal) {
try {
let triggeringPrincipal = Utils.serializePrincipal(shEntry.triggeringPrincipal);
if (triggeringPrincipal) {
entry.triggeringPrincipal_base64 = triggeringPrincipal;
}
} catch (e) {
debug(e);
}
}
entry.docIdentifier = shEntry.BFCacheEntry.ID;
if (shEntry.stateData != null) {
entry.structuredCloneState = shEntry.stateData.getDataAsBase64();
entry.structuredCloneVersion = shEntry.stateData.formatVersion;
}
if (!(shEntry instanceof Ci.nsISHContainer)) {
return entry;
}
if (shEntry.childCount > 0 && !shEntry.hasDynamicallyAddedChild()) {
let children = [];
for (let i = 0; i < shEntry.childCount; i++) {
let child = shEntry.GetChildAt(i);
if (child) {
// Don't try to restore framesets containing wyciwyg URLs.
// (cf. bug 424689 and bug 450595)
if (child.URI.schemeIs("wyciwyg")) {
children.length = 0;
break;
}
children.push(this.serializeEntry(child));
}
}
if (children.length) {
entry.children = children;
}
}
return entry;
},
/**
* 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++) {
let entry = tabData.entries[i];
//XXXzpao Wallpaper patch for bug 514751
if (!entry.url)
continue;
let persist = "persist" in entry ? entry.persist : true;
history.addEntry(this.deserializeEntry(entry, idMap, docIdentMap), persist);
}
// Select the right history entry.
let index = tabData.index - 1;
if (index < history.count && history.index != index) {
history.getEntryAtIndex(index, 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, entry.charset));
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);
shEntry.referrerPolicy = entry.referrerPolicy;
}
if (entry.originalURI) {
shEntry.originalURI = Utils.makeURI(entry.originalURI);
}
if (entry.loadReplace) {
shEntry.loadReplace = entry.loadReplace;
}
if (entry.isSrcdocEntry)
shEntry.srcdocData = entry.srcdocData;
if (entry.baseURI)
shEntry.baseURI = Utils.makeURI(entry.baseURI);
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.scrollRestorationIsManual) {
shEntry.scrollRestorationIsManual = true;
} else 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]);
}
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;
}
}
// The field entry.owner_b64 got renamed to entry.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 (entry.owner_b64) {
entry.triggeringPricipal_b64 = entry.owner_b64;
delete entry.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 (entry.triggeringPrincipal_base64 || entry.principalToInherit_base64) {
if (entry.triggeringPrincipal_base64) {
shEntry.triggeringPrincipal =
Utils.deserializePrincipal(entry.triggeringPrincipal_base64);
}
if (entry.principalToInherit_base64) {
shEntry.principalToInherit =
Utils.deserializePrincipal(entry.principalToInherit_base64);
}
} else if (entry.triggeringPrincipal_b64) {
shEntry.triggeringPrincipal = Utils.deserializePrincipal(entry.triggeringPrincipal_b64);
shEntry.principalToInherit = shEntry.triggeringPrincipal;
}
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;
},
};