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