Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: browser/components/sessionstore/test/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
/*
* Ensures SessionFile.read() reports what it observed of each candidate file
* while looking for one to load. The report describes the search rather than
* the profile directory: files after the one that loaded are never examined,
* and an upgrade backup that was never taken is distinct from one that has
* gone missing.
*/
"use strict";
do_get_profile();
const { SessionFile } = ChromeUtils.importESModule(
"moz-src:///browser/components/sessionstore/SessionFile.sys.mjs"
);
const PREF_UPGRADE_BACKUP = "browser.sessionstore.upgradeBackup.latestBuildID";
const VALID_SESSION = JSON.stringify({
version: ["sessionrestore", 1],
windows: [],
});
const CORRUPT_SESSION = "{ this is not json";
const ALL_KEYS = [
"clean",
"recovery",
"recoveryBackup",
"cleanBackup",
"upgradeBackup",
];
/**
* The path SessionFile reads from when it falls back to the deprecated
* uncompressed file format.
*
* @param {string} path - A path from SessionFile.Paths.
* @returns {string} The same path with the legacy extension.
*/
function legacyPath(path) {
return path.replace("jsonlz4", "js").replace("baklz4", "bak");
}
/**
* Empties the profile of session files and writes the ones supplied.
*
* @param {object} options
* @param {object} options.compressed - Session contents, keyed by load order
* key, written in the current jsonlz4 format.
* @param {object} options.legacy - Session contents, keyed by load order key,
* written to the deprecated uncompressed paths.
* @param {string} options.upgradeBackupId - Build ID to record as the latest
* upgrade backup, which is what puts "upgradeBackup" in the load order. Left
* unset, no upgrade backup was ever taken.
*/
async function promise_reset_session({
compressed = {},
legacy = {},
upgradeBackupId = null,
} = {}) {
await IOUtils.remove(SessionFile.Paths.backups, {
recursive: true,
ignoreAbsent: true,
});
await IOUtils.remove(SessionFile.Paths.clean, { ignoreAbsent: true });
await IOUtils.remove(legacyPath(SessionFile.Paths.clean), {
ignoreAbsent: true,
});
await IOUtils.makeDirectory(SessionFile.Paths.backups);
if (upgradeBackupId) {
Services.prefs.setCharPref(PREF_UPGRADE_BACKUP, upgradeBackupId);
} else {
Services.prefs.clearUserPref(PREF_UPGRADE_BACKUP);
}
for (let [key, contents] of Object.entries(compressed)) {
await IOUtils.writeUTF8(SessionFile.Paths[key], contents, {
compress: true,
});
}
for (let [key, contents] of Object.entries(legacy)) {
await IOUtils.writeUTF8(legacyPath(SessionFile.Paths[key]), contents);
}
}
/**
* @param {object} states - Expected states, keyed by load order key. Any key
* not supplied is expected to be "absent".
* @returns {object} A full set of expected file states.
*/
function expectStates(states) {
return Object.fromEntries(
ALL_KEYS.map(key => [key, states[key] ?? "absent"])
);
}
add_task(async function test_no_session_files() {
await promise_reset_session();
let result = await SessionFile.read();
Assert.equal(result.origin, "empty", "No session could be loaded.");
Assert.ok(result.noFilesFound, "Nothing was found to restore from.");
Assert.deepEqual(
result.fileStates,
expectStates({ upgradeBackup: "not_configured" }),
"Every candidate was looked for and found missing."
);
});
add_task(async function test_first_file_loads() {
await promise_reset_session({ compressed: { clean: VALID_SESSION } });
let result = await SessionFile.read();
Assert.equal(result.origin, "clean", "The clean file was loaded.");
Assert.ok(!result.noFilesFound, "A session file was found.");
Assert.deepEqual(
result.fileStates,
expectStates({
clean: "present",
recovery: "not_examined",
recoveryBackup: "not_examined",
cleanBackup: "not_examined",
upgradeBackup: "not_configured",
}),
"Once a file loads, the rest of the load order is never examined."
);
});
add_task(async function test_fallback_stops_at_first_loadable_file() {
await promise_reset_session({
compressed: { clean: CORRUPT_SESSION, recovery: VALID_SESSION },
});
let result = await SessionFile.read();
Assert.equal(result.origin, "recovery", "We fell back to the recovery file.");
Assert.ok(!result.noFilesFound, "A session file was found.");
Assert.deepEqual(
result.fileStates,
expectStates({
clean: "present",
recovery: "present",
recoveryBackup: "not_examined",
cleanBackup: "not_examined",
upgradeBackup: "not_configured",
}),
"A corrupt file counts as present, and the search stops after recovery."
);
});
add_task(async function test_present_but_unloadable_files() {
await promise_reset_session({
compressed: {
clean: CORRUPT_SESSION,
recovery: CORRUPT_SESSION,
recoveryBackup: CORRUPT_SESSION,
cleanBackup: CORRUPT_SESSION,
},
});
let result = await SessionFile.read();
Assert.equal(result.origin, "empty", "No session could be loaded.");
Assert.ok(
!result.noFilesFound,
"Files were found, even though none could be loaded."
);
Assert.deepEqual(
result.fileStates,
expectStates({
clean: "present",
recovery: "present",
recoveryBackup: "present",
cleanBackup: "present",
upgradeBackup: "not_configured",
}),
"Every candidate was examined, and being unreadable doesn't make a file absent."
);
});
add_task(async function test_upgrade_backup_present() {
await promise_reset_session({
upgradeBackupId: "20260101000000",
compressed: { upgradeBackup: VALID_SESSION },
});
let result = await SessionFile.read();
Assert.equal(
result.origin,
"upgradeBackup",
"We fell back to the upgrade backup."
);
Assert.deepEqual(
result.fileStates,
expectStates({ upgradeBackup: "present" }),
"An upgrade backup that exists is reported as present."
);
});
add_task(async function test_upgrade_backup_missing() {
await promise_reset_session({ upgradeBackupId: "20260101000000" });
let result = await SessionFile.read();
Assert.equal(result.origin, "empty", "No session could be loaded.");
Assert.deepEqual(
result.fileStates,
expectStates({}),
"An upgrade backup that was taken but has gone missing reads as absent, not as never taken."
);
});
add_task(async function test_legacy_format_session() {
await promise_reset_session({ legacy: { clean: VALID_SESSION } });
let result = await SessionFile.read();
Assert.equal(result.origin, "clean", "The legacy clean file was loaded.");
Assert.ok(
result.useOldExtension,
"The session was loaded from the deprecated file format."
);
Assert.ok(!result.noFilesFound, "A session file was found.");
Assert.deepEqual(
result.fileStates,
expectStates({
clean: "present",
upgradeBackup: "not_configured",
}),
"A file found only in the legacy format is still reported as present."
);
});