mirror of
https://github.com/ManchildProductions/binoc-central-mirror.git
synced 2026-06-11 13:29:06 +00:00
334 lines
10 KiB
JavaScript
334 lines
10 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* Session Storage and Restoration
|
|
*/
|
|
|
|
/* :::::::: Constants and Helpers ::::::::::::::: */
|
|
|
|
this.EXPORTED_SYMBOLS = ["sessionStoreManager"];
|
|
|
|
var Cc = Components.classes;
|
|
var Ci = Components.interfaces;
|
|
var Cu = Components.utils;
|
|
|
|
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
/**
|
|
* asuth arbitrarily chose this value to trade-off powersaving,
|
|
* processor usage, and recency of state in the face of the impossibility of
|
|
* our crashing; he also worded this.
|
|
*/
|
|
var SESSION_AUTO_SAVE_DEFAULT_MS = 300000; // 5 minutes
|
|
|
|
/* :::::::: The Module ::::::::::::::: */
|
|
|
|
var sessionStoreManager =
|
|
{
|
|
_initialized: false,
|
|
|
|
_sessionAutoSaveTimer: null,
|
|
|
|
_sessionAutoSaveTimerIntervalMS: SESSION_AUTO_SAVE_DEFAULT_MS,
|
|
|
|
/**
|
|
* The persisted state of the previous session. This is resurrected
|
|
* from disk when the module is initialized and cleared when all
|
|
* required windows have been restored.
|
|
*/
|
|
_initialState: null,
|
|
|
|
/**
|
|
* The string containing the JSON stringified representation of the last
|
|
* state we wrote to disk.
|
|
*/
|
|
_currentStateString: null,
|
|
|
|
/**
|
|
* A flag indicating whether the state "just before shutdown" of the current
|
|
* session has been persisted to disk. See |observe| and |unloadingWindow|
|
|
* for justification on why we need this.
|
|
*/
|
|
_shutdownStateSaved: false,
|
|
|
|
/**
|
|
* Cache the session file async writeAtomic Promise for AsyncShutdown.
|
|
*/
|
|
_promise: null,
|
|
|
|
/**
|
|
* Gets the nsIFile used for session storage.
|
|
*/
|
|
get sessionFile()
|
|
{
|
|
let sessionFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
|
sessionFile.append("session.json");
|
|
return sessionFile;
|
|
},
|
|
|
|
/**
|
|
* This is called on startup, and when a new 3 pane window is opened after
|
|
* the last 3 pane window was closed (e.g., on the mac, closing the last
|
|
* window doesn't shut down the app).
|
|
*/
|
|
_init: function ssm_init()
|
|
{
|
|
this._loadSessionFile();
|
|
|
|
// we listen for "quit-application-granted" instead of
|
|
// "quit-application-requested" because other observers of the
|
|
// latter can cancel the shutdown.
|
|
Services.obs.addObserver(this, "quit-application-granted", false);
|
|
|
|
this.startPeriodicSave();
|
|
|
|
this._initialized = true;
|
|
},
|
|
|
|
/**
|
|
* Loads the session file into _initialState. This should only be called by
|
|
* _init and a unit test.
|
|
*/
|
|
_loadSessionFile: function ssm_loadSessionFile()
|
|
{
|
|
if (!this.sessionFile.exists())
|
|
return;
|
|
|
|
// Read the session state data from file, synchronously.
|
|
let inStream = Cc["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Ci.nsIFileInputStream);
|
|
inStream.init(this.sessionFile, -1, 0, 0);
|
|
let data = NetUtil.readInputStreamToString(inStream,
|
|
inStream.available(),
|
|
{ charset: "UTF-8" });
|
|
inStream.close();
|
|
|
|
// Clear the current state so that subsequent writes won't think
|
|
// the state hasn't changed.
|
|
this._currentStateString = null;
|
|
|
|
try {
|
|
// Parse the session state into a JSON object.
|
|
this._initialState = JSON.parse(data);
|
|
}
|
|
catch (ex) {
|
|
Cu.reportError("sessionStoreManager: error in session state data, " + ex);
|
|
}
|
|
|
|
if (!data || !this._initialState) {
|
|
// If the file exists but there is a data read or parse fail, save the
|
|
// bad file.
|
|
let errorFile = "session_error_" +
|
|
(new Date().toISOString()).replace(/\D/g, "") + ".json";
|
|
let errorFilePath = OS.Path.join(OS.Constants.Path.profileDir, errorFile);
|
|
OS.File.move(this.sessionFile.path, errorFilePath)
|
|
.then(null, error => Cu.reportError("sessionStoreManager: failed to rename " +
|
|
this.sessionFile.path + " to " +
|
|
errorFilePath + ": " + error));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Opens the windows that were open in the previous session.
|
|
*/
|
|
_openOtherRequiredWindows: function ssm_openOtherRequiredWindows(aWindow)
|
|
{
|
|
// XXX we might want to display a restore page and let the user decide
|
|
// whether to restore the other windows, just like Firefox does.
|
|
|
|
if (!this._initialState || !this._initialState.windows || !aWindow)
|
|
return;
|
|
|
|
for (var i = 0; i < this._initialState.windows.length; ++i)
|
|
aWindow.open(
|
|
"chrome://messenger/content/messenger.xul",
|
|
"_blank",
|
|
"chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
|
|
},
|
|
|
|
/**
|
|
* Writes the state object to disk.
|
|
*/
|
|
_saveStateObject: function ssm_saveStateObject(aStateObj)
|
|
{
|
|
let data = JSON.stringify(aStateObj);
|
|
|
|
// Write async to disk only if state changed since last write.
|
|
if (data == this._currentStateString)
|
|
return;
|
|
|
|
this._promise = OS.File
|
|
.writeAtomic(this.sessionFile.path, data,
|
|
{ tmpPath: this.sessionFile.path + ".tmp",
|
|
flush: true })
|
|
.then(() => sessionStoreManager._currentStateString = data,
|
|
error => Cu.reportError("sessionStoreManager: error " +
|
|
"storing session state data, " +
|
|
error))
|
|
},
|
|
|
|
/**
|
|
* @return an empty state object that can be populated with window states.
|
|
*/
|
|
_createStateObject: function ssm_createStateObject()
|
|
{
|
|
return {
|
|
rev: 0,
|
|
windows: []
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Writes the state of all currently open 3pane windows to disk.
|
|
*/
|
|
_saveState: function ssm_saveState()
|
|
{
|
|
let state = this._createStateObject();
|
|
|
|
// XXX we'd like to support other window types in future, but for now
|
|
// only get the 3pane windows.
|
|
let enumerator = Services.wm.getEnumerator("mail:3pane");
|
|
while (enumerator.hasMoreElements()) {
|
|
let win = enumerator.getNext();
|
|
if (win && "complete" == win.document.readyState &&
|
|
win.getWindowStateForSessionPersistence)
|
|
state.windows.push(win.getWindowStateForSessionPersistence());
|
|
}
|
|
|
|
this._saveStateObject(state);
|
|
},
|
|
|
|
/* ........ Timer Callback ................*/
|
|
|
|
_sessionAutoSaveTimerCallback: function ssm_sessionAutoSaveTimerCallback()
|
|
{
|
|
sessionStoreManager._saveState();
|
|
},
|
|
|
|
/* ........ Observer Notification Handler ................*/
|
|
|
|
observe: function ssm_observe(aSubject, aTopic, aData) {
|
|
switch (aTopic) {
|
|
// This is observed before any windows start unloading if something other
|
|
// than the last 3pane window closing requested the application be
|
|
// shutdown. For example, when the user quits via the file menu.
|
|
case "quit-application-granted":
|
|
if (!this._shutdownStateSaved) {
|
|
this.stopPeriodicSave();
|
|
this._saveState();
|
|
|
|
// this is to ensure we don't clobber the saved state when the
|
|
// 3pane windows unload.
|
|
this._shutdownStateSaved = true;
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
/* ........ Public API ................*/
|
|
|
|
/**
|
|
* Called by each 3pane window instance when it loads.
|
|
*
|
|
* @return a window state object if aWindow was opened as a result of a
|
|
* session restoration, null otherwise.
|
|
*/
|
|
loadingWindow: function ssm_loadingWindow(aWindow)
|
|
{
|
|
let firstWindow = !this._initialized || this._shutdownStateSaved;
|
|
if (firstWindow)
|
|
this._init();
|
|
|
|
// If we are seeing a new 3-pane, we are obviously not in a shutdown
|
|
// state anymore. (This would happen if all the 3panes got closed but
|
|
// we did not quit because another window was open and then a 3pane showed
|
|
// up again. This can happen in both unit tests and real life.)
|
|
// We treat this case like the first window case, and do a session restore.
|
|
this._shutdownStateSaved = false;
|
|
|
|
let windowState = null;
|
|
if (this._initialState && this._initialState.windows) {
|
|
windowState = this._initialState.windows.pop();
|
|
if (0 == this._initialState.windows.length)
|
|
this._initialState = null;
|
|
}
|
|
|
|
if (firstWindow)
|
|
this._openOtherRequiredWindows(aWindow);
|
|
|
|
return windowState;
|
|
},
|
|
|
|
/**
|
|
* Called by each 3pane window instance when it unloads. If aWindow is the
|
|
* last 3pane window, its state is persisted. The last 3pane window unloads
|
|
* first before the "quit-application-granted" event is generated.
|
|
*/
|
|
unloadingWindow: function ssm_unloadingWindow(aWindow)
|
|
{
|
|
if (!this._shutdownStateSaved) {
|
|
// determine whether aWindow is the last open window
|
|
let lastWindow = true;
|
|
let enumerator = Services.wm.getEnumerator("mail:3pane");
|
|
while (enumerator.hasMoreElements()) {
|
|
if (enumerator.getNext() != aWindow)
|
|
lastWindow = false;
|
|
}
|
|
|
|
if (lastWindow) {
|
|
// last chance to save any state for the current session since
|
|
// aWindow is the last 3pane window and the "quit-application-granted"
|
|
// event is observed AFTER this.
|
|
this.stopPeriodicSave();
|
|
|
|
let state = this._createStateObject();
|
|
state.windows.push(aWindow.getWindowStateForSessionPersistence());
|
|
this._saveStateObject(state);
|
|
|
|
// XXX this is to ensure we don't clobber the saved state when we
|
|
// observe the "quit-application-granted" event.
|
|
this._shutdownStateSaved = true;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Stops periodic session persistence.
|
|
*/
|
|
stopPeriodicSave: function ssm_stopPeriodicSave()
|
|
{
|
|
if (this._sessionAutoSaveTimer) {
|
|
this._sessionAutoSaveTimer.cancel();
|
|
|
|
delete this._sessionAutoSaveTimer;
|
|
this._sessionAutoSaveTimer = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Starts periodic session persistence.
|
|
*/
|
|
startPeriodicSave: function ssm_startPeriodicSave()
|
|
{
|
|
if (!this._sessionAutoSaveTimer) {
|
|
this._sessionAutoSaveTimer = Cc["@mozilla.org/timer;1"]
|
|
.createInstance(Ci.nsITimer);
|
|
|
|
this._sessionAutoSaveTimer.initWithCallback(
|
|
this._sessionAutoSaveTimerCallback,
|
|
this._sessionAutoSaveTimerIntervalMS,
|
|
Ci.nsITimer.TYPE_REPEATING_SLACK);
|
|
}
|
|
}
|
|
};
|
|
|
|
AsyncShutdown.profileBeforeChange.addBlocker(
|
|
"sessionStoreManager: session.json",
|
|
() => { return sessionStoreManager._promise; }
|
|
);
|