summaryrefslogtreecommitdiff
path: root/application/basilisk/components/sessionstore/SessionFile.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/components/sessionstore/SessionFile.jsm')
-rw-r--r--application/basilisk/components/sessionstore/SessionFile.jsm399
1 files changed, 0 insertions, 399 deletions
diff --git a/application/basilisk/components/sessionstore/SessionFile.jsm b/application/basilisk/components/sessionstore/SessionFile.jsm
deleted file mode 100644
index 2fdf2e101e..0000000000
--- a/application/basilisk/components/sessionstore/SessionFile.jsm
+++ /dev/null
@@ -1,399 +0,0 @@
-/* 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 = ["SessionFile"];
-
-/**
- * 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;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/AsyncShutdown.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
- "resource://gre/modules/PromiseUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RunState",
- "resource:///modules/sessionstore/RunState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
- "resource://gre/modules/TelemetryStopwatch.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
- "@mozilla.org/base/telemetry;1", "nsITelemetry");
-XPCOMUtils.defineLazyServiceGetter(this, "sessionStartup",
- "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionWorker",
- "resource:///modules/sessionstore/SessionWorker.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
- "resource:///modules/sessionstore/SessionStore.jsm");
-
-const PREF_UPGRADE_BACKUP = "browser.sessionstore.upgradeBackup.latestBuildID";
-const PREF_MAX_UPGRADE_BACKUPS = "browser.sessionstore.upgradeBackup.maxUpgradeBackups";
-
-const PREF_MAX_SERIALIZE_BACK = "browser.sessionstore.max_serialize_back";
-const PREF_MAX_SERIALIZE_FWD = "browser.sessionstore.max_serialize_forward";
-
-this.SessionFile = {
- /**
- * Read the contents of the session file, asynchronously.
- */
- read: function () {
- return SessionFileInternal.read();
- },
- /**
- * Write the contents of the session file, asynchronously.
- */
- write: function (aData) {
- return SessionFileInternal.write(aData);
- },
- /**
- * Wipe the contents of the session file, asynchronously.
- */
- wipe: function () {
- return SessionFileInternal.wipe();
- },
-
- /**
- * Return the paths to the files used to store, backup, etc.
- * the state of the file.
- */
- get Paths() {
- return SessionFileInternal.Paths;
- }
-};
-
-Object.freeze(SessionFile);
-
-var Path = OS.Path;
-var profileDir = OS.Constants.Path.profileDir;
-
-var SessionFileInternal = {
- Paths: Object.freeze({
- // The path to the latest version of sessionstore written during a clean
- // shutdown. After startup, it is renamed `cleanBackup`.
- clean: Path.join(profileDir, "sessionstore.js"),
-
- // The path at which we store the previous version of `clean`. Updated
- // whenever we successfully load from `clean`.
- cleanBackup: Path.join(profileDir, "sessionstore-backups", "previous.js"),
-
- // The directory containing all sessionstore backups.
- backups: Path.join(profileDir, "sessionstore-backups"),
-
- // The path to the latest version of the sessionstore written
- // during runtime. Generally, this file contains more
- // privacy-sensitive information than |clean|, and this file is
- // therefore removed during clean shutdown. This file is designed to protect
- // against crashes / sudden shutdown.
- recovery: Path.join(profileDir, "sessionstore-backups", "recovery.js"),
-
- // The path to the previous version of the sessionstore written
- // during runtime (e.g. 15 seconds before recovery). In case of a
- // clean shutdown, this file is removed. Generally, this file
- // contains more privacy-sensitive information than |clean|, and
- // this file is therefore removed during clean shutdown. This
- // file is designed to protect against crashes that are nasty
- // enough to corrupt |recovery|.
- recoveryBackup: Path.join(profileDir, "sessionstore-backups", "recovery.bak"),
-
- // The path to a backup created during an upgrade of Firefox.
- // Having this backup protects the user essentially from bugs in
- // Firefox or add-ons, especially for users of Nightly. This file
- // does not contain any information more sensitive than |clean|.
- upgradeBackupPrefix: Path.join(profileDir, "sessionstore-backups", "upgrade.js-"),
-
- // The path to the backup of the version of the session store used
- // during the latest upgrade of Firefox. During load/recovery,
- // this file should be used if both |path|, |backupPath| and
- // |latestStartPath| are absent/incorrect. May be "" if no
- // upgrade backup has ever been performed. This file does not
- // contain any information more sensitive than |clean|.
- get upgradeBackup() {
- let latestBackupID = SessionFileInternal.latestUpgradeBackupID;
- if (!latestBackupID) {
- return "";
- }
- return this.upgradeBackupPrefix + latestBackupID;
- },
-
- // The path to a backup created during an upgrade of Firefox.
- // Having this backup protects the user essentially from bugs in
- // Firefox, especially for users of Nightly.
- get nextUpgradeBackup() {
- return this.upgradeBackupPrefix + Services.appinfo.platformBuildID;
- },
-
- /**
- * The order in which to search for a valid sessionstore file.
- */
- get loadOrder() {
- // If `clean` exists and has been written without corruption during
- // the latest shutdown, we need to use it.
- //
- // Otherwise, `recovery` and `recoveryBackup` represent the most
- // recent state of the session store.
- //
- // Finally, if nothing works, fall back to the last known state
- // that can be loaded (`cleanBackup`) or, if available, to the
- // backup performed during the latest upgrade.
- let order = ["clean",
- "recovery",
- "recoveryBackup",
- "cleanBackup"];
- if (SessionFileInternal.latestUpgradeBackupID) {
- // We have an upgradeBackup
- order.push("upgradeBackup");
- }
- return order;
- },
- }),
-
- // Number of attempted calls to `write`.
- // Note that we may have _attempts > _successes + _failures,
- // if attempts never complete.
- // Used for error reporting.
- _attempts: 0,
-
- // Number of successful calls to `write`.
- // Used for error reporting.
- _successes: 0,
-
- // Number of failed calls to `write`.
- // Used for error reporting.
- _failures: 0,
-
- // Resolved once initialization is complete.
- // The promise never rejects.
- _deferredInitialized: PromiseUtils.defer(),
-
- // `true` once we have started initialization, i.e. once something
- // has been scheduled that will eventually resolve `_deferredInitialized`.
- _initializationStarted: false,
-
- // The ID of the latest version of Goanna for which we have an upgrade backup
- // or |undefined| if no upgrade backup was ever written.
- get latestUpgradeBackupID() {
- try {
- return Services.prefs.getCharPref(PREF_UPGRADE_BACKUP);
- } catch (ex) {
- return undefined;
- }
- },
-
- // Find the correct session file, read it and setup the worker.
- read: Task.async(function* () {
- this._initializationStarted = true;
-
- let result;
- let noFilesFound = true;
- // Attempt to load by order of priority from the various backups
- for (let key of this.Paths.loadOrder) {
- let corrupted = false;
- let exists = true;
- try {
- let path = this.Paths[key];
- let startMs = Date.now();
-
- let source = yield OS.File.read(path, { encoding: "utf-8" });
- let parsed = JSON.parse(source);
-
- if (!SessionStore.isFormatVersionCompatible(parsed.version || ["sessionrestore", 0] /*fallback for old versions*/)) {
- // Skip sessionstore files that we don't understand.
- Cu.reportError("Cannot extract data from Session Restore file " + path + ". Wrong format/version: " + JSON.stringify(parsed.version) + ".");
- continue;
- }
- result = {
- origin: key,
- source: source,
- parsed: parsed
- };
- Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
- add(false);
- Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS").
- add(Date.now() - startMs);
- break;
- } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
- exists = false;
- } catch (ex if ex instanceof OS.File.Error) {
- // The file might be inaccessible due to wrong permissions
- // or similar failures. We'll just count it as "corrupted".
- console.error("Could not read session file ", ex, ex.stack);
- corrupted = true;
- } catch (ex if ex instanceof SyntaxError) {
- console.error("Corrupt session file (invalid JSON found) ", ex, ex.stack);
- // File is corrupted, try next file
- corrupted = true;
- } finally {
- if (exists) {
- noFilesFound = false;
- Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
- add(corrupted);
- }
- }
- }
-
- // All files are corrupted if files found but none could deliver a result.
- let allCorrupt = !noFilesFound && !result;
- Telemetry.getHistogramById("FX_SESSION_RESTORE_ALL_FILES_CORRUPT").
- add(allCorrupt);
-
- if (!result) {
- // If everything fails, start with an empty session.
- result = {
- origin: "empty",
- source: "",
- parsed: null
- };
- }
-
- result.noFilesFound = noFilesFound;
-
- // Initialize the worker (in the background) to let it handle backups and also
- // as a workaround for bug 964531.
- let promiseInitialized = SessionWorker.post("init", [result.origin, this.Paths, {
- maxUpgradeBackups: Preferences.get(PREF_MAX_UPGRADE_BACKUPS, 3),
- maxSerializeBack: Preferences.get(PREF_MAX_SERIALIZE_BACK, 10),
- maxSerializeForward: Preferences.get(PREF_MAX_SERIALIZE_FWD, -1)
- }]);
-
- promiseInitialized.catch(err => {
- // Ensure that we report errors but that they do not stop us.
- Promise.reject(err);
- }).then(() => this._deferredInitialized.resolve());
-
- return result;
- }),
-
- // Post a message to the worker, making sure that it has been initialized
- // first.
- _postToWorker: Task.async(function*(...args) {
- if (!this._initializationStarted) {
- // Initializing the worker is somewhat complex, as proper handling of
- // backups requires us to first read and check the session. Consequently,
- // the only way to initialize the worker is to first call `this.read()`.
-
- // The call to `this.read()` causes background initialization of the worker.
- // Initialization will be complete once `this._deferredInitialized.promise`
- // resolves.
- this.read();
- }
- yield this._deferredInitialized.promise;
- return SessionWorker.post(...args)
- }),
-
- write: function (aData) {
- if (RunState.isClosed) {
- return Promise.reject(new Error("SessionFile is closed"));
- }
-
- let isFinalWrite = false;
- if (RunState.isClosing) {
- // If shutdown has started, we will want to stop receiving
- // write instructions.
- isFinalWrite = true;
- RunState.setClosed();
- }
-
- let performShutdownCleanup = isFinalWrite &&
- !sessionStartup.isAutomaticRestoreEnabled();
-
- this._attempts++;
- let options = {isFinalWrite, performShutdownCleanup};
- let promise = this._postToWorker("write", [aData, options]);
-
- // Wait until the write is done.
- promise = promise.then(msg => {
- // Record how long the write took.
- this._recordTelemetry(msg.telemetry);
- this._successes++;
- if (msg.result.upgradeBackup) {
- // We have just completed a backup-on-upgrade, store the information
- // in preferences.
- Services.prefs.setCharPref(PREF_UPGRADE_BACKUP,
- Services.appinfo.platformBuildID);
- }
- }, err => {
- // Catch and report any errors.
- console.error("Could not write session state file ", err, err.stack);
- this._failures++;
- // By not doing anything special here we ensure that |promise| cannot
- // be rejected anymore. The shutdown/cleanup code at the end of the
- // function will thus always be executed.
- });
-
- // Ensure that we can write sessionstore.js cleanly before the profile
- // becomes unaccessible.
- AsyncShutdown.profileBeforeChange.addBlocker(
- "SessionFile: Finish writing Session Restore data",
- promise,
- {
- fetchState: () => ({
- options,
- attempts: this._attempts,
- successes: this._successes,
- failures: this._failures,
- })
- });
-
- // This code will always be executed because |promise| can't fail anymore.
- // We ensured that by having a reject handler that reports the failure but
- // doesn't forward the rejection.
- return promise.then(() => {
- // Remove the blocker, no matter if writing failed or not.
- AsyncShutdown.profileBeforeChange.removeBlocker(promise);
-
- if (isFinalWrite) {
- Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
- }
- });
- },
-
- wipe: function () {
- return this._postToWorker("wipe");
- },
-
- _recordTelemetry: function(telemetry) {
- for (let id of Object.keys(telemetry)){
- let value = telemetry[id];
- let samples = [];
- if (Array.isArray(value)) {
- samples.push(...value);
- } else {
- samples.push(value);
- }
- let histogram = Telemetry.getHistogramById(id);
- for (let sample of samples) {
- histogram.add(sample);
- }
- }
- }
-};