summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/sessionstore/test/unit')
-rw-r--r--browser/components/sessionstore/test/unit/.eslintrc.js7
-rw-r--r--browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json1
-rw-r--r--browser/components/sessionstore/test/unit/data/sessionstore_invalid.js3
-rw-r--r--browser/components/sessionstore/test/unit/data/sessionstore_valid.js3
-rw-r--r--browser/components/sessionstore/test/unit/head.js32
-rw-r--r--browser/components/sessionstore/test/unit/test_backup_once.js130
-rw-r--r--browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js114
-rw-r--r--browser/components/sessionstore/test/unit/test_shutdown_cleanup.js127
-rw-r--r--browser/components/sessionstore/test/unit/test_startup_invalid_session.js21
-rw-r--r--browser/components/sessionstore/test/unit/test_startup_nosession_async.js22
-rw-r--r--browser/components/sessionstore/test/unit/test_startup_session_async.js27
-rw-r--r--browser/components/sessionstore/test/unit/xpcshell.ini16
12 files changed, 503 insertions, 0 deletions
diff --git a/browser/components/sessionstore/test/unit/.eslintrc.js b/browser/components/sessionstore/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json
new file mode 100644
index 000000000..928de6a39
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json
@@ -0,0 +1 @@
+{"profile-after-change":true,"final-ui-startup":true,"sessionstore-windows-restored":true,"quit-application-granted":true,"quit-application":true,"sessionstore-final-state-write-complete":true,"profile-change-net-teardown":true,"profile-change-teardown":true,"profile-before-change":true} \ No newline at end of file
diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
new file mode 100644
index 000000000..a8c3ff2ff
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
@@ -0,0 +1,3 @@
+{
+ "windows": // invalid json
+}
diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_valid.js b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js
new file mode 100644
index 000000000..f9511f29f
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js
@@ -0,0 +1,3 @@
+{
+ "windows": []
+} \ No newline at end of file
diff --git a/browser/components/sessionstore/test/unit/head.js b/browser/components/sessionstore/test/unit/head.js
new file mode 100644
index 000000000..b62856012
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/head.js
@@ -0,0 +1,32 @@
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// Call a function once initialization of SessionStartup is complete
+function afterSessionStartupInitialization(cb) {
+ do_print("Waiting for session startup initialization");
+ let observer = function() {
+ try {
+ do_print("Session startup initialization observed");
+ Services.obs.removeObserver(observer, "sessionstore-state-finalized");
+ cb();
+ } catch (ex) {
+ do_throw(ex);
+ }
+ };
+
+ // We need the Crash Monitor initialized for sessionstartup to run
+ // successfully.
+ Components.utils.import("resource://gre/modules/CrashMonitor.jsm");
+ CrashMonitor.init();
+
+ // Start sessionstartup initialization.
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsIObserver);
+ Services.obs.addObserver(startup, "final-ui-startup", false);
+ Services.obs.addObserver(startup, "quit-application", false);
+ Services.obs.notifyObservers(null, "final-ui-startup", "");
+ Services.obs.addObserver(observer, "sessionstore-state-finalized", false);
+};
diff --git a/browser/components/sessionstore/test/unit/test_backup_once.js b/browser/components/sessionstore/test/unit/test_backup_once.js
new file mode 100644
index 000000000..fff34ad58
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_backup_once.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+var {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {});
+
+var File = OS.File;
+var Paths;
+var SessionFile;
+
+// We need a XULAppInfo to initialize SessionFile
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+updateAppInfo({
+ name: "SessionRestoreTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* init() {
+ // Make sure that we have a profile before initializing SessionFile
+ let profd = do_get_profile();
+ SessionFile = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}).SessionFile;
+ Paths = SessionFile.Paths;
+
+
+ let source = do_get_file("data/sessionstore_valid.js");
+ source.copyTo(profd, "sessionstore.js");
+
+ // Finish initialization of SessionFile
+ yield SessionFile.read();
+});
+
+var pathStore;
+var pathBackup;
+var decoder;
+
+function promise_check_exist(path, shouldExist) {
+ return Task.spawn(function*() {
+ do_print("Ensuring that " + path + (shouldExist?" exists":" does not exist"));
+ if ((yield OS.File.exists(path)) != shouldExist) {
+ throw new Error("File " + path + " should " + (shouldExist?"exist":"not exist"));
+ }
+ });
+}
+
+function promise_check_contents(path, expect) {
+ return Task.spawn(function*() {
+ do_print("Checking whether " + path + " has the right contents");
+ let actual = yield OS.File.read(path, { encoding: "utf-8"});
+ Assert.deepEqual(JSON.parse(actual), expect, `File ${path} contains the expected data.`);
+ });
+}
+
+function generateFileContents(id) {
+ let url = `http://example.com/test_backup_once#${id}_${Math.random()}`;
+ return {windows: [{tabs: [{entries: [{url}], index: 1}]}]}
+}
+
+// Write to the store, and check that it creates:
+// - $Path.recovery with the new data
+// - $Path.nextUpgradeBackup with the old data
+add_task(function* test_first_write_backup() {
+ let initial_content = generateFileContents("initial");
+ let new_content = generateFileContents("test_1");
+
+ do_print("Before the first write, none of the files should exist");
+ yield promise_check_exist(Paths.backups, false);
+
+ yield File.makeDir(Paths.backups);
+ yield File.writeAtomic(Paths.clean, JSON.stringify(initial_content), { encoding: "utf-8" });
+ yield SessionFile.write(new_content);
+
+ do_print("After first write, a few files should have been created");
+ yield promise_check_exist(Paths.backups, true);
+ yield promise_check_exist(Paths.clean, false);
+ yield promise_check_exist(Paths.cleanBackup, true);
+ yield promise_check_exist(Paths.recovery, true);
+ yield promise_check_exist(Paths.recoveryBackup, false);
+ yield promise_check_exist(Paths.nextUpgradeBackup, true);
+
+ yield promise_check_contents(Paths.recovery, new_content);
+ yield promise_check_contents(Paths.nextUpgradeBackup, initial_content);
+});
+
+// Write to the store again, and check that
+// - $Path.clean is not written
+// - $Path.recovery contains the new data
+// - $Path.recoveryBackup contains the previous data
+add_task(function* test_second_write_no_backup() {
+ let new_content = generateFileContents("test_2");
+ let previous_backup_content = yield File.read(Paths.recovery, { encoding: "utf-8" });
+ previous_backup_content = JSON.parse(previous_backup_content);
+
+ yield OS.File.remove(Paths.cleanBackup);
+
+ yield SessionFile.write(new_content);
+
+ yield promise_check_exist(Paths.backups, true);
+ yield promise_check_exist(Paths.clean, false);
+ yield promise_check_exist(Paths.cleanBackup, false);
+ yield promise_check_exist(Paths.recovery, true);
+ yield promise_check_exist(Paths.nextUpgradeBackup, true);
+
+ yield promise_check_contents(Paths.recovery, new_content);
+ yield promise_check_contents(Paths.recoveryBackup, previous_backup_content);
+});
+
+// Make sure that we create $Paths.clean and remove $Paths.recovery*
+// upon shutdown
+add_task(function* test_shutdown() {
+ let output = generateFileContents("test_3");
+
+ yield File.writeAtomic(Paths.recovery, "I should disappear");
+ yield File.writeAtomic(Paths.recoveryBackup, "I should also disappear");
+
+ yield SessionWorker.post("write", [output, { isFinalWrite: true, performShutdownCleanup: true}]);
+
+ do_check_false((yield File.exists(Paths.recovery)));
+ do_check_false((yield File.exists(Paths.recoveryBackup)));
+ yield promise_check_contents(Paths.clean, output);
+});
diff --git a/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js
new file mode 100644
index 000000000..c7d8b03ed
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * The primary purpose of this test is to ensure that
+ * the sessionstore component records information about
+ * corrupted backup files into a histogram.
+ */
+
+"use strict";
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+const Telemetry = Services.telemetry;
+const Path = OS.Path;
+const HistogramId = "FX_SESSION_RESTORE_ALL_FILES_CORRUPT";
+
+// Prepare the session file.
+var profd = do_get_profile();
+Cu.import("resource:///modules/sessionstore/SessionFile.jsm", this);
+
+/**
+ * A utility function for resetting the histogram and the contents
+ * of the backup directory.
+ */
+function reset_session(backups = {}) {
+
+ // Reset the histogram.
+ Telemetry.getHistogramById(HistogramId).clear();
+
+ // Reset the contents of the backups directory
+ OS.File.makeDir(SessionFile.Paths.backups);
+ for (let key of SessionFile.Paths.loadOrder) {
+ if (backups.hasOwnProperty(key)) {
+ OS.File.copy(backups[key], SessionFile.Paths[key]);
+ } else {
+ OS.File.remove(SessionFile.Paths[key]);
+ }
+ }
+}
+
+/**
+ * In order to use FX_SESSION_RESTORE_ALL_FILES_CORRUPT histogram
+ * it has to be registered in "toolkit/components/telemetry/Histograms.json".
+ * This test ensures that the histogram is registered and empty.
+ */
+add_task(function* test_ensure_histogram_exists_and_empty() {
+ let s = Telemetry.getHistogramById(HistogramId).snapshot();
+ Assert.equal(s.sum, 0, "Initially, the sum of probes is 0");
+});
+
+/**
+ * Makes sure that the histogram is negatively updated when no
+ * backup files are present.
+ */
+add_task(function* test_no_files_exist() {
+ // No session files are available to SessionFile.
+ reset_session();
+
+ yield SessionFile.read();
+ // Checking if the histogram is updated negatively
+ let h = Telemetry.getHistogramById(HistogramId);
+ let s = h.snapshot();
+ Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket.");
+ Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket.");
+});
+
+/**
+ * Makes sure that the histogram is negatively updated when at least one
+ * backup file is not corrupted.
+ */
+add_task(function* test_one_file_valid() {
+ // Corrupting some backup files.
+ let invalidSession = "data/sessionstore_invalid.js";
+ let validSession = "data/sessionstore_valid.js";
+ reset_session({
+ clean : invalidSession,
+ cleanBackup: validSession,
+ recovery: invalidSession,
+ recoveryBackup: invalidSession
+ });
+
+ yield SessionFile.read();
+ // Checking if the histogram is updated negatively.
+ let h = Telemetry.getHistogramById(HistogramId);
+ let s = h.snapshot();
+ Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket.");
+ Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket.");
+});
+
+/**
+ * Makes sure that the histogram is positively updated when all
+ * backup files are corrupted.
+ */
+add_task(function* test_all_files_corrupt() {
+ // Corrupting all backup files.
+ let invalidSession = "data/sessionstore_invalid.js";
+ reset_session({
+ clean : invalidSession,
+ cleanBackup: invalidSession,
+ recovery: invalidSession,
+ recoveryBackup: invalidSession
+ });
+
+ yield SessionFile.read();
+ // Checking if the histogram is positively updated.
+ let h = Telemetry.getHistogramById(HistogramId);
+ let s = h.snapshot();
+ Assert.equal(s.counts[1], 1, "One probe for the 'true' bucket.");
+ Assert.equal(s.counts[0], 0, "No probes in the 'false' bucket.");
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js
new file mode 100644
index 000000000..b99e566e9
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js
@@ -0,0 +1,127 @@
+"use strict";
+
+/**
+ * This test ensures that we correctly clean up the session state before
+ * writing to disk a last time on shutdown. For now it only tests that each
+ * tab's shistory is capped to a maximum number of preceding and succeeding
+ * entries.
+ */
+
+const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+const {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {});
+
+const profd = do_get_profile();
+const {SessionFile} = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {});
+const {Paths} = SessionFile;
+
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+const {File} = OS;
+
+const MAX_ENTRIES = 9;
+const URL = "http://example.com/#";
+
+// We need a XULAppInfo to initialize SessionFile
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+updateAppInfo({
+ name: "SessionRestoreTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+add_task(function* setup() {
+ let source = do_get_file("data/sessionstore_valid.js");
+ source.copyTo(profd, "sessionstore.js");
+
+ // Finish SessionFile initialization.
+ yield SessionFile.read();
+
+ // Reset prefs on cleanup.
+ do_register_cleanup(() => {
+ Services.prefs.clearUserPref("browser.sessionstore.max_serialize_back");
+ Services.prefs.clearUserPref("browser.sessionstore.max_serialize_forward");
+ });
+});
+
+function createSessionState(index) {
+ // Generate the tab state entries and set the one-based
+ // tab-state index to the middle session history entry.
+ let tabState = {entries: [], index};
+ for (let i = 0; i < MAX_ENTRIES; i++) {
+ tabState.entries.push({url: URL + i});
+ }
+
+ return {windows: [{tabs: [tabState]}]};
+}
+
+function* setMaxBackForward(back, fwd) {
+ Services.prefs.setIntPref("browser.sessionstore.max_serialize_back", back);
+ Services.prefs.setIntPref("browser.sessionstore.max_serialize_forward", fwd);
+ yield SessionFile.read();
+}
+
+function* writeAndParse(state, path, options = {}) {
+ yield SessionWorker.post("write", [state, options]);
+ return JSON.parse(yield File.read(path, {encoding: "utf-8"}));
+}
+
+add_task(function* test_shistory_cap_none() {
+ let state = createSessionState(5);
+
+ // Don't limit the number of shistory entries.
+ yield setMaxBackForward(-1, -1);
+
+ // Check that no caps are applied.
+ let diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ Assert.deepEqual(state, diskState, "no cap applied");
+});
+
+add_task(function* test_shistory_cap_middle() {
+ let state = createSessionState(5);
+ yield setMaxBackForward(2, 3);
+
+ // Cap is only applied on clean shutdown.
+ let diskState = yield writeAndParse(state, Paths.recovery);
+ Assert.deepEqual(state, diskState, "no cap applied");
+
+ // Check that the right number of shistory entries was discarded
+ // and the shistory index updated accordingly.
+ diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ let tabState = state.windows[0].tabs[0];
+ tabState.entries = tabState.entries.slice(2, 8);
+ tabState.index = 3;
+ Assert.deepEqual(state, diskState, "cap applied");
+});
+
+add_task(function* test_shistory_cap_lower_bound() {
+ let state = createSessionState(1);
+ yield setMaxBackForward(5, 5);
+
+ // Cap is only applied on clean shutdown.
+ let diskState = yield writeAndParse(state, Paths.recovery);
+ Assert.deepEqual(state, diskState, "no cap applied");
+
+ // Check that the right number of shistory entries was discarded.
+ diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ let tabState = state.windows[0].tabs[0];
+ tabState.entries = tabState.entries.slice(0, 6);
+ Assert.deepEqual(state, diskState, "cap applied");
+});
+
+add_task(function* test_shistory_cap_upper_bound() {
+ let state = createSessionState(MAX_ENTRIES);
+ yield setMaxBackForward(5, 5);
+
+ // Cap is only applied on clean shutdown.
+ let diskState = yield writeAndParse(state, Paths.recovery);
+ Assert.deepEqual(state, diskState, "no cap applied");
+
+ // Check that the right number of shistory entries was discarded
+ // and the shistory index updated accordingly.
+ diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ let tabState = state.windows[0].tabs[0];
+ tabState.entries = tabState.entries.slice(3);
+ tabState.index = 6;
+ Assert.deepEqual(state, diskState, "cap applied");
+});
diff --git a/browser/components/sessionstore/test/unit/test_startup_invalid_session.js b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js
new file mode 100644
index 000000000..9f6df8585
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ let profd = do_get_profile();
+
+ let sourceSession = do_get_file("data/sessionstore_invalid.js");
+ sourceSession.copyTo(profd, "sessionstore.js");
+
+ let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json");
+ sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json");
+
+ do_test_pending();
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+
+ afterSessionStartupInitialization(function cb() {
+ do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION);
+ do_test_finished();
+ });
+}
diff --git a/browser/components/sessionstore/test/unit/test_startup_nosession_async.js b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js
new file mode 100644
index 000000000..5185b02d6
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+// Test nsISessionStartup.sessionType in the following scenario:
+// - no sessionstore.js;
+// - the session store has been loaded, so no need to go
+// through the synchronous fallback
+
+function run_test() {
+ do_get_profile();
+ // Initialize the profile (the session startup uses it)
+
+ do_test_pending();
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+
+ afterSessionStartupInitialization(function cb() {
+ do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION);
+ do_test_finished();
+ });
+} \ No newline at end of file
diff --git a/browser/components/sessionstore/test/unit/test_startup_session_async.js b/browser/components/sessionstore/test/unit/test_startup_session_async.js
new file mode 100644
index 000000000..459acf885
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_startup_session_async.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+// Test nsISessionStartup.sessionType in the following scenario:
+// - valid sessionstore.js;
+// - valid sessionCheckpoints.json with all checkpoints;
+// - the session store has been loaded
+
+function run_test() {
+ let profd = do_get_profile();
+
+ let sourceSession = do_get_file("data/sessionstore_valid.js");
+ sourceSession.copyTo(profd, "sessionstore.js");
+
+ let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json");
+ sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json");
+
+ do_test_pending();
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+
+ afterSessionStartupInitialization(function cb() {
+ do_check_eq(startup.sessionType, Ci.nsISessionStartup.DEFER_SESSION);
+ do_test_finished();
+ });
+}
diff --git a/browser/components/sessionstore/test/unit/xpcshell.ini b/browser/components/sessionstore/test/unit/xpcshell.ini
new file mode 100644
index 000000000..09980f877
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/xpcshell.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ data/sessionCheckpoints_all.json
+ data/sessionstore_invalid.js
+ data/sessionstore_valid.js
+
+[test_backup_once.js]
+[test_histogram_corrupt_files.js]
+[test_shutdown_cleanup.js]
+[test_startup_nosession_async.js]
+[test_startup_session_async.js]
+[test_startup_invalid_session.js]