1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 const { NetUtil } = ChromeUtils.importESModule(
5 "resource://gre/modules/NetUtil.sys.mjs"
7 const { FileUtils } = ChromeUtils.importESModule(
8 "resource://gre/modules/FileUtils.sys.mjs"
10 const { AppConstants } = ChromeUtils.importESModule(
11 "resource://gre/modules/AppConstants.sys.mjs"
13 const { TelemetryTestUtils } = ChromeUtils.importESModule(
14 "resource://testing-common/TelemetryTestUtils.sys.mjs"
17 const NS_ERROR_START_PROFILE_MANAGER = 0x805800c9;
19 const UPDATE_CHANNEL = AppConstants.MOZ_UPDATE_CHANNEL;
21 let gProfD = do_get_profile();
22 let gDataHome = gProfD.clone();
23 gDataHome.append("data");
24 gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
25 let gDataHomeLocal = gProfD.clone();
26 gDataHomeLocal.append("local");
27 gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
29 let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
32 xreDirProvider.setUserDataDirectory(gDataHome, false);
33 xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
35 let gIsDefaultApp = false;
37 const ShellService = {
39 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
43 return ShellService.QueryInterface(iid);
47 registrar.registerFactory(
49 "ToolkitShellService",
55 isDefaultApplication() {
59 QueryInterface: ChromeUtils.generateQI(["nsIToolkitShellService"]),
60 ID: Components.ID("{ce724e0c-ed70-41c9-ab31-1033b0b591be}"),
61 CONTRACT: "@mozilla.org/toolkit/shell-service;1",
64 ShellService.register();
66 let gIsLegacy = false;
68 function enableLegacyProfiles() {
69 Services.env.set("MOZ_LEGACY_PROFILES", "1");
74 function getProfileService() {
75 return Cc["@mozilla.org/toolkit/profile-service;1"].getService(
76 Ci.nsIToolkitProfileService
80 let PROFILE_DEFAULT = "default";
81 let DEDICATED_NAME = `default-${UPDATE_CHANNEL}`;
82 if (AppConstants.MOZ_DEV_EDITION) {
83 DEDICATED_NAME = PROFILE_DEFAULT = "dev-edition-default";
86 // Shared data for backgroundtasks tests.
87 const BACKGROUNDTASKS_PROFILE_DATA = (() => {
88 let hash = xreDirProvider.getInstallHash();
91 startWithLastProfile: true,
110 backgroundTasksProfiles: [
112 name: `MozillaBackgroundTask-${hash}-unrelated_task`,
113 path: `saltsalt.MozillaBackgroundTask-${hash}-unrelated_task`,
121 * Creates a random profile path for use.
123 function makeRandomProfileDir(name) {
124 let file = gDataHome.clone();
126 file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
131 * A wrapper around nsIToolkitProfileService.selectStartupProfile to make it
132 * a bit nicer to use from JS.
134 function selectStartupProfile(args = [], isResetting = false, legacyHash = "") {
135 let service = getProfileService();
139 let didCreate = service.selectStartupProfile(
140 ["xpcshell", ...args],
151 rootDir.value.equals(profile.value.rootDir),
152 "Should have matched the root dir."
155 localDir.value.equals(profile.value.localDir),
156 "Should have matched the local dir."
159 service.currentProfile === profile.value,
160 "Should have marked the profile as the current profile."
163 Assert.ok(!service.currentProfile, "Should be no current profile.");
167 rootDir: rootDir.value,
168 localDir: localDir.value,
169 profile: profile.value,
174 function testStartsProfileManager(args = [], isResetting = false) {
176 selectStartupProfile(args, isResetting);
177 Assert.ok(false, "Should have started the profile manager");
178 checkStartupReason();
182 NS_ERROR_START_PROFILE_MANAGER,
183 "Should have started the profile manager"
188 function safeGet(ini, section, key) {
190 return ini.getString(section, key);
197 * Writes a compatibility.ini file that marks the give profile directory as last
198 * used by the given install path.
200 function writeCompatibilityIni(
202 appDir = FileUtils.getDir("CurProcD", []),
203 greDir = FileUtils.getDir("GreD", [])
205 let target = dir.clone();
206 target.append("compatibility.ini");
208 let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
209 Ci.nsIINIParserFactory
211 let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
213 // The profile service doesn't care about these so just use fixed values
217 "64.0a1_20180919123806/20180919123806"
219 ini.setString("Compatibility", "LastOSABI", "Darwin_x86_64-gcc3");
224 greDir.persistentDescriptor
226 ini.setString("Compatibility", "LastAppDir", appDir.persistentDescriptor);
228 ini.writeFile(target);
232 * Writes a profiles.ini based on the passed profile data.
233 * profileData should contain two properties, options and profiles.
234 * options contains a single property, startWithLastProfile.
235 * profiles is an array of profiles each containing name, path and default
238 function writeProfilesIni(profileData) {
239 let target = gDataHome.clone();
240 target.append("profiles.ini");
242 let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
243 Ci.nsIINIParserFactory
245 let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
251 backgroundTasksProfiles = null,
254 let { startWithLastProfile = true } = options;
257 "StartWithLastProfile",
258 startWithLastProfile ? "1" : "0"
261 for (let i = 0; i < profiles.length; i++) {
262 let profile = profiles[i];
263 let section = `Profile${i}`;
265 ini.setString(section, "Name", profile.name);
266 ini.setString(section, "IsRelative", 1);
267 ini.setString(section, "Path", profile.path);
269 if (profile.default) {
270 ini.setString(section, "Default", "1");
274 if (backgroundTasksProfiles) {
275 let section = "BackgroundTasksProfiles";
276 for (let backgroundTasksProfile of backgroundTasksProfiles) {
279 backgroundTasksProfile.name,
280 backgroundTasksProfile.path
286 ini.setString("General", "Version", "2");
288 for (let hash of Object.keys(installs)) {
289 ini.setString(`Install${hash}`, "Default", installs[hash].default);
290 if ("locked" in installs[hash]) {
294 installs[hash].locked ? "1" : "0"
299 writeInstallsIni({ installs });
301 writeInstallsIni(null);
304 ini.writeFile(target);
308 * Reads the existing profiles.ini into the same structure as that accepted by
309 * writeProfilesIni above. The profiles property is sorted according to name
310 * because the order is irrelevant and it makes testing easier if we can make
313 function readProfilesIni() {
314 let target = gDataHome.clone();
315 target.append("profiles.ini");
319 startWithLastProfile: true,
325 if (!target.exists()) {
329 let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
330 Ci.nsIINIParserFactory
332 let ini = factory.createINIParser(target);
334 profileData.options.startWithLastProfile =
335 safeGet(ini, "General", "StartWithLastProfile") == "1";
336 if (safeGet(ini, "General", "Version") == "2") {
337 profileData.installs = {};
340 let sections = ini.getSections();
341 while (sections.hasMore()) {
342 let section = sections.getNext();
344 if (section == "General") {
348 if (section.startsWith("Profile")) {
349 let isRelative = safeGet(ini, section, "IsRelative");
350 if (isRelative === null) {
356 "Paths should always be relative in these tests."
360 name: safeGet(ini, section, "Name"),
361 path: safeGet(ini, section, "Path"),
365 profile.default = ini.getString(section, "Default") == "1";
368 "The Default value is only written when true."
371 profile.default = false;
374 profileData.profiles.push(profile);
377 if (section.startsWith("Install")) {
379 profileData.installs,
380 "Should only see an install section if the ini version was correct."
383 profileData.installs[section.substring(7)] = {
384 default: safeGet(ini, section, "Default"),
387 let locked = safeGet(ini, section, "Locked");
388 if (locked !== null) {
389 profileData.installs[section.substring(7)].locked = locked;
393 if (section == "BackgroundTasksProfiles") {
394 profileData.backgroundTasksProfiles = [];
395 let backgroundTasksProfiles = ini.getKeys(section);
396 while (backgroundTasksProfiles.hasMore()) {
397 let name = backgroundTasksProfiles.getNext();
398 let path = ini.getString(section, name);
399 profileData.backgroundTasksProfiles.push({ name, path });
401 profileData.backgroundTasksProfiles.sort((a, b) =>
402 a.name.localeCompare(b.name)
407 profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
413 * Writes an installs.ini based on the supplied data. Should be an object with
414 * keys for every installation hash each mapping to an object. Each object
415 * should have a default property for the relative path to the profile.
417 function writeInstallsIni(installData) {
418 let target = gDataHome.clone();
419 target.append("installs.ini");
423 target.remove(false);
428 const { installs = {} } = installData;
430 let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
431 Ci.nsIINIParserFactory
433 let ini = factory.createINIParser(null).QueryInterface(Ci.nsIINIParserWriter);
435 for (let hash of Object.keys(installs)) {
436 ini.setString(hash, "Default", installs[hash].default);
437 if ("locked" in installs[hash]) {
438 ini.setString(hash, "Locked", installs[hash].locked ? "1" : "0");
442 ini.writeFile(target);
446 * Reads installs.ini into a structure like that used in the above function.
448 function readInstallsIni() {
449 let target = gDataHome.clone();
450 target.append("installs.ini");
456 if (!target.exists()) {
460 let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
461 Ci.nsIINIParserFactory
463 let ini = factory.createINIParser(target);
465 let sections = ini.getSections();
466 while (sections.hasMore()) {
467 let hash = sections.getNext();
468 if (hash != "General") {
469 installData.installs[hash] = {
470 default: safeGet(ini, hash, "Default"),
473 let locked = safeGet(ini, hash, "Locked");
474 if (locked !== null) {
475 installData.installs[hash].locked = locked;
484 * Check that the backup data in installs.ini matches the install data in
487 function checkBackup(
488 profileData = readProfilesIni(),
489 installData = readInstallsIni()
491 if (!profileData.installs) {
492 // If the profiles db isn't of the right version we wouldn't expect the
493 // backup to be accurate.
498 profileData.installs,
499 installData.installs,
500 "Backup installs.ini should match installs in profiles.ini"
505 * Checks that the profile service seems to have the right data in it compared
506 * to profile and install data structured as in the above functions.
508 function checkProfileService(
509 profileData = readProfilesIni(),
512 let service = getProfileService();
514 let expectedStartWithLast = true;
515 if ("options" in profileData) {
516 expectedStartWithLast = profileData.options.startWithLastProfile;
520 service.startWithLastProfile,
521 expectedStartWithLast,
522 "Start with last profile should match."
525 let serviceProfiles = Array.from(service.profiles);
528 serviceProfiles.length,
529 profileData.profiles.length,
530 "Should be the same number of profiles."
533 // Sort to make matching easy.
534 serviceProfiles.sort((a, b) => a.name.localeCompare(b.name));
535 profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
537 let hash = xreDirProvider.getInstallHash();
539 profileData.installs && hash in profileData.installs
540 ? profileData.installs[hash].default
542 let dedicatedProfile = null;
543 let legacyProfile = null;
545 for (let i = 0; i < serviceProfiles.length; i++) {
546 let serviceProfile = serviceProfiles[i];
547 let expectedProfile = profileData.profiles[i];
551 expectedProfile.name,
552 "Should have the same name."
555 let expectedPath = Cc["@mozilla.org/file/local;1"].createInstance(
558 expectedPath.setRelativeDescriptor(gDataHome, expectedProfile.path);
560 serviceProfile.rootDir.path,
562 "Should have the same path."
565 if (expectedProfile.path == defaultPath) {
566 dedicatedProfile = serviceProfile;
569 if (AppConstants.MOZ_DEV_EDITION) {
570 if (expectedProfile.name == PROFILE_DEFAULT) {
571 legacyProfile = serviceProfile;
573 } else if (expectedProfile.default) {
574 legacyProfile = serviceProfile;
578 if (gIsLegacy || Services.env.get("SNAP_NAME")) {
580 service.defaultProfile,
582 "Should have seen the right profile selected."
586 service.defaultProfile,
588 "Should have seen the right profile selected."
593 checkBackup(profileData);
597 // Maps the interesting scalar IDs to simple names that can be used as JS variables.
599 selectionReason: "startup.profile_selection_reason",
600 databaseVersion: "startup.profile_database_version",
601 profileCount: "startup.profile_count",
604 function getTelemetryScalars() {
605 let scalars = TelemetryTestUtils.getProcessScalars("parent");
608 for (let [prop, scalarId] of Object.entries(SCALARS)) {
609 results[prop] = scalars[scalarId];
615 function checkStartupReason(expected = undefined) {
616 let { selectionReason } = getTelemetryScalars();
621 "Should have seen the right startup reason."