Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
do_get_profile();
const { SessionFile } = ChromeUtils.importESModule(
"moz-src:///browser/components/sessionstore/SessionFile.sys.mjs"
);
const { FirefoxProfileMigrator } = ChromeUtils.importESModule(
"moz-src:///browser/components/migration/FirefoxProfileMigrator.sys.mjs"
);
// MigrationUtils is a browser-window global (from browser.js), but xpcshell
// tests don't have a browser window so the explicit import is necessary.
// eslint-disable-next-line mozilla/no-redeclare-with-import-autofix
const { MigrationUtils } = ChromeUtils.importESModule(
"moz-src:///browser/components/migration/MigrationUtils.sys.mjs"
);
const { updateAppInfo } = ChromeUtils.importESModule(
);
updateAppInfo({
name: "SessionRestoreTest",
ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
version: "1",
platformVersion: "",
});
const ENCRYPTION_AVAILABLE_PREF = "browser.sessionstore.encryption.available";
const ENCRYPTION_PREF = "browser.sessionstore.encryption.enabled";
const DEK_NAME_SESSIONSTORE = "sessionstore";
// Header IOUtils writes in front of encrypted files.
const ENC_MAGIC = [0x6d, 0x6f, 0x7a, 0x45, 0x6e, 0x63, 0x30, 0x00];
async function readFileMagic(path) {
let bytes = await IOUtils.read(path, { maxBytes: ENC_MAGIC.length });
return Array.from(bytes);
}
function nsIFileFromPath(path) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(path);
return file;
}
// Set up a source profile directory with a plaintext session file,
// sessionCheckpoints.json, and optionally a prefs.js.
async function makeSourceProfile(sourceDir, prefsContent) {
await IOUtils.makeDirectory(sourceDir);
let state = {
windows: [{ tabs: [{ entries: [{ url: "https://example.com" }] }] }],
};
await IOUtils.writeUTF8(
PathUtils.join(sourceDir, "sessionstore.jsonlz4"),
JSON.stringify(state),
{ compress: true }
);
await IOUtils.writeJSON(
PathUtils.join(sourceDir, "sessionCheckpoints.json"),
{ profile: true }
);
if (prefsContent) {
await IOUtils.writeUTF8(
PathUtils.join(sourceDir, "prefs.js"),
prefsContent
);
}
}
async function runSessionMigration(sourceDir, destDir) {
await IOUtils.makeDirectory(destDir);
Services.env.set("MOZ_RESET_PROFILE_SESSION", "1");
let migrator = new FirefoxProfileMigrator();
let resources = migrator.getResourcesInternal(
nsIFileFromPath(sourceDir),
nsIFileFromPath(destDir)
);
let sessionResource = resources.find(
r => r.type == MigrationUtils.resourceTypes.SESSION
);
Assert.ok(sessionResource, "SESSION resource was created");
let succeeded = await new Promise(resolve => {
sessionResource.migrate(resolve);
});
Assert.ok(succeeded, "migration callback reported success");
}
add_setup(async function () {
Services.prefs
.getDefaultBranch("")
.setBoolPref(ENCRYPTION_AVAILABLE_PREF, true);
Services.prefs.lockPref(ENCRYPTION_AVAILABLE_PREF);
Services.prefs.getDefaultBranch("").setBoolPref(ENCRYPTION_PREF, true);
registerCleanupFunction(() => {
Services.prefs.unlockPref(ENCRYPTION_AVAILABLE_PREF);
Services.prefs.clearUserPref(ENCRYPTION_AVAILABLE_PREF);
Services.prefs.clearUserPref(ENCRYPTION_PREF);
Services.prefs.getDefaultBranch("").deleteBranch(ENCRYPTION_PREF);
Services.prefs.getDefaultBranch("").deleteBranch(ENCRYPTION_AVAILABLE_PREF);
});
await SessionFile.read();
});
// Lockstore key files from the source profile should be copied to the
// destination so SessionMigration can decrypt the session data.
add_task(async function test_copies_lockstore_keys() {
let sourceDir = PathUtils.join(PathUtils.tempDir, "copy-keys-source");
let destDir = PathUtils.join(PathUtils.tempDir, "copy-keys-dest");
await makeSourceProfile(sourceDir);
// Place lockstore key files in the source.
await IOUtils.writeUTF8(
PathUtils.join(sourceDir, "lockstore.keys.sqlite"),
"fake-key-data"
);
await IOUtils.writeUTF8(
PathUtils.join(sourceDir, "lockstore.keys.sqlite-wal"),
"fake-wal-data"
);
await runSessionMigration(sourceDir, destDir);
Assert.ok(
await IOUtils.exists(PathUtils.join(destDir, "lockstore.keys.sqlite")),
"lockstore.keys.sqlite was copied"
);
Assert.ok(
await IOUtils.exists(PathUtils.join(destDir, "lockstore.keys.sqlite-wal")),
"lockstore.keys.sqlite-wal was copied"
);
await IOUtils.remove(sourceDir, { recursive: true });
await IOUtils.remove(destDir, { recursive: true });
});
// An encrypted source session is decrypted, converted and written back
// encrypted, with its history entries intact.
add_task(async function test_migrates_encrypted_session() {
let sourceDir = PathUtils.join(PathUtils.tempDir, "enc-session-source");
let destDir = PathUtils.join(PathUtils.tempDir, "enc-session-dest");
await makeSourceProfile(sourceDir);
let entries = [{ url: "https://example.com/encrypted", title: "Encrypted" }];
await SessionFile.write({
selectedWindow: 0,
windows: [
{
selected: 1,
tabs: [{ entries, index: 1, hidden: false, pinned: false }],
},
],
});
let sourcePath = PathUtils.join(sourceDir, "sessionstore.jsonlz4");
await IOUtils.copy(SessionFile.Paths.recovery, sourcePath);
Assert.deepEqual(
await readFileMagic(sourcePath),
ENC_MAGIC,
"source session file is encrypted"
);
await runSessionMigration(sourceDir, destDir);
let destPath = PathUtils.join(destDir, "sessionstore.jsonlz4");
Assert.deepEqual(
await readFileMagic(destPath),
ENC_MAGIC,
"migrated session file is encrypted"
);
let migrated = await IOUtils.readJSON(destPath, {
decompress: true,
decrypt: DEK_NAME_SESSIONSTORE,
});
Assert.deepEqual(
migrated.windows[0].tabs[0].formdata.id.sessionData.windows[0].tabs[0]
.entries,
entries,
"migrated session keeps the original history entries"
);
await IOUtils.remove(sourceDir, { recursive: true });
await IOUtils.remove(destDir, { recursive: true });
});
// The enabled pref is migrated from the source profile's prefs.js. The
// available pref is left to the destination build.
add_task(async function test_migrates_encryption_pref_true() {
let sourceDir = PathUtils.join(PathUtils.tempDir, "pref-true-source");
let destDir = PathUtils.join(PathUtils.tempDir, "pref-true-dest");
await makeSourceProfile(
sourceDir,
'user_pref("browser.sessionstore.encryption.available", true);\n' +
'user_pref("browser.sessionstore.encryption.enabled", true);\n'
);
await runSessionMigration(sourceDir, destDir);
Assert.ok(
!Services.prefs.prefHasUserValue(ENCRYPTION_AVAILABLE_PREF),
"encryption available pref is not migrated"
);
Assert.equal(
Services.prefs.getBoolPref(ENCRYPTION_PREF),
true,
"encryption pref migrated as true"
);
await IOUtils.remove(sourceDir, { recursive: true });
await IOUtils.remove(destDir, { recursive: true });
});
add_task(async function test_migrates_encryption_pref_false() {
let sourceDir = PathUtils.join(PathUtils.tempDir, "pref-false-source");
let destDir = PathUtils.join(PathUtils.tempDir, "pref-false-dest");
await makeSourceProfile(
sourceDir,
'user_pref("browser.sessionstore.encryption.available", false);\n' +
'user_pref("browser.sessionstore.encryption.enabled", false);\n'
);
await runSessionMigration(sourceDir, destDir);
Assert.ok(
!Services.prefs.prefHasUserValue(ENCRYPTION_AVAILABLE_PREF),
"encryption available pref is not migrated"
);
Assert.equal(
Services.prefs.getBoolPref(ENCRYPTION_PREF),
false,
"encryption pref migrated as false"
);
await IOUtils.remove(sourceDir, { recursive: true });
await IOUtils.remove(destDir, { recursive: true });
});