Bug 1885565 - Part 2: Fix up parameter ordering and kDoc descriptions in NavigationBa...
[gecko.git] / toolkit / profile / xpcshell / head.js
blobdc354f5b62f980089bcbc9b30ecb18d0d35674d7
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"
6 );
7 const { FileUtils } = ChromeUtils.importESModule(
8   "resource://gre/modules/FileUtils.sys.mjs"
9 );
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(
30   Ci.nsIXREDirProvider
32 xreDirProvider.setUserDataDirectory(gDataHome, false);
33 xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
35 let gIsDefaultApp = false;
37 const ShellService = {
38   register() {
39     let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
41     let factory = {
42       createInstance(iid) {
43         return ShellService.QueryInterface(iid);
44       },
45     };
47     registrar.registerFactory(
48       this.ID,
49       "ToolkitShellService",
50       this.CONTRACT,
51       factory
52     );
53   },
55   isDefaultApplication() {
56     return gIsDefaultApp;
57   },
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");
71   gIsLegacy = true;
74 function getProfileService() {
75   return Cc["@mozilla.org/toolkit/profile-service;1"].getService(
76     Ci.nsIToolkitProfileService
77   );
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();
89   let profileData = {
90     options: {
91       startWithLastProfile: true,
92     },
93     profiles: [
94       {
95         name: "Profile1",
96         path: "Path1",
97         default: false,
98       },
99       {
100         name: "Profile3",
101         path: "Path3",
102         default: false,
103       },
104     ],
105     installs: {
106       [hash]: {
107         default: "Path1",
108       },
109     },
110     backgroundTasksProfiles: [
111       {
112         name: `MozillaBackgroundTask-${hash}-unrelated_task`,
113         path: `saltsalt.MozillaBackgroundTask-${hash}-unrelated_task`,
114       },
115     ],
116   };
117   return profileData;
118 })();
121  * Creates a random profile path for use.
122  */
123 function makeRandomProfileDir(name) {
124   let file = gDataHome.clone();
125   file.append(name);
126   file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
127   return file;
131  * A wrapper around nsIToolkitProfileService.selectStartupProfile to make it
132  * a bit nicer to use from JS.
133  */
134 function selectStartupProfile(args = [], isResetting = false, legacyHash = "") {
135   let service = getProfileService();
136   let rootDir = {};
137   let localDir = {};
138   let profile = {};
139   let didCreate = service.selectStartupProfile(
140     ["xpcshell", ...args],
141     isResetting,
142     UPDATE_CHANNEL,
143     legacyHash,
144     rootDir,
145     localDir,
146     profile
147   );
149   if (profile.value) {
150     Assert.ok(
151       rootDir.value.equals(profile.value.rootDir),
152       "Should have matched the root dir."
153     );
154     Assert.ok(
155       localDir.value.equals(profile.value.localDir),
156       "Should have matched the local dir."
157     );
158     Assert.ok(
159       service.currentProfile === profile.value,
160       "Should have marked the profile as the current profile."
161     );
162   } else {
163     Assert.ok(!service.currentProfile, "Should be no current profile.");
164   }
166   return {
167     rootDir: rootDir.value,
168     localDir: localDir.value,
169     profile: profile.value,
170     didCreate,
171   };
174 function testStartsProfileManager(args = [], isResetting = false) {
175   try {
176     selectStartupProfile(args, isResetting);
177     Assert.ok(false, "Should have started the profile manager");
178     checkStartupReason();
179   } catch (e) {
180     Assert.equal(
181       e.result,
182       NS_ERROR_START_PROFILE_MANAGER,
183       "Should have started the profile manager"
184     );
185   }
188 function safeGet(ini, section, key) {
189   try {
190     return ini.getString(section, key);
191   } catch (e) {
192     return null;
193   }
197  * Writes a compatibility.ini file that marks the give profile directory as last
198  * used by the given install path.
199  */
200 function writeCompatibilityIni(
201   dir,
202   appDir = FileUtils.getDir("CurProcD", []),
203   greDir = FileUtils.getDir("GreD", [])
204 ) {
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
210   );
211   let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
213   // The profile service doesn't care about these so just use fixed values
214   ini.setString(
215     "Compatibility",
216     "LastVersion",
217     "64.0a1_20180919123806/20180919123806"
218   );
219   ini.setString("Compatibility", "LastOSABI", "Darwin_x86_64-gcc3");
221   ini.setString(
222     "Compatibility",
223     "LastPlatformDir",
224     greDir.persistentDescriptor
225   );
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
236  * properties.
237  */
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
244   );
245   let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
247   const {
248     options = {},
249     profiles = [],
250     installs = null,
251     backgroundTasksProfiles = null,
252   } = profileData;
254   let { startWithLastProfile = true } = options;
255   ini.setString(
256     "General",
257     "StartWithLastProfile",
258     startWithLastProfile ? "1" : "0"
259   );
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");
271     }
272   }
274   if (backgroundTasksProfiles) {
275     let section = "BackgroundTasksProfiles";
276     for (let backgroundTasksProfile of backgroundTasksProfiles) {
277       ini.setString(
278         section,
279         backgroundTasksProfile.name,
280         backgroundTasksProfile.path
281       );
282     }
283   }
285   if (installs) {
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]) {
291         ini.setString(
292           `Install${hash}`,
293           "Locked",
294           installs[hash].locked ? "1" : "0"
295         );
296       }
297     }
299     writeInstallsIni({ installs });
300   } else {
301     writeInstallsIni(null);
302   }
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
311  * that assumption.
312  */
313 function readProfilesIni() {
314   let target = gDataHome.clone();
315   target.append("profiles.ini");
317   let profileData = {
318     options: {
319       startWithLastProfile: true,
320     },
321     profiles: [],
322     installs: null,
323   };
325   if (!target.exists()) {
326     return profileData;
327   }
329   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
330     Ci.nsIINIParserFactory
331   );
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 = {};
338   }
340   let sections = ini.getSections();
341   while (sections.hasMore()) {
342     let section = sections.getNext();
344     if (section == "General") {
345       continue;
346     }
348     if (section.startsWith("Profile")) {
349       let isRelative = safeGet(ini, section, "IsRelative");
350       if (isRelative === null) {
351         break;
352       }
353       Assert.equal(
354         isRelative,
355         "1",
356         "Paths should always be relative in these tests."
357       );
359       let profile = {
360         name: safeGet(ini, section, "Name"),
361         path: safeGet(ini, section, "Path"),
362       };
364       try {
365         profile.default = ini.getString(section, "Default") == "1";
366         Assert.ok(
367           profile.default,
368           "The Default value is only written when true."
369         );
370       } catch (e) {
371         profile.default = false;
372       }
374       profileData.profiles.push(profile);
375     }
377     if (section.startsWith("Install")) {
378       Assert.ok(
379         profileData.installs,
380         "Should only see an install section if the ini version was correct."
381       );
383       profileData.installs[section.substring(7)] = {
384         default: safeGet(ini, section, "Default"),
385       };
387       let locked = safeGet(ini, section, "Locked");
388       if (locked !== null) {
389         profileData.installs[section.substring(7)].locked = locked;
390       }
391     }
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 });
400       }
401       profileData.backgroundTasksProfiles.sort((a, b) =>
402         a.name.localeCompare(b.name)
403       );
404     }
405   }
407   profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
409   return profileData;
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.
416  */
417 function writeInstallsIni(installData) {
418   let target = gDataHome.clone();
419   target.append("installs.ini");
421   if (!installData) {
422     try {
423       target.remove(false);
424     } catch (e) {}
425     return;
426   }
428   const { installs = {} } = installData;
430   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
431     Ci.nsIINIParserFactory
432   );
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");
439     }
440   }
442   ini.writeFile(target);
446  * Reads installs.ini into a structure like that used in the above function.
447  */
448 function readInstallsIni() {
449   let target = gDataHome.clone();
450   target.append("installs.ini");
452   let installData = {
453     installs: {},
454   };
456   if (!target.exists()) {
457     return installData;
458   }
460   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
461     Ci.nsIINIParserFactory
462   );
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"),
471       };
473       let locked = safeGet(ini, hash, "Locked");
474       if (locked !== null) {
475         installData.installs[hash].locked = locked;
476       }
477     }
478   }
480   return installData;
484  * Check that the backup data in installs.ini matches the install data in
485  * profiles.ini.
486  */
487 function checkBackup(
488   profileData = readProfilesIni(),
489   installData = readInstallsIni()
490 ) {
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.
494     return;
495   }
497   Assert.deepEqual(
498     profileData.installs,
499     installData.installs,
500     "Backup installs.ini should match installs in profiles.ini"
501   );
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.
507  */
508 function checkProfileService(
509   profileData = readProfilesIni(),
510   verifyBackup = true
511 ) {
512   let service = getProfileService();
514   let expectedStartWithLast = true;
515   if ("options" in profileData) {
516     expectedStartWithLast = profileData.options.startWithLastProfile;
517   }
519   Assert.equal(
520     service.startWithLastProfile,
521     expectedStartWithLast,
522     "Start with last profile should match."
523   );
525   let serviceProfiles = Array.from(service.profiles);
527   Assert.equal(
528     serviceProfiles.length,
529     profileData.profiles.length,
530     "Should be the same number of profiles."
531   );
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();
538   let defaultPath =
539     profileData.installs && hash in profileData.installs
540       ? profileData.installs[hash].default
541       : null;
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];
549     Assert.equal(
550       serviceProfile.name,
551       expectedProfile.name,
552       "Should have the same name."
553     );
555     let expectedPath = Cc["@mozilla.org/file/local;1"].createInstance(
556       Ci.nsIFile
557     );
558     expectedPath.setRelativeDescriptor(gDataHome, expectedProfile.path);
559     Assert.equal(
560       serviceProfile.rootDir.path,
561       expectedPath.path,
562       "Should have the same path."
563     );
565     if (expectedProfile.path == defaultPath) {
566       dedicatedProfile = serviceProfile;
567     }
569     if (AppConstants.MOZ_DEV_EDITION) {
570       if (expectedProfile.name == PROFILE_DEFAULT) {
571         legacyProfile = serviceProfile;
572       }
573     } else if (expectedProfile.default) {
574       legacyProfile = serviceProfile;
575     }
576   }
578   if (gIsLegacy || Services.env.get("SNAP_NAME")) {
579     Assert.equal(
580       service.defaultProfile,
581       legacyProfile,
582       "Should have seen the right profile selected."
583     );
584   } else {
585     Assert.equal(
586       service.defaultProfile,
587       dedicatedProfile,
588       "Should have seen the right profile selected."
589     );
590   }
592   if (verifyBackup) {
593     checkBackup(profileData);
594   }
597 // Maps the interesting scalar IDs to simple names that can be used as JS variables.
598 const SCALARS = {
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");
607   let results = {};
608   for (let [prop, scalarId] of Object.entries(SCALARS)) {
609     results[prop] = scalars[scalarId];
610   }
612   return results;
615 function checkStartupReason(expected = undefined) {
616   let { selectionReason } = getTelemetryScalars();
618   Assert.equal(
619     selectionReason,
620     expected,
621     "Should have seen the right startup reason."
622   );