summaryrefslogtreecommitdiffstats
path: root/toolkit/profile/xpcshell/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/profile/xpcshell/head.js')
-rw-r--r--toolkit/profile/xpcshell/head.js623
1 files changed, 623 insertions, 0 deletions
diff --git a/toolkit/profile/xpcshell/head.js b/toolkit/profile/xpcshell/head.js
new file mode 100644
index 0000000000..dc354f5b62
--- /dev/null
+++ b/toolkit/profile/xpcshell/head.js
@@ -0,0 +1,623 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+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 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 || Services.env.get("SNAP_NAME")) {
+ 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);
+ }
+}
+
+// Maps the interesting scalar IDs to simple names that can be used as JS variables.
+const SCALARS = {
+ selectionReason: "startup.profile_selection_reason",
+ databaseVersion: "startup.profile_database_version",
+ profileCount: "startup.profile_count",
+};
+
+function getTelemetryScalars() {
+ let scalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ let results = {};
+ for (let [prop, scalarId] of Object.entries(SCALARS)) {
+ results[prop] = scalars[scalarId];
+ }
+
+ return results;
+}
+
+function checkStartupReason(expected = undefined) {
+ let { selectionReason } = getTelemetryScalars();
+
+ Assert.equal(
+ selectionReason,
+ expected,
+ "Should have seen the right startup reason."
+ );
+}