2012-12-19 17:04:26 -08:00
|
|
|
/* 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";
|
|
|
|
|
2013-10-25 02:52:42 -07:00
|
|
|
this.EXPORTED_SYMBOLS = ["SessionFile"];
|
2012-12-19 17:04:26 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Implementation of all the disk I/O required by the session store.
|
|
|
|
* This is a private API, meant to be used only by the session store.
|
|
|
|
* It will change. Do not use it for any other purpose.
|
|
|
|
*
|
|
|
|
* Note that this module implicitly depends on one of two things:
|
|
|
|
* 1. either the asynchronous file I/O system enqueues its requests
|
|
|
|
* and never attempts to simultaneously execute two I/O requests on
|
|
|
|
* the files used by this module from two distinct threads; or
|
|
|
|
* 2. the clients of this API are well-behaved and do not place
|
|
|
|
* concurrent requests to the files used by this module.
|
|
|
|
*
|
|
|
|
* Otherwise, we could encounter bugs, especially under Windows,
|
|
|
|
* e.g. if a request attempts to write sessionstore.js while
|
|
|
|
* another attempts to copy that file.
|
|
|
|
*
|
|
|
|
* This implementation uses OS.File, which guarantees property 1.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const Cu = Components.utils;
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
2013-07-12 04:46:14 -07:00
|
|
|
Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
|
2013-07-25 12:50:15 -07:00
|
|
|
Cu.import("resource://gre/modules/Promise.jsm");
|
2013-09-17 01:26:56 -07:00
|
|
|
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
2012-12-19 17:04:26 -08:00
|
|
|
|
2014-01-06 12:27:25 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
|
|
|
"resource://gre/modules/devtools/Console.jsm");
|
2012-12-19 17:04:26 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
|
|
|
|
"resource://gre/modules/TelemetryStopwatch.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
|
|
|
"resource://gre/modules/Task.jsm");
|
2013-03-19 14:36:35 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
|
|
|
"@mozilla.org/base/telemetry;1", "nsITelemetry");
|
2012-12-19 17:04:26 -08:00
|
|
|
|
2013-10-25 02:52:42 -07:00
|
|
|
this.SessionFile = {
|
2012-12-19 17:04:26 -08:00
|
|
|
/**
|
|
|
|
* Read the contents of the session file, asynchronously.
|
|
|
|
*/
|
2013-07-12 04:46:14 -07:00
|
|
|
read: function () {
|
2012-12-19 17:04:26 -08:00
|
|
|
return SessionFileInternal.read();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Write the contents of the session file, asynchronously.
|
|
|
|
*/
|
2013-08-05 11:26:27 -07:00
|
|
|
write: function (aData) {
|
|
|
|
return SessionFileInternal.write(aData);
|
2012-12-19 17:04:26 -08:00
|
|
|
},
|
2013-07-12 04:46:14 -07:00
|
|
|
/**
|
|
|
|
* Writes the initial state to disk again only to change the session's load
|
|
|
|
* state. This must only be called once, it will throw an error otherwise.
|
|
|
|
*/
|
|
|
|
writeLoadStateOnceAfterStartup: function (aLoadState) {
|
2013-08-21 05:33:17 -07:00
|
|
|
SessionFileInternal.writeLoadStateOnceAfterStartup(aLoadState);
|
2013-07-12 04:46:14 -07:00
|
|
|
},
|
2013-06-27 10:57:05 -07:00
|
|
|
/**
|
|
|
|
* Create a backup copy, asynchronously.
|
|
|
|
* This is designed to perform backup on upgrade.
|
|
|
|
*/
|
2013-07-12 04:46:14 -07:00
|
|
|
createBackupCopy: function (ext) {
|
|
|
|
return SessionFileInternal.createBackupCopy(ext);
|
2013-06-27 10:57:05 -07:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Remove a backup copy, asynchronously.
|
|
|
|
* This is designed to clean up a backup on upgrade.
|
|
|
|
*/
|
2013-07-12 04:46:14 -07:00
|
|
|
removeBackupCopy: function (ext) {
|
|
|
|
return SessionFileInternal.removeBackupCopy(ext);
|
2013-06-27 10:57:05 -07:00
|
|
|
},
|
2012-12-19 17:04:26 -08:00
|
|
|
/**
|
|
|
|
* Wipe the contents of the session file, asynchronously.
|
|
|
|
*/
|
2013-07-12 04:46:14 -07:00
|
|
|
wipe: function () {
|
2013-08-21 05:33:17 -07:00
|
|
|
SessionFileInternal.wipe();
|
2012-12-19 17:04:26 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-25 02:52:42 -07:00
|
|
|
Object.freeze(SessionFile);
|
2012-12-19 17:04:26 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Utilities for dealing with promises and Task.jsm
|
|
|
|
*/
|
|
|
|
const TaskUtils = {
|
|
|
|
/**
|
|
|
|
* Add logging to a promise.
|
|
|
|
*
|
|
|
|
* @param {Promise} promise
|
|
|
|
* @return {Promise} A promise behaving as |promise|, but with additional
|
|
|
|
* logging in case of uncaught error.
|
|
|
|
*/
|
|
|
|
captureErrors: function captureErrors(promise) {
|
|
|
|
return promise.then(
|
|
|
|
null,
|
|
|
|
function onError(reason) {
|
2014-01-06 12:27:25 -08:00
|
|
|
console.error("Uncaught asynchronous error", reason, "at", reason.stack);
|
2012-12-19 17:04:26 -08:00
|
|
|
throw reason;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Spawn a new Task from a generator.
|
|
|
|
*
|
|
|
|
* This function behaves as |Task.spawn|, with the exception that it
|
|
|
|
* adds logging in case of uncaught error. For more information, see
|
|
|
|
* the documentation of |Task.jsm|.
|
|
|
|
*
|
|
|
|
* @param {generator} gen Some generator.
|
|
|
|
* @return {Promise} A promise built from |gen|, with the same semantics
|
|
|
|
* as |Task.spawn(gen)|.
|
|
|
|
*/
|
|
|
|
spawn: function spawn(gen) {
|
|
|
|
return this.captureErrors(Task.spawn(gen));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let SessionFileInternal = {
|
|
|
|
/**
|
|
|
|
* The path to sessionstore.js
|
|
|
|
*/
|
|
|
|
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The path to sessionstore.bak
|
|
|
|
*/
|
|
|
|
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
|
|
|
|
|
2013-09-17 01:26:56 -07:00
|
|
|
/**
|
|
|
|
* The promise returned by the latest call to |write|.
|
|
|
|
* We use it to ensure that AsyncShutdown.profileBeforeChange cannot
|
|
|
|
* interrupt a call to |write|.
|
|
|
|
*/
|
|
|
|
_latestWrite: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* |true| once we have decided to stop receiving write instructiosn
|
|
|
|
*/
|
|
|
|
_isClosed: false,
|
|
|
|
|
2013-07-12 04:46:14 -07:00
|
|
|
read: function () {
|
2013-07-31 18:15:26 -07:00
|
|
|
return SessionWorker.post("read").then(msg => {
|
|
|
|
this._recordTelemetry(msg.telemetry);
|
|
|
|
return msg.ok;
|
|
|
|
});
|
2013-04-30 05:07:40 -07:00
|
|
|
},
|
|
|
|
|
2013-08-05 11:26:27 -07:00
|
|
|
write: function (aData) {
|
2013-09-17 01:26:56 -07:00
|
|
|
if (this._isClosed) {
|
2013-10-25 02:52:42 -07:00
|
|
|
return Promise.reject(new Error("SessionFile is closed"));
|
2013-09-17 01:26:56 -07:00
|
|
|
}
|
2012-12-19 17:04:26 -08:00
|
|
|
let refObj = {};
|
2013-10-18 09:24:19 -07:00
|
|
|
|
|
|
|
let isFinalWrite = false;
|
|
|
|
if (Services.startup.shuttingDown) {
|
|
|
|
// If shutdown has started, we will want to stop receiving
|
|
|
|
// write instructions.
|
|
|
|
isFinalWrite = this._isClosed = true;
|
|
|
|
}
|
|
|
|
|
2013-09-17 01:26:56 -07:00
|
|
|
return this._latestWrite = TaskUtils.spawn(function task() {
|
2013-03-20 01:15:24 -07:00
|
|
|
TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
|
2012-12-19 17:04:26 -08:00
|
|
|
|
|
|
|
try {
|
2013-08-05 11:26:27 -07:00
|
|
|
let promise = SessionWorker.post("write", [aData]);
|
2013-03-20 01:15:24 -07:00
|
|
|
// At this point, we measure how long we stop the main thread
|
|
|
|
TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
|
|
|
|
|
2013-07-31 18:15:26 -07:00
|
|
|
// Now wait for the result and record how long the write took
|
|
|
|
let msg = yield promise;
|
|
|
|
this._recordTelemetry(msg.telemetry);
|
2012-12-19 17:04:26 -08:00
|
|
|
} catch (ex) {
|
2013-03-20 01:15:24 -07:00
|
|
|
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
|
2014-01-06 12:27:25 -08:00
|
|
|
console.error("Could not write session state file ", this.path, ex);
|
2012-12-19 17:04:26 -08:00
|
|
|
}
|
2013-10-18 09:24:19 -07:00
|
|
|
|
|
|
|
if (isFinalWrite) {
|
|
|
|
Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
|
2013-09-17 01:26:56 -07:00
|
|
|
}
|
2013-07-12 04:46:14 -07:00
|
|
|
}.bind(this));
|
2012-12-19 17:04:26 -08:00
|
|
|
},
|
|
|
|
|
2013-07-12 04:46:14 -07:00
|
|
|
writeLoadStateOnceAfterStartup: function (aLoadState) {
|
2013-08-21 05:33:17 -07:00
|
|
|
SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]).then(msg => {
|
2013-07-31 18:15:26 -07:00
|
|
|
this._recordTelemetry(msg.telemetry);
|
|
|
|
return msg;
|
2014-01-06 12:27:25 -08:00
|
|
|
}, console.error);
|
2012-12-19 17:04:26 -08:00
|
|
|
},
|
|
|
|
|
2013-07-12 04:46:14 -07:00
|
|
|
createBackupCopy: function (ext) {
|
|
|
|
return SessionWorker.post("createBackupCopy", [ext]);
|
2013-06-27 10:57:05 -07:00
|
|
|
},
|
2013-02-19 10:34:25 -08:00
|
|
|
|
2013-07-12 04:46:14 -07:00
|
|
|
removeBackupCopy: function (ext) {
|
|
|
|
return SessionWorker.post("removeBackupCopy", [ext]);
|
2012-12-19 17:04:26 -08:00
|
|
|
},
|
|
|
|
|
2013-07-12 04:46:14 -07:00
|
|
|
wipe: function () {
|
2013-08-21 05:33:17 -07:00
|
|
|
SessionWorker.post("wipe");
|
2013-07-31 18:15:26 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_recordTelemetry: function(telemetry) {
|
|
|
|
for (let histogramId in telemetry){
|
|
|
|
Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]);
|
|
|
|
}
|
2012-12-19 17:04:26 -08:00
|
|
|
}
|
|
|
|
};
|
2013-07-12 04:46:14 -07:00
|
|
|
|
|
|
|
// Interface to a dedicated thread handling I/O
|
|
|
|
let SessionWorker = (function () {
|
|
|
|
let worker = new PromiseWorker("resource:///modules/sessionstore/SessionWorker.js",
|
|
|
|
OS.Shared.LOG.bind("SessionWorker"));
|
|
|
|
return {
|
|
|
|
post: function post(...args) {
|
|
|
|
let promise = worker.post.apply(worker, args);
|
|
|
|
return promise.then(
|
|
|
|
null,
|
|
|
|
function onError(error) {
|
|
|
|
// Decode any serialized error
|
|
|
|
if (error instanceof PromiseWorker.WorkerError) {
|
|
|
|
throw OS.File.Error.fromMsg(error.data);
|
|
|
|
} else {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})();
|
2013-09-17 01:26:56 -07:00
|
|
|
|
|
|
|
// Ensure that we can write sessionstore.js cleanly before the profile
|
|
|
|
// becomes unaccessible.
|
|
|
|
AsyncShutdown.profileBeforeChange.addBlocker(
|
|
|
|
"SessionFile: Finish writing the latest sessionstore.js",
|
|
|
|
function() {
|
2013-10-25 02:52:42 -07:00
|
|
|
return SessionFile._latestWrite;
|
2013-09-17 01:26:56 -07:00
|
|
|
});
|