Bug 1875768 - Call the appropriate postfork handler on MacOS r=glandium
[gecko.git] / toolkit / modules / UpdateUtils.sys.mjs
blobbe9ce9d5402c30bf4a9712f1ce9026503c191a53
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
11   WindowsVersionInfo:
12     "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
13   ctypes: "resource://gre/modules/ctypes.sys.mjs",
14 });
16 const PER_INSTALLATION_PREFS_PLATFORMS = ["win"];
18 // The file that stores Application Update configuration settings. The file is
19 // located in the update directory which makes it a common setting across all
20 // application profiles and allows the Background Update Agent to read it.
21 const FILE_UPDATE_CONFIG_JSON = "update-config.json";
22 const FILE_UPDATE_LOCALE = "update.locale";
23 const PREF_APP_DISTRIBUTION = "distribution.id";
24 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
26 export var UpdateUtils = {
27   _locale: undefined,
28   _configFilePath: undefined,
30   /**
31    * Read the update channel from defaults only.  We do this to ensure that
32    * the channel is tightly coupled with the application and does not apply
33    * to other instances of the application that may use the same profile.
34    *
35    * @param [optional] aIncludePartners
36    *        Whether or not to include the partner bits. Default: true.
37    */
38   getUpdateChannel(aIncludePartners = true) {
39     let defaults = Services.prefs.getDefaultBranch(null);
40     let channel = defaults.getCharPref(
41       "app.update.channel",
42       AppConstants.MOZ_UPDATE_CHANNEL
43     );
45     if (aIncludePartners) {
46       try {
47         let partners = Services.prefs.getChildList("app.partner.").sort();
48         if (partners.length) {
49           channel += "-cck";
50           partners.forEach(function (prefName) {
51             channel += "-" + Services.prefs.getCharPref(prefName);
52           });
53         }
54       } catch (e) {
55         console.error(e);
56       }
57     }
59     return channel;
60   },
62   get UpdateChannel() {
63     return this.getUpdateChannel();
64   },
66   /**
67    * Formats a URL by replacing %...% values with OS, build and locale specific
68    * values.
69    *
70    * @param  url
71    *         The URL to format.
72    * @return The formatted URL.
73    */
74   async formatUpdateURL(url) {
75     const locale = await this.getLocale();
77     return url.replace(/%(\w+)%/g, (match, name) => {
78       let replacement = match;
79       switch (name) {
80         case "PRODUCT":
81           replacement = Services.appinfo.name;
82           break;
83         case "VERSION":
84           replacement = Services.appinfo.version;
85           break;
86         case "BUILD_ID":
87           replacement = Services.appinfo.appBuildID;
88           break;
89         case "BUILD_TARGET":
90           replacement = Services.appinfo.OS + "_" + this.ABI;
91           break;
92         case "OS_VERSION":
93           replacement = this.OSVersion;
94           break;
95         case "LOCALE":
96           replacement = locale;
97           break;
98         case "CHANNEL":
99           replacement = this.UpdateChannel;
100           break;
101         case "PLATFORM_VERSION":
102           replacement = Services.appinfo.platformVersion;
103           break;
104         case "SYSTEM_CAPABILITIES":
105           replacement = getSystemCapabilities();
106           break;
107         case "DISTRIBUTION":
108           replacement = getDistributionPrefValue(PREF_APP_DISTRIBUTION);
109           break;
110         case "DISTRIBUTION_VERSION":
111           replacement = getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION);
112           break;
113       }
114       return encodeURIComponent(replacement);
115     });
116   },
118   /**
119    * Gets the locale from the update.locale file for replacing %LOCALE% in the
120    * update url. The update.locale file can be located in the application
121    * directory or the GRE directory with preference given to it being located in
122    * the application directory.
123    */
124   async getLocale() {
125     if (this._locale !== undefined) {
126       return this._locale;
127     }
129     for (let res of ["app", "gre"]) {
130       const url = "resource://" + res + "/" + FILE_UPDATE_LOCALE;
131       let data;
132       try {
133         data = await fetch(url);
134       } catch (e) {
135         continue;
136       }
137       const locale = await data.text();
138       if (locale) {
139         return (this._locale = locale.trim());
140       }
141     }
143     console.error(
144       FILE_UPDATE_LOCALE,
145       " file doesn't exist in either the application or GRE directories"
146     );
148     return (this._locale = null);
149   },
151   /* Get the path to the config file. */
152   getConfigFilePath() {
153     let path = PathUtils.join(
154       Services.dirsvc.get("UpdRootD", Ci.nsIFile).path,
155       FILE_UPDATE_CONFIG_JSON
156     );
157     return (this._configFilePath = path);
158   },
160   get configFilePath() {
161     if (this._configFilePath !== undefined) {
162       return this._configFilePath;
163     }
164     return this.getConfigFilePath();
165   },
167   /**
168    * Determines whether or not the Application Update Service automatically
169    * downloads and installs updates. This corresponds to whether or not the user
170    * has selected "Automatically install updates" in about:preferences.
171    *
172    * On Windows, this setting is shared across all profiles for the installation
173    * and is read asynchronously from the file. On other operating systems, this
174    * setting is stored in a pref and is thus a per-profile setting.
175    *
176    * @return A Promise that resolves with a boolean.
177    */
178   async getAppUpdateAutoEnabled() {
179     return this.readUpdateConfigSetting("app.update.auto");
180   },
182   /**
183    * Toggles whether the Update Service automatically downloads and installs
184    * updates. This effectively selects between the "Automatically install
185    * updates" and "Check for updates but let you choose to install them" options
186    * in about:preferences.
187    *
188    * On Windows, this setting is shared across all profiles for the installation
189    * and is written asynchronously to the file. On other operating systems, this
190    * setting is stored in a pref and is thus a per-profile setting.
191    *
192    * If this method is called when the setting is locked, the returned promise
193    * will reject. The lock status can be determined with
194    * UpdateUtils.appUpdateAutoSettingIsLocked()
195    *
196    * @param  enabled If set to true, automatic download and installation of
197    *                 updates will be enabled. If set to false, this will be
198    *                 disabled.
199    * @return A Promise that, once the setting has been saved, resolves with the
200    *         boolean value that was saved. If the setting could not be
201    *         successfully saved, the Promise will reject.
202    *         On Windows, where this setting is stored in a file, this Promise
203    *         may reject with an I/O error.
204    *         On other operating systems, this promise should not reject as
205    *         this operation simply sets a pref.
206    */
207   async setAppUpdateAutoEnabled(enabledValue) {
208     return this.writeUpdateConfigSetting("app.update.auto", !!enabledValue);
209   },
211   /**
212    * This function should be used to determine if the automatic application
213    * update setting is locked by an enterprise policy
214    *
215    * @return true if the automatic update setting is currently locked.
216    *         Otherwise, false.
217    */
218   appUpdateAutoSettingIsLocked() {
219     return this.appUpdateSettingIsLocked("app.update.auto");
220   },
222   /**
223    * Indicates whether or not per-installation prefs are supported on this
224    * platform.
225    */
226   PER_INSTALLATION_PREFS_SUPPORTED: PER_INSTALLATION_PREFS_PLATFORMS.includes(
227     AppConstants.platform
228   ),
230   /**
231    * Possible per-installation pref types.
232    */
233   PER_INSTALLATION_PREF_TYPE_BOOL: "boolean",
234   PER_INSTALLATION_PREF_TYPE_ASCII_STRING: "ascii",
235   PER_INSTALLATION_PREF_TYPE_INT: "integer",
237   /**
238    * We want the preference definitions to be part of UpdateUtils for a couple
239    * of reasons. It's a clean way for consumers to look up things like observer
240    * topic names. It also allows us to manipulate the supported prefs during
241    * testing. However, we want to use values out of UpdateUtils (like pref
242    * types) to construct this object. Therefore, this will initially be a
243    * placeholder, which we will properly define after the UpdateUtils object
244    * definition.
245    */
246   PER_INSTALLATION_PREFS: null,
248   /**
249    * This function initializes per-installation prefs. Note that it does not
250    * need to be called manually; it is already called within the file.
251    *
252    * This function is called on startup, so it does not read or write to disk.
253    */
254   initPerInstallPrefs() {
255     // If we don't have per-installation prefs, we store the update config in
256     // preferences. In that case, the best way to notify observers of this
257     // setting is just to propagate it from a pref observer. This ensures that
258     // the expected observers still get notified, even if a user manually
259     // changes the pref value.
260     if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) {
261       let initialConfig = {};
262       for (const [prefName, pref] of Object.entries(
263         UpdateUtils.PER_INSTALLATION_PREFS
264       )) {
265         const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];
267         try {
268           let initialValue = prefTypeFns.getProfilePref(prefName);
269           initialConfig[prefName] = initialValue;
270         } catch (e) {}
272         Services.prefs.addObserver(prefName, async () => {
273           let config = { ...gUpdateConfigCache };
274           config[prefName] = await UpdateUtils.readUpdateConfigSetting(
275             prefName
276           );
277           maybeUpdateConfigChanged(config);
278         });
279       }
281       // On the first call to maybeUpdateConfigChanged, it has nothing to
282       // compare its input to, so it just populates the cache and doesn't notify
283       // any observers. This makes sense during normal usage, because the first
284       // call will be on the first config file read, and we don't want to notify
285       // observers of changes on the first read. But that means that when
286       // propagating pref observers, we need to make one initial call to
287       // simulate that initial read so that the cache will be populated when the
288       // first pref observer fires.
289       maybeUpdateConfigChanged(initialConfig);
290     }
291   },
293   /**
294    * Reads an installation-specific configuration setting from the update config
295    * JSON file. This function is guaranteed not to throw. If there are problems
296    * reading the file, the default value will be returned so that update can
297    * proceed. This is particularly important since the configuration file is
298    * writable by anyone and we don't want an unprivileged user to be able to
299    * break update for other users.
300    *
301    * If relevant policies are active, this function will read the policy value
302    * rather than the stored value.
303    *
304    * @param  prefName
305    *           The preference to read. Must be a key of the
306    *           PER_INSTALLATION_PREFS object.
307    * @return A Promise that resolves with the pref's value.
308    */
309   readUpdateConfigSetting(prefName) {
310     if (!(prefName in this.PER_INSTALLATION_PREFS)) {
311       return Promise.reject(
312         new Error(
313           `UpdateUtils.readUpdateConfigSetting: Unknown per-installation ` +
314             `pref '${prefName}'`
315         )
316       );
317     }
319     const pref = this.PER_INSTALLATION_PREFS[prefName];
320     const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];
322     if (Services.policies && "policyFn" in pref) {
323       let policyValue = pref.policyFn();
324       if (policyValue !== null) {
325         return Promise.resolve(policyValue);
326       }
327     }
329     if (!this.PER_INSTALLATION_PREFS_SUPPORTED) {
330       // If we don't have per-installation prefs, we use regular preferences.
331       let prefValue = prefTypeFns.getProfilePref(prefName, pref.defaultValue);
332       return Promise.resolve(prefValue);
333     }
335     let readPromise = updateConfigIOPromise
336       // All promises returned by (read|write)UpdateConfigSetting are part of a
337       // single promise chain in order to serialize disk operations. But we
338       // don't want the entire promise chain to reject when one operation fails.
339       // So we are going to silently clear any rejections the promise chain
340       // might contain.
341       //
342       // We will also pass an empty function for the first then() argument as
343       // well, just to make sure we are starting fresh rather than potentially
344       // propagating some stale value.
345       .then(
346         () => {},
347         () => {}
348       )
349       .then(readUpdateConfig)
350       .then(maybeUpdateConfigChanged)
351       .then(config => {
352         return readEffectiveValue(config, prefName);
353       });
354     updateConfigIOPromise = readPromise;
355     return readPromise;
356   },
358   /**
359    * Changes an installation-specific configuration setting by writing it to
360    * the update config JSON file.
361    *
362    * If this method is called on a prefName that is locked, the returned promise
363    * will reject. The lock status can be determined with
364    * appUpdateSettingIsLocked().
365    *
366    * @param  prefName
367    *           The preference to change. This must be a key of the
368    *           PER_INSTALLATION_PREFS object.
369    * @param  value
370    *           The value to be written. Its type must match
371    *           PER_INSTALLATION_PREFS[prefName].type
372    * @param  options
373    *           Optional. An object containing any of the following keys:
374    *             setDefaultOnly
375    *               If set to true, the default branch value will be set rather
376    *               than user value. If a user value is set for this pref, this
377    *               will have no effect on the pref's effective value.
378    *               NOTE - The behavior of the default pref branch currently
379    *                      differs depending on whether the current platform
380    *                      supports per-installation prefs. If they are
381    *                      supported, default branch values persist across
382    *                      Firefox sessions. If they aren't supported, default
383    *                      branch values reset when Firefox shuts down.
384    * @return A Promise that, once the setting has been saved, resolves with the
385    *         value that was saved.
386    * @throw  If there is an I/O error when attempting to write to the config
387    *         file, the returned Promise will reject with a DOMException.
388    */
389   writeUpdateConfigSetting(prefName, value, options) {
390     if (!(prefName in this.PER_INSTALLATION_PREFS)) {
391       return Promise.reject(
392         new Error(
393           `UpdateUtils.writeUpdateConfigSetting: Unknown per-installation ` +
394             `pref '${prefName}'`
395         )
396       );
397     }
399     if (this.appUpdateSettingIsLocked(prefName)) {
400       return Promise.reject(
401         new Error(
402           `UpdateUtils.writeUpdateConfigSetting: Unable to change value of ` +
403             `setting '${prefName}' because it is locked by policy`
404         )
405       );
406     }
408     if (!options) {
409       options = {};
410     }
412     const pref = this.PER_INSTALLATION_PREFS[prefName];
413     const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];
415     if (!prefTypeFns.isValid(value)) {
416       return Promise.reject(
417         new Error(
418           `UpdateUtils.writeUpdateConfigSetting: Attempted to change pref ` +
419             `'${prefName} to invalid value: ${JSON.stringify(value)}`
420         )
421       );
422     }
424     if (!this.PER_INSTALLATION_PREFS_SUPPORTED) {
425       // If we don't have per-installation prefs, we use regular preferences.
426       if (options.setDefaultOnly) {
427         prefTypeFns.setProfileDefaultPref(prefName, value);
428       } else {
429         prefTypeFns.setProfilePref(prefName, value);
430       }
431       // Rather than call maybeUpdateConfigChanged, a pref observer has
432       // been connected to the relevant pref. This allows us to catch direct
433       // changes to prefs (which Firefox shouldn't be doing, but the user
434       // might do in about:config).
435       return Promise.resolve(value);
436     }
438     let writePromise = updateConfigIOPromise
439       // All promises returned by (read|write)UpdateConfigSetting are part of a
440       // single promise chain in order to serialize disk operations. But we
441       // don't want the entire promise chain to reject when one operation fails.
442       // So we are going to silently clear any rejections the promise chain
443       // might contain.
444       //
445       // We will also pass an empty function for the first then() argument as
446       // well, just to make sure we are starting fresh rather than potentially
447       // propagating some stale value.
448       .then(
449         () => {},
450         () => {}
451       )
452       // We always re-read the update config before writing, rather than using a
453       // cached version. Otherwise, two simultaneous instances may overwrite
454       // each other's changes.
455       .then(readUpdateConfig)
456       .then(async config => {
457         setConfigValue(config, prefName, value, {
458           setDefaultOnly: !!options.setDefaultOnly,
459         });
461         try {
462           await writeUpdateConfig(config);
463           return config;
464         } catch (e) {
465           console.error(
466             "UpdateUtils.writeUpdateConfigSetting: App update configuration " +
467               "file write failed. Exception: ",
468             e
469           );
470           // Re-throw the error so the caller knows that writing the value in
471           // the app update config file failed.
472           throw e;
473         }
474       })
475       .then(maybeUpdateConfigChanged)
476       .then(() => {
477         // If this value wasn't written, a previous promise in the chain will
478         // have thrown, so we can unconditionally return the expected written
479         // value as the value that was written.
480         return value;
481       });
482     updateConfigIOPromise = writePromise;
483     return writePromise;
484   },
486   /**
487    * Returns true if the specified pref is controlled by policy and thus should
488    * not be changeable by the user.
489    */
490   appUpdateSettingIsLocked(prefName) {
491     if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) {
492       return Promise.reject(
493         new Error(
494           `UpdateUtils.appUpdateSettingIsLocked: Unknown per-installation pref '${prefName}'`
495         )
496       );
497     }
499     // If we don't have policy support, nothing can be locked.
500     if (!Services.policies) {
501       return false;
502     }
504     const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName];
505     if (!pref.policyFn) {
506       return false;
507     }
508     const policyValue = pref.policyFn();
509     return policyValue !== null;
510   },
513 const PER_INSTALLATION_DEFAULTS_BRANCH = "__DEFAULTS__";
516  * Some prefs are specific to the installation, not the profile. They are
517  * stored in JSON format in FILE_UPDATE_CONFIG_JSON.
518  * Not all platforms currently support per-installation prefs, in which case
519  * we fall back to using profile-specific prefs.
521  * Note: These prefs should always be accessed through UpdateUtils. Do NOT
522  *       attempt to read or write their prefs directly.
524  * Keys in this object should be the name of the pref. The same name will be
525  * used whether we are writing it to the per-installation or per-profile pref.
526  * Values in this object should be objects with the following keys:
527  *   type
528  *     Must be one of the Update.PER_INSTALLATION_PREF_TYPE_* values, defined
529  *     above.
530  *   defaultValue
531  *     The default value to use for this pref if no value is set. This must be
532  *     of a type that is compatible with the type value specified.
533  *   migrate
534  *     Optional - defaults to false. A boolean indicating whether an existing
535  *     value in the profile-specific prefs ought to be migrated to an
536  *     installation specific pref. This is useful for prefs like
537  *     app.update.auto that used to be profile-specific prefs.
538  *     Note - Migration currently happens only on the creation of the JSON
539  *            file. If we want to add more prefs that require migration, we
540  *            will probably need to change this.
541  *   observerTopic
542  *     When a config value is changed, an observer will be fired, much like
543  *     the existing preference observers. This specifies the topic of the
544  *     observer that will be fired.
545  *   policyFn
546  *     Optional. If defined, should be a function that returns null or a value
547  *     of the specified type of this pref. If null is returned, this has no
548  *     effect. If another value is returned, it will be used rather than
549  *     reading the pref. This function will only be called if
550  *     Services.policies is defined. Asynchronous functions are not currently
551  *     supported.
552  */
553 UpdateUtils.PER_INSTALLATION_PREFS = {
554   "app.update.auto": {
555     type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL,
556     defaultValue: true,
557     migrate: true,
558     observerTopic: "auto-update-config-change",
559     policyFn: () => {
560       if (!Services.policies.isAllowed("app-auto-updates-off")) {
561         // We aren't allowed to turn off auto-update - it is forced on.
562         return true;
563       }
564       if (!Services.policies.isAllowed("app-auto-updates-on")) {
565         // We aren't allowed to turn on auto-update - it is forced off.
566         return false;
567       }
568       return null;
569     },
570   },
571   "app.update.background.enabled": {
572     type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL,
573     defaultValue: true,
574     observerTopic: "background-update-config-change",
575     policyFn: () => {
576       if (!Services.policies.isAllowed("app-background-update-off")) {
577         // We aren't allowed to turn off background update - it is forced on.
578         return true;
579       }
580       if (!Services.policies.isAllowed("app-background-update-on")) {
581         // We aren't allowed to turn on background update - it is forced off.
582         return false;
583       }
584       return null;
585     },
586   },
589 const TYPE_SPECIFIC_PREF_FNS = {
590   [UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL]: {
591     getProfilePref: Services.prefs.getBoolPref,
592     setProfilePref: Services.prefs.setBoolPref,
593     setProfileDefaultPref: (pref, value) => {
594       let defaults = Services.prefs.getDefaultBranch("");
595       defaults.setBoolPref(pref, value);
596     },
597     isValid: value => typeof value == "boolean",
598   },
599   [UpdateUtils.PER_INSTALLATION_PREF_TYPE_ASCII_STRING]: {
600     getProfilePref: Services.prefs.getCharPref,
601     setProfilePref: Services.prefs.setCharPref,
602     setProfileDefaultPref: (pref, value) => {
603       let defaults = Services.prefs.getDefaultBranch("");
604       defaults.setCharPref(pref, value);
605     },
606     isValid: value => typeof value == "string",
607   },
608   [UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT]: {
609     getProfilePref: Services.prefs.getIntPref,
610     setProfilePref: Services.prefs.setIntPref,
611     setProfileDefaultPref: (pref, value) => {
612       let defaults = Services.prefs.getDefaultBranch("");
613       defaults.setIntPref(pref, value);
614     },
615     isValid: value => Number.isInteger(value),
616   },
620  * Used for serializing reads and writes of the app update json config file so
621  * the writes don't happen out of order and the last write is the one that
622  * the sets the value.
623  */
624 var updateConfigIOPromise = Promise.resolve();
627  * Returns a pref name that we will use to keep track of if the passed pref has
628  * been migrated already, so we don't end up migrating it twice.
629  */
630 function getPrefMigratedPref(prefName) {
631   return prefName + ".migrated";
635  * @return true if prefs need to be migrated from profile-specific prefs to
636  *         installation-specific prefs.
637  */
638 function updateConfigNeedsMigration() {
639   for (const [prefName, pref] of Object.entries(
640     UpdateUtils.PER_INSTALLATION_PREFS
641   )) {
642     if (pref.migrate) {
643       let migratedPrefName = getPrefMigratedPref(prefName);
644       let migrated = Services.prefs.getBoolPref(migratedPrefName, false);
645       if (!migrated) {
646         return true;
647       }
648     }
649   }
650   return false;
653 function setUpdateConfigMigrationDone() {
654   for (const [prefName, pref] of Object.entries(
655     UpdateUtils.PER_INSTALLATION_PREFS
656   )) {
657     if (pref.migrate) {
658       let migratedPrefName = getPrefMigratedPref(prefName);
659       Services.prefs.setBoolPref(migratedPrefName, true);
660     }
661   }
665  * Deletes the migrated data.
666  */
667 function onMigrationSuccessful() {
668   for (const [prefName, pref] of Object.entries(
669     UpdateUtils.PER_INSTALLATION_PREFS
670   )) {
671     if (pref.migrate) {
672       Services.prefs.clearUserPref(prefName);
673     }
674   }
677 function makeMigrationUpdateConfig() {
678   let config = makeDefaultUpdateConfig();
680   for (const [prefName, pref] of Object.entries(
681     UpdateUtils.PER_INSTALLATION_PREFS
682   )) {
683     if (!pref.migrate) {
684       continue;
685     }
686     let migratedPrefName = getPrefMigratedPref(prefName);
687     let alreadyMigrated = Services.prefs.getBoolPref(migratedPrefName, false);
688     if (alreadyMigrated) {
689       continue;
690     }
692     const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];
694     let prefHasValue = true;
695     let prefValue;
696     try {
697       // Without a second argument, this will throw if the pref has no user
698       // value or default value.
699       prefValue = prefTypeFns.getProfilePref(prefName);
700     } catch (e) {
701       prefHasValue = false;
702     }
703     if (prefHasValue) {
704       setConfigValue(config, prefName, prefValue);
705     }
706   }
708   return config;
711 function makeDefaultUpdateConfig() {
712   let config = {};
714   for (const [prefName, pref] of Object.entries(
715     UpdateUtils.PER_INSTALLATION_PREFS
716   )) {
717     setConfigValue(config, prefName, pref.defaultValue, {
718       setDefaultOnly: true,
719     });
720   }
722   return config;
726  * Sets the specified value in the config object.
728  * @param  config
729  *           The config object for which to set the value
730  * @param  prefName
731  *           The name of the preference to set.
732  * @param  prefValue
733  *           The value to set the preference to.
734  * @param  options
735  *           Optional. An object containing any of the following keys:
736  *             setDefaultOnly
737  *               If set to true, the default value will be set rather than
738  *               user value. If a user value is set for this pref, this will
739  *               have no effect on the pref's effective value.
740  */
741 function setConfigValue(config, prefName, prefValue, options) {
742   if (!options) {
743     options = {};
744   }
746   if (options.setDefaultOnly) {
747     if (!(PER_INSTALLATION_DEFAULTS_BRANCH in config)) {
748       config[PER_INSTALLATION_DEFAULTS_BRANCH] = {};
749     }
750     config[PER_INSTALLATION_DEFAULTS_BRANCH][prefName] = prefValue;
751   } else if (prefValue != readDefaultValue(config, prefName)) {
752     config[prefName] = prefValue;
753   } else {
754     delete config[prefName];
755   }
759  * Reads the specified pref out of the given configuration object.
760  * If a user value of the pref is set, that will be returned. If only a default
761  * branch value is set, that will be returned. Otherwise, the default value from
762  * PER_INSTALLATION_PREFS will be returned.
764  * Values will be validated before being returned. Invalid values are ignored.
766  * @param  config
767  *           The configuration object to read.
768  * @param  prefName
769  *           The name of the preference to read.
770  * @return The value of the preference.
771  */
772 function readEffectiveValue(config, prefName) {
773   if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) {
774     throw new Error(
775       `readEffectiveValue: Unknown per-installation pref '${prefName}'`
776     );
777   }
778   const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName];
779   const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];
781   if (prefName in config) {
782     if (prefTypeFns.isValid(config[prefName])) {
783       return config[prefName];
784     }
785     console.error(
786       `readEffectiveValue: Got invalid value for update config's` +
787         ` '${prefName}' value: "${config[prefName]}"`
788     );
789   }
790   return readDefaultValue(config, prefName);
794  * Reads the default branch pref out of the given configuration object. If one
795  * is not set, the default value from PER_INSTALLATION_PREFS will be returned.
797  * Values will be validated before being returned. Invalid values are ignored.
799  * @param  config
800  *           The configuration object to read.
801  * @param  prefName
802  *           The name of the preference to read.
803  * @return The value of the preference.
804  */
805 function readDefaultValue(config, prefName) {
806   if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) {
807     throw new Error(
808       `readDefaultValue: Unknown per-installation pref '${prefName}'`
809     );
810   }
811   const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName];
812   const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];
814   if (PER_INSTALLATION_DEFAULTS_BRANCH in config) {
815     let defaults = config[PER_INSTALLATION_DEFAULTS_BRANCH];
816     if (prefName in defaults) {
817       if (prefTypeFns.isValid(defaults[prefName])) {
818         return defaults[prefName];
819       }
820       console.error(
821         `readEffectiveValue: Got invalid default value for update` +
822           ` config's '${prefName}' value: "${defaults[prefName]}"`
823       );
824     }
825   }
826   return pref.defaultValue;
830  * Reads the update config and, if necessary, performs migration of un-migrated
831  * values. We don't want to completely give up on update if this file is
832  * unavailable, so default values will be returned on failure rather than
833  * throwing an error.
835  * @return An Update Config object.
836  */
837 async function readUpdateConfig() {
838   try {
839     let config = await IOUtils.readJSON(UpdateUtils.getConfigFilePath());
841     // We only migrate once. If we read something, the migration has already
842     // happened so we should make sure it doesn't happen again.
843     setUpdateConfigMigrationDone();
845     return config;
846   } catch (e) {
847     if (DOMException.isInstance(e) && e.name == "NotFoundError") {
848       if (updateConfigNeedsMigration()) {
849         const migrationConfig = makeMigrationUpdateConfig();
850         setUpdateConfigMigrationDone();
851         try {
852           await writeUpdateConfig(migrationConfig);
853           onMigrationSuccessful();
854           return migrationConfig;
855         } catch (e) {
856           console.error("readUpdateConfig: Migration failed: ", e);
857         }
858       }
859     } else {
860       // We only migrate once. If we got an error other than the file not
861       // existing, the migration has already happened so we should make sure
862       // it doesn't happen again.
863       setUpdateConfigMigrationDone();
865       console.error(
866         "readUpdateConfig: Unable to read app update configuration file. " +
867           "Exception: ",
868         e
869       );
870     }
871     return makeDefaultUpdateConfig();
872   }
876  * Writes the given configuration to the disk.
878  * @param  config
879  *           The configuration object to write.
880  * @return The configuration object written.
881  * @throw  A DOMException will be thrown on I/O error.
882  */
883 async function writeUpdateConfig(config) {
884   let path = UpdateUtils.getConfigFilePath();
885   await IOUtils.writeJSON(path, config, { tmpPath: `${path}.tmp` });
886   return config;
889 var gUpdateConfigCache;
891  * Notifies observers if any update config prefs have changed.
893  * @param  config
894  *           The most up-to-date config object.
895  * @return The same config object that was passed in.
896  */
897 function maybeUpdateConfigChanged(config) {
898   if (!gUpdateConfigCache) {
899     // We don't want to generate a change notification for every pref on the
900     // first read of the session.
901     gUpdateConfigCache = config;
902     return config;
903   }
905   for (const [prefName, pref] of Object.entries(
906     UpdateUtils.PER_INSTALLATION_PREFS
907   )) {
908     let newPrefValue = readEffectiveValue(config, prefName);
909     let oldPrefValue = readEffectiveValue(gUpdateConfigCache, prefName);
910     if (newPrefValue != oldPrefValue) {
911       Services.obs.notifyObservers(
912         null,
913         pref.observerTopic,
914         newPrefValue.toString()
915       );
916     }
917   }
919   gUpdateConfigCache = config;
920   return config;
924  * Note that this function sets up observers only, it does not do any I/O.
925  */
926 UpdateUtils.initPerInstallPrefs();
928 /* Get the distribution pref values, from defaults only */
929 function getDistributionPrefValue(aPrefName) {
930   let value = Services.prefs
931     .getDefaultBranch(null)
932     .getCharPref(aPrefName, "default");
933   if (!value) {
934     value = "default";
935   }
936   return value;
939 function getSystemCapabilities() {
940   return "ISET:" + lazy.gInstructionSet + ",MEM:" + getMemoryMB();
944  * Gets the RAM size in megabytes. This will round the value because sysinfo
945  * doesn't always provide RAM in multiples of 1024.
946  */
947 function getMemoryMB() {
948   let memoryMB = "unknown";
949   try {
950     memoryMB = Services.sysinfo.getProperty("memsize");
951     if (memoryMB) {
952       memoryMB = Math.round(memoryMB / 1024 / 1024);
953     }
954   } catch (e) {
955     console.error("Error getting system info memsize property. Exception: ", e);
956   }
957   return memoryMB;
961  * Gets the supported CPU instruction set.
962  */
963 ChromeUtils.defineLazyGetter(lazy, "gInstructionSet", function aus_gIS() {
964   const CPU_EXTENSIONS = [
965     "hasSSE4_2",
966     "hasSSE4_1",
967     "hasSSE4A",
968     "hasSSSE3",
969     "hasSSE3",
970     "hasSSE2",
971     "hasSSE",
972     "hasMMX",
973     "hasNEON",
974     "hasARMv7",
975     "hasARMv6",
976   ];
977   for (let ext of CPU_EXTENSIONS) {
978     if (Services.sysinfo.getProperty(ext)) {
979       return ext.substring(3);
980     }
981   }
983   return "unknown";
986 /* Windows only getter that returns the processor architecture. */
987 ChromeUtils.defineLazyGetter(lazy, "gWinCPUArch", function aus_gWinCPUArch() {
988   // Get processor architecture
989   let arch = "unknown";
991   const WORD = lazy.ctypes.uint16_t;
992   const DWORD = lazy.ctypes.uint32_t;
994   // This structure is described at:
995   // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
996   const SYSTEM_INFO = new lazy.ctypes.StructType("SYSTEM_INFO", [
997     { wProcessorArchitecture: WORD },
998     { wReserved: WORD },
999     { dwPageSize: DWORD },
1000     { lpMinimumApplicationAddress: lazy.ctypes.voidptr_t },
1001     { lpMaximumApplicationAddress: lazy.ctypes.voidptr_t },
1002     { dwActiveProcessorMask: DWORD.ptr },
1003     { dwNumberOfProcessors: DWORD },
1004     { dwProcessorType: DWORD },
1005     { dwAllocationGranularity: DWORD },
1006     { wProcessorLevel: WORD },
1007     { wProcessorRevision: WORD },
1008   ]);
1010   let kernel32 = false;
1011   try {
1012     kernel32 = lazy.ctypes.open("Kernel32");
1013   } catch (e) {
1014     console.error("Unable to open kernel32! Exception: ", e);
1015   }
1017   if (kernel32) {
1018     try {
1019       let GetNativeSystemInfo = kernel32.declare(
1020         "GetNativeSystemInfo",
1021         lazy.ctypes.winapi_abi,
1022         lazy.ctypes.void_t,
1023         SYSTEM_INFO.ptr
1024       );
1025       let winSystemInfo = SYSTEM_INFO();
1026       // Default to unknown
1027       winSystemInfo.wProcessorArchitecture = 0xffff;
1029       GetNativeSystemInfo(winSystemInfo.address());
1030       switch (winSystemInfo.wProcessorArchitecture) {
1031         case 12:
1032           arch = "aarch64";
1033           break;
1034         case 9:
1035           arch = "x64";
1036           break;
1037         case 6:
1038           arch = "IA64";
1039           break;
1040         case 0:
1041           arch = "x86";
1042           break;
1043       }
1044     } catch (e) {
1045       console.error("Error getting processor architecture. Exception: ", e);
1046     } finally {
1047       kernel32.close();
1048     }
1049   }
1051   return arch;
1054 ChromeUtils.defineLazyGetter(UpdateUtils, "ABI", function () {
1055   let abi = null;
1056   try {
1057     abi = Services.appinfo.XPCOMABI;
1058   } catch (e) {
1059     console.error("XPCOM ABI unknown");
1060   }
1062   if (AppConstants.platform == "win") {
1063     // Windows build should report the CPU architecture that it's running on.
1064     abi += "-" + lazy.gWinCPUArch;
1065   }
1067   if (AppConstants.ASAN) {
1068     // Allow ASan builds to receive their own updates
1069     abi += "-asan";
1070   }
1072   return abi;
1075 ChromeUtils.defineLazyGetter(UpdateUtils, "OSVersion", function () {
1076   let osVersion;
1077   try {
1078     osVersion =
1079       Services.sysinfo.getProperty("name") +
1080       " " +
1081       Services.sysinfo.getProperty("version");
1082   } catch (e) {
1083     console.error("OS Version unknown.");
1084   }
1086   if (osVersion) {
1087     if (AppConstants.platform == "win") {
1088       // Add service pack and build number
1089       try {
1090         const { servicePackMajor, servicePackMinor, buildNumber } =
1091           lazy.WindowsVersionInfo.get();
1092         osVersion += `.${servicePackMajor}.${servicePackMinor}.${buildNumber}`;
1093       } catch (err) {
1094         console.error("Unable to retrieve windows version information: ", err);
1095         osVersion += ".unknown";
1096       }
1098       // add UBR if on Windows 10
1099       if (
1100         Services.vc.compare(Services.sysinfo.getProperty("version"), "10") >= 0
1101       ) {
1102         const WINDOWS_UBR_KEY_PATH =
1103           "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
1104         let ubr = lazy.WindowsRegistry.readRegKey(
1105           Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
1106           WINDOWS_UBR_KEY_PATH,
1107           "UBR",
1108           Ci.nsIWindowsRegKey.WOW64_64
1109         );
1110         if (ubr !== undefined) {
1111           osVersion += `.${ubr}`;
1112         } else {
1113           osVersion += ".unknown";
1114         }
1115       }
1117       // Add processor architecture
1118       osVersion += " (" + lazy.gWinCPUArch + ")";
1119     }
1121     try {
1122       osVersion +=
1123         " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
1124     } catch (e) {
1125       // Not all platforms have a secondary widget library, so an error is nothing to worry about.
1126     }
1127     osVersion = encodeURIComponent(osVersion);
1128   }
1129   return osVersion;