mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 02:05:42 +00:00
Bug 894595 - part 1.6 - Move session history collection into a module; r=yoric
This commit is contained in:
parent
1f68f49d38
commit
548fded61a
272
browser/components/sessionstore/src/SessionHistory.jsm
Normal file
272
browser/components/sessionstore/src/SessionHistory.jsm
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
/* 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, "PrivacyLevel",
|
||||||
|
"resource:///modules/sessionstore/PrivacyLevel.jsm");
|
||||||
|
|
||||||
|
function debug(msg) {
|
||||||
|
Services.console.logStringMessage("SessionHistory: " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The preference value that determines how much post data to save.
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "gPostData", function () {
|
||||||
|
const PREF = "browser.sessionstore.postdata";
|
||||||
|
|
||||||
|
// Observer that updates the cached value when the preference changes.
|
||||||
|
Services.prefs.addObserver(PREF, () => {
|
||||||
|
this.gPostData = Services.prefs.getIntPref(PREF);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
return Services.prefs.getIntPref(PREF);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The external API exported by this module.
|
||||||
|
*/
|
||||||
|
this.SessionHistory = Object.freeze({
|
||||||
|
read: function (docShell, includePrivateData) {
|
||||||
|
return SessionHistoryInternal.read(docShell, includePrivateData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal API for the SessionHistory module.
|
||||||
|
*/
|
||||||
|
let SessionHistoryInternal = {
|
||||||
|
/**
|
||||||
|
* Collects session history data for a given docShell.
|
||||||
|
*
|
||||||
|
* @param docShell
|
||||||
|
* The docShell that owns the session history.
|
||||||
|
* @param includePrivateData (optional)
|
||||||
|
* True to always include private data and skip any privacy checks.
|
||||||
|
*/
|
||||||
|
read: function (docShell, includePrivateData = false) {
|
||||||
|
let data = {entries: []};
|
||||||
|
let isPinned = docShell.isAppTab;
|
||||||
|
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||||
|
let history = webNavigation.sessionHistory;
|
||||||
|
|
||||||
|
if (history && history.count > 0) {
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < history.count; i++) {
|
||||||
|
let shEntry = history.getEntryAtIndex(i, false);
|
||||||
|
let entry = this._serializeEntry(shEntry, includePrivateData, isPinned);
|
||||||
|
data.entries.push(entry);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
// In some cases, getEntryAtIndex will throw. This seems to be due to
|
||||||
|
// history.count being higher than it should be. By doing this in a
|
||||||
|
// try-catch, we'll update history to where it breaks, print an error
|
||||||
|
// message, and still save sessionstore.js.
|
||||||
|
debug("SessionStore failed gathering complete history " +
|
||||||
|
"for the focused window/tab. See bug 669196.");
|
||||||
|
}
|
||||||
|
data.index = history.index + 1;
|
||||||
|
} else {
|
||||||
|
let uri = webNavigation.currentURI.spec;
|
||||||
|
// 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" || webNavigation.document.body.hasChildNodes()) {
|
||||||
|
data.entries.push({ url: uri });
|
||||||
|
data.index = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an object that is a serialized representation of a History entry.
|
||||||
|
*
|
||||||
|
* @param shEntry
|
||||||
|
* nsISHEntry instance
|
||||||
|
* @param includePrivateData
|
||||||
|
* Always return privacy sensitive data (use with care).
|
||||||
|
* @param isPinned
|
||||||
|
* The tab is pinned and should be treated differently for privacy.
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
_serializeEntry: function (shEntry, includePrivateData, isPinned) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (shEntry.srcdocData)
|
||||||
|
entry.srcdocData = shEntry.srcdocData;
|
||||||
|
|
||||||
|
if (shEntry.isSrcdocEntry)
|
||||||
|
entry.isSrcdocEntry = shEntry.isSrcdocEntry;
|
||||||
|
|
||||||
|
if (shEntry.contentType)
|
||||||
|
entry.contentType = shEntry.contentType;
|
||||||
|
|
||||||
|
let x = {}, y = {};
|
||||||
|
shEntry.getScrollPosition(x, y);
|
||||||
|
if (x.value != 0 || y.value != 0)
|
||||||
|
entry.scroll = x.value + "," + y.value;
|
||||||
|
|
||||||
|
// Collect post data for the current history entry.
|
||||||
|
try {
|
||||||
|
let postdata = this._serializePostData(shEntry, isPinned);
|
||||||
|
if (postdata) {
|
||||||
|
entry.postdata_b64 = postdata;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
// POSTDATA is tricky - especially since some extensions don't get it right
|
||||||
|
debug("Failed serializing post data: " + ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect owner data for the current history entry.
|
||||||
|
try {
|
||||||
|
let owner = this._serializeOwner(shEntry);
|
||||||
|
if (owner) {
|
||||||
|
entry.owner_b64 = owner;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
// Not catching anything specific here, just possible errors
|
||||||
|
// from writeCompoundObject() and the like.
|
||||||
|
debug("Failed serializing owner data: " + ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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, includePrivateData, isPinned));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length) {
|
||||||
|
entry.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize post data contained in the given session history entry.
|
||||||
|
*
|
||||||
|
* @param shEntry
|
||||||
|
* The session history entry.
|
||||||
|
* @param isPinned
|
||||||
|
* Whether the docShell is owned by a pinned tab.
|
||||||
|
* @return The base64 encoded post data.
|
||||||
|
*/
|
||||||
|
_serializePostData: function (shEntry, isPinned) {
|
||||||
|
let isHttps = shEntry.URI.schemeIs("https");
|
||||||
|
if (!shEntry.postData || !gPostData ||
|
||||||
|
!PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
shEntry.postData.QueryInterface(Ci.nsISeekableStream)
|
||||||
|
.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
|
||||||
|
let stream = Cc["@mozilla.org/binaryinputstream;1"]
|
||||||
|
.createInstance(Ci.nsIBinaryInputStream);
|
||||||
|
stream.setInputStream(shEntry.postData);
|
||||||
|
let postBytes = stream.readByteArray(stream.available());
|
||||||
|
let postdata = String.fromCharCode.apply(null, postBytes);
|
||||||
|
if (gPostData != -1 &&
|
||||||
|
postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length > gPostData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can stop doing base64 encoding once our serialization into JSON
|
||||||
|
// is guaranteed to handle all chars in strings, including embedded
|
||||||
|
// nulls.
|
||||||
|
return btoa(postdata);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize owner data contained in the given session history entry.
|
||||||
|
*
|
||||||
|
* @param shEntry
|
||||||
|
* The session history entry.
|
||||||
|
* @return The base64 encoded owner data.
|
||||||
|
*/
|
||||||
|
_serializeOwner: function (shEntry) {
|
||||||
|
if (!shEntry.owner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
|
||||||
|
createInstance(Ci.nsIObjectOutputStream);
|
||||||
|
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
|
||||||
|
pipe.init(false, false, 0, 0xffffffff, null);
|
||||||
|
binaryStream.setOutputStream(pipe.outputStream);
|
||||||
|
binaryStream.writeCompoundObject(shEntry.owner, Ci.nsISupports, true);
|
||||||
|
binaryStream.close();
|
||||||
|
|
||||||
|
// Now we want to read the data from the pipe's input end and encode it.
|
||||||
|
let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
|
||||||
|
createInstance(Ci.nsIBinaryInputStream);
|
||||||
|
scriptableStream.setInputStream(pipe.inputStream);
|
||||||
|
let ownerBytes =
|
||||||
|
scriptableStream.readByteArray(scriptableStream.available());
|
||||||
|
|
||||||
|
// We can stop doing base64 encoding once our serialization into JSON
|
||||||
|
// is guaranteed to handle all chars in strings, including embedded
|
||||||
|
// nulls.
|
||||||
|
return btoa(String.fromCharCode.apply(null, ownerBytes));
|
||||||
|
}
|
||||||
|
};
|
@ -132,6 +132,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
|
|||||||
"resource:///modules/sessionstore/SessionStorage.jsm");
|
"resource:///modules/sessionstore/SessionStorage.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
|
||||||
"resource:///modules/sessionstore/SessionCookies.jsm");
|
"resource:///modules/sessionstore/SessionCookies.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
|
||||||
|
"resource:///modules/sessionstore/SessionHistory.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
|
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
|
||||||
"resource:///modules/sessionstore/_SessionFile.jsm");
|
"resource:///modules/sessionstore/_SessionFile.jsm");
|
||||||
|
|
||||||
@ -393,10 +395,6 @@ let SessionStoreInternal = {
|
|||||||
this._initPrefs();
|
this._initPrefs();
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
|
|
||||||
// this pref is only read at startup, so no need to observe it
|
|
||||||
this._sessionhistory_max_entries =
|
|
||||||
this._prefBranch.getIntPref("sessionhistory.max_entries");
|
|
||||||
|
|
||||||
// Wait until nsISessionStartup has finished reading the session data.
|
// Wait until nsISessionStartup has finished reading the session data.
|
||||||
gSessionStartup.onceInitialized.then(() => {
|
gSessionStartup.onceInitialized.then(() => {
|
||||||
// Parse session data and start restoring.
|
// Parse session data and start restoring.
|
||||||
@ -4377,60 +4375,17 @@ let TabState = {
|
|||||||
*/
|
*/
|
||||||
_collectTabHistory: function (tab, tabData, options = {}) {
|
_collectTabHistory: function (tab, tabData, options = {}) {
|
||||||
let includePrivateData = options && options.includePrivateData;
|
let includePrivateData = options && options.includePrivateData;
|
||||||
let browser = tab.linkedBrowser;
|
let docShell = tab.linkedBrowser.docShell;
|
||||||
let history = null;
|
|
||||||
try {
|
|
||||||
history = browser.sessionHistory;
|
|
||||||
} catch (ex) {
|
|
||||||
// this could happen if we catch a tab during (de)initialization
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
|
if (docShell instanceof Ci.nsIDocShell) {
|
||||||
// data even when we shouldn't (e.g. Back, different anchor)
|
let history = SessionHistory.read(docShell, includePrivateData);
|
||||||
if (history && browser.__SS_data &&
|
tabData.entries = history.entries;
|
||||||
browser.__SS_data.entries[history.index] &&
|
|
||||||
browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
|
|
||||||
history.index < this._sessionhistory_max_entries - 1 && !includePrivateData) {
|
|
||||||
tabData = browser.__SS_data;
|
|
||||||
tabData.index = history.index + 1;
|
|
||||||
}
|
|
||||||
else if (history && history.count > 0) {
|
|
||||||
try {
|
|
||||||
for (let j = 0; j < history.count; j++) {
|
|
||||||
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
|
|
||||||
includePrivateData, tab.pinned);
|
|
||||||
tabData.entries.push(entry);
|
|
||||||
}
|
|
||||||
// If we make it through the for loop, then we're ok and we should clear
|
|
||||||
// any indicator of brokenness.
|
|
||||||
delete tab.__SS_broken_history;
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
// In some cases, getEntryAtIndex will throw. This seems to be due to
|
|
||||||
// history.count being higher than it should be. By doing this in a
|
|
||||||
// try-catch, we'll update history to where it breaks, assert for
|
|
||||||
// non-release builds, and still save sessionstore.js. We'll track if
|
|
||||||
// we've shown the assert for this tab so we only show it once.
|
|
||||||
// cf. bug 669196.
|
|
||||||
if (!tab.__SS_broken_history) {
|
|
||||||
// First Focus the window & tab we're having trouble with.
|
|
||||||
tab.ownerDocument.defaultView.focus();
|
|
||||||
tab.ownerDocument.defaultView.gBrowser.selectedTab = tab;
|
|
||||||
debug("SessionStore failed gathering complete history " +
|
|
||||||
"for the focused window/tab. See bug 669196.");
|
|
||||||
tab.__SS_broken_history = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tabData.index = history.index + 1;
|
|
||||||
|
|
||||||
// make sure not to cache privacy sensitive data which shouldn't get out
|
// For blank tabs without any history entries,
|
||||||
if (!includePrivateData)
|
// there will not be an 'index' property.
|
||||||
browser.__SS_data = tabData;
|
if ("index" in history) {
|
||||||
}
|
tabData.index = history.index;
|
||||||
else if (browser.currentURI.spec != "about:blank" ||
|
}
|
||||||
browser.contentDocument.body.hasChildNodes()) {
|
|
||||||
tabData.entries[0] = { url: browser.currentURI.spec };
|
|
||||||
tabData.index = 1;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -4446,160 +4401,16 @@ let TabState = {
|
|||||||
*/
|
*/
|
||||||
_collectTabSessionStorage: function (tab, tabData, options = {}) {
|
_collectTabSessionStorage: function (tab, tabData, options = {}) {
|
||||||
let includePrivateData = options && options.includePrivateData;
|
let includePrivateData = options && options.includePrivateData;
|
||||||
let browser = tab.linkedBrowser;
|
let docShell = tab.linkedBrowser.docShell;
|
||||||
let history = null;
|
|
||||||
try {
|
|
||||||
history = browser.sessionHistory;
|
|
||||||
} catch (ex) {
|
|
||||||
// this could happen if we catch a tab during (de)initialization
|
|
||||||
}
|
|
||||||
|
|
||||||
if (history && browser.docShell instanceof Ci.nsIDocShell) {
|
if (docShell instanceof Ci.nsIDocShell) {
|
||||||
let storageData = SessionStorage.serialize(browser.docShell, includePrivateData)
|
let storageData = SessionStorage.serialize(docShell, includePrivateData)
|
||||||
if (Object.keys(storageData).length) {
|
if (Object.keys(storageData).length) {
|
||||||
tabData.storage = storageData;
|
tabData.storage = storageData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an object that is a serialized representation of a History entry.
|
|
||||||
*
|
|
||||||
* @param shEntry
|
|
||||||
* nsISHEntry instance
|
|
||||||
* @param includePrivateData
|
|
||||||
* Always return privacy sensitive data (use with care).
|
|
||||||
* @param isPinned
|
|
||||||
* The tab is pinned and should be treated differently for privacy.
|
|
||||||
* @return object
|
|
||||||
*/
|
|
||||||
_serializeHistoryEntry: function (shEntry, includePrivateData, isPinned) {
|
|
||||||
let entry = { url: shEntry.URI.spec };
|
|
||||||
|
|
||||||
if (shEntry.title && shEntry.title != entry.url) {
|
|
||||||
entry.title = shEntry.title;
|
|
||||||
}
|
|
||||||
if (shEntry.isSubFrame) {
|
|
||||||
entry.subframe = true;
|
|
||||||
}
|
|
||||||
if (!(shEntry instanceof Ci.nsISHEntry)) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (shEntry.srcdocData)
|
|
||||||
entry.srcdocData = shEntry.srcdocData;
|
|
||||||
|
|
||||||
if (shEntry.isSrcdocEntry)
|
|
||||||
entry.isSrcdocEntry = shEntry.isSrcdocEntry;
|
|
||||||
|
|
||||||
if (shEntry.contentType)
|
|
||||||
entry.contentType = shEntry.contentType;
|
|
||||||
|
|
||||||
let x = {}, y = {};
|
|
||||||
shEntry.getScrollPosition(x, y);
|
|
||||||
if (x.value != 0 || y.value != 0)
|
|
||||||
entry.scroll = x.value + "," + y.value;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let isHttps = shEntry.URI.schemeIs("https");
|
|
||||||
let prefPostdata = Services.prefs.getIntPref("browser.sessionstore.postdata");
|
|
||||||
if (shEntry.postData && (includePrivateData || prefPostdata &&
|
|
||||||
PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned}))) {
|
|
||||||
shEntry.postData.QueryInterface(Ci.nsISeekableStream).
|
|
||||||
seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
|
|
||||||
let stream = Cc["@mozilla.org/binaryinputstream;1"].
|
|
||||||
createInstance(Ci.nsIBinaryInputStream);
|
|
||||||
stream.setInputStream(shEntry.postData);
|
|
||||||
let postBytes = stream.readByteArray(stream.available());
|
|
||||||
let postdata = String.fromCharCode.apply(null, postBytes);
|
|
||||||
if (includePrivateData || prefPostdata == -1 ||
|
|
||||||
postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
|
|
||||||
prefPostdata) {
|
|
||||||
// We can stop doing base64 encoding once our serialization into JSON
|
|
||||||
// is guaranteed to handle all chars in strings, including embedded
|
|
||||||
// nulls.
|
|
||||||
entry.postdata_b64 = btoa(postdata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
|
|
||||||
|
|
||||||
if (shEntry.owner) {
|
|
||||||
// Not catching anything specific here, just possible errors
|
|
||||||
// from writeCompoundObject and the like.
|
|
||||||
try {
|
|
||||||
let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
|
|
||||||
createInstance(Ci.nsIObjectOutputStream);
|
|
||||||
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
|
|
||||||
pipe.init(false, false, 0, 0xffffffff, null);
|
|
||||||
binaryStream.setOutputStream(pipe.outputStream);
|
|
||||||
binaryStream.writeCompoundObject(shEntry.owner, Ci.nsISupports, true);
|
|
||||||
binaryStream.close();
|
|
||||||
|
|
||||||
// Now we want to read the data from the pipe's input end and encode it.
|
|
||||||
let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
|
|
||||||
createInstance(Ci.nsIBinaryInputStream);
|
|
||||||
scriptableStream.setInputStream(pipe.inputStream);
|
|
||||||
let ownerBytes =
|
|
||||||
scriptableStream.readByteArray(scriptableStream.available());
|
|
||||||
// We can stop doing base64 encoding once our serialization into JSON
|
|
||||||
// is guaranteed to handle all chars in strings, including embedded
|
|
||||||
// nulls.
|
|
||||||
entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
|
|
||||||
}
|
|
||||||
catch (ex) { debug(ex); }
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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 = [];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(this._serializeHistoryEntry(child, includePrivateData,
|
|
||||||
isPinned));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (children.length)
|
|
||||||
entry.children = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go through all frames and store the current scroll positions
|
* Go through all frames and store the current scroll positions
|
||||||
* and innerHTML content of WYSIWYG editors
|
* and innerHTML content of WYSIWYG editors
|
||||||
|
@ -16,6 +16,7 @@ EXTRA_JS_MODULES = [
|
|||||||
'DocumentUtils.jsm',
|
'DocumentUtils.jsm',
|
||||||
'PrivacyLevel.jsm',
|
'PrivacyLevel.jsm',
|
||||||
'SessionCookies.jsm',
|
'SessionCookies.jsm',
|
||||||
|
'SessionHistory.jsm',
|
||||||
'SessionMigration.jsm',
|
'SessionMigration.jsm',
|
||||||
'SessionStorage.jsm',
|
'SessionStorage.jsm',
|
||||||
'SessionWorker.js',
|
'SessionWorker.js',
|
||||||
|
@ -58,7 +58,6 @@ function test() {
|
|||||||
ss.getBrowserState();
|
ss.getBrowserState();
|
||||||
|
|
||||||
is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
|
is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
|
||||||
ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
|
|
||||||
|
|
||||||
// Start a load and interrupt it by closing the tab
|
// Start a load and interrupt it by closing the tab
|
||||||
tab.linkedBrowser.loadURI(URI_TO_LOAD);
|
tab.linkedBrowser.loadURI(URI_TO_LOAD);
|
||||||
|
Loading…
Reference in New Issue
Block a user