From 5a433f3adb024003af9bef5f02026ed736dbf41b Mon Sep 17 00:00:00 2001 From: "dietrich%mozilla.com" Date: Sun, 18 Jun 2006 00:42:08 +0000 Subject: [PATCH] Split session restore into 2 services (337320), r=mconnor git-svn-id: svn://10.0.0.236/trunk@200222 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/browser/base/content/browser.js | 21 + .../components/sessionstore/Makefile.in | 5 +- .../sessionstore/nsISessionStartup.idl | 57 ++ .../sessionstore/nsISessionStore.idl | 6 +- .../components/sessionstore/src/Makefile.in | 5 +- .../sessionstore/src/nsSessionStartup.js | 580 ++++++++++++++++++ .../sessionstore/src/nsSessionStore.js | 223 +++---- 7 files changed, 747 insertions(+), 150 deletions(-) create mode 100644 mozilla/browser/components/sessionstore/nsISessionStartup.idl create mode 100644 mozilla/browser/components/sessionstore/src/nsSessionStartup.js diff --git a/mozilla/browser/base/content/browser.js b/mozilla/browser/base/content/browser.js index c13235163b0..7b9b48632a9 100644 --- a/mozilla/browser/base/content/browser.js +++ b/mozilla/browser/base/content/browser.js @@ -1100,6 +1100,27 @@ function delayedStartup() // Initialize the microsummary service by retrieving it, prompting its factory // to create its singleton, whose constructor initializes the service. Cc["@mozilla.org/microsummary/service;1"].getService(Ci.nsIMicrosummaryService); + + // initialize the session-restore service + var ssEnabled = true; + var prefBranch = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + try { + ssEnabled = prefBranch.getBoolPref("browser.sessionstore.enabled"); + } catch (ex) {} + + if (ssEnabled) { + var wType = window.document.documentElement.getAttribute("windowtype"); + if (wType == "navigator:browser") { + try { + var ss = Cc["@mozilla.org/browser/sessionstore;1"]. + getService(Ci.nsISessionStore); + ss.init(window); + } catch(ex) { + dump("nsSessionStore could not be initialized: " + ex + "\n"); + } + } + } } function BrowserShutdown() diff --git a/mozilla/browser/components/sessionstore/Makefile.in b/mozilla/browser/components/sessionstore/Makefile.in index e78a71a1319..d83c272318f 100644 --- a/mozilla/browser/components/sessionstore/Makefile.in +++ b/mozilla/browser/components/sessionstore/Makefile.in @@ -43,7 +43,10 @@ include $(DEPTH)/config/autoconf.mk MODULE = sessionstore XPIDL_MODULE = sessionstore -XPIDLSRCS = nsISessionStore.idl +XPIDLSRCS = \ + nsISessionStartup.idl \ + nsISessionStore.idl \ + $(NULL) DIRS = src diff --git a/mozilla/browser/components/sessionstore/nsISessionStartup.idl b/mozilla/browser/components/sessionstore/nsISessionStartup.idl new file mode 100644 index 00000000000..97c52d72acc --- /dev/null +++ b/mozilla/browser/components/sessionstore/nsISessionStartup.idl @@ -0,0 +1,57 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Session Restore component. + * + * The Initial Developer of the Original Code is + * Simon Bünzli + * + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dietrich Ayala + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +/** + * nsISessionStore keeps track of the current browsing state - i.e. + * tab history, cookies, scroll state, form data, POSTDATA and window features + * - and allows to restore everything into one window. + */ + +[scriptable, uuid(41cfaf30-f0ce-11da-8ad9-0800200c9a66)] +interface nsISessionStartup: nsISupports +{ + // Get session state as string + readonly attribute string state; + + /** + * Determine if session should be restored + */ + boolean doRestore(); +}; diff --git a/mozilla/browser/components/sessionstore/nsISessionStore.idl b/mozilla/browser/components/sessionstore/nsISessionStore.idl index f9fa90f1906..8343eb127fe 100644 --- a/mozilla/browser/components/sessionstore/nsISessionStore.idl +++ b/mozilla/browser/components/sessionstore/nsISessionStore.idl @@ -38,17 +38,19 @@ #include "nsISupports.idl" +interface nsIDOMWindow; + /** * nsISessionStore keeps track of the current browsing state - i.e. * tab history, cookies, scroll state, form data, POSTDATA and window features * - and allows to restore everything into one window. */ -[scriptable, uuid(07fada1e-c3a6-11da-95a7-00e08161165f)] +[scriptable, uuid(ca4e1d40-ec1b-11da-8ad9-0800200c9a66)] interface nsISessionStore : nsISupports { /** * Initialize the service */ - void init(); + void init(in nsIDOMWindow aWindow); }; diff --git a/mozilla/browser/components/sessionstore/src/Makefile.in b/mozilla/browser/components/sessionstore/src/Makefile.in index e8be7e92322..38a8c25031f 100644 --- a/mozilla/browser/components/sessionstore/src/Makefile.in +++ b/mozilla/browser/components/sessionstore/src/Makefile.in @@ -39,6 +39,9 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -EXTRA_PP_COMPONENTS = nsSessionStore.js +EXTRA_PP_COMPONENTS = \ + nsSessionStore.js \ + nsSessionStartup.js \ + $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/mozilla/browser/components/sessionstore/src/nsSessionStartup.js b/mozilla/browser/components/sessionstore/src/nsSessionStartup.js new file mode 100644 index 00000000000..75adc83322a --- /dev/null +++ b/mozilla/browser/components/sessionstore/src/nsSessionStartup.js @@ -0,0 +1,580 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the nsSessionStore component. + * + * The Initial Developer of the Original Code is + * Simon Bünzli + * + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dietrich Ayala + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Session Storage and Restoration + * + * Overview + * This service user's session file at startup, and makes a determination as to + * whether the session should be restored. It fill restore the session under + * the circumstances described below. + * + * Crash Detection + * The initial segment of the INI file is has a single field, "state", that + * indicates whether the browser is currently running. When the browser shuts + * down, the field is changed to "stopped". At startup, this field is read, and + * if it's value is "running", then it's assumed that the browser had previously + * crashed, or at the very least that something bad happened, and that we should + * restore the session. + * + * Forced Restarts + * In the event that a restart is required due to application update or extension + * installation, set the browser.sessionstore.resume_session_once pref to true, + * and the session will be restored the next time the browser starts. + * + * Always Resume + * This service will always resume the session if the boolean pref + * browser.sessionstore.resume_session is set to true. + */ + +/* :::::::: Constants and Helpers ::::::::::::::: */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +const CID = Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}"); +const CONTRACT_ID = "@mozilla.org/browser/sessionstartup;1"; +const CLASS_NAME = "Browser Session Startup Service"; + +const STATE_STOPPED = 0; +const STATE_RUNNING = 1; +const STATE_QUITTING = -1; + +const STATE_STOPPED_STR = "stopped"; +const STATE_RUNNING_STR = "running"; + +/* :::::::: Pref Defaults :::::::::::::::::::: */ + +// whether the service is enabled +const DEFAULT_ENABLED = true; + +// resume the current session at startup (otherwise just recover) +const DEFAULT_RESUME_SESSION = false; + +// resume the current session at startup just this once +const DEFAULT_RESUME_SESSION_ONCE = false; + +// resume the current session at startup if it had previously crashed +const DEFAULT_RESUME_FROM_CRASH = true; + +function debug(aMsg) { + aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n"); + Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService) + .logStringMessage(aMsg); +} + +/* :::::::: The Service ::::::::::::::: */ + +function SessionStartup() { +} + +SessionStartup.prototype = { + + // set default load state + _loadState: STATE_STOPPED, + +/* ........ Global Event Handlers .............. */ + + /** + * Initialize the component + */ + init: function sss_init() { + debug("startup init"); + this._prefBranch = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefService). + getBranch("browser."); + this._prefBranch.QueryInterface(Ci.nsIPrefBranch2); + + // if the service is disabled, do not init + if (!this._getPref("sessionstore.enabled", DEFAULT_ENABLED)) + return; + + var observerService = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + + // get file references + var dirService = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile); + this._sessionFileBackup = this._sessionFile.clone(); + this._sessionFile.append("sessionstore.ini"); + this._sessionFileBackup.append("sessionstore.bak"); + + // only read the session file if config allows possibility of restoring + var resumeFromCrash = this._getPref("sessionstore.resume_from_crash", DEFAULT_RESUME_FROM_CRASH); + if (resumeFromCrash || this._doResumeSession()) { + // get string containing session state + this._iniString = this._readFile(this._getSessionFile()); + if (this._iniString) { + try { + // parse the session state into JS objects + this._initialState = IniObjectSerializer.decode(this._iniString); + // set bool detecting crash + this._lastSessionCrashed = + this._initialState.Session && this._initialState.Session.state && + this._initialState.Session.state == STATE_RUNNING_STR; + // invalid .INI file - nothing can be restored + } + catch (ex) { debug("The session file is invalid: " + ex); } + } + } + + // prompt and check prefs + this._doRestore = this._lastSessionCrashed ? this._doRecoverSession() : this._doResumeSession(); + if (this._initialState && !this._doRestore) { + delete this._iniString; // delete state string + delete this._initialState; // delete state + } + }, + + /** + * Handle notifications + */ + observe: function sss_observe(aSubject, aTopic, aData) { + var observerService = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + + // for event listeners + var _this = this; + + switch (aTopic) { + case "app-startup": + observerService.addObserver(this, "final-ui-startup", true); + break; + case "final-ui-startup": + observerService.removeObserver(this, "final-ui-startup"); + this.init(); + break; + } + }, + +/* ........ Public API ................*/ + + /** + * Get the session state as a string + */ + get state() { + return this._iniString; + }, + + /** + * Determine if session should be restored + * @returns bool + */ + doRestore: function sss_doRestore() { + return this._doRestore; + }, + +/* ........ Disk Access .............. */ + + /** + * get session datafile (or its backup) + * @returns nsIFile + */ + _getSessionFile: function sss_getSessionFile(aBackup) { + return aBackup ? this._sessionFileBackup : this._sessionFile; + }, + +/* ........ Auxiliary Functions .............. */ + + /** + * Whether or not to resume session, if not recovering from a crash + * Returns true if: + * - pref is set to always resume sessions + * - pref is set to resume session once + * - user configured startup page to be the last-visited page + * @returns bool + */ + _doResumeSession: function sss_doResumeSession() { + return this._getPref("sessionstore.resume_session", DEFAULT_RESUME_SESSION) || + this._getPref("sessionstore.resume_session_once", DEFAULT_RESUME_SESSION_ONCE) || + this._getPref("startup.page", 1) == 2; + }, + + /** + * prompt user whether or not to restore the previous session, + * if the browser crashed + * @returns bool + */ + _doRecoverSession: function sss_doRecoverSession() { + // do not prompt or resume, post-crash + if (!this._getPref("sessionstore.resume_from_crash", DEFAULT_RESUME_FROM_CRASH)) + return false; + + // if the prompt fails, recover anyway + var recover = true; + // allow extensions to hook in a more elaborate restore prompt + //zeniko: drop this when we're using our own dialog instead of a standard prompt + var dialogURI = this._getPref("sessionstore.restore_prompt_uri"); + + try { + if (dialogURI) { // extension provided dialog + var params = Cc["@mozilla.org/embedcomp/dialogparam;1"]. + createInstance(Ci.nsIDialogParamBlock); + // default to recovering + params.SetInt(0, 0); + Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher). + openWindow(null, dialogURI, "_blank", + "chrome,modal,centerscreen,titlebar", params); + recover = params.GetInt(0) == 0; + } + else { // basic prompt with no options + // get app name from branding properties + var brandStringBundle = this._getStringBundle("chrome://branding/locale/brand.properties"); + var brandShortName = brandStringBundle.GetStringFromName("brandShortName"); + + // create prompt strings + var ssStringBundle = this._getStringBundle("chrome://browser/locale/sessionstore.properties"); + var restoreTitle = ssStringBundle.formatStringFromName("restoreTitle", [brandShortName], 1); + var restoreText = ssStringBundle.formatStringFromName("restoreText", [brandShortName], 1); + var buttonTitle = ssStringBundle.GetStringFromName("buttonTitle"); + + var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Ci.nsIPromptService); + + // set the buttons that will appear on the dialog + var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 + + promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_0; + + var buttonChoice = promptService.confirmEx(null, restoreTitle, restoreText, + flags, null, buttonTitle, null, + null, {}); + recover = (buttonChoice == 1); + } + } + catch (ex) { dump(ex + "\n"); } // if the prompt fails, recover anyway + return recover; + }, + + /** + * Convenience method to get localized string bundles + * @param aURI + * @returns nsIStringBundle + */ + _getStringBundle: function sss_getStringBundle(aURI) { + var bundleService = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService); + var appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"]. + getService(Ci.nsILocaleService).getApplicationLocale(); + return bundleService.createBundle(aURI, appLocale); + }, + +/* ........ Storage API .............. */ + + /** + * basic pref reader + * @param aName + * @param aDefault + * @param aUseRootBranch + */ + _getPref: function sss_getPref(aName, aDefault) { + var pb = this._prefBranch; + try { + switch (pb.getPrefType(aName)) { + case pb.PREF_STRING: + return pb.getCharPref(aName); + case pb.PREF_BOOL: + return pb.getBoolPref(aName); + case pb.PREF_INT: + return pb.getIntPref(aName); + default: + return aDefault; + } + } + catch(ex) { + return aDefault; + } + }, + + /** + * reads a file into a string + * @param aFile + * nsIFile + * @returns string + */ + _readFile: function sss_readFile(aFile) { + try { + var stream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + stream.init(aFile, 0x01, 0, 0); + var cvstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Ci.nsIConverterInputStream); + cvstream.init(stream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + var content = ""; + var data = {}; + while (cvstream.readString(4096, data)) { + content += data.value; + } + cvstream.close(); + + return content.replace(/\r\n?/g, "\n"); + } + catch (ex) { } // inexisting file? + + return null; + }, + +/* ........ QueryInterface .............. */ + + QueryInterface: function(aIID) { + if (!aIID.equals(Ci.nsISupports) && !aIID.equals(Ci.nsIObserver) && + !aIID.equals(Ci.nsISupportsWeakReference) && + !aIID.equals(Ci.nsISessionStartup)) { + Components.returnCode = Cr.NS_ERROR_NO_INTERFACE; + return null; + } + + return this; + } +}; + +/* :::::::: File Format Converter ::::::::::::::: */ + +/** + * Sessions are serialized to and restored from .INI files + * + * The serialization can store nested JS objects and arrays. Each object or array + * gets a session header containing the "path" to it (using dots as name separators). + * Arrays are stored as a list of objects where the path name is the array name plus + * the object's index (e.g. [Window2.Cookies] is the Cookies object of the second entry + * of the Window array). + * + * The .INI format used here is for performance and convenience reasons somewhat restricted: + * * files must be stored in a Unicode compatible encoding (such as UTF-8) + * * section headers and key names are case-sensitive + * * dots in section names have special meaning (separating the names in the object hierarchy) + * * numbers can have some special meaning as well (see below) + * * keys could occur in the "root" section (i.e. before the first section header) + * + * Despite these restrictions, these files should be quite interoperable with other .INI + * parsers since we're quite conservative in what we emit and pretty tolerant in what we + * accept (except for the encoding and the case sensitivity issues - which could be + * remedied by encoding all values containing non US-ASCII characters and by requesting + * all keys to be lower-cased (and enforcing this when restoring). + * + * Implementation details you have to be aware of: + * * empty (string) values aren't stored at all + * * keys beginning with an underscore are ignored + * * Arrays are stored with index-base 1 (NOT: 0!) + * * Array lengths are stored implicitely + * * true and false are (re)stored as boolean values + * * positive integers are returned as Number (all other numbers as String) + * * string values can contain all possible characters + * (they are automatically URI encoded/decoded should that be necessary) + */ +var IniObjectSerializer = { + encode: function(aObj, aPrefix) { + aPrefix = aPrefix ? aPrefix + "." : ""; + + var ini = [], iniChildren = []; + + for (var key in aObj) { + if (!key || /[;\[\]=]/.test(key)) { + debug("Ignoring invalid key name: '" + key + "'!"); + continue; + } + else if (key.charAt(0) == "_") { // ignore this key + continue; + } + + var value = aObj[key]; + if (typeof value == "boolean" || typeof value == "number") { + ini.push(key + "=" + value); + } + else if (typeof value == "string" && value) { + ini.push(key + "=" + (/^\s|[%\t\r\n;]|\s$/.test(value) ? encodeURI(value).replace(/;/g, "%3B") : value)); + } + else if (value instanceof Array) { + for (var i = 0; i < value.length; i++) { + if (value[i] instanceof Object) { + iniChildren.push("[" + aPrefix + key + (i + 1) + "]"); + iniChildren.push(this.encode(value[i], aPrefix + key + (i + 1))); + } + } + } + else if (typeof value == "object" && value) { + iniChildren.push("[" + aPrefix + key + "]"); + iniChildren.push(this.encode(value, aPrefix + key)); + } + } + + return ini.concat(iniChildren).join("\n"); + }, + + decode: function(aIniString) { + var rootObject = {}; + var obj = rootObject; + var lines = aIniString.split("\n"); + + for (var i = 0; i < lines.length; i++) { + var line = lines[i].replace(/;.*/, ""); + try { + if (line.charAt(0) == "[") { + obj = this._getObjForHeader(rootObject, line); + } + else if (/\S/.test(line)) { // ignore blank lines + this._setValueForLine(obj, line); + } + } + catch (ex) { + throw new Error("Error at line " + (i + 1) + ": " + ex.description); + } + } + + return rootObject; + }, + + // Object which results after traversing the path indicated in the section header + _getObjForHeader: function(aObj, aLine) { + var names = aLine.split("]")[0].substr(1).split("."); + + for (var i = 0; i < names.length; i++) { + var name = names[i]; + if (!names[i]) { + throw new Error("Invalid header: [" + names.join(".") + "]!"); + } + if (/(\d+)$/.test(names[i])) { + names[i] = names[i].slice(0, -RegExp.$1.length); + var ix = parseInt(RegExp.$1) - 1; + aObj = aObj[names[i]] = aObj[names[i]] || []; + aObj = aObj[ix] = aObj[ix] || {}; + } + else { + aObj = aObj[names[i]] = aObj[names[i]] || {}; + } + } + + return aObj; + }, + + _setValueForLine: function(aObj, aLine) { + var ix = aLine.indexOf("="); + if (ix < 1) { + throw new Error("Invalid entry: " + aLine + "!"); + } + + var key = this._trim(aLine.substr(0, ix)); + var value = this._trim(aLine.substr(ix + 1)); + if (value == "true" || value == "false") { + value = (value == "true"); + } + else if (/^\d+$/.test(value)) { + value = parseInt(value); + } + else if (value.indexOf("%") > -1) { + value = decodeURI(value.replace(/%3B/gi, ";")); + } + + aObj[key] = value; + }, + + _trim: function(aString) { + return aString.replace(/^\s+|\s+$/g, ""); + } +}; + +/* :::::::: Service Registration & Initialization ::::::::::::::: */ + +/* ........ nsIModule .............. */ + +const SessionStartupModule = { + + getClassObject: function(aCompMgr, aCID, aIID) { + if (aCID.equals(CID)) { + return SessionStartupFactory; + } + + Components.returnCode = Cr.NS_ERROR_NOT_REGISTERED; + return null; + }, + + registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { + aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); + aCompMgr.registerFactoryLocation(CID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); + + var catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + catMan.addCategoryEntry("app-startup", CLASS_NAME, "service," + CONTRACT_ID, true, true); + }, + + unregisterSelf: function(aCompMgr, aLocation, aType) { + aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); + aCompMgr.unregisterFactoryLocation(CID, aLocation); + + var catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + catMan.deleteCategoryEntry( "app-startup", "service," + CONTRACT_ID, true); + }, + + canUnload: function(aCompMgr) { + return true; + } +} + +/* ........ nsIFactory .............. */ + +const SessionStartupFactory = { + + createInstance: function(aOuter, aIID) { + if (aOuter != null) { + Components.returnCode = Cr.NS_ERROR_NO_AGGREGATION; + return null; + } + + return (new SessionStartup()).QueryInterface(aIID); + }, + + lockFactory: function(aLock) { }, + + QueryInterface: function(aIID) { + if (!aIID.equals(Ci.nsISupports) && !aIID.equals(Ci.nsIModule) && + !aIID.equals(Ci.nsIFactory) && !aIID.equals(Ci.nsISessionStartup)) { + Components.returnCode = Cr.NS_ERROR_NO_INTERFACE; + return null; + } + + return this; + } +}; + +function NSGetModule(aComMgr, aFileSpec) { + return SessionStartupModule; +} diff --git a/mozilla/browser/components/sessionstore/src/nsSessionStore.js b/mozilla/browser/components/sessionstore/src/nsSessionStore.js index 84a4de59172..7020f05301c 100644 --- a/mozilla/browser/components/sessionstore/src/nsSessionStore.js +++ b/mozilla/browser/components/sessionstore/src/nsSessionStore.js @@ -43,26 +43,8 @@ * This service keeps track of a user's session, storing the various bits * required to return the browser to it's current state. The relevant data is * stored in memory, and is periodically saved to disk in an ini file in the - * profile directory. The service starts when the browser starts, and will restore - * the session from the information contained in the ini file under circumstances - * described below. - * - * Crash Detection - * The initial segment of the INI file is has a single field, "state", that - * indicates whether the browser is currently running. When the browser shuts - * down, the field is changed to "stopped". At startup, this field is read, and if - * it's value is "running", then it's assumed that the browser had previously - * crashed, or at the very least that something bad happened, and that we should - * restore the session. - * - * Forced Restarts - * In the event that a restart is required due to application update or extension - * installation, set the browser.sessionstore.resume_session_once pref to true, - * and the session will be restored the next time the browser starts. - * - * Always Resume - * This service will always resume the session if the boolean pref - * browser.sessionstore.resume_session is set to true. + * profile directory. The service is started at first window load, in delayedStartup, and will restore + * the session from the data received from the nsSessionStartup service. */ /* :::::::: Constants and Helpers ::::::::::::::: */ @@ -149,8 +131,8 @@ const CAPABILITIES = [ function debug(aMsg) { aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n"); - Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService). - logStringMessage(aMsg); + Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService) + .logStringMessage(aMsg); } /* :::::::: The Service ::::::::::::::: */ @@ -192,18 +174,24 @@ SessionStoreService.prototype = { /** * Initialize the component */ - init: function sss_init() { - this._prefBranch = - Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). - getBranch("browser."); + init: function sss_init(aWindow) { + debug("store init"); + if (!aWindow || aWindow == null) + return; + + if (this._loadState == STATE_RUNNING) + return; + + this._prefBranch = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefService).getBranch("browser."); this._prefBranch.QueryInterface(Ci.nsIPrefBranch2); // if the service is disabled, do not init - if(!this._getPref("sessionstore.enabled", DEFAULT_ENABLED)) + if (!this._getPref("sessionstore.enabled", DEFAULT_ENABLED)) return; - var observerService = - Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + var observerService = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); OBSERVING.forEach(function(aTopic) { observerService.addObserver(this, aTopic, true); @@ -218,16 +206,23 @@ SessionStoreService.prototype = { this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true); // get file references - this._sessionFile = - Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties) - .get("ProfD", Ci.nsILocalFile); + var dirService = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile); this._sessionFileBackup = this._sessionFile.clone(); this._sessionFile.append("sessionstore.ini"); this._sessionFileBackup.append("sessionstore.bak"); - // for the recover prompt: // get string containing session state - var iniString = this._readFile(this._getSessionFile()); + var iniString; + try { + var ss = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + if (ss.doRestore()) + 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 @@ -247,13 +242,18 @@ SessionStoreService.prototype = { } // if last session crashed, backup the session - // and try to restore the disk cache if (this._lastSessionCrashed) { try { this._writeFile(this._getSessionFile(true), iniString); } catch (ex) { } // nothing else we can do here } + + // As this is called at delayedStartup, restoration must be initiated here + var windowLoad = function(self) { + self.onLoad(this); + } + aWindow.setTimeout(windowLoad, 0, this); }, /** @@ -278,20 +278,13 @@ SessionStoreService.prototype = { * Handle notifications */ observe: function sss_observe(aSubject, aTopic, aData) { - var observerService = - Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + var observerService = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); // for event listeners var _this = this; switch (aTopic) { - case "app-startup": - observerService.addObserver(this, "final-ui-startup", true); - break; - case "final-ui-startup": - observerService.removeObserver(this, "final-ui-startup"); - this.init(); - break; case "domwindowopened": // catch new windows aSubject.addEventListener("load", function(aEvent) { aEvent.currentTarget.removeEventListener("load", arguments.callee, false); @@ -392,23 +385,13 @@ SessionStoreService.prototype = { // perform additional initialization when the first window is loading if (this._loadState == STATE_STOPPED) { - // delete the initial state if the user doesn't want to restore it - // (since this might delay startup notably, don't set _loadState before - // this or we might get a corrupted session if Firefox crashes on - // startup) - if (this._initialState && !(this._lastSessionCrashed ? this._doRecoverSession() : this._doResumeSession())) { - delete this._initialState; - } - if (this._getPref("sessionstore.resume_session_once", DEFAULT_RESUME_SESSION_ONCE)) { - this._prefBranch.setBoolPref("sessionstore.resume_session_once", false); - } this._loadState = STATE_RUNNING; this._lastSaveTime = Date.now(); // don't save during the first five seconds // (until most of the pages have been restored) this.saveStateDelayed(aWindow, 10000); - + // restore a crashed session resp. resume the last session if requested if (this._initialState) { // make sure that the restored tabs are first in the window @@ -871,7 +854,7 @@ SessionStoreService.prototype = { aEntry.postData.QueryInterface(Ci.nsISeekableStream). seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); var stream = Cc["@mozilla.org/scriptableinputstream;1"]. - createInstance(Ci.nsIScriptableInputStream); + createInstance(Ci.nsIScriptableInputStream); stream.init(aEntry.postData); // Warning: LiveHTTPHeaders crashes around here! var postdata = stream.read(stream.available()); @@ -1015,7 +998,7 @@ SessionStoreService.prototype = { */ _updateCookies: function sss_updateCookies(aWindows) { var cookiesEnum = Cc["@mozilla.org/cookiemanager;1"]. - getService(Ci.nsICookieManager).enumerator; + getService(Ci.nsICookieManager).enumerator; // collect the cookies per window for (var i = 0; i < aWindows.length; i++) { aWindows[i].Cookies = { count: 0 }; @@ -1367,10 +1350,10 @@ SessionStoreService.prototype = { */ _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) { var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"]. - createInstance(Ci.nsISHEntry); + createInstance(Ci.nsISHEntry); - var ioService = - Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); shEntry.setURI(ioService.newURI(aEntry.url, null, null)); shEntry.setTitle(aEntry.title || aEntry.url); shEntry.setIsSubFrame(aEntry.subframe || false); @@ -1378,7 +1361,7 @@ SessionStoreService.prototype = { if (aEntry.cacheKey) { var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]. - createInstance(Ci.nsISupportsPRUint32); + createInstance(Ci.nsISupportsPRUint32); cacheKey.data = aEntry.cacheKey; shEntry.cacheKey = cacheKey; } @@ -1400,7 +1383,7 @@ SessionStoreService.prototype = { if (aEntry.postdata) { var stream = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); + createInstance(Ci.nsIStringInputStream); stream.setData(aEntry.postdata, -1); shEntry.postData = stream; } @@ -1549,10 +1532,10 @@ SessionStoreService.prototype = { * Array of cookie data */ restoreCookies: function sss_restoreCookies(aCookies) { - var cookieService = - Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService); - var ioService = - Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var cookieService = Cc["@mozilla.org/cookieService;1"]. + getService(Ci.nsICookieService); + var ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); for (var i = 1; i <= aCookies.count; i++) { try { @@ -1568,15 +1551,15 @@ SessionStoreService.prototype = { * Window reference */ retryDownloads: function sss_retryDownloads(aWindow) { - var downloadManager = - Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); - var rdfService = - Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService); - var ioService = - Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var downloadManager = Cc["@mozilla.org/download-manager;1"]. + getService(Ci.nsIDownloadManager); + var rdfService = Cc["@mozilla.org/rdf/rdf-service;1"]. + getService(Ci.nsIRDFService); + var ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); - var rdfContainer = - Cc["@mozilla.org/rdf/container;1"].createInstance(Ci.nsIRDFContainer); + var rdfContainer = Cc["@mozilla.org/rdf/container;1"]. + createInstance(Ci.nsIRDFContainer); var datasource = downloadManager.datasource; try { @@ -1609,8 +1592,8 @@ SessionStoreService.prototype = { node = datasource.GetTarget(rdfService.GetResource(download.Value), rdfService.GetResource("http://home.netscape.com/NC-rdf#File"), true); node.QueryInterface(Ci.nsIRDFResource); - var linkChecker = - Cc["@mozilla.org/network/urichecker;1"].createInstance(Ci.nsIURIChecker); + var linkChecker = Cc["@mozilla.org/network/urichecker;1"]. + createInstance(Ci.nsIURIChecker); linkChecker.init(ioService.newURI(url, null, null)); linkChecker.loadFlags = Ci.nsIRequest.LOAD_BACKGROUND; @@ -1725,7 +1708,7 @@ SessionStoreService.prototype = { */ _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() { var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator); + getService(Ci.nsIWindowMediator); return windowMediator.getMostRecentWindow("navigator:browser"); }, @@ -1739,8 +1722,8 @@ SessionStoreService.prototype = { //zeniko: why isn't it possible to set the window's dimensions here (as feature)? var window = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher). - openWindow(null, this._getPref("chromeURL", null), "_blank", "chrome,dialog=no,all", null); + getService(Ci.nsIWindowWatcher). + openWindow(null, this._getPref("chromeURL", null), "_blank", "chrome,dialog=no,all", null); window.__SS_state = aState; var _this = this; @@ -1765,60 +1748,6 @@ SessionStoreService.prototype = { || this._getPref("startup.page", 1) == 2; }, - /** - * prompt user whether or not to restore the previous session, - * if the browser crashed - * @returns bool - */ - _doRecoverSession: function sss_doRecoverSession() { - // do not prompt or resume, post-crash - if (!this._getPref("sessionstore.resume_from_crash", DEFAULT_RESUME_FROM_CRASH)) - return false; - - // if the prompt fails, recover anyway - var recover = true; - // allow extensions to hook in a more elaborate restore prompt - //zeniko: drop this when we're using our own dialog instead of a standard prompt - var dialogURI = this._getPref("sessionstore.restore_prompt_uri"); - - try { - if (dialogURI) { // extension provided dialog - var params = Cc["@mozilla.org/embedcomp/dialogparam;1"]. - createInstance(Ci.nsIDialogParamBlock); - // default to recovering - params.SetInt(0, 0); - Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher). - openWindow(null, dialogURI, "_blank", "chrome,modal,centerscreen,titlebar", params); - recover = params.GetInt(0) == 0; - } - else { // basic prompt with no options - // get app name from branding properties - var brandStringBundle = this._getStringBundle("chrome://branding/locale/brand.properties"); - var brandShortName = brandStringBundle.GetStringFromName("brandShortName"); - - // create prompt strings - var ssStringBundle = this._getStringBundle("chrome://browser/locale/sessionstore.properties"); - var restoreTitle = ssStringBundle.formatStringFromName("restoreTitle", [brandShortName], 1); - var restoreText = ssStringBundle.formatStringFromName("restoreText", [brandShortName], 1); - var buttonTitle = ssStringBundle.GetStringFromName("buttonTitle"); - - var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService); - - // set the buttons that will appear on the dialog - var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 + - promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_0; - - var buttonChoice = promptService.confirmEx(null, restoreTitle, restoreText, - flags, null, buttonTitle, null, - null, {}); - recover = (buttonChoice == 1); - } - } - catch (ex) { dump(ex); } // if the prompt fails for some reason, recover anyway - return recover; - }, - /** * whether the user wants to load any other page at startup * (except the homepage) - needed for determining whether to overwrite the current tabs @@ -1844,7 +1773,7 @@ SessionStoreService.prototype = { break; case 2: homepage = Cc["@mozilla.org/browser/global-history;2"]. - getService(Ci.nsIBrowserHistory).lastPageVisited; + getService(Ci.nsIBrowserHistory).lastPageVisited; break; } @@ -1918,9 +1847,9 @@ SessionStoreService.prototype = { */ _getStringBundle: function sss_getStringBundle(aURI) { var bundleService = Cc["@mozilla.org/intl/stringbundle;1"]. - getService(Ci.nsIStringBundleService); + getService(Ci.nsIStringBundleService); var appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"]. - getService(Ci.nsILocaleService).getApplicationLocale(); + getService(Ci.nsILocaleService).getApplicationLocale(); return bundleService.createBundle(aURI, appLocale); }, @@ -1931,7 +1860,7 @@ SessionStoreService.prototype = { */ _getURIFromString: function sss_getURIFromString(aString) { var ioService = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); + getService(Ci.nsIIOService); return ioService.newURI(aString, null, null); }, @@ -1971,10 +1900,10 @@ SessionStoreService.prototype = { _readFile: function sss_readFile(aFile) { try { var stream = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); + createInstance(Ci.nsIFileInputStream); stream.init(aFile, 0x01, 0, 0); var cvstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Ci.nsIConverterInputStream); + createInstance(Ci.nsIConverterInputStream); cvstream.init(stream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); var content = ""; @@ -2008,9 +1937,9 @@ SessionStoreService.prototype = { /* ........ QueryInterface .............. */ QueryInterface: function(aIID) { - if (!aIID.equals(Ci.nsISupports) && !aIID.equals(Ci.nsIObserver) - && !aIID.equals(Ci.nsISupportsWeakReference) - && !aIID.equals(Ci.nsISessionStore)) { + if (!aIID.equals(Ci.nsISupports) && !aIID.equals(Ci.nsIObserver) && + !aIID.equals(Ci.nsISupportsWeakReference) && + !aIID.equals(Ci.nsISessionStore)) { Components.returnCode = Cr.NS_ERROR_NO_INTERFACE; return null; } @@ -2031,12 +1960,12 @@ FileWriter.prototype = { run: function FileWriter_run() { // init stream var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); + createInstance(Ci.nsIFileOutputStream); stream.init(this._file, 0x02 | 0x08 | 0x20, 0600, 0); // convert to UTF-8 var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. - createInstance(Ci.nsIScriptableUnicodeConverter); + createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; var convertedData = converter.ConvertFromUnicode(this._data); convertedData += converter.Finish(); @@ -2239,7 +2168,8 @@ const SessionStoreModule = { aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); - var catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + var catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); catMan.addCategoryEntry("app-startup", CLASS_NAME, "service," + CONTRACT_ID, true, true); }, @@ -2247,7 +2177,8 @@ const SessionStoreModule = { aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CID, aLocation); - var catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + var catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); catMan.deleteCategoryEntry( "app-startup", "service," + CONTRACT_ID, true); },