|
|
|
@ -201,6 +201,23 @@ SessionStoreService.prototype = {
|
|
|
|
|
// whether the last window was closed and should be restored
|
|
|
|
|
_restoreLastWindow: false,
|
|
|
|
|
|
|
|
|
|
// The state from the previous session (after restoring pinned tabs)
|
|
|
|
|
_lastSessionState: null,
|
|
|
|
|
|
|
|
|
|
/* ........ Public Getters .............. */
|
|
|
|
|
|
|
|
|
|
get canRestoreLastSession() {
|
|
|
|
|
// Always disallow restoring the previous session when in private browsing
|
|
|
|
|
return this._lastSessionState && !this._inPrivateBrowsing;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
set canRestoreLastSession(val) {
|
|
|
|
|
// Cheat a bit; only allow false.
|
|
|
|
|
if (val)
|
|
|
|
|
return;
|
|
|
|
|
this._lastSessionState = null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* ........ Global Event Handlers .............. */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -250,40 +267,54 @@ SessionStoreService.prototype = {
|
|
|
|
|
|
|
|
|
|
// get string containing session state
|
|
|
|
|
var iniString;
|
|
|
|
|
var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
|
|
|
|
|
getService(Ci.nsISessionStartup);
|
|
|
|
|
try {
|
|
|
|
|
var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
|
|
|
|
|
getService(Ci.nsISessionStartup);
|
|
|
|
|
if (ss.doRestore())
|
|
|
|
|
if (ss.doRestore() ||
|
|
|
|
|
ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION)
|
|
|
|
|
iniString = ss.state;
|
|
|
|
|
}
|
|
|
|
|
catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
|
|
|
|
|
|
|
|
|
|
if (iniString) {
|
|
|
|
|
try {
|
|
|
|
|
// parse the session state into JS objects
|
|
|
|
|
this._initialState = JSON.parse(iniString);
|
|
|
|
|
|
|
|
|
|
let lastSessionCrashed =
|
|
|
|
|
this._initialState.session && this._initialState.session.state &&
|
|
|
|
|
this._initialState.session.state == STATE_RUNNING_STR;
|
|
|
|
|
if (lastSessionCrashed) {
|
|
|
|
|
this._recentCrashes = (this._initialState.session &&
|
|
|
|
|
this._initialState.session.recentCrashes || 0) + 1;
|
|
|
|
|
|
|
|
|
|
if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
|
|
|
|
|
// replace the crashed session with a restore-page-only session
|
|
|
|
|
let pageData = {
|
|
|
|
|
url: "about:sessionrestore",
|
|
|
|
|
formdata: { "#sessionData": iniString }
|
|
|
|
|
};
|
|
|
|
|
this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
|
|
|
|
|
}
|
|
|
|
|
// If we're doing a DEFERRED session, then we want to pull pinned tabs
|
|
|
|
|
// out so they can be restored.
|
|
|
|
|
if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
|
|
|
|
|
let [iniState, remainingState] = this._prepDataForDeferredRestore(iniString);
|
|
|
|
|
// If we have a iniState with windows, that means that we have windows
|
|
|
|
|
// with app tabs to restore.
|
|
|
|
|
if (iniState.windows.length)
|
|
|
|
|
this._initialState = iniState;
|
|
|
|
|
if (remainingState.windows.length)
|
|
|
|
|
this._lastSessionState = remainingState;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// parse the session state into JS objects
|
|
|
|
|
this._initialState = JSON.parse(iniString);
|
|
|
|
|
|
|
|
|
|
let lastSessionCrashed =
|
|
|
|
|
this._initialState.session && this._initialState.session.state &&
|
|
|
|
|
this._initialState.session.state == STATE_RUNNING_STR;
|
|
|
|
|
if (lastSessionCrashed) {
|
|
|
|
|
this._recentCrashes = (this._initialState.session &&
|
|
|
|
|
this._initialState.session.recentCrashes || 0) + 1;
|
|
|
|
|
|
|
|
|
|
if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
|
|
|
|
|
// replace the crashed session with a restore-page-only session
|
|
|
|
|
let pageData = {
|
|
|
|
|
url: "about:sessionrestore",
|
|
|
|
|
formdata: { "#sessionData": iniString }
|
|
|
|
|
};
|
|
|
|
|
this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// make sure that at least the first window doesn't have anything hidden
|
|
|
|
|
delete this._initialState.windows[0].hidden;
|
|
|
|
|
// Since nothing is hidden in the first window, it cannot be a popup
|
|
|
|
|
delete this._initialState.windows[0].isPopup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// make sure that at least the first window doesn't have anything hidden
|
|
|
|
|
delete this._initialState.windows[0].hidden;
|
|
|
|
|
// Since nothing is hidden in the first window, it cannot be a popup
|
|
|
|
|
delete this._initialState.windows[0].isPopup;
|
|
|
|
|
}
|
|
|
|
|
catch (ex) { debug("The session file is invalid: " + ex); }
|
|
|
|
|
}
|
|
|
|
@ -317,11 +348,6 @@ SessionStoreService.prototype = {
|
|
|
|
|
// save all data for session resuming
|
|
|
|
|
this.saveState(true);
|
|
|
|
|
|
|
|
|
|
if (!this._doResumeSession()) {
|
|
|
|
|
// discard all session related data
|
|
|
|
|
this._clearDisk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure to break our cycle with the save timer
|
|
|
|
|
if (this._saveTimer) {
|
|
|
|
|
this._saveTimer.cancel();
|
|
|
|
@ -1134,6 +1160,78 @@ SessionStoreService.prototype = {
|
|
|
|
|
this.saveStateDelayed();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Restores the session state stored in _lastSessionState. This will attempt
|
|
|
|
|
* to merge data into the current session. If a window was opened at startup
|
|
|
|
|
* with pinned tab(s), then the remaining data from the previous session for
|
|
|
|
|
* that window will be opened into that winddow. Otherwise new windows will
|
|
|
|
|
* be opened.
|
|
|
|
|
*/
|
|
|
|
|
restoreLastSession: function sss_restoreLastSession() {
|
|
|
|
|
// Use the public getter since it also checks PB mode
|
|
|
|
|
if (!this.canRestoreLastSession)
|
|
|
|
|
throw (Components.returnCode = Cr.NS_ERROR_FAILURE);
|
|
|
|
|
|
|
|
|
|
// First collect each window with its id...
|
|
|
|
|
let windows = {};
|
|
|
|
|
this._forEachBrowserWindow(function(aWindow) {
|
|
|
|
|
if (aWindow.__SS_lastSessionWindowID)
|
|
|
|
|
windows[aWindow.__SS_lastSessionWindowID] = aWindow;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let lastSessionState = this._lastSessionState;
|
|
|
|
|
|
|
|
|
|
// This shouldn't ever be the case...
|
|
|
|
|
if (!lastSessionState.windows.length)
|
|
|
|
|
throw (Components.returnCode = Cr.NS_ERROR_UNEXPECTED);
|
|
|
|
|
|
|
|
|
|
// We're technically doing a restore, so set things up so we send the
|
|
|
|
|
// notification when we're done. We want to send "sessionstore-browser-state-restored".
|
|
|
|
|
this._restoreCount = lastSessionState.windows.length;
|
|
|
|
|
this._browserSetState = true;
|
|
|
|
|
|
|
|
|
|
// Restore into windows or open new ones as needed.
|
|
|
|
|
for (let i = 0; i < lastSessionState.windows.length; i++) {
|
|
|
|
|
let winState = lastSessionState.windows[i];
|
|
|
|
|
let lastSessionWindowID = winState.__lastSessionWindowID;
|
|
|
|
|
// delete lastSessionWindowID so we don't add that to the window again
|
|
|
|
|
delete winState.__lastSessionWindowID;
|
|
|
|
|
// Look to see if this window is already open...
|
|
|
|
|
if (windows[lastSessionWindowID]) {
|
|
|
|
|
// Since we're not overwriting existing tabs, we want to merge _closedTabs,
|
|
|
|
|
// putting existing ones first. Then make sure we're respecting the max pref.
|
|
|
|
|
if (winState._closedTabs && winState._closedTabs.length) {
|
|
|
|
|
let curWinState = this._windows[windows[lastSessionWindowID].__SSi];
|
|
|
|
|
curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
|
|
|
|
|
curWinState._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore into that window - pretend it's a followup since we'll already
|
|
|
|
|
// have a focused window.
|
|
|
|
|
//XXXzpao This is going to merge extData together (taking what was in
|
|
|
|
|
// winState over what is in the window already), so this is going
|
|
|
|
|
// to have an effect on Tab Candy.
|
|
|
|
|
// Bug 588217 should make this go away by merging the group data.
|
|
|
|
|
this.restoreWindow(windows[lastSessionWindowID], { windows: [winState] },
|
|
|
|
|
false, true);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this._openWindowWithState({ windows: [winState] });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Merge closed windows from this session with ones from last session
|
|
|
|
|
if (lastSessionState._closedWindows) {
|
|
|
|
|
this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
|
|
|
|
|
this._capClosedWindows();
|
|
|
|
|
}
|
|
|
|
|
// Set recent crashes
|
|
|
|
|
this._recentCrashes = lastSessionState.session &&
|
|
|
|
|
lastSessionState.session.recentCrashes || 0;
|
|
|
|
|
|
|
|
|
|
this._lastSessionState = null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* ........ Saving Functionality .............. */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -1191,9 +1289,11 @@ SessionStoreService.prototype = {
|
|
|
|
|
tabData.index = history.index + 1;
|
|
|
|
|
}
|
|
|
|
|
else if (history && history.count > 0) {
|
|
|
|
|
for (var j = 0; j < history.count; j++)
|
|
|
|
|
tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
|
|
|
|
|
aFullData));
|
|
|
|
|
for (var j = 0; j < history.count; j++) {
|
|
|
|
|
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
|
|
|
|
|
aFullData, aTab.pinned);
|
|
|
|
|
tabData.entries.push(entry);
|
|
|
|
|
}
|
|
|
|
|
tabData.index = history.index + 1;
|
|
|
|
|
|
|
|
|
|
// make sure not to cache privacy sensitive data which shouldn't get out
|
|
|
|
@ -1242,7 +1342,8 @@ SessionStoreService.prototype = {
|
|
|
|
|
delete tabData.extData;
|
|
|
|
|
|
|
|
|
|
if (history && browser.docShell instanceof Ci.nsIDocShell)
|
|
|
|
|
this._serializeSessionStorage(tabData, history, browser.docShell, aFullData);
|
|
|
|
|
this._serializeSessionStorage(tabData, history, browser.docShell, aFullData,
|
|
|
|
|
aTab.pinned);
|
|
|
|
|
|
|
|
|
|
return tabData;
|
|
|
|
|
},
|
|
|
|
@ -1254,9 +1355,12 @@ SessionStoreService.prototype = {
|
|
|
|
|
* nsISHEntry instance
|
|
|
|
|
* @param aFullData
|
|
|
|
|
* always return privacy sensitive data (use with care)
|
|
|
|
|
* @param aIsPinned
|
|
|
|
|
* the tab is pinned and should be treated differently for privacy
|
|
|
|
|
* @returns object
|
|
|
|
|
*/
|
|
|
|
|
_serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
|
|
|
|
|
_serializeHistoryEntry:
|
|
|
|
|
function sss_serializeHistoryEntry(aEntry, aFullData, aIsPinned) {
|
|
|
|
|
var entry = { url: aEntry.URI.spec };
|
|
|
|
|
|
|
|
|
|
if (aEntry.title && aEntry.title != entry.url) {
|
|
|
|
@ -1291,8 +1395,8 @@ SessionStoreService.prototype = {
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
|
|
|
|
|
if (aEntry.postData && (aFullData ||
|
|
|
|
|
prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) {
|
|
|
|
|
if (aEntry.postData && (aFullData || prefPostdata &&
|
|
|
|
|
this._checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
|
|
|
|
|
aEntry.postData.QueryInterface(Ci.nsISeekableStream).
|
|
|
|
|
seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
|
|
|
|
|
var stream = Cc["@mozilla.org/binaryinputstream;1"].
|
|
|
|
@ -1355,7 +1459,8 @@ SessionStoreService.prototype = {
|
|
|
|
|
for (var i = 0; i < aEntry.childCount; i++) {
|
|
|
|
|
var child = aEntry.GetChildAt(i);
|
|
|
|
|
if (child) {
|
|
|
|
|
entry.children.push(this._serializeHistoryEntry(child, aFullData));
|
|
|
|
|
entry.children.push(this._serializeHistoryEntry(child, aFullData,
|
|
|
|
|
aIsPinned));
|
|
|
|
|
}
|
|
|
|
|
else { // to maintain the correct frame order, insert a dummy entry
|
|
|
|
|
entry.children.push({ url: "about:blank" });
|
|
|
|
@ -1381,9 +1486,11 @@ SessionStoreService.prototype = {
|
|
|
|
|
* That tab's docshell (containing the sessionStorage)
|
|
|
|
|
* @param aFullData
|
|
|
|
|
* always return privacy sensitive data (use with care)
|
|
|
|
|
* @param aIsPinned
|
|
|
|
|
* the tab is pinned and should be treated differently for privacy
|
|
|
|
|
*/
|
|
|
|
|
_serializeSessionStorage:
|
|
|
|
|
function sss_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData) {
|
|
|
|
|
function sss_serializeSessionStorage(aTabData, aHistory, aDocShell, aFullData, aIsPinned) {
|
|
|
|
|
let storageData = {};
|
|
|
|
|
let hasContent = false;
|
|
|
|
|
|
|
|
|
@ -1396,7 +1503,8 @@ SessionStoreService.prototype = {
|
|
|
|
|
domain = uri.prePath;
|
|
|
|
|
}
|
|
|
|
|
catch (ex) { /* this throws for host-less URIs (such as about: or jar:) */ }
|
|
|
|
|
if (storageData[domain] || !(aFullData || this._checkPrivacyLevel(uri.schemeIs("https"))))
|
|
|
|
|
if (storageData[domain] ||
|
|
|
|
|
!(aFullData || this._checkPrivacyLevel(uri.schemeIs("https"), aIsPinned)))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
let storage, storageItemCount = 0;
|
|
|
|
@ -1479,7 +1587,8 @@ SessionStoreService.prototype = {
|
|
|
|
|
|
|
|
|
|
this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
|
|
|
|
|
aTabData.entries[tabIndex],
|
|
|
|
|
!aTabData._formDataSaved, aFullData);
|
|
|
|
|
!aTabData._formDataSaved, aFullData,
|
|
|
|
|
!!aTabData.pinned);
|
|
|
|
|
aTabData._formDataSaved = true;
|
|
|
|
|
if (aBrowser.currentURI.spec == "about:config")
|
|
|
|
|
aTabData.entries[tabIndex].formdata = {
|
|
|
|
@ -1500,18 +1609,21 @@ SessionStoreService.prototype = {
|
|
|
|
|
* update all form data for this tab
|
|
|
|
|
* @param aFullData
|
|
|
|
|
* always return privacy sensitive data (use with care)
|
|
|
|
|
* @param aIsPinned
|
|
|
|
|
* the tab is pinned and should be treated differently for privacy
|
|
|
|
|
*/
|
|
|
|
|
_updateTextAndScrollDataForFrame:
|
|
|
|
|
function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
|
|
|
|
|
aUpdateFormData, aFullData) {
|
|
|
|
|
aUpdateFormData, aFullData, aIsPinned) {
|
|
|
|
|
for (var i = 0; i < aContent.frames.length; i++) {
|
|
|
|
|
if (aData.children && aData.children[i])
|
|
|
|
|
this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
|
|
|
|
|
aData.children[i], aUpdateFormData, aFullData);
|
|
|
|
|
aData.children[i], aUpdateFormData,
|
|
|
|
|
aFullData, aIsPinned);
|
|
|
|
|
}
|
|
|
|
|
var isHTTPS = this._getURIFromString((aContent.parent || aContent).
|
|
|
|
|
document.location.href).schemeIs("https");
|
|
|
|
|
if (aFullData || this._checkPrivacyLevel(isHTTPS) ||
|
|
|
|
|
if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) ||
|
|
|
|
|
aContent.top.document.location.href == "about:sessionrestore") {
|
|
|
|
|
if (aFullData || aUpdateFormData) {
|
|
|
|
|
let formData = this._collectFormDataForFrame(aContent.document);
|
|
|
|
@ -1642,6 +1754,42 @@ SessionStoreService.prototype = {
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* extract the base domain from a history entry and its children
|
|
|
|
|
* @param aEntry
|
|
|
|
|
* the history entry, serialized
|
|
|
|
|
* @param aHosts
|
|
|
|
|
* the hash that will be used to store hosts eg, { hostname: true }
|
|
|
|
|
* @param aCheckPrivacy
|
|
|
|
|
* should we check the privacy level for https
|
|
|
|
|
* @param aIsPinned
|
|
|
|
|
* is the entry we're evaluating for a pinned tab; used only if
|
|
|
|
|
* aCheckPrivacy
|
|
|
|
|
*/
|
|
|
|
|
_extractHostsForCookies:
|
|
|
|
|
function sss__extractHostsForCookies(aEntry, aHosts, aCheckPrivacy, aIsPinned) {
|
|
|
|
|
let match;
|
|
|
|
|
|
|
|
|
|
if ((match = /^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.exec(aEntry.url)) != null) {
|
|
|
|
|
if (!aHosts[match[1]] &&
|
|
|
|
|
(!aCheckPrivacy ||
|
|
|
|
|
this._checkPrivacyLevel(this._getURIFromString(aEntry.url).schemeIs("https"),
|
|
|
|
|
aIsPinned))) {
|
|
|
|
|
// By setting this to true or false, we can determine when looking at
|
|
|
|
|
// the host in _updateCookies if we should check for privacy.
|
|
|
|
|
aHosts[match[1]] = aIsPinned;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ((match = /^file:\/\/([^\/]*)/.exec(aEntry.url)) != null) {
|
|
|
|
|
aHosts[match[1]] = true;
|
|
|
|
|
}
|
|
|
|
|
if (aEntry.children) {
|
|
|
|
|
aEntry.children.forEach(function(entry) {
|
|
|
|
|
this._extractHostsForCookies(entry, aHosts, aCheckPrivacy, aIsPinned);
|
|
|
|
|
}, this);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* store all hosts for a URL
|
|
|
|
|
* @param aWindow
|
|
|
|
@ -1649,25 +1797,12 @@ SessionStoreService.prototype = {
|
|
|
|
|
*/
|
|
|
|
|
_updateCookieHosts: function sss_updateCookieHosts(aWindow) {
|
|
|
|
|
var hosts = this._windows[aWindow.__SSi]._hosts = {};
|
|
|
|
|
|
|
|
|
|
// get the domain for each URL
|
|
|
|
|
function extractHosts(aEntry) {
|
|
|
|
|
var match;
|
|
|
|
|
if ((match = /^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.exec(aEntry.url)) != null) {
|
|
|
|
|
if (!hosts[match[1]] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
|
|
|
|
|
hosts[match[1]] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ((match = /^file:\/\/([^\/]*)/.exec(aEntry.url)) != null) {
|
|
|
|
|
hosts[match[1]] = true;
|
|
|
|
|
}
|
|
|
|
|
if (aEntry.children) {
|
|
|
|
|
aEntry.children.forEach(extractHosts);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _this = this;
|
|
|
|
|
this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
|
|
|
|
|
this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) {
|
|
|
|
|
aTabData.entries.forEach(function(entry) {
|
|
|
|
|
this._extractHostsForCookies(entry, hosts, true, !!aTabData.pinned);
|
|
|
|
|
}, this);
|
|
|
|
|
}, this);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -1695,11 +1830,16 @@ SessionStoreService.prototype = {
|
|
|
|
|
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
|
|
|
|
|
var MAX_EXPIRY = Math.pow(2, 62);
|
|
|
|
|
aWindows.forEach(function(aWindow) {
|
|
|
|
|
for (var host in aWindow._hosts) {
|
|
|
|
|
if (!aWindow._hosts)
|
|
|
|
|
return;
|
|
|
|
|
for (var [host, isPinned] in Iterator(aWindow._hosts)) {
|
|
|
|
|
var list = CookieSvc.getCookiesFromHost(host);
|
|
|
|
|
while (list.hasMoreElements()) {
|
|
|
|
|
var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
|
|
|
|
|
if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure)) {
|
|
|
|
|
// aWindow._hosts will only have hosts with the right privacy rules,
|
|
|
|
|
// so there is no need to do anything special with this call to
|
|
|
|
|
// _checkPrivacyLevel.
|
|
|
|
|
if (cookie.isSession && _this._checkPrivacyLevel(cookie.isSecure, isPinned)) {
|
|
|
|
|
// use the cookie's host, path, and name as keys into a hash,
|
|
|
|
|
// to make sure we serialize each cookie only once
|
|
|
|
|
if (!(cookie.host in jscookies &&
|
|
|
|
@ -1870,7 +2010,13 @@ SessionStoreService.prototype = {
|
|
|
|
|
this._updateTextAndScrollData(aWindow);
|
|
|
|
|
this._updateCookieHosts(aWindow);
|
|
|
|
|
this._updateWindowFeatures(aWindow);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure we keep __SS_lastSessionWindowID around for cases like entering
|
|
|
|
|
// or leaving PB mode.
|
|
|
|
|
if (aWindow.__SS_lastSessionWindowID)
|
|
|
|
|
this._windows[aWindow.__SSi].__lastSessionWindowID =
|
|
|
|
|
aWindow.__SS_lastSessionWindowID;
|
|
|
|
|
|
|
|
|
|
this._dirtyWindows[aWindow.__SSi] = false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@ -1968,6 +2114,13 @@ SessionStoreService.prototype = {
|
|
|
|
|
tabs[t].hidden = winData.tabs[t].hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We want to correlate the window with data from the last session, so
|
|
|
|
|
// assign another id if we have one. Otherwise clear so we don't do
|
|
|
|
|
// anything with it.
|
|
|
|
|
delete aWindow.__SS_lastSessionWindowID;
|
|
|
|
|
if (winData.__lastSessionWindowID)
|
|
|
|
|
aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
|
|
|
|
|
|
|
|
|
|
// when overwriting tabs, remove all superflous ones
|
|
|
|
|
if (aOverwriteTabs && newTabCount < openTabCount) {
|
|
|
|
|
Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
|
|
|
|
@ -2618,11 +2771,9 @@ SessionStoreService.prototype = {
|
|
|
|
|
if (this._inPrivateBrowsing)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var pinnedOnly = false;
|
|
|
|
|
if (this._loadState == STATE_QUITTING && !this._doResumeSession() ||
|
|
|
|
|
/* if crash recovery is disabled, only save session resuming information */
|
|
|
|
|
this._loadState == STATE_RUNNING && !this._resume_from_crash)
|
|
|
|
|
pinnedOnly = true;
|
|
|
|
|
// If crash recovery is disabled, we only want to resume with pinned tabs
|
|
|
|
|
// if we crash.
|
|
|
|
|
let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash;
|
|
|
|
|
|
|
|
|
|
var oState = this._getCurrentState(aUpdateAll, pinnedOnly);
|
|
|
|
|
if (!oState)
|
|
|
|
@ -2816,10 +2967,18 @@ SessionStoreService.prototype = {
|
|
|
|
|
* (distinguishes between encrypted and non-encrypted sites)
|
|
|
|
|
* @param aIsHTTPS
|
|
|
|
|
* Bool is encrypted
|
|
|
|
|
* @param aUseDefaultPref
|
|
|
|
|
* don't do normal check for deferred
|
|
|
|
|
* @returns bool
|
|
|
|
|
*/
|
|
|
|
|
_checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
|
|
|
|
|
return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
|
|
|
|
|
_checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
|
|
|
|
|
let pref = "sessionstore.privacy_level";
|
|
|
|
|
// If we're in the process of quitting and we're not autoresuming the session
|
|
|
|
|
// then we should treat it as a deferred session. We have a different privacy
|
|
|
|
|
// pref for that case.
|
|
|
|
|
if (!aUseDefaultPref && this._loadState == STATE_QUITTING && !this._doResumeSession())
|
|
|
|
|
pref = "sessionstore.privacy_level_deferred";
|
|
|
|
|
return this._prefBranch.getIntPref(pref) < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -2931,6 +3090,133 @@ SessionStoreService.prototype = {
|
|
|
|
|
sessionAge && sessionAge >= SIX_HOURS_IN_MS);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is going to take a state as provided at startup (via
|
|
|
|
|
* nsISessionStartup.state) and split it into 2 parts. The first part
|
|
|
|
|
* (defaultState) will be a state that should still be restored at startup,
|
|
|
|
|
* while the second part (state) is a state that should be saved for later.
|
|
|
|
|
* defaultState will be comprised of windows with only pinned tabs, extracted
|
|
|
|
|
* from state. It will contain the cookies that go along with the history
|
|
|
|
|
* entries in those tabs. It will also contain window position information.
|
|
|
|
|
*
|
|
|
|
|
* defaultState will be restored at startup. state will be placed into
|
|
|
|
|
* this._lastSessionState and will be kept in case the user explicitly wants
|
|
|
|
|
* to restore the previous session (publicly exposed as restoreLastSession).
|
|
|
|
|
*
|
|
|
|
|
* @param stateString
|
|
|
|
|
* The state string, presumably from nsISessionStartup.state
|
|
|
|
|
* @returns [defaultState, state]
|
|
|
|
|
*/
|
|
|
|
|
_prepDataForDeferredRestore: function sss__prepDataForDeferredRestore(stateString) {
|
|
|
|
|
let state = JSON.parse(stateString);
|
|
|
|
|
let defaultState = { windows: [], selectedWindow: 1 };
|
|
|
|
|
|
|
|
|
|
state.selectedWindow = state.selectedWindow || 1;
|
|
|
|
|
|
|
|
|
|
// Look at each window, remove pinned tabs, adjust selectedindex,
|
|
|
|
|
// remove window if necessary.
|
|
|
|
|
for (let wIndex = 0; wIndex < state.windows.length;) {
|
|
|
|
|
let window = state.windows[wIndex];
|
|
|
|
|
window.selected = window.selected || 1;
|
|
|
|
|
// We're going to put the state of the window into this object
|
|
|
|
|
let pinnedWindowState = { tabs: [], cookies: []};
|
|
|
|
|
for (let tIndex = 0; tIndex < window.tabs.length;) {
|
|
|
|
|
if (window.tabs[tIndex].pinned) {
|
|
|
|
|
// Adjust window.selected
|
|
|
|
|
if (tIndex + 1 < window.selected)
|
|
|
|
|
window.selected -= 1;
|
|
|
|
|
else if (tIndex + 1 == window.selected)
|
|
|
|
|
pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
|
|
|
|
|
// + 2 because the tab isn't actually in the array yet
|
|
|
|
|
|
|
|
|
|
// Now add the pinned tab to our window
|
|
|
|
|
pinnedWindowState.tabs =
|
|
|
|
|
pinnedWindowState.tabs.concat(window.tabs.splice(tIndex, 1));
|
|
|
|
|
// We don't want to increment tIndex here.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
tIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// At this point the window in the state object has been modified (or not)
|
|
|
|
|
// We want to build the rest of this new window object if we have pinnedTabs.
|
|
|
|
|
if (pinnedWindowState.tabs.length) {
|
|
|
|
|
// First get the other attributes off the window
|
|
|
|
|
WINDOW_ATTRIBUTES.forEach(function(attr) {
|
|
|
|
|
if (attr in window) {
|
|
|
|
|
pinnedWindowState[attr] = window[attr];
|
|
|
|
|
delete window[attr];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// We're just copying position data into the pinned window.
|
|
|
|
|
// Not copying over:
|
|
|
|
|
// - _closedTabs
|
|
|
|
|
// - extData
|
|
|
|
|
// - isPopup
|
|
|
|
|
// - hidden
|
|
|
|
|
|
|
|
|
|
// Assign a unique ID to correlate the window to be opened with the
|
|
|
|
|
// remaining data
|
|
|
|
|
window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
|
|
|
|
|
= "" + Date.now() + Math.random();
|
|
|
|
|
|
|
|
|
|
// Extract the cookies that belong with each pinned tab
|
|
|
|
|
this._splitCookiesFromWindow(window, pinnedWindowState);
|
|
|
|
|
|
|
|
|
|
// Actually add this window to our defaultState
|
|
|
|
|
defaultState.windows.push(pinnedWindowState);
|
|
|
|
|
// Remove the window from the state if it doesn't have any tabs
|
|
|
|
|
if (!window.tabs.length) {
|
|
|
|
|
if (wIndex + 1 <= state.selectedWindow)
|
|
|
|
|
state.selectedWindow -= 1;
|
|
|
|
|
else if (wIndex + 1 == state.selectedWindow)
|
|
|
|
|
defaultState.selectedIndex = defaultState.windows.length + 1;
|
|
|
|
|
|
|
|
|
|
state.windows.splice(wIndex, 1);
|
|
|
|
|
// We don't want to increment wIndex here.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
wIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [defaultState, state];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Splits out the cookies from aWinState into aTargetWinState based on the
|
|
|
|
|
* tabs that are in aTargetWinState.
|
|
|
|
|
* This alters the state of aWinState and aTargetWinState.
|
|
|
|
|
*/
|
|
|
|
|
_splitCookiesFromWindow:
|
|
|
|
|
function sss__splitCookiesFromWindow(aWinState, aTargetWinState) {
|
|
|
|
|
if (!aWinState.cookies || !aWinState.cookies.length)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Get the hosts for history entries in aTargetWinState
|
|
|
|
|
let cookieHosts = {};
|
|
|
|
|
aTargetWinState.tabs.forEach(function(tab) {
|
|
|
|
|
tab.entries.forEach(function(entry) {
|
|
|
|
|
this._extractHostsForCookies(entry, cookieHosts, false)
|
|
|
|
|
}, this);
|
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
|
|
// By creating a regex we reduce overhead and there is only one loop pass
|
|
|
|
|
// through either array (cookieHosts and aWinState.cookies).
|
|
|
|
|
let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g");
|
|
|
|
|
let cookieRegex = new RegExp(".*(" + hosts + ")");
|
|
|
|
|
for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
|
|
|
|
|
if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
|
|
|
|
|
aTargetWinState.cookies =
|
|
|
|
|
aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
cIndex++;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a JavaScript object into a JSON string
|
|
|
|
|
* (see http://www.json.org/ for more information).
|
|
|
|
@ -2941,9 +3227,16 @@ SessionStoreService.prototype = {
|
|
|
|
|
* @returns the object's JSON representation
|
|
|
|
|
*/
|
|
|
|
|
_toJSONString: function sss_toJSONString(aJSObject) {
|
|
|
|
|
// We never want to save __lastSessionWindowID across sessions, but we do
|
|
|
|
|
// want it exported to consumers when running (eg. Private Browsing).
|
|
|
|
|
let internalKeys = INTERNAL_KEYS;
|
|
|
|
|
if (this._loadState == STATE_QUITTING) {
|
|
|
|
|
internalKeys = internalKeys.slice();
|
|
|
|
|
internalKeys.push("__lastSessionWindowID");
|
|
|
|
|
}
|
|
|
|
|
function exclude(key, value) {
|
|
|
|
|
// returning undefined results in the exclusion of that key
|
|
|
|
|
return INTERNAL_KEYS.indexOf(key) == -1 ? value : undefined;
|
|
|
|
|
return internalKeys.indexOf(key) == -1 ? value : undefined;
|
|
|
|
|
}
|
|
|
|
|
return JSON.stringify(aJSObject, exclude);
|
|
|
|
|
},
|
|
|
|
|