mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 11:55:49 +00:00
merge fx-team to mozilla-central
This commit is contained in:
commit
972d9d6e50
@ -308,7 +308,8 @@ WebContentConverterRegistrar.prototype = {
|
|||||||
function WCCR_checkAndGetURI(aURIString, aContentWindow)
|
function WCCR_checkAndGetURI(aURIString, aContentWindow)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
var uri = this._makeURI(aURIString);
|
let baseURI = aContentWindow.document.baseURIObject;
|
||||||
|
var uri = this._makeURI(aURIString, null, baseURI);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// not supposed to throw according to spec
|
// not supposed to throw according to spec
|
||||||
return;
|
return;
|
||||||
|
@ -46,9 +46,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402788
|
|||||||
is(false, testRegisterHandler(true, "foo", "http://mochi.test:8888/", "Foo handler"), "a protocol handler uri should contain %s");
|
is(false, testRegisterHandler(true, "foo", "http://mochi.test:8888/", "Foo handler"), "a protocol handler uri should contain %s");
|
||||||
is(false, testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/", "Foo handler"), "a content handler uri should contain %s");
|
is(false, testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/", "Foo handler"), "a content handler uri should contain %s");
|
||||||
|
|
||||||
// the spec says we should not throw here, but it probably needs to be changed
|
// the spec explicitly allows relative urls to be passed
|
||||||
is(false, testRegisterHandler(true, "foo", "foo/%s", "Foo handler"), "a protocol handler uri should be valid");
|
is(true, testRegisterHandler(true, "foo", "foo/%s", "Foo handler"), "a protocol handler uri should be valid");
|
||||||
is(false, testRegisterHandler(false, "application/rss+xml", "foo/%s", "Foo handler"), "a content handler uri should be valid");
|
is(true, testRegisterHandler(false, "application/rss+xml", "foo/%s", "Foo handler"), "a content handler uri should be valid");
|
||||||
|
|
||||||
// we should only accept to register when the handler has the same host as the current page (bug 402287)
|
// we should only accept to register when the handler has the same host as the current page (bug 402287)
|
||||||
is(false, testRegisterHandler(true, "foo", "http://remotehost:8888/%s", "Foo handler"), "registering a foo protocol handler with a different host should not work");
|
is(false, testRegisterHandler(true, "foo", "http://remotehost:8888/%s", "Foo handler"), "registering a foo protocol handler with a different host should not work");
|
||||||
|
249
browser/components/sessionstore/src/SessionCookies.jsm
Normal file
249
browser/components/sessionstore/src/SessionCookies.jsm
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
/* 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 = ["SessionCookies"];
|
||||||
|
|
||||||
|
const Cu = Components.utils;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||||
|
|
||||||
|
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
|
||||||
|
const MAX_EXPIRY = Math.pow(2, 62);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The external API implemented by the SessionCookies module.
|
||||||
|
*/
|
||||||
|
this.SessionCookies = Object.freeze({
|
||||||
|
getCookiesForHost: function (host) {
|
||||||
|
return SessionCookiesInternal.getCookiesForHost(host);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal API.
|
||||||
|
*/
|
||||||
|
let SessionCookiesInternal = {
|
||||||
|
/**
|
||||||
|
* Stores whether we're initialized, yet.
|
||||||
|
*/
|
||||||
|
_initialized: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of active session cookies for a given host.
|
||||||
|
*/
|
||||||
|
getCookiesForHost: function (host) {
|
||||||
|
this._ensureInitialized();
|
||||||
|
return CookieStore.getCookiesForHost(host);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles observers notifications that are sent whenever cookies are added,
|
||||||
|
* changed, or removed. Ensures that the storage is updated accordingly.
|
||||||
|
*/
|
||||||
|
observe: function (subject, topic, data) {
|
||||||
|
switch (data) {
|
||||||
|
case "added":
|
||||||
|
case "changed":
|
||||||
|
this._updateCookie(subject);
|
||||||
|
break;
|
||||||
|
case "deleted":
|
||||||
|
this._removeCookie(subject);
|
||||||
|
break;
|
||||||
|
case "cleared":
|
||||||
|
CookieStore.clear();
|
||||||
|
break;
|
||||||
|
case "batch-deleted":
|
||||||
|
this._removeCookies(subject);
|
||||||
|
break;
|
||||||
|
case "reload":
|
||||||
|
CookieStore.clear();
|
||||||
|
this._reloadCookies();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Unhandled cookie-changed notification.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If called for the first time in a session, iterates all cookies in the
|
||||||
|
* cookies service and puts them into the store if they're session cookies.
|
||||||
|
*/
|
||||||
|
_ensureInitialized: function () {
|
||||||
|
if (!this._initialized) {
|
||||||
|
this._reloadCookies();
|
||||||
|
this._initialized = true;
|
||||||
|
Services.obs.addObserver(this, "cookie-changed", false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates or adds a given cookie to the store.
|
||||||
|
*/
|
||||||
|
_updateCookie: function (cookie) {
|
||||||
|
cookie.QueryInterface(Ci.nsICookie2);
|
||||||
|
|
||||||
|
if (cookie.isSession) {
|
||||||
|
CookieStore.set(cookie);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a given cookie from the store.
|
||||||
|
*/
|
||||||
|
_removeCookie: function (cookie) {
|
||||||
|
cookie.QueryInterface(Ci.nsICookie2);
|
||||||
|
|
||||||
|
if (cookie.isSession) {
|
||||||
|
CookieStore.delete(cookie);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a given list of cookies from the store.
|
||||||
|
*/
|
||||||
|
_removeCookies: function (cookies) {
|
||||||
|
for (let i = 0; i < cookies.length; i++) {
|
||||||
|
this._removeCookie(cookies.queryElementAt(i, Ci.nsICookie2));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates all cookies in the cookies service and puts them into the store
|
||||||
|
* if they're session cookies.
|
||||||
|
*/
|
||||||
|
_reloadCookies: function () {
|
||||||
|
let iter = Services.cookies.enumerator;
|
||||||
|
while (iter.hasMoreElements()) {
|
||||||
|
this._updateCookie(iter.getNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal cookie storage that keeps track of every active session cookie.
|
||||||
|
* These are stored using maps per host, path, and cookie name.
|
||||||
|
*/
|
||||||
|
let CookieStore = {
|
||||||
|
/**
|
||||||
|
* The internal structure holding all known cookies.
|
||||||
|
*
|
||||||
|
* Host =>
|
||||||
|
* Path =>
|
||||||
|
* Name => {path: "/", name: "sessionid", secure: true}
|
||||||
|
*
|
||||||
|
* Maps are used for storage but the data structure is equivalent to this:
|
||||||
|
*
|
||||||
|
* this._hosts = {
|
||||||
|
* "www.mozilla.org": {
|
||||||
|
* "/": {
|
||||||
|
* "username": {name: "username", value: "my_name_is", etc...},
|
||||||
|
* "sessionid": {name: "sessionid", value: "1fdb3a", etc...}
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* "tbpl.mozilla.org": {
|
||||||
|
* "/path": {
|
||||||
|
* "cookiename": {name: "cookiename", value: "value", etc...}
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
_hosts: new Map(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of stored session cookies for a given host.
|
||||||
|
*
|
||||||
|
* @param host
|
||||||
|
* A string containing the host name we want to get cookies for.
|
||||||
|
*/
|
||||||
|
getCookiesForHost: function (host) {
|
||||||
|
if (!this._hosts.has(host)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookies = [];
|
||||||
|
|
||||||
|
for (let pathToNamesMap of this._hosts.get(host).values()) {
|
||||||
|
cookies = cookies.concat([cookie for (cookie of pathToNamesMap.values())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a given cookie.
|
||||||
|
*
|
||||||
|
* @param cookie
|
||||||
|
* The nsICookie2 object to add to the storage.
|
||||||
|
*/
|
||||||
|
set: function (cookie) {
|
||||||
|
let jscookie = {host: cookie.host, value: cookie.value};
|
||||||
|
|
||||||
|
// Only add properties with non-default values to save a few bytes.
|
||||||
|
if (cookie.path) {
|
||||||
|
jscookie.path = cookie.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie.name) {
|
||||||
|
jscookie.name = cookie.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie.isSecure) {
|
||||||
|
jscookie.secure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie.isHttpOnly) {
|
||||||
|
jscookie.httponly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie.expiry < MAX_EXPIRY) {
|
||||||
|
jscookie.expiry = cookie.expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._ensureMap(cookie).set(cookie.name, jscookie);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a given cookie.
|
||||||
|
*
|
||||||
|
* @param cookie
|
||||||
|
* The nsICookie2 object to be removed from storage.
|
||||||
|
*/
|
||||||
|
delete: function (cookie) {
|
||||||
|
this._ensureMap(cookie).delete(cookie.name);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all cookies.
|
||||||
|
*/
|
||||||
|
clear: function () {
|
||||||
|
this._hosts.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates all maps necessary to store a given cookie.
|
||||||
|
*
|
||||||
|
* @param cookie
|
||||||
|
* The nsICookie2 object to create maps for.
|
||||||
|
*
|
||||||
|
* @return The newly created Map instance mapping cookie names to
|
||||||
|
* internal jscookies, in the given path of the given host.
|
||||||
|
*/
|
||||||
|
_ensureMap: function (cookie) {
|
||||||
|
if (!this._hosts.has(cookie.host)) {
|
||||||
|
this._hosts.set(cookie.host, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathToNamesMap = this._hosts.get(cookie.host);
|
||||||
|
|
||||||
|
if (!pathToNamesMap.has(cookie.path)) {
|
||||||
|
pathToNamesMap.set(cookie.path, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathToNamesMap.get(cookie.path);
|
||||||
|
}
|
||||||
|
};
|
283
browser/components/sessionstore/src/SessionSaver.jsm
Normal file
283
browser/components/sessionstore/src/SessionSaver.jsm
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
/* 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 = ["SessionSaver"];
|
||||||
|
|
||||||
|
const Cu = Components.utils;
|
||||||
|
const Cc = Components.classes;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Timer.jsm", this);
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||||
|
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
|
||||||
|
"resource:///modules/sessionstore/SessionStore.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
|
||||||
|
"resource:///modules/sessionstore/_SessionFile.jsm");
|
||||||
|
|
||||||
|
// Minimal interval between two save operations (in milliseconds).
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
|
||||||
|
const PREF = "browser.sessionstore.interval";
|
||||||
|
|
||||||
|
// Observer that updates the cached value when the preference changes.
|
||||||
|
Services.prefs.addObserver(PREF, () => {
|
||||||
|
this.gInterval = Services.prefs.getIntPref(PREF);
|
||||||
|
|
||||||
|
// Cancel any pending runs and call runDelayed() with
|
||||||
|
// zero to apply the newly configured interval.
|
||||||
|
SessionSaverInternal.cancel();
|
||||||
|
SessionSaverInternal.runDelayed(0);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
return Services.prefs.getIntPref(PREF);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wrap a string as a nsISupports.
|
||||||
|
function createSupportsString(data) {
|
||||||
|
let string = Cc["@mozilla.org/supports-string;1"]
|
||||||
|
.createInstance(Ci.nsISupportsString);
|
||||||
|
string.data = data;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify observers about a given topic with a given subject.
|
||||||
|
function notify(subject, topic) {
|
||||||
|
Services.obs.notifyObservers(subject, topic, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TelemetryStopwatch helper functions.
|
||||||
|
function stopWatch(method) {
|
||||||
|
return function (...histograms) {
|
||||||
|
for (let hist of histograms) {
|
||||||
|
TelemetryStopwatch[method]("FX_SESSION_RESTORE_" + hist);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let stopWatchStart = stopWatch("start");
|
||||||
|
let stopWatchCancel = stopWatch("cancel");
|
||||||
|
let stopWatchFinish = stopWatch("finish");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The external API implemented by the SessionSaver module.
|
||||||
|
*/
|
||||||
|
this.SessionSaver = Object.freeze({
|
||||||
|
/**
|
||||||
|
* Immediately saves the current session to disk.
|
||||||
|
*/
|
||||||
|
run: function () {
|
||||||
|
SessionSaverInternal.run();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current session to disk delayed by a given amount of time. Should
|
||||||
|
* another delayed run be scheduled already, we will ignore the given delay
|
||||||
|
* and state saving may occur a little earlier.
|
||||||
|
*/
|
||||||
|
runDelayed: function () {
|
||||||
|
SessionSaverInternal.runDelayed();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last save time to the current time. This will cause us to wait for
|
||||||
|
* at least the configured interval when runDelayed() is called next.
|
||||||
|
*/
|
||||||
|
updateLastSaveTime: function () {
|
||||||
|
SessionSaverInternal.updateLastSaveTime();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last save time to zero. This will cause us to
|
||||||
|
* immediately save the next time runDelayed() is called.
|
||||||
|
*/
|
||||||
|
clearLastSaveTime: function () {
|
||||||
|
SessionSaverInternal.clearLastSaveTime();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels all pending session saves.
|
||||||
|
*/
|
||||||
|
cancel: function () {
|
||||||
|
SessionSaverInternal.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal API.
|
||||||
|
*/
|
||||||
|
let SessionSaverInternal = {
|
||||||
|
/**
|
||||||
|
* The timeout ID referencing an active timer for a delayed save. When no
|
||||||
|
* save is pending, this is null.
|
||||||
|
*/
|
||||||
|
_timeoutID: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A timestamp that keeps track of when we saved the session last. We will
|
||||||
|
* this to determine the correct interval between delayed saves to not deceed
|
||||||
|
* the configured session write interval.
|
||||||
|
*/
|
||||||
|
_lastSaveTime: 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately saves the current session to disk.
|
||||||
|
*/
|
||||||
|
run: function () {
|
||||||
|
this._saveState(true /* force-update all windows */);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current session to disk delayed by a given amount of time. Should
|
||||||
|
* another delayed run be scheduled already, we will ignore the given delay
|
||||||
|
* and state saving may occur a little earlier.
|
||||||
|
*
|
||||||
|
* @param delay (optional)
|
||||||
|
* The minimum delay in milliseconds to wait for until we collect and
|
||||||
|
* save the current session.
|
||||||
|
*/
|
||||||
|
runDelayed: function (delay = 2000) {
|
||||||
|
// Bail out if there's a pending run.
|
||||||
|
if (this._timeoutID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interval until the next disk operation is allowed.
|
||||||
|
delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0);
|
||||||
|
|
||||||
|
// Schedule a state save.
|
||||||
|
this._timeoutID = setTimeout(() => this._saveState(), delay);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last save time to the current time. This will cause us to wait for
|
||||||
|
* at least the configured interval when runDelayed() is called next.
|
||||||
|
*/
|
||||||
|
updateLastSaveTime: function () {
|
||||||
|
this._lastSaveTime = Date.now();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last save time to zero. This will cause us to
|
||||||
|
* immediately save the next time runDelayed() is called.
|
||||||
|
*/
|
||||||
|
clearLastSaveTime: function () {
|
||||||
|
this._lastSaveTime = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels all pending session saves.
|
||||||
|
*/
|
||||||
|
cancel: function () {
|
||||||
|
clearTimeout(this._timeoutID);
|
||||||
|
this._timeoutID = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current session state. Collects data and writes to disk.
|
||||||
|
*
|
||||||
|
* @param forceUpdateAllWindows (optional)
|
||||||
|
* Forces us to recollect data for all windows and will bypass and
|
||||||
|
* update the corresponding caches.
|
||||||
|
*/
|
||||||
|
_saveState: function (forceUpdateAllWindows = false) {
|
||||||
|
// Cancel any pending timeouts or just clear
|
||||||
|
// the timeout if this is why we've been called.
|
||||||
|
this.cancel();
|
||||||
|
|
||||||
|
stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
|
||||||
|
|
||||||
|
let state = SessionStore.getCurrentState(forceUpdateAllWindows);
|
||||||
|
if (!state) {
|
||||||
|
stopWatchCancel("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forget about private windows.
|
||||||
|
for (let i = state.windows.length - 1; i >= 0; i--) {
|
||||||
|
if (state.windows[i].isPrivate) {
|
||||||
|
state.windows.splice(i, 1);
|
||||||
|
if (state.selectedWindow >= i) {
|
||||||
|
state.selectedWindow--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef XP_MACOSX
|
||||||
|
// Don't save invalid states.
|
||||||
|
// Looks like we currently have private windows, only.
|
||||||
|
if (state.windows.length == 0) {
|
||||||
|
stopWatchCancel("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Remove private windows from the list of closed windows.
|
||||||
|
for (let i = state._closedWindows.length - 1; i >= 0; i--) {
|
||||||
|
if (state._closedWindows[i].isPrivate) {
|
||||||
|
state._closedWindows.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef XP_MACOSX
|
||||||
|
// We want to restore closed windows that are marked with _shouldRestore.
|
||||||
|
// We're doing this here because we want to control this only when saving
|
||||||
|
// the file.
|
||||||
|
while (state._closedWindows.length) {
|
||||||
|
let i = state._closedWindows.length - 1;
|
||||||
|
|
||||||
|
if (!state._closedWindows[i]._shouldRestore) {
|
||||||
|
// We only need to go until _shouldRestore
|
||||||
|
// is falsy since we're going in reverse.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete state._closedWindows[i]._shouldRestore;
|
||||||
|
state.windows.unshift(state._closedWindows.pop());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
|
||||||
|
this._writeState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the given state object to disk.
|
||||||
|
*/
|
||||||
|
_writeState: function (state) {
|
||||||
|
stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
|
||||||
|
let data = JSON.stringify(state);
|
||||||
|
stopWatchFinish("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
|
||||||
|
|
||||||
|
// Give observers a chance to modify session data.
|
||||||
|
data = this._notifyObserversBeforeStateWrite(data);
|
||||||
|
|
||||||
|
// Don't touch the file if an observer has deleted all state data.
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write (atomically) to a session file, using a tmp file. Once the session
|
||||||
|
// file is successfully updated, save the time stamp of the last save and
|
||||||
|
// notify the observers.
|
||||||
|
_SessionFile.write(data).then(() => {
|
||||||
|
this.updateLastSaveTime();
|
||||||
|
notify(null, "sessionstore-state-write-complete");
|
||||||
|
}, Cu.reportError);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify sessionstore-state-write observer and give them a
|
||||||
|
* chance to modify session data before we'll write it to disk.
|
||||||
|
*/
|
||||||
|
_notifyObserversBeforeStateWrite: function (data) {
|
||||||
|
let stateString = createSupportsString(data);
|
||||||
|
notify(stateString, "sessionstore-state-write");
|
||||||
|
return stateString.data;
|
||||||
|
}
|
||||||
|
};
|
@ -118,8 +118,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
|
|||||||
"resource:///modules/devtools/scratchpad-manager.jsm");
|
"resource:///modules/devtools/scratchpad-manager.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
|
XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
|
||||||
"resource:///modules/sessionstore/DocumentUtils.jsm");
|
"resource:///modules/sessionstore/DocumentUtils.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
|
||||||
|
"resource:///modules/sessionstore/SessionSaver.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
|
||||||
"resource:///modules/sessionstore/SessionStorage.jsm");
|
"resource:///modules/sessionstore/SessionStorage.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
|
||||||
|
"resource:///modules/sessionstore/SessionCookies.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
|
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
|
||||||
"resource:///modules/sessionstore/_SessionFile.jsm");
|
"resource:///modules/sessionstore/_SessionFile.jsm");
|
||||||
|
|
||||||
@ -261,6 +265,10 @@ this.SessionStore = {
|
|||||||
return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref);
|
return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getCurrentState: function (aUpdateAll) {
|
||||||
|
return SessionStoreInternal.getCurrentState(aUpdateAll);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backstage pass to implementation details, used for testing purpose.
|
* Backstage pass to implementation details, used for testing purpose.
|
||||||
* Controlled by preference "browser.sessionstore.testmode".
|
* Controlled by preference "browser.sessionstore.testmode".
|
||||||
@ -286,9 +294,6 @@ let SessionStoreInternal = {
|
|||||||
// set default load state
|
// set default load state
|
||||||
_loadState: STATE_STOPPED,
|
_loadState: STATE_STOPPED,
|
||||||
|
|
||||||
// initial state to restore after startup
|
|
||||||
_initialState: null,
|
|
||||||
|
|
||||||
// During the initial restore and setBrowserState calls tracks the number of
|
// During the initial restore and setBrowserState calls tracks the number of
|
||||||
// windows yet to be restored
|
// windows yet to be restored
|
||||||
_restoreCount: -1,
|
_restoreCount: -1,
|
||||||
@ -296,9 +301,6 @@ let SessionStoreInternal = {
|
|||||||
// whether a setBrowserState call is in progress
|
// whether a setBrowserState call is in progress
|
||||||
_browserSetState: false,
|
_browserSetState: false,
|
||||||
|
|
||||||
// time in milliseconds (Date.now()) when the session was last written to file
|
|
||||||
_lastSaveTime: 0,
|
|
||||||
|
|
||||||
// time in milliseconds when the session was started (saved across sessions),
|
// time in milliseconds when the session was started (saved across sessions),
|
||||||
// defaults to now if no session was restored or timestamp doesn't exist
|
// defaults to now if no session was restored or timestamp doesn't exist
|
||||||
_sessionStartTime: Date.now(),
|
_sessionStartTime: Date.now(),
|
||||||
@ -389,11 +391,11 @@ let SessionStoreInternal = {
|
|||||||
// 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.
|
||||||
this.initSession();
|
let initialState = this.initSession();
|
||||||
|
|
||||||
// Start tracking the given (initial) browser window.
|
// Start tracking the given (initial) browser window.
|
||||||
if (!aWindow.closed) {
|
if (!aWindow.closed) {
|
||||||
this.onLoad(aWindow);
|
this.onLoad(aWindow, initialState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let everyone know we're done.
|
// Let everyone know we're done.
|
||||||
@ -402,74 +404,76 @@ let SessionStoreInternal = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
initSession: function ssi_initSession() {
|
initSession: function ssi_initSession() {
|
||||||
|
let state;
|
||||||
let ss = gSessionStartup;
|
let ss = gSessionStartup;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ss.doRestore() ||
|
if (ss.doRestore() ||
|
||||||
ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION)
|
ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION)
|
||||||
this._initialState = ss.state;
|
state = ss.state;
|
||||||
}
|
}
|
||||||
catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
|
catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
|
||||||
|
|
||||||
if (this._initialState) {
|
if (state) {
|
||||||
try {
|
try {
|
||||||
// If we're doing a DEFERRED session, then we want to pull pinned tabs
|
// If we're doing a DEFERRED session, then we want to pull pinned tabs
|
||||||
// out so they can be restored.
|
// out so they can be restored.
|
||||||
if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
|
if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
|
||||||
let [iniState, remainingState] = this._prepDataForDeferredRestore(this._initialState);
|
let [iniState, remainingState] = this._prepDataForDeferredRestore(state);
|
||||||
// If we have a iniState with windows, that means that we have windows
|
// If we have a iniState with windows, that means that we have windows
|
||||||
// with app tabs to restore.
|
// with app tabs to restore.
|
||||||
if (iniState.windows.length)
|
if (iniState.windows.length)
|
||||||
this._initialState = iniState;
|
state = iniState;
|
||||||
else
|
else
|
||||||
this._initialState = null;
|
state = null;
|
||||||
if (remainingState.windows.length)
|
if (remainingState.windows.length)
|
||||||
this._lastSessionState = remainingState;
|
this._lastSessionState = remainingState;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Get the last deferred session in case the user still wants to
|
// Get the last deferred session in case the user still wants to
|
||||||
// restore it
|
// restore it
|
||||||
this._lastSessionState = this._initialState.lastSessionState;
|
this._lastSessionState = state.lastSessionState;
|
||||||
|
|
||||||
let lastSessionCrashed =
|
let lastSessionCrashed =
|
||||||
this._initialState.session && this._initialState.session.state &&
|
state.session && state.session.state &&
|
||||||
this._initialState.session.state == STATE_RUNNING_STR;
|
state.session.state == STATE_RUNNING_STR;
|
||||||
if (lastSessionCrashed) {
|
if (lastSessionCrashed) {
|
||||||
this._recentCrashes = (this._initialState.session &&
|
this._recentCrashes = (state.session &&
|
||||||
this._initialState.session.recentCrashes || 0) + 1;
|
state.session.recentCrashes || 0) + 1;
|
||||||
|
|
||||||
if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
|
if (this._needsRestorePage(state, this._recentCrashes)) {
|
||||||
// replace the crashed session with a restore-page-only session
|
// replace the crashed session with a restore-page-only session
|
||||||
let pageData = {
|
let pageData = {
|
||||||
url: "about:sessionrestore",
|
url: "about:sessionrestore",
|
||||||
formdata: {
|
formdata: {
|
||||||
id: { "sessionData": this._initialState },
|
id: { "sessionData": state },
|
||||||
xpath: {}
|
xpath: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
|
state = { windows: [{ tabs: [{ entries: [pageData] }] }] };
|
||||||
} else if (this._hasSingleTabWithURL(this._initialState.windows,
|
} else if (this._hasSingleTabWithURL(state.windows,
|
||||||
"about:welcomeback")) {
|
"about:welcomeback")) {
|
||||||
// On a single about:welcomeback URL that crashed, replace about:welcomeback
|
// On a single about:welcomeback URL that crashed, replace about:welcomeback
|
||||||
// with about:sessionrestore, to make clear to the user that we crashed.
|
// with about:sessionrestore, to make clear to the user that we crashed.
|
||||||
this._initialState.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
|
state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the session start time from the previous state
|
// Load the session start time from the previous state
|
||||||
this._sessionStartTime = this._initialState.session &&
|
this._sessionStartTime = state.session &&
|
||||||
this._initialState.session.startTime ||
|
state.session.startTime ||
|
||||||
this._sessionStartTime;
|
this._sessionStartTime;
|
||||||
|
|
||||||
// make sure that at least the first window doesn't have anything hidden
|
// make sure that at least the first window doesn't have anything hidden
|
||||||
delete this._initialState.windows[0].hidden;
|
delete state.windows[0].hidden;
|
||||||
// Since nothing is hidden in the first window, it cannot be a popup
|
// Since nothing is hidden in the first window, it cannot be a popup
|
||||||
delete this._initialState.windows[0].isPopup;
|
delete state.windows[0].isPopup;
|
||||||
// We don't want to minimize and then open a window at startup.
|
// We don't want to minimize and then open a window at startup.
|
||||||
if (this._initialState.windows[0].sizemode == "minimized")
|
if (state.windows[0].sizemode == "minimized")
|
||||||
this._initialState.windows[0].sizemode = "normal";
|
state.windows[0].sizemode = "normal";
|
||||||
// clear any lastSessionWindowID attributes since those don't matter
|
// clear any lastSessionWindowID attributes since those don't matter
|
||||||
// during normal restore
|
// during normal restore
|
||||||
this._initialState.windows.forEach(function(aWindow) {
|
state.windows.forEach(function(aWindow) {
|
||||||
delete aWindow.__lastSessionWindowID;
|
delete aWindow.__lastSessionWindowID;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -484,8 +488,9 @@ let SessionStoreInternal = {
|
|||||||
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
|
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
|
||||||
|
|
||||||
this._performUpgradeBackup();
|
this._performUpgradeBackup();
|
||||||
|
|
||||||
this._sessionInitialized = true;
|
this._sessionInitialized = true;
|
||||||
|
|
||||||
|
return state;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -526,13 +531,6 @@ let SessionStoreInternal = {
|
|||||||
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
|
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
// minimal interval between two save operations (in milliseconds)
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "_interval", function () {
|
|
||||||
// used often, so caching/observing instead of fetching on-demand
|
|
||||||
this._prefBranch.addObserver("sessionstore.interval", this, true);
|
|
||||||
return this._prefBranch.getIntPref("sessionstore.interval");
|
|
||||||
});
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "_max_tabs_undo", function () {
|
XPCOMUtils.defineLazyGetter(this, "_max_tabs_undo", function () {
|
||||||
this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
|
this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
|
||||||
return this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
|
return this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
|
||||||
@ -555,17 +553,14 @@ let SessionStoreInternal = {
|
|||||||
|
|
||||||
// save all data for session resuming
|
// save all data for session resuming
|
||||||
if (this._sessionInitialized) {
|
if (this._sessionInitialized) {
|
||||||
this.saveState(true);
|
SessionSaver.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear out priority queue in case it's still holding refs
|
// clear out priority queue in case it's still holding refs
|
||||||
TabRestoreQueue.reset();
|
TabRestoreQueue.reset();
|
||||||
|
|
||||||
// Make sure to break our cycle with the save timer
|
// Make sure to cancel pending saves.
|
||||||
if (this._saveTimer) {
|
SessionSaver.cancel();
|
||||||
this._saveTimer.cancel();
|
|
||||||
this._saveTimer = null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -700,8 +695,10 @@ let SessionStoreInternal = {
|
|||||||
* Set up event listeners for this window's tabs
|
* Set up event listeners for this window's tabs
|
||||||
* @param aWindow
|
* @param aWindow
|
||||||
* Window reference
|
* Window reference
|
||||||
|
* @param aInitialState
|
||||||
|
* The initial state to be loaded after startup (optional)
|
||||||
*/
|
*/
|
||||||
onLoad: function ssi_onLoad(aWindow) {
|
onLoad: function ssi_onLoad(aWindow, aInitialState = null) {
|
||||||
// return if window has already been initialized
|
// return if window has already been initialized
|
||||||
if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
|
if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
|
||||||
return;
|
return;
|
||||||
@ -728,27 +725,26 @@ let SessionStoreInternal = {
|
|||||||
// perform additional initialization when the first window is loading
|
// perform additional initialization when the first window is loading
|
||||||
if (this._loadState == STATE_STOPPED) {
|
if (this._loadState == STATE_STOPPED) {
|
||||||
this._loadState = STATE_RUNNING;
|
this._loadState = STATE_RUNNING;
|
||||||
this._lastSaveTime = Date.now();
|
SessionSaver.updateLastSaveTime();
|
||||||
|
|
||||||
// restore a crashed session resp. resume the last session if requested
|
// restore a crashed session resp. resume the last session if requested
|
||||||
if (this._initialState) {
|
if (aInitialState) {
|
||||||
if (isPrivateWindow) {
|
if (isPrivateWindow) {
|
||||||
// We're starting with a single private window. Save the state we
|
// We're starting with a single private window. Save the state we
|
||||||
// actually wanted to restore so that we can do it later in case
|
// actually wanted to restore so that we can do it later in case
|
||||||
// the user opens another, non-private window.
|
// the user opens another, non-private window.
|
||||||
this._deferredInitialState = this._initialState;
|
this._deferredInitialState = aInitialState;
|
||||||
this._initialState = null;
|
|
||||||
|
|
||||||
// Nothing to restore now, notify observers things are complete.
|
// Nothing to restore now, notify observers things are complete.
|
||||||
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
|
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
|
||||||
} else {
|
} else {
|
||||||
TelemetryTimestamps.add("sessionRestoreRestoring");
|
TelemetryTimestamps.add("sessionRestoreRestoring");
|
||||||
// make sure that the restored tabs are first in the window
|
// make sure that the restored tabs are first in the window
|
||||||
this._initialState._firstTabs = true;
|
aInitialState._firstTabs = true;
|
||||||
this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
|
this._restoreCount = aInitialState.windows ? aInitialState.windows.length : 0;
|
||||||
this.restoreWindow(aWindow, this._initialState,
|
|
||||||
this._isCmdLineEmpty(aWindow, this._initialState));
|
let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
|
||||||
this._initialState = null;
|
this.restoreWindow(aWindow, aInitialState, overwrite);
|
||||||
|
|
||||||
// _loadState changed from "stopped" to "running". Save the session's
|
// _loadState changed from "stopped" to "running". Save the session's
|
||||||
// load state immediately so that crashes happening during startup
|
// load state immediately so that crashes happening during startup
|
||||||
@ -760,8 +756,8 @@ let SessionStoreInternal = {
|
|||||||
// Nothing to restore, notify observers things are complete.
|
// Nothing to restore, notify observers things are complete.
|
||||||
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
|
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
|
||||||
|
|
||||||
// the next delayed save request should execute immediately
|
// The next delayed save request should execute immediately.
|
||||||
this._lastSaveTime -= this._interval;
|
SessionSaver.clearLastSaveTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// this window was opened by _openWindowWithState
|
// this window was opened by _openWindowWithState
|
||||||
@ -1032,7 +1028,6 @@ let SessionStoreInternal = {
|
|||||||
* On purge of session history
|
* On purge of session history
|
||||||
*/
|
*/
|
||||||
onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
|
onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
|
||||||
var _this = this;
|
|
||||||
_SessionFile.wipe();
|
_SessionFile.wipe();
|
||||||
// If the browser is shutting down, simply return after clearing the
|
// If the browser is shutting down, simply return after clearing the
|
||||||
// session data on disk as this notification fires after the
|
// session data on disk as this notification fires after the
|
||||||
@ -1064,10 +1059,11 @@ let SessionStoreInternal = {
|
|||||||
this._closedWindows = [];
|
this._closedWindows = [];
|
||||||
// give the tabbrowsers a chance to clear their histories first
|
// give the tabbrowsers a chance to clear their histories first
|
||||||
var win = this._getMostRecentBrowserWindow();
|
var win = this._getMostRecentBrowserWindow();
|
||||||
if (win)
|
if (win) {
|
||||||
win.setTimeout(function() { _this.saveState(true); }, 0);
|
win.setTimeout(() => SessionSaver.run(), 0);
|
||||||
else if (this._loadState == STATE_RUNNING)
|
} else if (this._loadState == STATE_RUNNING) {
|
||||||
this.saveState(true);
|
SessionSaver.run();
|
||||||
|
}
|
||||||
|
|
||||||
this._clearRestoringWindows();
|
this._clearRestoringWindows();
|
||||||
},
|
},
|
||||||
@ -1124,8 +1120,10 @@ let SessionStoreInternal = {
|
|||||||
this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
|
this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._loadState == STATE_RUNNING)
|
|
||||||
this.saveState(true);
|
if (this._loadState == STATE_RUNNING) {
|
||||||
|
SessionSaver.run();
|
||||||
|
}
|
||||||
|
|
||||||
this._clearRestoringWindows();
|
this._clearRestoringWindows();
|
||||||
},
|
},
|
||||||
@ -1149,26 +1147,9 @@ let SessionStoreInternal = {
|
|||||||
this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
|
this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
|
||||||
this._capClosedWindows();
|
this._capClosedWindows();
|
||||||
break;
|
break;
|
||||||
case "sessionstore.interval":
|
|
||||||
this._interval = this._prefBranch.getIntPref("sessionstore.interval");
|
|
||||||
// reset timer and save
|
|
||||||
if (this._saveTimer) {
|
|
||||||
this._saveTimer.cancel();
|
|
||||||
this._saveTimer = null;
|
|
||||||
}
|
|
||||||
this.saveStateDelayed(null, -1);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* On timer callback
|
|
||||||
*/
|
|
||||||
onTimerCallback: function ssi_onTimerCallback() {
|
|
||||||
this._saveTimer = null;
|
|
||||||
this.saveState();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set up listeners for a new tab
|
* set up listeners for a new tab
|
||||||
* @param aWindow
|
* @param aWindow
|
||||||
@ -1309,7 +1290,7 @@ let SessionStoreInternal = {
|
|||||||
|
|
||||||
TabStateCache.delete(aBrowser);
|
TabStateCache.delete(aBrowser);
|
||||||
|
|
||||||
this.saveStateDelayed(aWindow, 3000);
|
this.saveStateDelayed(aWindow);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1371,7 +1352,12 @@ let SessionStoreInternal = {
|
|||||||
/* ........ nsISessionStore API .............. */
|
/* ........ nsISessionStore API .............. */
|
||||||
|
|
||||||
getBrowserState: function ssi_getBrowserState() {
|
getBrowserState: function ssi_getBrowserState() {
|
||||||
return this._toJSONString(this._getCurrentState());
|
let state = this.getCurrentState();
|
||||||
|
|
||||||
|
// Don't include the last session state in getBrowserState().
|
||||||
|
delete state.lastSessionState;
|
||||||
|
|
||||||
|
return this._toJSONString(state);
|
||||||
},
|
},
|
||||||
|
|
||||||
setBrowserState: function ssi_setBrowserState(aState) {
|
setBrowserState: function ssi_setBrowserState(aState) {
|
||||||
@ -2402,21 +2388,6 @@ let SessionStoreInternal = {
|
|||||||
* { tabs: [ ... ], etc. }
|
* { tabs: [ ... ], etc. }
|
||||||
*/
|
*/
|
||||||
_updateCookies: function ssi_updateCookies(aWindows) {
|
_updateCookies: function ssi_updateCookies(aWindows) {
|
||||||
function addCookieToHash(aHash, aHost, aPath, aName, aCookie) {
|
|
||||||
// lazily build up a 3-dimensional hash, with
|
|
||||||
// aHost, aPath, and aName as keys
|
|
||||||
if (!aHash[aHost])
|
|
||||||
aHash[aHost] = {};
|
|
||||||
if (!aHash[aHost][aPath])
|
|
||||||
aHash[aHost][aPath] = {};
|
|
||||||
aHash[aHost][aPath][aName] = aCookie;
|
|
||||||
}
|
|
||||||
|
|
||||||
var jscookies = {};
|
|
||||||
var _this = this;
|
|
||||||
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
|
|
||||||
var MAX_EXPIRY = Math.pow(2, 62);
|
|
||||||
|
|
||||||
for (let window of aWindows) {
|
for (let window of aWindows) {
|
||||||
window.cookies = [];
|
window.cookies = [];
|
||||||
|
|
||||||
@ -2429,35 +2400,12 @@ let SessionStoreInternal = {
|
|||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
for (var [host, isPinned] in Iterator(hosts)) {
|
for (var [host, isPinned] in Iterator(hosts)) {
|
||||||
let list;
|
for (let cookie of SessionCookies.getCookiesForHost(host)) {
|
||||||
try {
|
|
||||||
list = Services.cookies.getCookiesFromHost(host);
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
debug("getCookiesFromHost failed. Host: " + host);
|
|
||||||
}
|
|
||||||
while (list && list.hasMoreElements()) {
|
|
||||||
var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
|
|
||||||
// window._hosts will only have hosts with the right privacy rules,
|
// window._hosts will only have hosts with the right privacy rules,
|
||||||
// so there is no need to do anything special with this call to
|
// so there is no need to do anything special with this call to
|
||||||
// checkPrivacyLevel.
|
// checkPrivacyLevel.
|
||||||
if (cookie.isSession && _this.checkPrivacyLevel(cookie.isSecure, isPinned)) {
|
if (this.checkPrivacyLevel(cookie.secure, isPinned)) {
|
||||||
// use the cookie's host, path, and name as keys into a hash,
|
window.cookies.push(cookie);
|
||||||
// to make sure we serialize each cookie only once
|
|
||||||
if (!(cookie.host in jscookies &&
|
|
||||||
cookie.path in jscookies[cookie.host] &&
|
|
||||||
cookie.name in jscookies[cookie.host][cookie.path])) {
|
|
||||||
var jscookie = { "host": cookie.host, "value": cookie.value };
|
|
||||||
// only add attributes with non-default values (saving a few bits)
|
|
||||||
if (cookie.path) jscookie.path = cookie.path;
|
|
||||||
if (cookie.name) jscookie.name = cookie.name;
|
|
||||||
if (cookie.isSecure) jscookie.secure = true;
|
|
||||||
if (cookie.isHttpOnly) jscookie.httponly = true;
|
|
||||||
if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
|
|
||||||
|
|
||||||
addCookieToHash(jscookies, cookie.host, cookie.path, cookie.name, jscookie);
|
|
||||||
}
|
|
||||||
window.cookies.push(jscookies[cookie.host][cookie.path][cookie.name]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2501,7 +2449,7 @@ let SessionStoreInternal = {
|
|||||||
* Bool update all windows
|
* Bool update all windows
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
_getCurrentState: function ssi_getCurrentState(aUpdateAll) {
|
getCurrentState: function (aUpdateAll) {
|
||||||
this._handleClosedWindows();
|
this._handleClosedWindows();
|
||||||
|
|
||||||
var activeWindow = this._getMostRecentBrowserWindow();
|
var activeWindow = this._getMostRecentBrowserWindow();
|
||||||
@ -2587,13 +2535,20 @@ let SessionStoreInternal = {
|
|||||||
// get open Scratchpad window states too
|
// get open Scratchpad window states too
|
||||||
var scratchpads = ScratchpadManager.getSessionState();
|
var scratchpads = ScratchpadManager.getSessionState();
|
||||||
|
|
||||||
return {
|
let state = {
|
||||||
windows: total,
|
windows: total,
|
||||||
selectedWindow: ix + 1,
|
selectedWindow: ix + 1,
|
||||||
_closedWindows: lastClosedWindowsCopy,
|
_closedWindows: lastClosedWindowsCopy,
|
||||||
session: session,
|
session: session,
|
||||||
scratchpads: scratchpads
|
scratchpads: scratchpads
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Persist the last session if we deferred restoring it
|
||||||
|
if (this._lastSessionState) {
|
||||||
|
state.lastSessionState = this._lastSessionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3649,146 +3604,22 @@ let SessionStoreInternal = {
|
|||||||
/* ........ Disk Access .............. */
|
/* ........ Disk Access .............. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save state delayed by N ms
|
* Save the current session state to disk, after a delay.
|
||||||
* marks window as dirty (i.e. data update can't be skipped)
|
*
|
||||||
* @param aWindow
|
* @param aWindow (optional)
|
||||||
* Window reference
|
* Will mark the given window as dirty so that we will recollect its
|
||||||
* @param aDelay
|
* data before we start writing.
|
||||||
* Milliseconds to delay
|
|
||||||
*/
|
*/
|
||||||
saveStateDelayed: function ssi_saveStateDelayed(aWindow = null, aDelay = 2000) {
|
saveStateDelayed: function (aWindow = null) {
|
||||||
if (aWindow) {
|
if (aWindow) {
|
||||||
DirtyWindows.add(aWindow);
|
DirtyWindows.add(aWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._saveTimer) {
|
SessionSaver.runDelayed();
|
||||||
// interval until the next disk operation is allowed
|
|
||||||
var minimalDelay = this._lastSaveTime + this._interval - Date.now();
|
|
||||||
|
|
||||||
// if we have to wait, set a timer, otherwise saveState directly
|
|
||||||
aDelay = Math.max(minimalDelay, aDelay);
|
|
||||||
if (aDelay > 0) {
|
|
||||||
this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
||||||
this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.saveState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* save state to disk
|
|
||||||
* @param aUpdateAll
|
|
||||||
* Bool update all windows
|
|
||||||
*/
|
|
||||||
saveState: function ssi_saveState(aUpdateAll) {
|
|
||||||
// If crash recovery is disabled, we only want to resume with pinned tabs
|
|
||||||
// if we crash.
|
|
||||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_MS");
|
|
||||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
|
|
||||||
|
|
||||||
var oState = this._getCurrentState(aUpdateAll);
|
|
||||||
if (!oState) {
|
|
||||||
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_MS");
|
|
||||||
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forget about private windows.
|
|
||||||
for (let i = oState.windows.length - 1; i >= 0; i--) {
|
|
||||||
if (oState.windows[i].isPrivate) {
|
|
||||||
oState.windows.splice(i, 1);
|
|
||||||
if (oState.selectedWindow >= i) {
|
|
||||||
oState.selectedWindow--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef XP_MACOSX
|
|
||||||
// Don't save invalid states.
|
|
||||||
// Looks like we currently have private windows, only.
|
|
||||||
if (oState.windows.length == 0) {
|
|
||||||
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_MS");
|
|
||||||
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (let i = oState._closedWindows.length - 1; i >= 0; i--) {
|
|
||||||
if (oState._closedWindows[i].isPrivate) {
|
|
||||||
oState._closedWindows.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef XP_MACOSX
|
|
||||||
// We want to restore closed windows that are marked with _shouldRestore.
|
|
||||||
// We're doing this here because we want to control this only when saving
|
|
||||||
// the file.
|
|
||||||
while (oState._closedWindows.length) {
|
|
||||||
let i = oState._closedWindows.length - 1;
|
|
||||||
if (oState._closedWindows[i]._shouldRestore) {
|
|
||||||
delete oState._closedWindows[i]._shouldRestore;
|
|
||||||
oState.windows.unshift(oState._closedWindows.pop());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// We only need to go until we hit !needsRestore since we're going in reverse
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Persist the last session if we deferred restoring it
|
|
||||||
if (this._lastSessionState)
|
|
||||||
oState.lastSessionState = this._lastSessionState;
|
|
||||||
|
|
||||||
TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_DATA_MS");
|
|
||||||
TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
|
|
||||||
|
|
||||||
this._saveStateObject(oState);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* write a state object to disk
|
|
||||||
*/
|
|
||||||
_saveStateObject: function ssi_saveStateObject(aStateObj) {
|
|
||||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_SERIALIZE_DATA_MS");
|
|
||||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_SERIALIZE_DATA_LONGEST_OP_MS");
|
|
||||||
let data = this._toJSONString(aStateObj);
|
|
||||||
TelemetryStopwatch.finish("FX_SESSION_RESTORE_SERIALIZE_DATA_MS");
|
|
||||||
TelemetryStopwatch.finish("FX_SESSION_RESTORE_SERIALIZE_DATA_LONGEST_OP_MS");
|
|
||||||
|
|
||||||
let stateString = this._createSupportsString(data);
|
|
||||||
Services.obs.notifyObservers(stateString, "sessionstore-state-write", "");
|
|
||||||
data = stateString.data;
|
|
||||||
|
|
||||||
// Don't touch the file if an observer has deleted all state data.
|
|
||||||
if (!data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write (atomically) to a session file, using a tmp file.
|
|
||||||
let promise = _SessionFile.write(data);
|
|
||||||
|
|
||||||
// Once the session file is successfully updated, save the time stamp of the
|
|
||||||
// last save and notify the observers.
|
|
||||||
promise = promise.then(() => {
|
|
||||||
this._lastSaveTime = Date.now();
|
|
||||||
Services.obs.notifyObservers(null, "sessionstore-state-write-complete",
|
|
||||||
"");
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ........ Auxiliary Functions .............. */
|
/* ........ Auxiliary Functions .............. */
|
||||||
|
|
||||||
// Wrap a string as a nsISupports
|
|
||||||
_createSupportsString: function ssi_createSupportsString(aData) {
|
|
||||||
let string = Cc["@mozilla.org/supports-string;1"]
|
|
||||||
.createInstance(Ci.nsISupportsString);
|
|
||||||
string.data = aData;
|
|
||||||
return string;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* call a callback for all currently opened browser windows
|
* call a callback for all currently opened browser windows
|
||||||
* (might miss the most recent one)
|
* (might miss the most recent one)
|
||||||
@ -4647,7 +4478,7 @@ let DyingWindowCache = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// A weak set of dirty windows. We use it to determine which windows we need to
|
// A weak set of dirty windows. We use it to determine which windows we need to
|
||||||
// recollect data for when _getCurrentState() is called.
|
// recollect data for when getCurrentState() is called.
|
||||||
let DirtyWindows = {
|
let DirtyWindows = {
|
||||||
_data: new WeakMap(),
|
_data: new WeakMap(),
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ JS_MODULES_PATH = 'modules/sessionstore'
|
|||||||
|
|
||||||
EXTRA_JS_MODULES = [
|
EXTRA_JS_MODULES = [
|
||||||
'DocumentUtils.jsm',
|
'DocumentUtils.jsm',
|
||||||
|
'SessionCookies.jsm',
|
||||||
'SessionMigration.jsm',
|
'SessionMigration.jsm',
|
||||||
'SessionStorage.jsm',
|
'SessionStorage.jsm',
|
||||||
'SessionWorker.js',
|
'SessionWorker.js',
|
||||||
@ -22,6 +23,7 @@ EXTRA_JS_MODULES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_PP_JS_MODULES += [
|
EXTRA_PP_JS_MODULES += [
|
||||||
|
'SessionSaver.jsm',
|
||||||
'SessionStore.jsm',
|
'SessionStore.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -343,13 +343,9 @@ HiddenBrowser.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resize: function (width, height) {
|
resize: function (width, height) {
|
||||||
if (this._browser) {
|
this._width = width;
|
||||||
this._browser.style.width = width + "px";
|
this._height = height;
|
||||||
this._browser.style.height = height + "px";
|
this._applySize();
|
||||||
} else {
|
|
||||||
this._width = width;
|
|
||||||
this._height = height;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
@ -357,12 +353,20 @@ HiddenBrowser.prototype = {
|
|||||||
this._timer = clearTimer(this._timer);
|
this._timer = clearTimer(this._timer);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_applySize: function () {
|
||||||
|
if (this._browser) {
|
||||||
|
this._browser.style.width = this._width + "px";
|
||||||
|
this._browser.style.height = this._height + "px";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_createBrowser: function () {
|
_createBrowser: function () {
|
||||||
HostFrame.get().then(aFrame => {
|
HostFrame.get().then(aFrame => {
|
||||||
let doc = aFrame.document;
|
let doc = aFrame.document;
|
||||||
this._browser = doc.createElementNS(XUL_NS, "browser");
|
this._browser = doc.createElementNS(XUL_NS, "browser");
|
||||||
this._browser.setAttribute("type", "content");
|
this._browser.setAttribute("type", "content");
|
||||||
this._browser.setAttribute("src", NEWTAB_URL);
|
this._browser.setAttribute("src", NEWTAB_URL);
|
||||||
|
this._applySize();
|
||||||
doc.getElementById("win").appendChild(this._browser);
|
doc.getElementById("win").appendChild(this._browser);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user