Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: browser/components/backup/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { ExperimentAPI } = ChromeUtils.importESModule(
"resource://nimbus/ExperimentAPI.sys.mjs"
);
const { NimbusTestUtils } = ChromeUtils.importESModule(
);
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
SelectableProfileService:
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
});
const BACKUP_DIR_PREF_NAME = "browser.backup.location";
const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled";
const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled";
add_setup(async () => {
setupProfile();
NimbusTestUtils.init(this);
const { cleanup: nimbusCleanup } = await NimbusTestUtils.setupTest();
await ExperimentAPI.ready();
let backupDir = await IOUtils.createUniqueDirectory(
PathUtils.profileDir,
"backup"
);
// Use temporary directory for backups.
Services.prefs.setStringPref(BACKUP_DIR_PREF_NAME, backupDir);
registerCleanupFunction(async () => {
const nimbusCleanupPromise = nimbusCleanup();
const backupDirCleanupPromise = IOUtils.remove(backupDir, {
recursive: true,
});
await Promise.all([nimbusCleanupPromise, backupDirCleanupPromise]);
Services.prefs.clearUserPref(BACKUP_DIR_PREF_NAME);
});
});
add_task(async function test_archive_killswitch_enrollment() {
let cleanupExperiment;
await archiveTemplate({
internalReason: "nimbus",
async disable() {
cleanupExperiment = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "backupService",
value: { archiveKillswitch: true },
});
},
async enable() {
await cleanupExperiment();
},
});
});
add_task(async function test_archive_enabled_pref() {
await archiveTemplate({
internalReason: "pref",
async disable() {
Services.prefs.unlockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, false);
},
async enable() {
Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true);
},
});
});
add_task(async function test_archive_policy() {
let storedDefault;
await archiveTemplate({
internalReason: "policy",
disable: () => {
Services.prefs.unlockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
const defaults = Services.prefs.getDefaultBranch("");
storedDefault = defaults.getBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
defaults.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, false);
defaults.lockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
},
enable: () => {
const defaults = Services.prefs.getDefaultBranch("");
defaults.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, storedDefault);
Services.prefs.unlockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true);
},
});
});
add_task(async function test_restore_killswitch_enrollment() {
let cleanupExperiment;
await restoreTemplate({
internalReason: "nimbus",
async disable() {
cleanupExperiment = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "backupService",
value: { restoreKillswitch: true },
});
},
async enable() {
await cleanupExperiment();
},
});
});
add_task(async function test_restore_enabled_pref() {
await restoreTemplate({
internalReason: "pref",
async disable() {
Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, false);
},
async enable() {
Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, true);
},
});
});
add_task(async function test_restore_policy() {
let storedDefault;
await restoreTemplate({
internalReason: "policy",
async disable() {
const defaults = Services.prefs.getDefaultBranch("");
storedDefault = defaults.getBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME);
defaults.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, false);
Services.prefs.lockPref(BACKUP_RESTORE_ENABLED_PREF_NAME);
},
async enable() {
Services.prefs.unlockPref(BACKUP_RESTORE_ENABLED_PREF_NAME);
const defaults = Services.prefs.getDefaultBranch("");
defaults.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, storedDefault);
},
});
});
add_task(
async function test_selectableProfilesAllowed_updates_on_enableChanged() {
let sandbox = sinon.createSandbox();
let isEnabledValue = false;
sandbox
.stub(lazy.SelectableProfileService, "isEnabled")
.get(() => isEnabledValue);
let bs = new BackupService();
bs.initStatusObservers();
Assert.equal(
bs.state.selectableProfilesAllowed,
false,
"selectableProfilesAllowed should initially be false"
);
let stateUpdatePromise = new Promise(resolve => {
bs.addEventListener("BackupService:StateUpdate", resolve, { once: true });
});
isEnabledValue = true;
lazy.SelectableProfileService.emit("enableChanged", true);
await stateUpdatePromise;
Assert.equal(
bs.state.selectableProfilesAllowed,
true,
"selectableProfilesAllowed should be true after enableChanged fires"
);
stateUpdatePromise = new Promise(resolve => {
bs.addEventListener("BackupService:StateUpdate", resolve, { once: true });
});
isEnabledValue = false;
lazy.SelectableProfileService.emit("enableChanged", false);
await stateUpdatePromise;
Assert.equal(
bs.state.selectableProfilesAllowed,
false,
"selectableProfilesAllowed should be false after disabling"
);
bs.uninitStatusObservers();
sandbox.restore();
}
);
async function archiveTemplate({ internalReason, disable, enable }) {
Services.telemetry.clearScalars();
Services.fog.testResetFOG();
let bs = new BackupService();
bs.initStatusObservers();
assertStatus("archive", bs.archiveEnabledStatus, true, null);
let calledCount = 0;
let callback = () => calledCount++;
Services.obs.addObserver(callback, "backup-service-status-updated");
await disable();
Assert.equal(calledCount, 1, "Observers were notified on disable");
assertStatus("archive", bs.archiveEnabledStatus, false, internalReason);
let backup = await bs.createBackup();
Assert.ok(
!backup,
"Creating a backup should fail when archiving is disabled."
);
await enable();
Assert.equal(calledCount, 2, "Observers were notified on re-enable");
assertStatus("archive", bs.archiveEnabledStatus, true, "reenabled");
backup = await bs.createBackup();
ok(
backup,
"Creating a backup should succeed once the archive killswitch experiment ends."
);
ok(
await IOUtils.exists(backup.archivePath),
"Archive file should exist on disk."
);
await IOUtils.remove(backup.archivePath);
bs.uninitStatusObservers();
// Also check that it works at startup.
await disable();
bs = new BackupService();
bs.initStatusObservers();
Assert.equal(calledCount, 3, "Observers were notified at startup");
assertStatus("archive", bs.archiveEnabledStatus, false, internalReason);
await enable();
bs.uninitStatusObservers();
Services.obs.removeObserver(callback, "backup-service-status-updated");
}
async function restoreTemplate({ internalReason, disable, enable }) {
Services.telemetry.clearScalars();
Services.fog.testResetFOG();
let bs = new BackupService();
bs.initStatusObservers();
assertStatus("restore", bs.restoreEnabledStatus, true, null);
const backup = await bs.createBackup();
Assert.ok(
backup && backup.archivePath,
"Archive should have been created on disk."
);
let calledCount = 0;
let callback = () => calledCount++;
Services.obs.addObserver(callback, "backup-service-status-updated");
await disable();
Assert.equal(calledCount, 1, "Observers were notified on disable");
assertStatus("restore", bs.restoreEnabledStatus, false, internalReason);
const recoveryDir = await IOUtils.createUniqueDirectory(
PathUtils.profileDir,
"recovered-profiles"
);
await Assert.rejects(
bs.recoverFromBackupArchive(
backup.archivePath,
null,
false,
PathUtils.profileDir,
recoveryDir,
true
),
/.*disabled.*/,
"Recovery should throw when the restore is disabled."
);
await enable();
Assert.equal(calledCount, 2, "Observers were notified on re-enable");
assertStatus("restore", bs.restoreEnabledStatus, true, "reenabled");
let recoveredProfile = await bs.recoverFromBackupArchive(
backup.archivePath,
null,
false,
PathUtils.profileDir,
recoveryDir,
true
);
Assert.ok(
recoveredProfile,
"Recovery should succeed once restore is re-enabled."
);
Assert.ok(
await IOUtils.exists(recoveredProfile.rootDir.path),
"Recovered profile directory should exist on disk."
);
bs.uninitStatusObservers();
// Also check that it works at startup.
await disable();
bs = new BackupService();
bs.initStatusObservers();
Assert.equal(calledCount, 3, "Observers were notified at startup");
assertStatus("restore", bs.restoreEnabledStatus, false, internalReason);
await enable();
bs.uninitStatusObservers();
Services.obs.removeObserver(callback, "backup-service-status-updated");
}
/**
* Checks that the status object matches the expected values, and that the
* telemetry matches the object.
*
* @param {string} kind
* The kind of status object.
* @param {string} status
* The status object given.
* @param {boolean} enabled
* Whether the feature should be enabled or disabled.
* @param {string?} internalReason
* The internal reason that should be given.
*/
function assertStatus(kind, status, enabled, internalReason) {
Assert.equal(
status.enabled,
enabled,
`${kind} status is ${enabled ? "" : "not "}enabled.`
);
Assert.equal(
status.internalReason ?? null,
// 'reenabled' is only for telemetry, the status is null
internalReason == "reenabled" ? null : internalReason,
`${kind} status has the expected internal reason.`
);
Assert.equal(
Glean.browserBackup[kind + "Enabled"].testGetValue(),
enabled,
`Glean ${kind}_enabled metric should be ${enabled}.`
);
TelemetryTestUtils.assertScalar(
TelemetryTestUtils.getProcessScalars("parent", false, true),
`browser.backup.${kind}_enabled`,
enabled,
`Legacy ${kind}_enabled metric should be ${enabled}.`
);
Assert.equal(
Glean.browserBackup[kind + "DisabledReason"].testGetValue(),
internalReason,
`Glean ${kind}_disabled_reason metric is ${internalReason}.`
);
}