summaryrefslogtreecommitdiffstats
path: root/toolkit/profile/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/profile/xpcshell')
-rw-r--r--toolkit/profile/xpcshell/head.js622
-rw-r--r--toolkit/profile/xpcshell/test_check_backup.js49
-rw-r--r--toolkit/profile/xpcshell/test_claim_locked.js68
-rw-r--r--toolkit/profile/xpcshell/test_clean.js165
-rw-r--r--toolkit/profile/xpcshell/test_conflict_installs.js40
-rw-r--r--toolkit/profile/xpcshell/test_conflict_profiles.js57
-rw-r--r--toolkit/profile/xpcshell/test_create_default.js52
-rw-r--r--toolkit/profile/xpcshell/test_fix_directory_case.js113
-rw-r--r--toolkit/profile/xpcshell/test_ignore_legacy_directory.js129
-rw-r--r--toolkit/profile/xpcshell/test_invalid_descriptor.js55
-rw-r--r--toolkit/profile/xpcshell/test_legacy_empty.js28
-rw-r--r--toolkit/profile/xpcshell/test_legacy_select.js67
-rw-r--r--toolkit/profile/xpcshell/test_lock.js72
-rw-r--r--toolkit/profile/xpcshell/test_missing_profilesini.js55
-rw-r--r--toolkit/profile/xpcshell/test_new_default.js122
-rw-r--r--toolkit/profile/xpcshell/test_previous_dedicated.js62
-rw-r--r--toolkit/profile/xpcshell/test_profile_reset.js59
-rw-r--r--toolkit/profile/xpcshell/test_register_app_services_logger.js26
-rw-r--r--toolkit/profile/xpcshell/test_remove.js103
-rw-r--r--toolkit/profile/xpcshell/test_remove_default.js79
-rw-r--r--toolkit/profile/xpcshell/test_select_backgroundtasks_ephemeral.js30
-rw-r--r--toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_create.js78
-rw-r--r--toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_exists.js66
-rw-r--r--toolkit/profile/xpcshell/test_select_default.js71
-rw-r--r--toolkit/profile/xpcshell/test_select_environment.js45
-rw-r--r--toolkit/profile/xpcshell/test_select_environment_named.js61
-rw-r--r--toolkit/profile/xpcshell/test_select_missing.js34
-rw-r--r--toolkit/profile/xpcshell/test_select_named.js43
-rw-r--r--toolkit/profile/xpcshell/test_select_noname.js35
-rw-r--r--toolkit/profile/xpcshell/test_select_profile_argument.js50
-rw-r--r--toolkit/profile/xpcshell/test_select_profile_argument_new.js43
-rw-r--r--toolkit/profile/xpcshell/test_select_profilemanager.js34
-rw-r--r--toolkit/profile/xpcshell/test_single_profile_selected.js73
-rw-r--r--toolkit/profile/xpcshell/test_single_profile_unselected.js74
-rw-r--r--toolkit/profile/xpcshell/test_skip_locked_environment.js131
-rw-r--r--toolkit/profile/xpcshell/test_snap.js67
-rw-r--r--toolkit/profile/xpcshell/test_snap_empty.js28
-rw-r--r--toolkit/profile/xpcshell/test_snatch_environment.js101
-rw-r--r--toolkit/profile/xpcshell/test_snatch_environment_default.js101
-rw-r--r--toolkit/profile/xpcshell/test_startswithlast.js28
-rw-r--r--toolkit/profile/xpcshell/test_steal_inuse.js77
-rw-r--r--toolkit/profile/xpcshell/test_update_selected_dedicated.js72
-rw-r--r--toolkit/profile/xpcshell/test_update_unknown_dedicated.js85
-rw-r--r--toolkit/profile/xpcshell/test_update_unselected_dedicated.js89
-rw-r--r--toolkit/profile/xpcshell/test_use_dedicated.js100
-rw-r--r--toolkit/profile/xpcshell/xpcshell.ini52
46 files changed, 3691 insertions, 0 deletions
diff --git a/toolkit/profile/xpcshell/head.js b/toolkit/profile/xpcshell/head.js
new file mode 100644
index 0000000000..48cc712891
--- /dev/null
+++ b/toolkit/profile/xpcshell/head.js
@@ -0,0 +1,622 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const NS_ERROR_START_PROFILE_MANAGER = 0x805800c9;
+
+const UPDATE_CHANNEL = AppConstants.MOZ_UPDATE_CHANNEL;
+
+let gProfD = do_get_profile();
+let gDataHome = gProfD.clone();
+gDataHome.append("data");
+gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+let gDataHomeLocal = gProfD.clone();
+gDataHomeLocal.append("local");
+gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
+ Ci.nsIXREDirProvider
+);
+xreDirProvider.setUserDataDirectory(gDataHome, false);
+xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
+
+let gIsDefaultApp = false;
+
+const ShellService = {
+ register() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ let factory = {
+ createInstance(iid) {
+ return ShellService.QueryInterface(iid);
+ },
+ };
+
+ registrar.registerFactory(
+ this.ID,
+ "ToolkitShellService",
+ this.CONTRACT,
+ factory
+ );
+ },
+
+ isDefaultApplication() {
+ return gIsDefaultApp;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIToolkitShellService"]),
+ ID: Components.ID("{ce724e0c-ed70-41c9-ab31-1033b0b591be}"),
+ CONTRACT: "@mozilla.org/toolkit/shell-service;1",
+};
+
+ShellService.register();
+
+let gIsLegacy = false;
+
+function simulateSnapEnvironment() {
+ Services.env.set("SNAP_INSTANCE_NAME", AppConstants.MOZ_APP_NAME);
+
+ gIsLegacy = true;
+}
+
+function enableLegacyProfiles() {
+ Services.env.set("MOZ_LEGACY_PROFILES", "1");
+
+ gIsLegacy = true;
+}
+
+function getProfileService() {
+ return Cc["@mozilla.org/toolkit/profile-service;1"].getService(
+ Ci.nsIToolkitProfileService
+ );
+}
+
+let PROFILE_DEFAULT = "default";
+let DEDICATED_NAME = `default-${UPDATE_CHANNEL}`;
+if (AppConstants.MOZ_DEV_EDITION) {
+ DEDICATED_NAME = PROFILE_DEFAULT = "dev-edition-default";
+}
+
+// Shared data for backgroundtasks tests.
+const BACKGROUNDTASKS_PROFILE_DATA = (() => {
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ default: false,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ default: false,
+ },
+ ],
+ installs: {
+ [hash]: {
+ default: "Path1",
+ },
+ },
+ backgroundTasksProfiles: [
+ {
+ name: `MozillaBackgroundTask-${hash}-unrelated_task`,
+ path: `saltsalt.MozillaBackgroundTask-${hash}-unrelated_task`,
+ },
+ ],
+ };
+ return profileData;
+})();
+
+/**
+ * Creates a random profile path for use.
+ */
+function makeRandomProfileDir(name) {
+ let file = gDataHome.clone();
+ file.append(name);
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return file;
+}
+
+/**
+ * A wrapper around nsIToolkitProfileService.selectStartupProfile to make it
+ * a bit nicer to use from JS.
+ */
+function selectStartupProfile(args = [], isResetting = false, legacyHash = "") {
+ let service = getProfileService();
+ let rootDir = {};
+ let localDir = {};
+ let profile = {};
+ let didCreate = service.selectStartupProfile(
+ ["xpcshell", ...args],
+ isResetting,
+ UPDATE_CHANNEL,
+ legacyHash,
+ rootDir,
+ localDir,
+ profile
+ );
+
+ if (profile.value) {
+ Assert.ok(
+ rootDir.value.equals(profile.value.rootDir),
+ "Should have matched the root dir."
+ );
+ Assert.ok(
+ localDir.value.equals(profile.value.localDir),
+ "Should have matched the local dir."
+ );
+ Assert.ok(
+ service.currentProfile === profile.value,
+ "Should have marked the profile as the current profile."
+ );
+ } else {
+ Assert.ok(!service.currentProfile, "Should be no current profile.");
+ }
+
+ return {
+ rootDir: rootDir.value,
+ localDir: localDir.value,
+ profile: profile.value,
+ didCreate,
+ };
+}
+
+function testStartsProfileManager(args = [], isResetting = false) {
+ try {
+ selectStartupProfile(args, isResetting);
+ Assert.ok(false, "Should have started the profile manager");
+ checkStartupReason();
+ } catch (e) {
+ Assert.equal(
+ e.result,
+ NS_ERROR_START_PROFILE_MANAGER,
+ "Should have started the profile manager"
+ );
+ }
+}
+
+function safeGet(ini, section, key) {
+ try {
+ return ini.getString(section, key);
+ } catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Writes a compatibility.ini file that marks the give profile directory as last
+ * used by the given install path.
+ */
+function writeCompatibilityIni(
+ dir,
+ appDir = FileUtils.getDir("CurProcD", []),
+ greDir = FileUtils.getDir("GreD", [])
+) {
+ let target = dir.clone();
+ target.append("compatibility.ini");
+
+ let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
+ Ci.nsIINIParserFactory
+ );
+ let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
+
+ // The profile service doesn't care about these so just use fixed values
+ ini.setString(
+ "Compatibility",
+ "LastVersion",
+ "64.0a1_20180919123806/20180919123806"
+ );
+ ini.setString("Compatibility", "LastOSABI", "Darwin_x86_64-gcc3");
+
+ ini.setString(
+ "Compatibility",
+ "LastPlatformDir",
+ greDir.persistentDescriptor
+ );
+ ini.setString("Compatibility", "LastAppDir", appDir.persistentDescriptor);
+
+ ini.writeFile(target);
+}
+
+/**
+ * Writes a profiles.ini based on the passed profile data.
+ * profileData should contain two properties, options and profiles.
+ * options contains a single property, startWithLastProfile.
+ * profiles is an array of profiles each containing name, path and default
+ * properties.
+ */
+function writeProfilesIni(profileData) {
+ let target = gDataHome.clone();
+ target.append("profiles.ini");
+
+ let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
+ Ci.nsIINIParserFactory
+ );
+ let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
+
+ const {
+ options = {},
+ profiles = [],
+ installs = null,
+ backgroundTasksProfiles = null,
+ } = profileData;
+
+ let { startWithLastProfile = true } = options;
+ ini.setString(
+ "General",
+ "StartWithLastProfile",
+ startWithLastProfile ? "1" : "0"
+ );
+
+ for (let i = 0; i < profiles.length; i++) {
+ let profile = profiles[i];
+ let section = `Profile${i}`;
+
+ ini.setString(section, "Name", profile.name);
+ ini.setString(section, "IsRelative", 1);
+ ini.setString(section, "Path", profile.path);
+
+ if (profile.default) {
+ ini.setString(section, "Default", "1");
+ }
+ }
+
+ if (backgroundTasksProfiles) {
+ let section = "BackgroundTasksProfiles";
+ for (let backgroundTasksProfile of backgroundTasksProfiles) {
+ ini.setString(
+ section,
+ backgroundTasksProfile.name,
+ backgroundTasksProfile.path
+ );
+ }
+ }
+
+ if (installs) {
+ ini.setString("General", "Version", "2");
+
+ for (let hash of Object.keys(installs)) {
+ ini.setString(`Install${hash}`, "Default", installs[hash].default);
+ if ("locked" in installs[hash]) {
+ ini.setString(
+ `Install${hash}`,
+ "Locked",
+ installs[hash].locked ? "1" : "0"
+ );
+ }
+ }
+
+ writeInstallsIni({ installs });
+ } else {
+ writeInstallsIni(null);
+ }
+
+ ini.writeFile(target);
+}
+
+/**
+ * Reads the existing profiles.ini into the same structure as that accepted by
+ * writeProfilesIni above. The profiles property is sorted according to name
+ * because the order is irrelevant and it makes testing easier if we can make
+ * that assumption.
+ */
+function readProfilesIni() {
+ let target = gDataHome.clone();
+ target.append("profiles.ini");
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [],
+ installs: null,
+ };
+
+ if (!target.exists()) {
+ return profileData;
+ }
+
+ let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
+ Ci.nsIINIParserFactory
+ );
+ let ini = factory.createINIParser(target);
+
+ profileData.options.startWithLastProfile =
+ safeGet(ini, "General", "StartWithLastProfile") == "1";
+ if (safeGet(ini, "General", "Version") == "2") {
+ profileData.installs = {};
+ }
+
+ let sections = ini.getSections();
+ while (sections.hasMore()) {
+ let section = sections.getNext();
+
+ if (section == "General") {
+ continue;
+ }
+
+ if (section.startsWith("Profile")) {
+ let isRelative = safeGet(ini, section, "IsRelative");
+ if (isRelative === null) {
+ break;
+ }
+ Assert.equal(
+ isRelative,
+ "1",
+ "Paths should always be relative in these tests."
+ );
+
+ let profile = {
+ name: safeGet(ini, section, "Name"),
+ path: safeGet(ini, section, "Path"),
+ };
+
+ try {
+ profile.default = ini.getString(section, "Default") == "1";
+ Assert.ok(
+ profile.default,
+ "The Default value is only written when true."
+ );
+ } catch (e) {
+ profile.default = false;
+ }
+
+ profileData.profiles.push(profile);
+ }
+
+ if (section.startsWith("Install")) {
+ Assert.ok(
+ profileData.installs,
+ "Should only see an install section if the ini version was correct."
+ );
+
+ profileData.installs[section.substring(7)] = {
+ default: safeGet(ini, section, "Default"),
+ };
+
+ let locked = safeGet(ini, section, "Locked");
+ if (locked !== null) {
+ profileData.installs[section.substring(7)].locked = locked;
+ }
+ }
+
+ if (section == "BackgroundTasksProfiles") {
+ profileData.backgroundTasksProfiles = [];
+ let backgroundTasksProfiles = ini.getKeys(section);
+ while (backgroundTasksProfiles.hasMore()) {
+ let name = backgroundTasksProfiles.getNext();
+ let path = ini.getString(section, name);
+ profileData.backgroundTasksProfiles.push({ name, path });
+ }
+ profileData.backgroundTasksProfiles.sort((a, b) =>
+ a.name.localeCompare(b.name)
+ );
+ }
+ }
+
+ profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
+
+ return profileData;
+}
+
+/**
+ * Writes an installs.ini based on the supplied data. Should be an object with
+ * keys for every installation hash each mapping to an object. Each object
+ * should have a default property for the relative path to the profile.
+ */
+function writeInstallsIni(installData) {
+ let target = gDataHome.clone();
+ target.append("installs.ini");
+
+ if (!installData) {
+ try {
+ target.remove(false);
+ } catch (e) {}
+ return;
+ }
+
+ const { installs = {} } = installData;
+
+ let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
+ Ci.nsIINIParserFactory
+ );
+ let ini = factory.createINIParser(null).QueryInterface(Ci.nsIINIParserWriter);
+
+ for (let hash of Object.keys(installs)) {
+ ini.setString(hash, "Default", installs[hash].default);
+ if ("locked" in installs[hash]) {
+ ini.setString(hash, "Locked", installs[hash].locked ? "1" : "0");
+ }
+ }
+
+ ini.writeFile(target);
+}
+
+/**
+ * Reads installs.ini into a structure like that used in the above function.
+ */
+function readInstallsIni() {
+ let target = gDataHome.clone();
+ target.append("installs.ini");
+
+ let installData = {
+ installs: {},
+ };
+
+ if (!target.exists()) {
+ return installData;
+ }
+
+ let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
+ Ci.nsIINIParserFactory
+ );
+ let ini = factory.createINIParser(target);
+
+ let sections = ini.getSections();
+ while (sections.hasMore()) {
+ let hash = sections.getNext();
+ if (hash != "General") {
+ installData.installs[hash] = {
+ default: safeGet(ini, hash, "Default"),
+ };
+
+ let locked = safeGet(ini, hash, "Locked");
+ if (locked !== null) {
+ installData.installs[hash].locked = locked;
+ }
+ }
+ }
+
+ return installData;
+}
+
+/**
+ * Check that the backup data in installs.ini matches the install data in
+ * profiles.ini.
+ */
+function checkBackup(
+ profileData = readProfilesIni(),
+ installData = readInstallsIni()
+) {
+ if (!profileData.installs) {
+ // If the profiles db isn't of the right version we wouldn't expect the
+ // backup to be accurate.
+ return;
+ }
+
+ Assert.deepEqual(
+ profileData.installs,
+ installData.installs,
+ "Backup installs.ini should match installs in profiles.ini"
+ );
+}
+
+/**
+ * Checks that the profile service seems to have the right data in it compared
+ * to profile and install data structured as in the above functions.
+ */
+function checkProfileService(
+ profileData = readProfilesIni(),
+ verifyBackup = true
+) {
+ let service = getProfileService();
+
+ let expectedStartWithLast = true;
+ if ("options" in profileData) {
+ expectedStartWithLast = profileData.options.startWithLastProfile;
+ }
+
+ Assert.equal(
+ service.startWithLastProfile,
+ expectedStartWithLast,
+ "Start with last profile should match."
+ );
+
+ let serviceProfiles = Array.from(service.profiles);
+
+ Assert.equal(
+ serviceProfiles.length,
+ profileData.profiles.length,
+ "Should be the same number of profiles."
+ );
+
+ // Sort to make matching easy.
+ serviceProfiles.sort((a, b) => a.name.localeCompare(b.name));
+ profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
+
+ let hash = xreDirProvider.getInstallHash();
+ let defaultPath =
+ profileData.installs && hash in profileData.installs
+ ? profileData.installs[hash].default
+ : null;
+ let dedicatedProfile = null;
+ let legacyProfile = null;
+
+ for (let i = 0; i < serviceProfiles.length; i++) {
+ let serviceProfile = serviceProfiles[i];
+ let expectedProfile = profileData.profiles[i];
+
+ Assert.equal(
+ serviceProfile.name,
+ expectedProfile.name,
+ "Should have the same name."
+ );
+
+ let expectedPath = Cc["@mozilla.org/file/local;1"].createInstance(
+ Ci.nsIFile
+ );
+ expectedPath.setRelativeDescriptor(gDataHome, expectedProfile.path);
+ Assert.equal(
+ serviceProfile.rootDir.path,
+ expectedPath.path,
+ "Should have the same path."
+ );
+
+ if (expectedProfile.path == defaultPath) {
+ dedicatedProfile = serviceProfile;
+ }
+
+ if (AppConstants.MOZ_DEV_EDITION) {
+ if (expectedProfile.name == PROFILE_DEFAULT) {
+ legacyProfile = serviceProfile;
+ }
+ } else if (expectedProfile.default) {
+ legacyProfile = serviceProfile;
+ }
+ }
+
+ if (gIsLegacy) {
+ Assert.equal(
+ service.defaultProfile,
+ legacyProfile,
+ "Should have seen the right profile selected."
+ );
+ } else {
+ Assert.equal(
+ service.defaultProfile,
+ dedicatedProfile,
+ "Should have seen the right profile selected."
+ );
+ }
+
+ if (verifyBackup) {
+ checkBackup(profileData);
+ }
+}
+
+function checkStartupReason(expected = undefined) {
+ const tId = "startup.profile_selection_reason";
+ let scalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ if (expected === undefined) {
+ Assert.ok(
+ !(tId in scalars),
+ "Startup telemetry should not have been recorded."
+ );
+ return;
+ }
+
+ if (tId in scalars) {
+ Assert.equal(
+ scalars[tId],
+ expected,
+ "Should have seen the right startup reason."
+ );
+ } else {
+ Assert.ok(false, "Startup telemetry should have been recorded.");
+ }
+}
diff --git a/toolkit/profile/xpcshell/test_check_backup.js b/toolkit/profile/xpcshell/test_check_backup.js
new file mode 100644
index 0000000000..4ca91caa1d
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_check_backup.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that when the profiles DB is missing the install data we reload it.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ },
+ ],
+ };
+
+ let installs = {
+ [hash]: {
+ default: "Path2",
+ },
+ };
+
+ writeProfilesIni(profileData);
+ writeInstallsIni({ installs });
+
+ let { profile, didCreate } = selectStartupProfile();
+ checkStartupReason("default");
+
+ // Should have added the backup data to the service, check that is true.
+ profileData.installs = installs;
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.equal(
+ profile.name,
+ "Profile2",
+ "Should have selected the right profile"
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_claim_locked.js b/toolkit/profile/xpcshell/test_claim_locked.js
new file mode 100644
index 0000000000..bffdea39dc
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_claim_locked.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile already locked to a different install
+ * isn't claimed by this install.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "Foo",
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ installs: {
+ other: {
+ default: defaultProfile.leafName,
+ locked: true,
+ },
+ },
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 2,
+ "Should have the right number of profiles."
+ );
+
+ let hash = xreDirProvider.getInstallHash();
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 2,
+ "Should be two known installs."
+ );
+ Assert.notEqual(
+ profileData.installs[hash].default,
+ defaultProfile.leafName,
+ "Should not have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we created this profile for this install."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.ok(
+ !selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using a different directory."
+ );
+ Assert.equal(selectedProfile.name, DEDICATED_NAME);
+});
diff --git a/toolkit/profile/xpcshell/test_clean.js b/toolkit/profile/xpcshell/test_clean.js
new file mode 100644
index 0000000000..3132d5457d
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_clean.js
@@ -0,0 +1,165 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests from a clean state.
+ * Then does some testing that creating new profiles and marking them as
+ * selected works.
+ */
+
+add_task(async () => {
+ let service = getProfileService();
+
+ let target = gDataHome.clone();
+ target.append("profiles.ini");
+ Assert.ok(!target.exists(), "profiles.ini should not exist yet.");
+ target.leafName = "installs.ini";
+ Assert.ok(!target.exists(), "installs.ini should not exist yet.");
+
+ // Create a new profile to use.
+ let newProfile = service.createProfile(null, "dedicated");
+ service.flush();
+
+ let profileData = readProfilesIni();
+
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, "dedicated", "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ // The new profile hasn't been marked as the default yet!
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 0,
+ "Should be no defaults for installs yet."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(
+ service.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ service.startWithLastProfile = false;
+ Assert.ok(
+ !service.startWithLastProfile,
+ "Should be set to not start with the last profile."
+ );
+
+ service.defaultProfile = newProfile;
+ service.flush();
+
+ profileData = readProfilesIni();
+
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ profile = profileData.profiles[0];
+ Assert.equal(profile.name, "dedicated", "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ let hash = xreDirProvider.getInstallHash();
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ profileData.profiles[0].path,
+ "Should have marked the new profile as the default for this install."
+ );
+
+ checkProfileService(profileData);
+
+ let otherProfile = service.createProfile(null, "another");
+ service.defaultProfile = otherProfile;
+
+ service.flush();
+
+ profileData = readProfilesIni();
+
+ Assert.equal(
+ profileData.profiles.length,
+ 2,
+ "Should have the right number of profiles."
+ );
+
+ profile = profileData.profiles[0];
+ Assert.equal(profile.name, "another", "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, "dedicated", "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ profileData.profiles[0].path,
+ "Should have marked the new profile as the default for this install."
+ );
+
+ checkProfileService(profileData);
+
+ newProfile.remove(true);
+ service.flush();
+
+ profileData = readProfilesIni();
+
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ profile = profileData.profiles[0];
+ Assert.equal(profile.name, "another", "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ profileData.profiles[0].path,
+ "Should have marked the new profile as the default for this install."
+ );
+
+ checkProfileService(profileData);
+
+ otherProfile.remove(true);
+ service.flush();
+
+ profileData = readProfilesIni();
+
+ Assert.equal(
+ profileData.profiles.length,
+ 0,
+ "Should have the right number of profiles."
+ );
+
+ // We leave a reference to the missing profile to stop us trying to steal the
+ // old-style default profile on next startup.
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+
+ checkProfileService(profileData);
+});
diff --git a/toolkit/profile/xpcshell/test_conflict_installs.js b/toolkit/profile/xpcshell/test_conflict_installs.js
new file mode 100644
index 0000000000..741e7ed70f
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_conflict_installs.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the profile service refuses to flush when the install.ini file
+ * has been modified.
+ */
+
+function check_unchanged(service) {
+ Assert.ok(
+ !service.isListOutdated,
+ "Should not have detected a modification."
+ );
+ try {
+ service.flush();
+ Assert.ok(true, "Should have flushed.");
+ } catch (e) {
+ Assert.ok(false, "Should have succeeded flushing.");
+ }
+}
+
+add_task(async () => {
+ let service = getProfileService();
+
+ Assert.ok(!service.isListOutdated, "Should not be modified yet.");
+
+ let installsini = gDataHome.clone();
+ installsini.append("installs.ini");
+
+ Assert.ok(!installsini.exists(), "File should not exist yet.");
+ installsini.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+ installsini.remove(false);
+ // We have to do profile selection to actually have any install data.
+ selectStartupProfile();
+ check_unchanged(service);
+
+ // We can't reset the modification time back to exactly what it was, so I
+ // guess we can't do much more here :(
+});
diff --git a/toolkit/profile/xpcshell/test_conflict_profiles.js b/toolkit/profile/xpcshell/test_conflict_profiles.js
new file mode 100644
index 0000000000..7028c1d5c2
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_conflict_profiles.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the profile service refuses to flush when the profiles.ini file
+ * has been modified.
+ */
+
+function check_unchanged(service) {
+ Assert.ok(
+ !service.isListOutdated,
+ "Should not have detected a modification."
+ );
+ try {
+ service.flush();
+ Assert.ok(true, "Should have flushed.");
+ } catch (e) {
+ Assert.ok(false, "Should have succeeded flushing.");
+ }
+}
+
+function check_outdated(service) {
+ Assert.ok(service.isListOutdated, "Should have detected a modification.");
+ try {
+ service.flush();
+ Assert.ok(false, "Should have failed to flush.");
+ } catch (e) {
+ Assert.equal(
+ e.result,
+ Cr.NS_ERROR_DATABASE_CHANGED,
+ "Should have refused to flush."
+ );
+ }
+}
+
+add_task(async () => {
+ let service = getProfileService();
+
+ Assert.ok(!service.isListOutdated, "Should not be modified yet.");
+
+ let profilesini = gDataHome.clone();
+ profilesini.append("profiles.ini");
+
+ Assert.ok(!profilesini.exists(), "File should not exist yet.");
+ profilesini.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ check_outdated(service);
+
+ profilesini.remove(false);
+ check_unchanged(service);
+
+ let oldTime = profilesini.lastModifiedTime;
+ profilesini.lastModifiedTime = oldTime - 10000;
+ check_outdated(service);
+
+ // We can't reset the modification time back to exactly what it was, so I
+ // guess we can't do much more here :(
+});
diff --git a/toolkit/profile/xpcshell/test_create_default.js b/toolkit/profile/xpcshell/test_create_default.js
new file mode 100644
index 0000000000..cdac1832bf
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_create_default.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that from an empty database a default profile is created.
+ */
+
+add_task(async () => {
+ let service = getProfileService();
+ let { profile, didCreate } = selectStartupProfile();
+
+ checkStartupReason("firstrun-created-default");
+
+ let profileData = readProfilesIni();
+ checkProfileService(profileData);
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.equal(
+ profile,
+ service.defaultProfile,
+ "Should now be the default profile."
+ );
+ Assert.equal(
+ profile.name,
+ DEDICATED_NAME,
+ "Should have created a new profile with the right name."
+ );
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 2,
+ "Should have the right number of profiles."
+ );
+
+ profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, DEDICATED_NAME, "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ let hash = xreDirProvider.getInstallHash();
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked the profile"
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_fix_directory_case.js b/toolkit/profile/xpcshell/test_fix_directory_case.js
new file mode 100644
index 0000000000..60e3acbe4e
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_fix_directory_case.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the case where the user has an default profile set for the legacy
+ * install hash. This should be switched to the new hash and correctly used as
+ * the default.
+ */
+
+add_task(async () => {
+ let currentHash = xreDirProvider.getInstallHash();
+ let legacyHash = "F87E39E944FE466E";
+
+ let defaultProfile = makeRandomProfileDir("default");
+ let dedicatedProfile = makeRandomProfileDir("dedicated");
+ let devProfile = makeRandomProfileDir("devedition");
+
+ // Make sure we don't steal the old-style default.
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ {
+ name: "dedicated",
+ path: dedicatedProfile.leafName,
+ },
+ {
+ name: "dev-edition-default",
+ path: devProfile.leafName,
+ },
+ ],
+ installs: {
+ [legacyHash]: {
+ default: dedicatedProfile.leafName,
+ },
+ otherhash: {
+ default: "foobar",
+ },
+ },
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile(
+ [],
+ false,
+ legacyHash
+ );
+ checkStartupReason("default");
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 3,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, `dedicated`, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ dedicatedProfile.leafName,
+ "Should be the expected dedicated profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 3,
+ "Should be three known installs."
+ );
+ Assert.equal(
+ profileData.installs[currentHash].default,
+ dedicatedProfile.leafName,
+ "Should have switched to the new install hash."
+ );
+ Assert.equal(
+ profileData.installs[legacyHash].default,
+ dedicatedProfile.leafName,
+ "Should have kept the old install hash."
+ );
+ Assert.equal(
+ profileData.installs.otherhash.default,
+ "foobar",
+ "Should have kept the default for the other install."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(dedicatedProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, "dedicated");
+});
diff --git a/toolkit/profile/xpcshell/test_ignore_legacy_directory.js b/toolkit/profile/xpcshell/test_ignore_legacy_directory.js
new file mode 100644
index 0000000000..29b1601cc5
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_ignore_legacy_directory.js
@@ -0,0 +1,129 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the case where the user has an default profile set for both the legacy
+ * and new install hash. This should just use the default for the new install
+ * hash.
+ */
+
+add_task(async () => {
+ let currentHash = xreDirProvider.getInstallHash();
+ let legacyHash = "F87E39E944FE466E";
+
+ let defaultProfile = makeRandomProfileDir("default");
+ let dedicatedProfile = makeRandomProfileDir("dedicated");
+ let devProfile = makeRandomProfileDir("devedition");
+
+ // Make sure we don't steal the old-style default.
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ {
+ name: "dedicated",
+ path: dedicatedProfile.leafName,
+ },
+ {
+ name: "dev-edition-default",
+ path: devProfile.leafName,
+ },
+ ],
+ installs: {
+ [legacyHash]: {
+ default: defaultProfile.leafName,
+ },
+ [currentHash]: {
+ default: dedicatedProfile.leafName,
+ },
+ otherhash: {
+ default: "foobar",
+ },
+ },
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile(
+ [],
+ false,
+ legacyHash
+ );
+ checkStartupReason("default");
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 3,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, `dedicated`, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ dedicatedProfile.leafName,
+ "Should be the expected dedicated profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ profile = profileData.profiles[2];
+ Assert.equal(
+ profile.name,
+ "dev-edition-default",
+ "Should have the right name."
+ );
+ Assert.equal(
+ profile.path,
+ devProfile.leafName,
+ "Should not be the original default profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 3,
+ "Should be three known installs."
+ );
+ Assert.equal(
+ profileData.installs[currentHash].default,
+ dedicatedProfile.leafName,
+ "Should have switched to the new install hash."
+ );
+ Assert.equal(
+ profileData.installs[legacyHash].default,
+ defaultProfile.leafName,
+ "Should have ignored old install hash."
+ );
+ Assert.equal(
+ profileData.installs.otherhash.default,
+ "foobar",
+ "Should have kept the default for the other install."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(dedicatedProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, "dedicated");
+});
diff --git a/toolkit/profile/xpcshell/test_invalid_descriptor.js b/toolkit/profile/xpcshell/test_invalid_descriptor.js
new file mode 100644
index 0000000000..87638d301d
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_invalid_descriptor.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * If a user has modified a relative profile path then there may be issues where
+ * the profile default setting doesn't match.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "../data/test",
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ },
+ ],
+ installs: {
+ [hash]: {
+ default: "test",
+ },
+ },
+ };
+
+ writeProfilesIni(profileData);
+
+ let { profile, didCreate } = selectStartupProfile();
+ checkStartupReason("default");
+
+ let service = getProfileService();
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.equal(
+ profile.name,
+ "Profile1",
+ "Should have selected the expected profile"
+ );
+
+ Assert.equal(
+ profile.name,
+ service.defaultProfile.name,
+ "Should have selected the right default."
+ );
+
+ service.flush();
+ checkProfileService();
+});
diff --git a/toolkit/profile/xpcshell/test_legacy_empty.js b/toolkit/profile/xpcshell/test_legacy_empty.js
new file mode 100644
index 0000000000..7348006f8b
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_legacy_empty.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that setting MOZ_LEGACY_PROFILES disables dedicated profiles.
+ */
+
+add_task(async () => {
+ enableLegacyProfiles();
+
+ let service = getProfileService();
+ let { profile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-created-default");
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.equal(
+ profile.name,
+ PROFILE_DEFAULT,
+ "Should have used the normal name."
+ );
+ if (AppConstants.MOZ_DEV_EDITION) {
+ Assert.equal(service.profileCount, 2, "Should be two profiles.");
+ } else {
+ Assert.equal(service.profileCount, 1, "Should be only one profile.");
+ }
+
+ checkProfileService();
+});
diff --git a/toolkit/profile/xpcshell/test_legacy_select.js b/toolkit/profile/xpcshell/test_legacy_select.js
new file mode 100644
index 0000000000..e919169d59
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_legacy_select.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile not previously used by this build
+ * gets selected when configured for legacy profiles.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ // Just pretend this profile was last used by something in the profile dir.
+ let greDir = gProfD.clone();
+ greDir.append("app");
+ writeCompatibilityIni(defaultProfile, greDir, greDir);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ enableLegacyProfiles();
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("default");
+
+ let profileData = readProfilesIni();
+ let installsINI = gDataHome.clone();
+ installsINI.append("installs.ini");
+ Assert.ok(
+ !installsINI.exists(),
+ "Installs database should not have been created."
+ );
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, PROFILE_DEFAULT);
+});
diff --git a/toolkit/profile/xpcshell/test_lock.js b/toolkit/profile/xpcshell/test_lock.js
new file mode 100644
index 0000000000..7283406e00
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_lock.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that when the default application claims the old-style default profile
+ * it locks it to itself.
+ */
+
+add_task(async () => {
+ gIsDefaultApp = true;
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ defaultProfile.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we're the default app."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, PROFILE_DEFAULT);
+});
diff --git a/toolkit/profile/xpcshell/test_missing_profilesini.js b/toolkit/profile/xpcshell/test_missing_profilesini.js
new file mode 100644
index 0000000000..c1602aeb06
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_missing_profilesini.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * When profiles.ini is missing there isn't any point in restoring from any
+ * installs.ini, the profiles it refers to are gone anyway.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+
+ let installs = {
+ [hash]: {
+ default: "Path2",
+ },
+ otherhash: {
+ default: "foo",
+ },
+ anotherhash: {
+ default: "bar",
+ },
+ };
+
+ writeInstallsIni({ installs });
+
+ let { profile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-created-default");
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.equal(
+ profile.name,
+ DEDICATED_NAME,
+ "Should have created the right profile"
+ );
+
+ let profilesData = readProfilesIni();
+ Assert.equal(
+ Object.keys(profilesData.installs).length,
+ 1,
+ "Should be only one known install"
+ );
+ Assert.ok(hash in profilesData.installs, "Should be the expected install.");
+ Assert.notEqual(
+ profilesData.installs[hash].default,
+ "Path2",
+ "Didn't import the previous data."
+ );
+ Assert.equal(
+ profilesData.profiles.length,
+ 2,
+ "Should be two profiles (old-style default and dedicated default)."
+ );
+
+ checkProfileService(profilesData);
+});
diff --git a/toolkit/profile/xpcshell/test_new_default.js b/toolkit/profile/xpcshell/test_new_default.js
new file mode 100644
index 0000000000..71dfe252c3
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_new_default.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile previously used by this build gets
+ * updated to a dedicated profile for this build.
+ */
+
+add_task(async () => {
+ let mydefaultProfile = makeRandomProfileDir("mydefault");
+ let defaultProfile = makeRandomProfileDir("default");
+ let devDefaultProfile = makeRandomProfileDir("devedition");
+
+ writeCompatibilityIni(mydefaultProfile);
+ writeCompatibilityIni(devDefaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "mydefault",
+ path: mydefaultProfile.leafName,
+ default: true,
+ },
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ },
+ {
+ name: "dev-edition-default",
+ path: devDefaultProfile.leafName,
+ },
+ ],
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-claimed-default");
+
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 3,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original non-default profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(
+ profile.name,
+ "dev-edition-default",
+ "Should have the right name."
+ );
+ Assert.equal(
+ profile.path,
+ devDefaultProfile.leafName,
+ "Should be the original dev default profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ profile = profileData.profiles[2];
+ Assert.equal(profile.name, "mydefault", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ mydefaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ if (AppConstants.MOZ_DEV_EDITION) {
+ Assert.equal(
+ profileData.installs[hash].default,
+ devDefaultProfile.leafName,
+ "Should have marked the original dev default profile as the default for this install."
+ );
+ } else {
+ Assert.equal(
+ profileData.installs[hash].default,
+ mydefaultProfile.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ }
+
+ Assert.ok(
+ !profileData.installs[hash].locked,
+ "Should not be locked as we're not the default app."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ if (AppConstants.MOZ_DEV_EDITION) {
+ Assert.ok(
+ selectedProfile.rootDir.equals(devDefaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, "dev-edition-default");
+ } else {
+ Assert.ok(
+ selectedProfile.rootDir.equals(mydefaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, "mydefault");
+ }
+});
diff --git a/toolkit/profile/xpcshell/test_previous_dedicated.js b/toolkit/profile/xpcshell/test_previous_dedicated.js
new file mode 100644
index 0000000000..6dd80b4028
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_previous_dedicated.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * If install.ini lists a default profile for this build but that profile no
+ * longer exists don't try to steal the old-style default even if it was used
+ * by this build. It means this install has previously used dedicated profiles.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ installs: {
+ [hash]: {
+ default: "foobar",
+ },
+ },
+ });
+
+ testStartsProfileManager();
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ // We keep the data here so we don't steal on the next reboot...
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Still list the broken reference."
+ );
+
+ checkProfileService(profileData);
+});
diff --git a/toolkit/profile/xpcshell/test_profile_reset.js b/toolkit/profile/xpcshell/test_profile_reset.js
new file mode 100644
index 0000000000..7cfb5ed3d1
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_profile_reset.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that from an empty database with profile reset requested a new profile
+ * is still created.
+ */
+
+add_task(async () => {
+ let { profile: selectedProfile, didCreate } = selectStartupProfile([], true);
+ // With no profile we're just create a new profile and skip resetting it.
+ checkStartupReason("firstrun-created-default");
+ checkProfileService();
+
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 2,
+ "Should have the right number of profiles, ours and the old-style default."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, DEDICATED_NAME, "Should have the right name.");
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should only be one known installs."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ profile.path,
+ "Should have taken the new profile as the default for the current install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we created this profile."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.equal(
+ selectedProfile.name,
+ profile.name,
+ "Should be using the right profile."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_register_app_services_logger.js b/toolkit/profile/xpcshell/test_register_app_services_logger.js
new file mode 100644
index 0000000000..cf777c4c6c
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_register_app_services_logger.js
@@ -0,0 +1,26 @@
+let waitForDebugLog = target =>
+ new Promise(resolve => {
+ Cc["@mozilla.org/appservices/logger;1"]
+ .getService(Ci.mozIAppServicesLogger)
+ .register(target, {
+ maxLevel: Ci.mozIServicesLogSink.LEVEL_INFO,
+ info: resolve,
+ });
+ });
+
+let rustLog = (target, message) => {
+ Cc["@mozilla.org/xpcom/debug;1"]
+ .getService(Ci.nsIDebug2)
+ .rustLog(target, message);
+};
+
+add_task(async () => {
+ let target = "app-services:webext_storage:sync";
+ let expectedMessage = "info error: uh oh";
+ let promiseMessage = waitForDebugLog(target);
+
+ rustLog(target, expectedMessage);
+
+ let actualMessage = await promiseMessage;
+ Assert.ok(actualMessage.includes(expectedMessage));
+});
diff --git a/toolkit/profile/xpcshell/test_remove.js b/toolkit/profile/xpcshell/test_remove.js
new file mode 100644
index 0000000000..8b5025d612
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_remove.js
@@ -0,0 +1,103 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests adding and removing functions correctly.
+ */
+
+function compareLists(service, knownProfiles) {
+ Assert.equal(
+ service.profileCount,
+ knownProfiles.length,
+ "profileCount should be correct."
+ );
+ let serviceProfiles = Array.from(service.profiles);
+ Assert.equal(
+ serviceProfiles.length,
+ knownProfiles.length,
+ "Enumerator length should be correct."
+ );
+
+ for (let i = 0; i < knownProfiles.length; i++) {
+ // Cannot use strictEqual here, it attempts to print out a string
+ // representation of the profile objects and on some platforms that recurses
+ // infinitely.
+ Assert.ok(
+ serviceProfiles[i] === knownProfiles[i],
+ `Should have the right profile in position ${i}.`
+ );
+ }
+}
+
+function removeProfile(profiles, position) {
+ dump(`Removing profile in position ${position}.`);
+ Assert.greaterOrEqual(position, 0, "Should be removing a valid position.");
+ Assert.less(
+ position,
+ profiles.length,
+ "Should be removing a valid position."
+ );
+
+ let last = profiles.pop();
+
+ if (profiles.length == position) {
+ // We were asked to remove the last profile.
+ last.remove(false);
+ return;
+ }
+
+ profiles[position].remove(false);
+ profiles[position] = last;
+}
+
+add_task(async () => {
+ let service = getProfileService();
+ let profiles = [];
+ compareLists(service, profiles);
+
+ profiles.push(service.createProfile(null, "profile1"));
+ profiles.push(service.createProfile(null, "profile2"));
+ profiles.push(service.createProfile(null, "profile3"));
+ profiles.push(service.createProfile(null, "profile4"));
+ profiles.push(service.createProfile(null, "profile5"));
+ profiles.push(service.createProfile(null, "profile6"));
+ profiles.push(service.createProfile(null, "profile7"));
+ profiles.push(service.createProfile(null, "profile8"));
+ profiles.push(service.createProfile(null, "profile9"));
+ compareLists(service, profiles);
+
+ // Test removing the first profile.
+ removeProfile(profiles, 0);
+ compareLists(service, profiles);
+
+ // And the last profile.
+ removeProfile(profiles, profiles.length - 1);
+ compareLists(service, profiles);
+
+ // Last but one...
+ removeProfile(profiles, profiles.length - 2);
+ compareLists(service, profiles);
+
+ // Second one...
+ removeProfile(profiles, 1);
+ compareLists(service, profiles);
+
+ // Something in the middle.
+ removeProfile(profiles, 2);
+ compareLists(service, profiles);
+
+ let expectedNames = ["profile9", "profile7", "profile5", "profile4"];
+
+ let serviceProfiles = Array.from(service.profiles);
+ for (let i = 0; i < expectedNames.length; i++) {
+ Assert.equal(serviceProfiles[i].name, expectedNames[i]);
+ }
+
+ removeProfile(profiles, 0);
+ removeProfile(profiles, 0);
+ removeProfile(profiles, 0);
+ removeProfile(profiles, 0);
+
+ Assert.equal(Array.from(service.profiles).length, 0, "All profiles gone.");
+ Assert.equal(service.profileCount, 0, "All profiles gone.");
+});
diff --git a/toolkit/profile/xpcshell/test_remove_default.js b/toolkit/profile/xpcshell/test_remove_default.js
new file mode 100644
index 0000000000..f77f2d87d9
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_remove_default.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that calling nsIToolkitProfile.remove on the default profile correctly
+ * removes the profile.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+ let defaultProfile = makeRandomProfileDir("default");
+
+ let profilesIni = {
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ installs: {
+ [hash]: {
+ default: defaultProfile.leafName,
+ },
+ },
+ };
+ writeProfilesIni(profilesIni);
+
+ let service = getProfileService();
+ checkProfileService(profilesIni);
+
+ let { profile, didCreate } = selectStartupProfile();
+ Assert.ok(!didCreate, "Should have not created a new profile.");
+ Assert.equal(
+ profile.name,
+ "default",
+ "Should have selected the default profile."
+ );
+ Assert.equal(
+ profile,
+ service.defaultProfile,
+ "Should have selected the default profile."
+ );
+
+ checkProfileService(profilesIni);
+
+ // In an actual run of Firefox we wouldn't be able to delete the profile in
+ // use because it would be locked. But we don't actually lock the profile in
+ // tests.
+ profile.remove(false);
+
+ Assert.ok(!service.defaultProfile, "Should no longer be a default profile.");
+ Assert.equal(
+ profile,
+ service.currentProfile,
+ "Should still be the profile in use."
+ );
+
+ // These are the modifications that should have been made.
+ profilesIni.profiles.pop();
+ profilesIni.installs[hash].default = "";
+
+ // The data isn't flushed to disk so don't check the backup here.
+ checkProfileService(profilesIni, false);
+
+ service.flush();
+
+ // And that should have flushed to disk correctly.
+ checkProfileService();
+
+ // checkProfileService doesn't differentiate between a blank default profile
+ // for the install and a missing install.
+ profilesIni = readProfilesIni();
+ Assert.equal(
+ profilesIni.installs[hash].default,
+ "",
+ "Should be a blank default profile."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_backgroundtasks_ephemeral.js b/toolkit/profile/xpcshell/test_select_backgroundtasks_ephemeral.js
new file mode 100644
index 0000000000..982df276db
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_backgroundtasks_ephemeral.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Verify that background tasks don't touch `profiles.ini` for ephemeral profile
+ * tasks.
+ */
+
+let condition = {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+};
+
+add_task(condition, async () => {
+ writeProfilesIni(BACKGROUNDTASKS_PROFILE_DATA);
+
+ // Pretend that this is a background task. For a task that uses an ephemeral
+ // profile, `profiles.ini` is untouched.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ bts.overrideBackgroundTaskNameForTesting("ephemeral_profile");
+
+ let { didCreate } = selectStartupProfile();
+ checkStartupReason("backgroundtask-ephemeral");
+
+ Assert.equal(didCreate, true, "Created new ephemeral profile");
+
+ let profileData = readProfilesIni();
+ Assert.deepEqual(BACKGROUNDTASKS_PROFILE_DATA, profileData);
+});
diff --git a/toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_create.js b/toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_create.js
new file mode 100644
index 0000000000..ebed889360
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_create.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Verify that background tasks that create non-ephemeral profiles update
+ * `profiles.ini` with a salted profile location.
+ */
+
+let condition = {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+};
+
+// MOZ_APP_VENDOR is empty on Thunderbird.
+let vendor = AppConstants.MOZ_APP_NAME == "thunderbird" ? "" : "Mozilla";
+
+add_task(condition, async () => {
+ let hash = xreDirProvider.getInstallHash();
+
+ writeProfilesIni(BACKGROUNDTASKS_PROFILE_DATA);
+
+ // Pretend that this is a background task. For a task that does *not* use an
+ // ephemeral profile, i.e., that uses a persistent profile, `profiles.ini` is
+ // updated with a new entry in section `BackgroundTaskProfiles`.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ // "not_ephemeral_profile" is a special name recognized by the
+ // background task system.
+ bts.overrideBackgroundTaskNameForTesting("not_ephemeral_profile");
+
+ let { didCreate, rootDir } = selectStartupProfile();
+ checkStartupReason("backgroundtask-not-ephemeral");
+
+ Assert.equal(didCreate, true, "Created new non-ephemeral profile");
+
+ let profileData = readProfilesIni();
+
+ // Profile names are lexicographically ordered, and `not_ephemeral_profile`
+ // sorts before `unrelated_task`.
+ Assert.equal(profileData.backgroundTasksProfiles.length, 2);
+ Assert.deepEqual(
+ [profileData.backgroundTasksProfiles[1]],
+ BACKGROUNDTASKS_PROFILE_DATA.backgroundTasksProfiles
+ );
+
+ let saltedPath = profileData.backgroundTasksProfiles[0].path;
+ Assert.ok(
+ saltedPath.endsWith(
+ `.${vendor}BackgroundTask-${hash}-not_ephemeral_profile`
+ ),
+ `${saltedPath} ends with ".${vendor}BackgroundTask-${hash}-not_ephemeral_profile"`
+ );
+ Assert.ok(
+ !saltedPath.startsWith(
+ `.${vendor}BackgroundTask-${hash}-not_ephemeral_profile`
+ ),
+ `${saltedPath} is really salted`
+ );
+ Assert.deepEqual(profileData.backgroundTasksProfiles[0], {
+ name: `${vendor}BackgroundTask-${hash}-not_ephemeral_profile`,
+ path: saltedPath,
+ });
+
+ Assert.ok(
+ rootDir.path.endsWith(saltedPath),
+ `rootDir "${rootDir.path}" ends with salted path "${saltedPath}"`
+ );
+
+ // Really, "UAppData", but this is xpcshell.
+ let backgroundTasksProfilesPath = gDataHome;
+ if (!AppConstants.XP_UNIX || AppConstants.platform === "macosx") {
+ backgroundTasksProfilesPath.append("Background Tasks Profiles");
+ }
+ Assert.ok(
+ rootDir.path.startsWith(backgroundTasksProfilesPath.path),
+ `rootDir "${rootDir.path}" is sibling to user profiles directory ${backgroundTasksProfilesPath}`
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_exists.js b/toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_exists.js
new file mode 100644
index 0000000000..6fa3ec5882
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_backgroundtasks_not_ephemeral_exists.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Verify that background tasks that use non-ephemeral profiles re-use existing
+ * salted profile locations from `profiles.ini`.
+ */
+
+let condition = {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+};
+
+// MOZ_APP_VENDOR is empty on Thunderbird.
+let vendor = AppConstants.MOZ_APP_NAME == "thunderbird" ? "" : "Mozilla";
+
+add_task(condition, async () => {
+ let hash = xreDirProvider.getInstallHash();
+
+ let saltedPath = `saltSALT.${vendor}BackgroundTask-${hash}-not_ephemeral_profile`;
+
+ // See note about ordering below.
+ BACKGROUNDTASKS_PROFILE_DATA.backgroundTasksProfiles.splice(0, 0, {
+ name: `${vendor}BackgroundTask-${hash}-not_ephemeral_profile`,
+ path: saltedPath,
+ });
+
+ writeProfilesIni(BACKGROUNDTASKS_PROFILE_DATA);
+
+ // Pretend that this is a background task. For a task that does *not* use an
+ // ephemeral profile, i.e., that uses a persistent profile, when
+ // `profiles.ini` section `BackgroundTasksProfiles` contains the relevant
+ // entry, that profile path is re-used.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ // "not_ephemeral_profile" is a special name recognized by the
+ // background task system.
+ bts.overrideBackgroundTaskNameForTesting("not_ephemeral_profile");
+
+ let { didCreate, rootDir } = selectStartupProfile();
+ checkStartupReason("backgroundtask-not-ephemeral");
+
+ Assert.equal(didCreate, false, "Re-used existing non-ephemeral profile");
+
+ let profileData = readProfilesIni();
+
+ // Profile names are lexicographically ordered, and `not_ephemeral_profile`
+ // sorts before `unrelated_task`.
+ Assert.equal(profileData.backgroundTasksProfiles.length, 2);
+ Assert.deepEqual(profileData, BACKGROUNDTASKS_PROFILE_DATA);
+
+ Assert.ok(
+ rootDir.path.endsWith(saltedPath),
+ `rootDir "${rootDir.path}" ends with salted path "${saltedPath}"`
+ );
+
+ // Really, "UAppData", but this is xpcshell.
+ let backgroundTasksProfilesPath = gDataHome;
+ if (!AppConstants.XP_UNIX || AppConstants.platform === "macosx") {
+ backgroundTasksProfilesPath.append("Background Tasks Profiles");
+ }
+ Assert.ok(
+ rootDir.path.startsWith(backgroundTasksProfilesPath.path),
+ `rootDir "${rootDir.path}" is sibling to user profiles directory ${backgroundTasksProfilesPath}`
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_default.js b/toolkit/profile/xpcshell/test_select_default.js
new file mode 100644
index 0000000000..df4b27df7e
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_default.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that from a database of profiles the default profile is selected.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ installs: {
+ [hash]: {
+ default: "Path2",
+ },
+ },
+ };
+
+ if (AppConstants.MOZ_DEV_EDITION) {
+ profileData.profiles.push(
+ {
+ name: "default",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: PROFILE_DEFAULT,
+ path: "Path4",
+ }
+ );
+ } else {
+ profileData.profiles.push({
+ name: PROFILE_DEFAULT,
+ path: "Path2",
+ default: true,
+ });
+ }
+
+ writeProfilesIni(profileData);
+
+ let { profile, didCreate } = selectStartupProfile();
+ checkStartupReason("default");
+
+ let service = getProfileService();
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.equal(
+ profile,
+ service.defaultProfile,
+ "Should have returned the default profile."
+ );
+ Assert.equal(
+ profile.name,
+ "default",
+ "Should have selected the right profile"
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_environment.js b/toolkit/profile/xpcshell/test_select_environment.js
new file mode 100644
index 0000000000..f7e3138191
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_environment.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the environment variables are used to select a profile.
+ */
+
+add_task(async () => {
+ let dir = makeRandomProfileDir("foo");
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: dir.leafName,
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ Services.env.set("XRE_PROFILE_PATH", dir.path);
+ Services.env.set("XRE_PROFILE_LOCAL_PATH", dir.path);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
+ checkStartupReason("restart");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(rootDir.equals(dir), "Should have selected the right root dir.");
+ Assert.ok(localDir.equals(dir), "Should have selected the right local dir.");
+ Assert.ok(!profile, "No named profile matches this.");
+});
diff --git a/toolkit/profile/xpcshell/test_select_environment_named.js b/toolkit/profile/xpcshell/test_select_environment_named.js
new file mode 100644
index 0000000000..348ce0ff0a
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_environment_named.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the environment variables are used to select a profile.
+ */
+
+add_task(async () => {
+ let root = makeRandomProfileDir("foo");
+ let local = gDataHomeLocal.clone();
+ local.append("foo");
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: root.leafName,
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ Services.env.set("XRE_PROFILE_PATH", root.path);
+ Services.env.set("XRE_PROFILE_LOCAL_PATH", local.path);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile([
+ "-P",
+ "Profile3",
+ ]);
+ checkStartupReason("restart");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(rootDir.equals(root), "Should have selected the right root dir.");
+ Assert.ok(
+ localDir.equals(local),
+ "Should have selected the right local dir."
+ );
+ Assert.ok(profile, "A named profile matches this.");
+ Assert.equal(profile.name, "Profile1", "The right profile was matched.");
+
+ let service = getProfileService();
+ Assert.notEqual(
+ service.defaultProfile,
+ profile,
+ "Should not be the default profile."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_missing.js b/toolkit/profile/xpcshell/test_select_missing.js
new file mode 100644
index 0000000000..97da6d76b3
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_missing.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that when choosing an unknown profile the profile manager is shown.
+ */
+
+add_task(async () => {
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ testStartsProfileManager(["-P", "foo"]);
+});
diff --git a/toolkit/profile/xpcshell/test_select_named.js b/toolkit/profile/xpcshell/test_select_named.js
new file mode 100644
index 0000000000..abecba1f4d
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_named.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that from a database of profiles the correct profile is selected.
+ */
+
+add_task(async () => {
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+
+ checkProfileService(profileData);
+
+ let { profile, didCreate } = selectStartupProfile(["-P", "Profile1"]);
+ checkStartupReason("argument-p");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.equal(
+ profile.name,
+ "Profile1",
+ "Should have chosen the right profile"
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_noname.js b/toolkit/profile/xpcshell/test_select_noname.js
new file mode 100644
index 0000000000..278b38089c
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_noname.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that when passing the -P command line argument and not passing a
+ * profile name the profile manager is opened.
+ */
+
+add_task(async () => {
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ testStartsProfileManager(["-P"]);
+});
diff --git a/toolkit/profile/xpcshell/test_select_profile_argument.js b/toolkit/profile/xpcshell/test_select_profile_argument.js
new file mode 100644
index 0000000000..70be506538
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_profile_argument.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that selecting a profile directory with the "profile" argument finds
+ * the matching profile.
+ */
+
+add_task(async () => {
+ let root = makeRandomProfileDir("foo");
+ let local = gDataHomeLocal.clone();
+ local.append("foo");
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: root.leafName,
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile([
+ "-profile",
+ root.path,
+ ]);
+ checkStartupReason("argument-profile");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(rootDir.equals(root), "Should have selected the right root dir.");
+ Assert.ok(
+ localDir.equals(local),
+ "Should have selected the right local dir."
+ );
+ Assert.ok(profile, "A named profile matches this.");
+ Assert.equal(profile.name, "Profile1", "The right profile was matched.");
+
+ let service = getProfileService();
+ Assert.notEqual(
+ service.defaultProfile,
+ profile,
+ "Should not be the default profile."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_select_profile_argument_new.js b/toolkit/profile/xpcshell/test_select_profile_argument_new.js
new file mode 100644
index 0000000000..570abbd19f
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_profile_argument_new.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that selecting a profile directory with the "profile" argument finds
+ * doesn't match the incorrect profile.
+ */
+
+add_task(async () => {
+ let root = makeRandomProfileDir("foo");
+ let local = gDataHomeLocal.clone();
+ local.append("foo");
+ let empty = makeRandomProfileDir("empty");
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: root.leafName,
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile([
+ "-profile",
+ empty.path,
+ ]);
+ checkStartupReason("argument-profile");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(rootDir.equals(empty), "Should have selected the right root dir.");
+ Assert.ok(
+ localDir.equals(empty),
+ "Should have selected the right local dir."
+ );
+ Assert.ok(!profile, "No named profile matches this.");
+});
diff --git a/toolkit/profile/xpcshell/test_select_profilemanager.js b/toolkit/profile/xpcshell/test_select_profilemanager.js
new file mode 100644
index 0000000000..7c8bdd820e
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_select_profilemanager.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that when requested the profile manager is shown.
+ */
+
+add_task(async () => {
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: "Profile1",
+ path: "Path1",
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ default: true,
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ testStartsProfileManager(["-profilemanager"]);
+});
diff --git a/toolkit/profile/xpcshell/test_single_profile_selected.js b/toolkit/profile/xpcshell/test_single_profile_selected.js
new file mode 100644
index 0000000000..1dc1f5c872
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_single_profile_selected.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Previous versions of Firefox automatically used a single profile even if it
+ * wasn't marked as the default. So we should try to upgrade that one if it was
+ * last used by this build. This test checks the case where it was.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: false,
+ },
+ ],
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-claimed-default");
+
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ defaultProfile.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ !profileData.installs[hash].locked,
+ "Should not have locked as we're not the default app."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, "default");
+});
diff --git a/toolkit/profile/xpcshell/test_single_profile_unselected.js b/toolkit/profile/xpcshell/test_single_profile_unselected.js
new file mode 100644
index 0000000000..3ad36de387
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_single_profile_unselected.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Previous versions of Firefox automatically used a single profile even if it
+ * wasn't marked as the default. So we should try to upgrade that one if it was
+ * last used by this build. This test checks the case where it wasn't.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ // Just pretend this profile was last used by something in the profile dir.
+ let greDir = gProfD.clone();
+ greDir.append("app");
+ writeCompatibilityIni(defaultProfile, greDir, greDir);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: false,
+ },
+ ],
+ });
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.ok(!profileData.installs, "Should be no defaults for installs yet.");
+
+ checkProfileService(profileData);
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-skipped-default");
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.ok(
+ !selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, DEDICATED_NAME);
+
+ profileData = readProfilesIni();
+
+ profile = profileData.profiles[0];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should now be marked as the old-style default.");
+
+ checkProfileService(profileData);
+});
diff --git a/toolkit/profile/xpcshell/test_skip_locked_environment.js b/toolkit/profile/xpcshell/test_skip_locked_environment.js
new file mode 100644
index 0000000000..f98f6dd88f
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_skip_locked_environment.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the environment variables are used to select a profile and that
+ * on the first run of a dedicated profile build we don't snatch it if it is
+ * locked by another install.
+ */
+
+add_task(async () => {
+ let root = makeRandomProfileDir("foo");
+ let local = gDataHomeLocal.clone();
+ local.append("foo");
+
+ writeCompatibilityIni(root);
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: root.leafName,
+ default: true,
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ // Another install is using the profile and it is locked.
+ installs: {
+ otherinstall: {
+ default: root.leafName,
+ locked: true,
+ },
+ },
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ Services.env.set("XRE_PROFILE_PATH", root.path);
+ Services.env.set("XRE_PROFILE_LOCAL_PATH", local.path);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
+ checkStartupReason("restart-skipped-default");
+
+ // Since there is already a profile with the desired name on dev-edition, a
+ // unique version will be used.
+ let expectedName = AppConstants.MOZ_DEV_EDITION
+ ? `${DEDICATED_NAME}-1`
+ : DEDICATED_NAME;
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.ok(!rootDir.equals(root), "Should have selected the right root dir.");
+ Assert.ok(
+ !localDir.equals(local),
+ "Should have selected the right local dir."
+ );
+ Assert.ok(profile, "A named profile was returned.");
+ Assert.equal(profile.name, expectedName, "The right profile name was used.");
+
+ let service = getProfileService();
+ Assert.equal(
+ service.defaultProfile,
+ profile,
+ "Should be the default profile."
+ );
+ Assert.equal(
+ service.currentProfile,
+ profile,
+ "Should be the current profile."
+ );
+
+ profileData = readProfilesIni();
+
+ Assert.equal(
+ profileData.profiles[0].name,
+ PROFILE_DEFAULT,
+ "Should be the right profile."
+ );
+ Assert.ok(
+ profileData.profiles[0].default,
+ "Should be the old default profile."
+ );
+ Assert.equal(
+ profileData.profiles[0].path,
+ root.leafName,
+ "Should be the correct path."
+ );
+ Assert.equal(
+ profileData.profiles[1].name,
+ expectedName,
+ "Should be the right profile."
+ );
+ Assert.ok(
+ !profileData.profiles[1].default,
+ "Should not be the old default profile."
+ );
+
+ let hash = xreDirProvider.getInstallHash();
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 2,
+ "Should be one known install."
+ );
+ Assert.notEqual(
+ profileData.installs[hash].default,
+ root.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we created the profile for this install."
+ );
+ Assert.equal(
+ profileData.installs.otherinstall.default,
+ root.leafName,
+ "Should have left the other profile as the default for the other install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should still be locked to the other install."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_snap.js b/toolkit/profile/xpcshell/test_snap.js
new file mode 100644
index 0000000000..a9bf4ed08a
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_snap.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile not previously used by this build gets
+ * used in a snap environment.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ // Just pretend this profile was last used by something in the profile dir.
+ let greDir = gProfD.clone();
+ greDir.append("app");
+ writeCompatibilityIni(defaultProfile, greDir, greDir);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ simulateSnapEnvironment();
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("default");
+
+ let profileData = readProfilesIni();
+ let installsINI = gDataHome.clone();
+ installsINI.append("installs.ini");
+ Assert.ok(
+ !installsINI.exists(),
+ "Installs database should not have been created."
+ );
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, PROFILE_DEFAULT);
+});
diff --git a/toolkit/profile/xpcshell/test_snap_empty.js b/toolkit/profile/xpcshell/test_snap_empty.js
new file mode 100644
index 0000000000..1dbdc1d18a
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_snap_empty.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that from a clean slate snap builds create an appropriate profile.
+ */
+
+add_task(async () => {
+ simulateSnapEnvironment();
+
+ let service = getProfileService();
+ let { profile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-created-default");
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.equal(
+ profile.name,
+ PROFILE_DEFAULT,
+ "Should have used the normal name."
+ );
+ if (AppConstants.MOZ_DEV_EDITION) {
+ Assert.equal(service.profileCount, 2, "Should be two profiles.");
+ } else {
+ Assert.equal(service.profileCount, 1, "Should be only one profile.");
+ }
+
+ checkProfileService();
+});
diff --git a/toolkit/profile/xpcshell/test_snatch_environment.js b/toolkit/profile/xpcshell/test_snatch_environment.js
new file mode 100644
index 0000000000..2241fb691c
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_snatch_environment.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the environment variables are used to select a profile and that
+ * on the first run of a dedicated profile build we snatch it if it was the
+ * default profile.
+ */
+
+add_task(async () => {
+ let root = makeRandomProfileDir("foo");
+ let local = gDataHomeLocal.clone();
+ local.append("foo");
+
+ writeCompatibilityIni(root);
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: root.leafName,
+ default: true,
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ // Another install is using the profile but it isn't locked.
+ installs: {
+ otherinstall: {
+ default: root.leafName,
+ },
+ },
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ Services.env.set("XRE_PROFILE_PATH", root.path);
+ Services.env.set("XRE_PROFILE_LOCAL_PATH", local.path);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
+ checkStartupReason("restart-claimed-default");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(rootDir.equals(root), "Should have selected the right root dir.");
+ Assert.ok(
+ localDir.equals(local),
+ "Should have selected the right local dir."
+ );
+ Assert.ok(profile, "A named profile matches this.");
+ Assert.equal(profile.name, PROFILE_DEFAULT, "The right profile was matched.");
+
+ let service = getProfileService();
+ Assert.equal(
+ service.defaultProfile,
+ profile,
+ "Should be the default profile."
+ );
+ Assert.equal(
+ service.currentProfile,
+ profile,
+ "Should be the current profile."
+ );
+
+ profileData = readProfilesIni();
+ Assert.equal(
+ profileData.profiles[0].name,
+ PROFILE_DEFAULT,
+ "Should be the right profile."
+ );
+ Assert.ok(
+ profileData.profiles[0].default,
+ "Should still be the old default profile."
+ );
+
+ let hash = xreDirProvider.getInstallHash();
+ // The info about the other install will have been removed so it goes through first run on next startup.
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ root.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ !profileData.installs[hash].locked,
+ "Should not have locked as we're not the default app."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_snatch_environment_default.js b/toolkit/profile/xpcshell/test_snatch_environment_default.js
new file mode 100644
index 0000000000..d93a0cfc12
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_snatch_environment_default.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that the environment variables are used to select a profile and that
+ * on the first run of a dedicated profile build we snatch it if it was the
+ * default profile and lock it when we're the default app.
+ */
+
+add_task(async () => {
+ gIsDefaultApp = true;
+
+ let root = makeRandomProfileDir("foo");
+ let local = gDataHomeLocal.clone();
+ local.append("foo");
+
+ writeCompatibilityIni(root);
+
+ let profileData = {
+ options: {
+ startWithLastProfile: true,
+ },
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: root.leafName,
+ default: true,
+ },
+ {
+ name: "Profile2",
+ path: "Path2",
+ },
+ {
+ name: "Profile3",
+ path: "Path3",
+ },
+ ],
+ // Another install is using the profile but it isn't locked.
+ installs: {
+ otherinstall: {
+ default: root.leafName,
+ },
+ },
+ };
+
+ writeProfilesIni(profileData);
+ checkProfileService(profileData);
+
+ Services.env.set("XRE_PROFILE_PATH", root.path);
+ Services.env.set("XRE_PROFILE_LOCAL_PATH", local.path);
+
+ let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
+ checkStartupReason("restart-claimed-default");
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(rootDir.equals(root), "Should have selected the right root dir.");
+ Assert.ok(
+ localDir.equals(local),
+ "Should have selected the right local dir."
+ );
+ Assert.ok(!!profile, "A named profile matches this.");
+ Assert.equal(profile.name, PROFILE_DEFAULT, "The right profile was matched.");
+
+ let service = getProfileService();
+ Assert.ok(
+ service.defaultProfile === profile,
+ "Should be the default profile."
+ );
+ Assert.ok(
+ service.currentProfile === profile,
+ "Should be the current profile."
+ );
+
+ profileData = readProfilesIni();
+ Assert.equal(
+ profileData.profiles[0].name,
+ PROFILE_DEFAULT,
+ "Should be the right profile."
+ );
+ Assert.ok(
+ profileData.profiles[0].default,
+ "Should still be the old default profile."
+ );
+
+ let hash = xreDirProvider.getInstallHash();
+ // The info about the other install will have been removed so it goes through first run on next startup.
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ root.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we're the default app."
+ );
+});
diff --git a/toolkit/profile/xpcshell/test_startswithlast.js b/toolkit/profile/xpcshell/test_startswithlast.js
new file mode 100644
index 0000000000..1b1fef4415
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_startswithlast.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that if profiles.ini is set to not start with the last profile then
+ * we show the profile manager in preference to assigning the old default.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ options: {
+ startWithLastProfile: false,
+ },
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ testStartsProfileManager();
+});
diff --git a/toolkit/profile/xpcshell/test_steal_inuse.js b/toolkit/profile/xpcshell/test_steal_inuse.js
new file mode 100644
index 0000000000..68263ceae1
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_steal_inuse.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile previously used by this build but
+ * that has already been claimed by a different build gets stolen by this build.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ installs: {
+ otherhash: {
+ default: defaultProfile.leafName,
+ },
+ },
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-claimed-default");
+
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should only be one known installs."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ defaultProfile.leafName,
+ "Should have taken the original default profile as the default for the current install."
+ );
+ Assert.ok(
+ !profileData.installs[hash].locked,
+ "Should not have locked as we're not the default app."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, PROFILE_DEFAULT);
+});
diff --git a/toolkit/profile/xpcshell/test_update_selected_dedicated.js b/toolkit/profile/xpcshell/test_update_selected_dedicated.js
new file mode 100644
index 0000000000..a4c387b8f4
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_update_selected_dedicated.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile previously used by this build gets
+ * updated to a dedicated profile for this build.
+ */
+
+add_task(async () => {
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-claimed-default");
+
+ let hash = xreDirProvider.getInstallHash();
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 1,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be only one known install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ defaultProfile.leafName,
+ "Should have marked the original default profile as the default for this install."
+ );
+ Assert.ok(
+ !profileData.installs[hash].locked,
+ "Should not have locked as we're not the default app."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, PROFILE_DEFAULT);
+});
diff --git a/toolkit/profile/xpcshell/test_update_unknown_dedicated.js b/toolkit/profile/xpcshell/test_update_unknown_dedicated.js
new file mode 100644
index 0000000000..1fd8c30fbf
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_update_unknown_dedicated.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile not previously used by any build
+ * doesn't get updated to a dedicated profile for this build and we don't set
+ * the flag to show the user info about dedicated profiles.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+ let defaultProfile = makeRandomProfileDir("default");
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-created-default");
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 2,
+ "Should have the right number of profiles."
+ );
+
+ // Since there is already a profile with the desired name on dev-edition, a
+ // unique version will be used.
+ let expectedName = AppConstants.MOZ_DEV_EDITION
+ ? `${DEDICATED_NAME}-1`
+ : DEDICATED_NAME;
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, expectedName, "Should have the right name.");
+ Assert.notEqual(
+ profile.path,
+ defaultProfile.leafName,
+ "Should not be the original default profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be a default for installs."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ profile.path,
+ "Should have the right default profile."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we created this profile for this install."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.ok(
+ !selectedProfile.rootDir.equals(defaultProfile),
+ "Should not be using the old directory."
+ );
+ Assert.equal(selectedProfile.name, expectedName);
+});
diff --git a/toolkit/profile/xpcshell/test_update_unselected_dedicated.js b/toolkit/profile/xpcshell/test_update_unselected_dedicated.js
new file mode 100644
index 0000000000..4aa56eaaac
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_update_unselected_dedicated.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that an old-style default profile not previously used by this build gets
+ * ignored.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+ let defaultProfile = makeRandomProfileDir("default");
+
+ // Just pretend this profile was last used by something in the profile dir.
+ let greDir = gProfD.clone();
+ greDir.append("app");
+ writeCompatibilityIni(defaultProfile, greDir, greDir);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: PROFILE_DEFAULT,
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ ],
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("firstrun-skipped-default");
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 2,
+ "Should have the right number of profiles."
+ );
+
+ // Since there is already a profile with the desired name on dev-edition, a
+ // unique version will be used.
+ let expectedName = AppConstants.MOZ_DEV_EDITION
+ ? `${DEDICATED_NAME}-1`
+ : DEDICATED_NAME;
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, expectedName, "Should have the right name.");
+ Assert.notEqual(
+ profile.path,
+ defaultProfile.leafName,
+ "Should not be the original default profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 1,
+ "Should be a default for this install."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ profile.path,
+ "Should have marked the new profile as the default for this install."
+ );
+ Assert.ok(
+ profileData.installs[hash].locked,
+ "Should have locked as we created this profile for this install."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(didCreate, "Should have created a new profile.");
+ Assert.ok(
+ !selectedProfile.rootDir.equals(defaultProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, expectedName);
+});
diff --git a/toolkit/profile/xpcshell/test_use_dedicated.js b/toolkit/profile/xpcshell/test_use_dedicated.js
new file mode 100644
index 0000000000..d6bbdca4d8
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_use_dedicated.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that if installs.ini lists a profile we use it as the default.
+ */
+
+add_task(async () => {
+ let hash = xreDirProvider.getInstallHash();
+ let defaultProfile = makeRandomProfileDir("default");
+ let dedicatedProfile = makeRandomProfileDir("dedicated");
+ let devProfile = makeRandomProfileDir("devedition");
+
+ // Make sure we don't steal the old-style default.
+ writeCompatibilityIni(defaultProfile);
+
+ writeProfilesIni({
+ profiles: [
+ {
+ name: "default",
+ path: defaultProfile.leafName,
+ default: true,
+ },
+ {
+ name: "dedicated",
+ path: dedicatedProfile.leafName,
+ },
+ {
+ name: "dev-edition-default",
+ path: devProfile.leafName,
+ },
+ ],
+ installs: {
+ [hash]: {
+ default: dedicatedProfile.leafName,
+ },
+ otherhash: {
+ default: "foobar",
+ },
+ },
+ });
+
+ let { profile: selectedProfile, didCreate } = selectStartupProfile();
+ checkStartupReason("default");
+
+ let profileData = readProfilesIni();
+
+ Assert.ok(
+ profileData.options.startWithLastProfile,
+ "Should be set to start with the last profile."
+ );
+ Assert.equal(
+ profileData.profiles.length,
+ 3,
+ "Should have the right number of profiles."
+ );
+
+ let profile = profileData.profiles[0];
+ Assert.equal(profile.name, `dedicated`, "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ dedicatedProfile.leafName,
+ "Should be the expected dedicated profile."
+ );
+ Assert.ok(!profile.default, "Should not be marked as the old-style default.");
+
+ profile = profileData.profiles[1];
+ Assert.equal(profile.name, "default", "Should have the right name.");
+ Assert.equal(
+ profile.path,
+ defaultProfile.leafName,
+ "Should be the original default profile."
+ );
+ Assert.ok(profile.default, "Should be marked as the old-style default.");
+
+ Assert.equal(
+ Object.keys(profileData.installs).length,
+ 2,
+ "Should be two known installs."
+ );
+ Assert.equal(
+ profileData.installs[hash].default,
+ dedicatedProfile.leafName,
+ "Should have kept the default for this install."
+ );
+ Assert.equal(
+ profileData.installs.otherhash.default,
+ "foobar",
+ "Should have kept the default for the other install."
+ );
+
+ checkProfileService(profileData);
+
+ Assert.ok(!didCreate, "Should not have created a new profile.");
+ Assert.ok(
+ selectedProfile.rootDir.equals(dedicatedProfile),
+ "Should be using the right directory."
+ );
+ Assert.equal(selectedProfile.name, "dedicated");
+});
diff --git a/toolkit/profile/xpcshell/xpcshell.ini b/toolkit/profile/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..f0793bad2e
--- /dev/null
+++ b/toolkit/profile/xpcshell/xpcshell.ini
@@ -0,0 +1,52 @@
+[DEFAULT]
+head = head.js
+skip-if = toolkit == 'android'
+
+[test_select_backgroundtasks_ephemeral.js]
+[test_select_backgroundtasks_not_ephemeral_create.js]
+[test_select_backgroundtasks_not_ephemeral_exists.js]
+[test_select_default.js]
+[test_select_profilemanager.js]
+[test_select_named.js]
+[test_select_missing.js]
+[test_select_noname.js]
+[test_create_default.js]
+[test_select_environment.js]
+[test_select_environment_named.js]
+[test_profile_reset.js]
+[test_clean.js]
+[test_previous_dedicated.js]
+[test_single_profile_selected.js]
+skip-if = devedition
+[test_single_profile_unselected.js]
+skip-if = devedition
+[test_update_selected_dedicated.js]
+[test_update_unknown_dedicated.js]
+[test_update_unselected_dedicated.js]
+[test_use_dedicated.js]
+[test_new_default.js]
+[test_steal_inuse.js]
+[test_snap.js]
+skip-if = toolkit != 'gtk'
+[test_snap_empty.js]
+skip-if = toolkit != 'gtk'
+[test_remove_default.js]
+[test_claim_locked.js]
+[test_lock.js]
+[test_startswithlast.js]
+[test_snatch_environment.js]
+[test_skip_locked_environment.js]
+[test_snatch_environment_default.js]
+[test_check_backup.js]
+[test_missing_profilesini.js]
+[test_remove.js]
+[test_conflict_profiles.js]
+[test_conflict_installs.js]
+[test_invalid_descriptor.js]
+[test_legacy_empty.js]
+[test_legacy_select.js]
+[test_fix_directory_case.js]
+[test_ignore_legacy_directory.js]
+[test_select_profile_argument.js]
+[test_select_profile_argument_new.js]
+[test_register_app_services_logger.js]