/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs", WindowsVersionInfo: "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs", ctypes: "resource://gre/modules/ctypes.sys.mjs", }); const PER_INSTALLATION_PREFS_PLATFORMS = ["win"]; // The file that stores Application Update configuration settings. The file is // located in the update directory which makes it a common setting across all // application profiles and allows the Background Update Agent to read it. const FILE_UPDATE_CONFIG_JSON = "update-config.json"; const FILE_UPDATE_LOCALE = "update.locale"; const PREF_APP_DISTRIBUTION = "distribution.id"; const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; export var UpdateUtils = { _locale: undefined, _configFilePath: undefined, /** * Read the update channel from defaults only. We do this to ensure that * the channel is tightly coupled with the application and does not apply * to other instances of the application that may use the same profile. * * @param [optional] aIncludePartners * Whether or not to include the partner bits. Default: true. */ getUpdateChannel(aIncludePartners = true) { let defaults = Services.prefs.getDefaultBranch(null); let channel = defaults.getCharPref( "app.update.channel", AppConstants.MOZ_UPDATE_CHANNEL ); if (aIncludePartners) { try { let partners = Services.prefs.getChildList("app.partner.").sort(); if (partners.length) { channel += "-cck"; partners.forEach(function (prefName) { channel += "-" + Services.prefs.getCharPref(prefName); }); } } catch (e) { console.error(e); } } return channel; }, get UpdateChannel() { return this.getUpdateChannel(); }, /** * Formats a URL by replacing %...% values with OS, build and locale specific * values. * * @param url * The URL to format. * @return The formatted URL. */ async formatUpdateURL(url) { const locale = await this.getLocale(); return url .replace(/%(\w+)%/g, (match, name) => { switch (name) { case "PRODUCT": return Services.appinfo.name; case "VERSION": return Services.appinfo.version; case "BUILD_ID": return Services.appinfo.appBuildID; case "BUILD_TARGET": return Services.appinfo.OS + "_" + this.ABI; case "OS_VERSION": return this.OSVersion; case "LOCALE": return locale; case "CHANNEL": return this.UpdateChannel; case "PLATFORM_VERSION": return Services.appinfo.platformVersion; case "SYSTEM_CAPABILITIES": return getSystemCapabilities(); case "DISTRIBUTION": return getDistributionPrefValue(PREF_APP_DISTRIBUTION); case "DISTRIBUTION_VERSION": return getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION); } return match; }) .replace(/\+/g, "%2B"); }, /** * Gets the locale from the update.locale file for replacing %LOCALE% in the * update url. The update.locale file can be located in the application * directory or the GRE directory with preference given to it being located in * the application directory. */ async getLocale() { if (this._locale !== undefined) { return this._locale; } for (let res of ["app", "gre"]) { const url = "resource://" + res + "/" + FILE_UPDATE_LOCALE; let data; try { data = await fetch(url); } catch (e) { continue; } const locale = await data.text(); if (locale) { return (this._locale = locale.trim()); } } console.error( FILE_UPDATE_LOCALE, " file doesn't exist in either the application or GRE directories" ); return (this._locale = null); }, /* Get the path to the config file. */ getConfigFilePath() { let path = PathUtils.join( Services.dirsvc.get("UpdRootD", Ci.nsIFile).path, FILE_UPDATE_CONFIG_JSON ); return (this._configFilePath = path); }, get configFilePath() { if (this._configFilePath !== undefined) { return this._configFilePath; } return this.getConfigFilePath(); }, /** * Determines whether or not the Application Update Service automatically * downloads and installs updates. This corresponds to whether or not the user * has selected "Automatically install updates" in about:preferences. * * On Windows, this setting is shared across all profiles for the installation * and is read asynchronously from the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * * @return A Promise that resolves with a boolean. */ async getAppUpdateAutoEnabled() { return this.readUpdateConfigSetting("app.update.auto"); }, /** * Toggles whether the Update Service automatically downloads and installs * updates. This effectively selects between the "Automatically install * updates" and "Check for updates but let you choose to install them" options * in about:preferences. * * On Windows, this setting is shared across all profiles for the installation * and is written asynchronously to the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * * If this method is called when the setting is locked, the returned promise * will reject. The lock status can be determined with * UpdateUtils.appUpdateAutoSettingIsLocked() * * @param enabled If set to true, automatic download and installation of * updates will be enabled. If set to false, this will be * disabled. * @return A Promise that, once the setting has been saved, resolves with the * boolean value that was saved. If the setting could not be * successfully saved, the Promise will reject. * On Windows, where this setting is stored in a file, this Promise * may reject with an I/O error. * On other operating systems, this promise should not reject as * this operation simply sets a pref. */ async setAppUpdateAutoEnabled(enabledValue) { return this.writeUpdateConfigSetting("app.update.auto", !!enabledValue); }, /** * This function should be used to determine if the automatic application * update setting is locked by an enterprise policy * * @return true if the automatic update setting is currently locked. * Otherwise, false. */ appUpdateAutoSettingIsLocked() { return this.appUpdateSettingIsLocked("app.update.auto"); }, /** * Indicates whether or not per-installation prefs are supported on this * platform. */ PER_INSTALLATION_PREFS_SUPPORTED: PER_INSTALLATION_PREFS_PLATFORMS.includes( AppConstants.platform ), /** * Possible per-installation pref types. */ PER_INSTALLATION_PREF_TYPE_BOOL: "boolean", PER_INSTALLATION_PREF_TYPE_ASCII_STRING: "ascii", PER_INSTALLATION_PREF_TYPE_INT: "integer", /** * We want the preference definitions to be part of UpdateUtils for a couple * of reasons. It's a clean way for consumers to look up things like observer * topic names. It also allows us to manipulate the supported prefs during * testing. However, we want to use values out of UpdateUtils (like pref * types) to construct this object. Therefore, this will initially be a * placeholder, which we will properly define after the UpdateUtils object * definition. */ PER_INSTALLATION_PREFS: null, /** * This function initializes per-installation prefs. Note that it does not * need to be called manually; it is already called within the file. * * This function is called on startup, so it does not read or write to disk. */ initPerInstallPrefs() { // If we don't have per-installation prefs, we store the update config in // preferences. In that case, the best way to notify observers of this // setting is just to propagate it from a pref observer. This ensures that // the expected observers still get notified, even if a user manually // changes the pref value. if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) { let initialConfig = {}; for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type]; try { let initialValue = prefTypeFns.getProfilePref(prefName); initialConfig[prefName] = initialValue; } catch (e) {} Services.prefs.addObserver(prefName, async (subject, topic, data) => { let config = { ...gUpdateConfigCache }; config[prefName] = await UpdateUtils.readUpdateConfigSetting( prefName ); maybeUpdateConfigChanged(config); }); } // On the first call to maybeUpdateConfigChanged, it has nothing to // compare its input to, so it just populates the cache and doesn't notify // any observers. This makes sense during normal usage, because the first // call will be on the first config file read, and we don't want to notify // observers of changes on the first read. But that means that when // propagating pref observers, we need to make one initial call to // simulate that initial read so that the cache will be populated when the // first pref observer fires. maybeUpdateConfigChanged(initialConfig); } }, /** * Reads an installation-specific configuration setting from the update config * JSON file. This function is guaranteed not to throw. If there are problems * reading the file, the default value will be returned so that update can * proceed. This is particularly important since the configuration file is * writable by anyone and we don't want an unprivileged user to be able to * break update for other users. * * If relevant policies are active, this function will read the policy value * rather than the stored value. * * @param prefName * The preference to read. Must be a key of the * PER_INSTALLATION_PREFS object. * @return A Promise that resolves with the pref's value. */ readUpdateConfigSetting(prefName) { if (!(prefName in this.PER_INSTALLATION_PREFS)) { return Promise.reject( new Error( `UpdateUtils.readUpdateConfigSetting: Unknown per-installation ` + `pref '${prefName}'` ) ); } const pref = this.PER_INSTALLATION_PREFS[prefName]; const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type]; if (Services.policies && "policyFn" in pref) { let policyValue = pref.policyFn(); if (policyValue !== null) { return Promise.resolve(policyValue); } } if (!this.PER_INSTALLATION_PREFS_SUPPORTED) { // If we don't have per-installation prefs, we use regular preferences. let prefValue = prefTypeFns.getProfilePref(prefName, pref.defaultValue); return Promise.resolve(prefValue); } let readPromise = updateConfigIOPromise // All promises returned by (read|write)UpdateConfigSetting are part of a // single promise chain in order to serialize disk operations. But we // don't want the entire promise chain to reject when one operation fails. // So we are going to silently clear any rejections the promise chain // might contain. // // We will also pass an empty function for the first then() argument as // well, just to make sure we are starting fresh rather than potentially // propagating some stale value. .then( () => {}, () => {} ) .then(readUpdateConfig) .then(maybeUpdateConfigChanged) .then(config => { return readEffectiveValue(config, prefName); }); updateConfigIOPromise = readPromise; return readPromise; }, /** * Changes an installation-specific configuration setting by writing it to * the update config JSON file. * * If this method is called on a prefName that is locked, the returned promise * will reject. The lock status can be determined with * appUpdateSettingIsLocked(). * * @param prefName * The preference to change. This must be a key of the * PER_INSTALLATION_PREFS object. * @param value * The value to be written. Its type must match * PER_INSTALLATION_PREFS[prefName].type * @param options * Optional. An object containing any of the following keys: * setDefaultOnly * If set to true, the default branch value will be set rather * than user value. If a user value is set for this pref, this * will have no effect on the pref's effective value. * NOTE - The behavior of the default pref branch currently * differs depending on whether the current platform * supports per-installation prefs. If they are * supported, default branch values persist across * Firefox sessions. If they aren't supported, default * branch values reset when Firefox shuts down. * @return A Promise that, once the setting has been saved, resolves with the * value that was saved. * @throw If there is an I/O error when attempting to write to the config * file, the returned Promise will reject with a DOMException. */ writeUpdateConfigSetting(prefName, value, options) { if (!(prefName in this.PER_INSTALLATION_PREFS)) { return Promise.reject( new Error( `UpdateUtils.writeUpdateConfigSetting: Unknown per-installation ` + `pref '${prefName}'` ) ); } if (this.appUpdateSettingIsLocked(prefName)) { return Promise.reject( new Error( `UpdateUtils.writeUpdateConfigSetting: Unable to change value of ` + `setting '${prefName}' because it is locked by policy` ) ); } if (!options) { options = {}; } const pref = this.PER_INSTALLATION_PREFS[prefName]; const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type]; if (!prefTypeFns.isValid(value)) { return Promise.reject( new Error( `UpdateUtils.writeUpdateConfigSetting: Attempted to change pref ` + `'${prefName} to invalid value: ${JSON.stringify(value)}` ) ); } if (!this.PER_INSTALLATION_PREFS_SUPPORTED) { // If we don't have per-installation prefs, we use regular preferences. if (options.setDefaultOnly) { prefTypeFns.setProfileDefaultPref(prefName, value); } else { prefTypeFns.setProfilePref(prefName, value); } // Rather than call maybeUpdateConfigChanged, a pref observer has // been connected to the relevant pref. This allows us to catch direct // changes to prefs (which Firefox shouldn't be doing, but the user // might do in about:config). return Promise.resolve(value); } let writePromise = updateConfigIOPromise // All promises returned by (read|write)UpdateConfigSetting are part of a // single promise chain in order to serialize disk operations. But we // don't want the entire promise chain to reject when one operation fails. // So we are going to silently clear any rejections the promise chain // might contain. // // We will also pass an empty function for the first then() argument as // well, just to make sure we are starting fresh rather than potentially // propagating some stale value. .then( () => {}, () => {} ) // We always re-read the update config before writing, rather than using a // cached version. Otherwise, two simultaneous instances may overwrite // each other's changes. .then(readUpdateConfig) .then(async config => { setConfigValue(config, prefName, value, { setDefaultOnly: !!options.setDefaultOnly, }); try { await writeUpdateConfig(config); return config; } catch (e) { console.error( "UpdateUtils.writeUpdateConfigSetting: App update configuration " + "file write failed. Exception: ", e ); // Re-throw the error so the caller knows that writing the value in // the app update config file failed. throw e; } }) .then(maybeUpdateConfigChanged) .then(() => { // If this value wasn't written, a previous promise in the chain will // have thrown, so we can unconditionally return the expected written // value as the value that was written. return value; }); updateConfigIOPromise = writePromise; return writePromise; }, /** * Returns true if the specified pref is controlled by policy and thus should * not be changeable by the user. */ appUpdateSettingIsLocked(prefName) { if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) { return Promise.reject( new Error( `UpdateUtils.appUpdateSettingIsLocked: Unknown per-installation pref '${prefName}'` ) ); } // If we don't have policy support, nothing can be locked. if (!Services.policies) { return false; } const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName]; if (!pref.policyFn) { return false; } const policyValue = pref.policyFn(); return policyValue !== null; }, }; const PER_INSTALLATION_DEFAULTS_BRANCH = "__DEFAULTS__"; /** * Some prefs are specific to the installation, not the profile. They are * stored in JSON format in FILE_UPDATE_CONFIG_JSON. * Not all platforms currently support per-installation prefs, in which case * we fall back to using profile-specific prefs. * * Note: These prefs should always be accessed through UpdateUtils. Do NOT * attempt to read or write their prefs directly. * * Keys in this object should be the name of the pref. The same name will be * used whether we are writing it to the per-installation or per-profile pref. * Values in this object should be objects with the following keys: * type * Must be one of the Update.PER_INSTALLATION_PREF_TYPE_* values, defined * above. * defaultValue * The default value to use for this pref if no value is set. This must be * of a type that is compatible with the type value specified. * migrate * Optional - defaults to false. A boolean indicating whether an existing * value in the profile-specific prefs ought to be migrated to an * installation specific pref. This is useful for prefs like * app.update.auto that used to be profile-specific prefs. * Note - Migration currently happens only on the creation of the JSON * file. If we want to add more prefs that require migration, we * will probably need to change this. * observerTopic * When a config value is changed, an observer will be fired, much like * the existing preference observers. This specifies the topic of the * observer that will be fired. * policyFn * Optional. If defined, should be a function that returns null or a value * of the specified type of this pref. If null is returned, this has no * effect. If another value is returned, it will be used rather than * reading the pref. This function will only be called if * Services.policies is defined. Asynchronous functions are not currently * supported. */ UpdateUtils.PER_INSTALLATION_PREFS = { "app.update.auto": { type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL, defaultValue: true, migrate: true, observerTopic: "auto-update-config-change", policyFn: () => { if (!Services.policies.isAllowed("app-auto-updates-off")) { // We aren't allowed to turn off auto-update - it is forced on. return true; } if (!Services.policies.isAllowed("app-auto-updates-on")) { // We aren't allowed to turn on auto-update - it is forced off. return false; } return null; }, }, "app.update.background.enabled": { type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL, defaultValue: true, observerTopic: "background-update-config-change", policyFn: () => { if (!Services.policies.isAllowed("app-background-update-off")) { // We aren't allowed to turn off background update - it is forced on. return true; } if (!Services.policies.isAllowed("app-background-update-on")) { // We aren't allowed to turn on background update - it is forced off. return false; } return null; }, }, }; const TYPE_SPECIFIC_PREF_FNS = { [UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL]: { getProfilePref: Services.prefs.getBoolPref, setProfilePref: Services.prefs.setBoolPref, setProfileDefaultPref: (pref, value) => { let defaults = Services.prefs.getDefaultBranch(""); defaults.setBoolPref(pref, value); }, isValid: value => typeof value == "boolean", }, [UpdateUtils.PER_INSTALLATION_PREF_TYPE_ASCII_STRING]: { getProfilePref: Services.prefs.getCharPref, setProfilePref: Services.prefs.setCharPref, setProfileDefaultPref: (pref, value) => { let defaults = Services.prefs.getDefaultBranch(""); defaults.setCharPref(pref, value); }, isValid: value => typeof value == "string", }, [UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT]: { getProfilePref: Services.prefs.getIntPref, setProfilePref: Services.prefs.setIntPref, setProfileDefaultPref: (pref, value) => { let defaults = Services.prefs.getDefaultBranch(""); defaults.setIntPref(pref, value); }, isValid: value => Number.isInteger(value), }, }; /** * Used for serializing reads and writes of the app update json config file so * the writes don't happen out of order and the last write is the one that * the sets the value. */ var updateConfigIOPromise = Promise.resolve(); /** * Returns a pref name that we will use to keep track of if the passed pref has * been migrated already, so we don't end up migrating it twice. */ function getPrefMigratedPref(prefName) { return prefName + ".migrated"; } /** * @return true if prefs need to be migrated from profile-specific prefs to * installation-specific prefs. */ function updateConfigNeedsMigration() { for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { if (pref.migrate) { let migratedPrefName = getPrefMigratedPref(prefName); let migrated = Services.prefs.getBoolPref(migratedPrefName, false); if (!migrated) { return true; } } } return false; } function setUpdateConfigMigrationDone() { for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { if (pref.migrate) { let migratedPrefName = getPrefMigratedPref(prefName); Services.prefs.setBoolPref(migratedPrefName, true); } } } /** * Deletes the migrated data. */ function onMigrationSuccessful() { for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { if (pref.migrate) { Services.prefs.clearUserPref(prefName); } } } function makeMigrationUpdateConfig() { let config = makeDefaultUpdateConfig(); for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { if (!pref.migrate) { continue; } let migratedPrefName = getPrefMigratedPref(prefName); let alreadyMigrated = Services.prefs.getBoolPref(migratedPrefName, false); if (alreadyMigrated) { continue; } const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type]; let prefHasValue = true; let prefValue; try { // Without a second argument, this will throw if the pref has no user // value or default value. prefValue = prefTypeFns.getProfilePref(prefName); } catch (e) { prefHasValue = false; } if (prefHasValue) { setConfigValue(config, prefName, prefValue); } } return config; } function makeDefaultUpdateConfig() { let config = {}; for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { setConfigValue(config, prefName, pref.defaultValue, { setDefaultOnly: true, }); } return config; } /** * Sets the specified value in the config object. * * @param config * The config object for which to set the value * @param prefName * The name of the preference to set. * @param prefValue * The value to set the preference to. * @param options * Optional. An object containing any of the following keys: * setDefaultOnly * If set to true, the default value will be set rather than * user value. If a user value is set for this pref, this will * have no effect on the pref's effective value. */ function setConfigValue(config, prefName, prefValue, options) { if (!options) { options = {}; } if (options.setDefaultOnly) { if (!(PER_INSTALLATION_DEFAULTS_BRANCH in config)) { config[PER_INSTALLATION_DEFAULTS_BRANCH] = {}; } config[PER_INSTALLATION_DEFAULTS_BRANCH][prefName] = prefValue; } else if (prefValue != readDefaultValue(config, prefName)) { config[prefName] = prefValue; } else { delete config[prefName]; } } /** * Reads the specified pref out of the given configuration object. * If a user value of the pref is set, that will be returned. If only a default * branch value is set, that will be returned. Otherwise, the default value from * PER_INSTALLATION_PREFS will be returned. * * Values will be validated before being returned. Invalid values are ignored. * * @param config * The configuration object to read. * @param prefName * The name of the preference to read. * @return The value of the preference. */ function readEffectiveValue(config, prefName) { if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) { throw new Error( `readEffectiveValue: Unknown per-installation pref '${prefName}'` ); } const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName]; const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type]; if (prefName in config) { if (prefTypeFns.isValid(config[prefName])) { return config[prefName]; } console.error( `readEffectiveValue: Got invalid value for update config's` + ` '${prefName}' value: "${config[prefName]}"` ); } return readDefaultValue(config, prefName); } /** * Reads the default branch pref out of the given configuration object. If one * is not set, the default value from PER_INSTALLATION_PREFS will be returned. * * Values will be validated before being returned. Invalid values are ignored. * * @param config * The configuration object to read. * @param prefName * The name of the preference to read. * @return The value of the preference. */ function readDefaultValue(config, prefName) { if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) { throw new Error( `readDefaultValue: Unknown per-installation pref '${prefName}'` ); } const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName]; const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type]; if (PER_INSTALLATION_DEFAULTS_BRANCH in config) { let defaults = config[PER_INSTALLATION_DEFAULTS_BRANCH]; if (prefName in defaults) { if (prefTypeFns.isValid(defaults[prefName])) { return defaults[prefName]; } console.error( `readEffectiveValue: Got invalid default value for update` + ` config's '${prefName}' value: "${defaults[prefName]}"` ); } } return pref.defaultValue; } /** * Reads the update config and, if necessary, performs migration of un-migrated * values. We don't want to completely give up on update if this file is * unavailable, so default values will be returned on failure rather than * throwing an error. * * @return An Update Config object. */ async function readUpdateConfig() { try { let config = await IOUtils.readJSON(UpdateUtils.getConfigFilePath()); // We only migrate once. If we read something, the migration has already // happened so we should make sure it doesn't happen again. setUpdateConfigMigrationDone(); return config; } catch (e) { if (DOMException.isInstance(e) && e.name == "NotFoundError") { if (updateConfigNeedsMigration()) { const migrationConfig = makeMigrationUpdateConfig(); setUpdateConfigMigrationDone(); try { await writeUpdateConfig(migrationConfig); onMigrationSuccessful(); return migrationConfig; } catch (e) { console.error("readUpdateConfig: Migration failed: ", e); } } } else { // We only migrate once. If we got an error other than the file not // existing, the migration has already happened so we should make sure // it doesn't happen again. setUpdateConfigMigrationDone(); console.error( "readUpdateConfig: Unable to read app update configuration file. " + "Exception: ", e ); } return makeDefaultUpdateConfig(); } } /** * Writes the given configuration to the disk. * * @param config * The configuration object to write. * @return The configuration object written. * @throw A DOMException will be thrown on I/O error. */ async function writeUpdateConfig(config) { let path = UpdateUtils.getConfigFilePath(); await IOUtils.writeJSON(path, config, { tmpPath: `${path}.tmp` }); return config; } var gUpdateConfigCache; /** * Notifies observers if any update config prefs have changed. * * @param config * The most up-to-date config object. * @return The same config object that was passed in. */ function maybeUpdateConfigChanged(config) { if (!gUpdateConfigCache) { // We don't want to generate a change notification for every pref on the // first read of the session. gUpdateConfigCache = config; return config; } for (const [prefName, pref] of Object.entries( UpdateUtils.PER_INSTALLATION_PREFS )) { let newPrefValue = readEffectiveValue(config, prefName); let oldPrefValue = readEffectiveValue(gUpdateConfigCache, prefName); if (newPrefValue != oldPrefValue) { Services.obs.notifyObservers( null, pref.observerTopic, newPrefValue.toString() ); } } gUpdateConfigCache = config; return config; } /** * Note that this function sets up observers only, it does not do any I/O. */ UpdateUtils.initPerInstallPrefs(); /* Get the distribution pref values, from defaults only */ function getDistributionPrefValue(aPrefName) { let value = Services.prefs .getDefaultBranch(null) .getCharPref(aPrefName, "default"); if (!value) { value = "default"; } return value; } function getSystemCapabilities() { return "ISET:" + lazy.gInstructionSet + ",MEM:" + getMemoryMB(); } /** * Gets the RAM size in megabytes. This will round the value because sysinfo * doesn't always provide RAM in multiples of 1024. */ function getMemoryMB() { let memoryMB = "unknown"; try { memoryMB = Services.sysinfo.getProperty("memsize"); if (memoryMB) { memoryMB = Math.round(memoryMB / 1024 / 1024); } } catch (e) { console.error("Error getting system info memsize property. Exception: ", e); } return memoryMB; } /** * Gets the supported CPU instruction set. */ XPCOMUtils.defineLazyGetter(lazy, "gInstructionSet", function aus_gIS() { const CPU_EXTENSIONS = [ "hasSSE4_2", "hasSSE4_1", "hasSSE4A", "hasSSSE3", "hasSSE3", "hasSSE2", "hasSSE", "hasMMX", "hasNEON", "hasARMv7", "hasARMv6", ]; for (let ext of CPU_EXTENSIONS) { if (Services.sysinfo.getProperty(ext)) { return ext.substring(3); } } return "unknown"; }); /* Windows only getter that returns the processor architecture. */ XPCOMUtils.defineLazyGetter(lazy, "gWinCPUArch", function aus_gWinCPUArch() { // Get processor architecture let arch = "unknown"; const WORD = lazy.ctypes.uint16_t; const DWORD = lazy.ctypes.uint32_t; // This structure is described at: // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx const SYSTEM_INFO = new lazy.ctypes.StructType("SYSTEM_INFO", [ { wProcessorArchitecture: WORD }, { wReserved: WORD }, { dwPageSize: DWORD }, { lpMinimumApplicationAddress: lazy.ctypes.voidptr_t }, { lpMaximumApplicationAddress: lazy.ctypes.voidptr_t }, { dwActiveProcessorMask: DWORD.ptr }, { dwNumberOfProcessors: DWORD }, { dwProcessorType: DWORD }, { dwAllocationGranularity: DWORD }, { wProcessorLevel: WORD }, { wProcessorRevision: WORD }, ]); let kernel32 = false; try { kernel32 = lazy.ctypes.open("Kernel32"); } catch (e) { console.error("Unable to open kernel32! Exception: ", e); } if (kernel32) { try { let GetNativeSystemInfo = kernel32.declare( "GetNativeSystemInfo", lazy.ctypes.winapi_abi, lazy.ctypes.void_t, SYSTEM_INFO.ptr ); let winSystemInfo = SYSTEM_INFO(); // Default to unknown winSystemInfo.wProcessorArchitecture = 0xffff; GetNativeSystemInfo(winSystemInfo.address()); switch (winSystemInfo.wProcessorArchitecture) { case 12: arch = "aarch64"; break; case 9: arch = "x64"; break; case 6: arch = "IA64"; break; case 0: arch = "x86"; break; } } catch (e) { console.error("Error getting processor architecture. Exception: ", e); } finally { kernel32.close(); } } return arch; }); XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function () { let abi = null; try { abi = Services.appinfo.XPCOMABI; } catch (e) { console.error("XPCOM ABI unknown"); } if (AppConstants.platform == "win") { // Windows build should report the CPU architecture that it's running on. abi += "-" + lazy.gWinCPUArch; } if (AppConstants.ASAN) { // Allow ASan builds to receive their own updates abi += "-asan"; } return abi; }); XPCOMUtils.defineLazyGetter(UpdateUtils, "OSVersion", function () { let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (e) { console.error("OS Version unknown."); } if (osVersion) { if (AppConstants.platform == "win") { // Add service pack and build number try { const { servicePackMajor, servicePackMinor, buildNumber } = lazy.WindowsVersionInfo.get(); osVersion += `.${servicePackMajor}.${servicePackMinor}.${buildNumber}`; } catch (err) { console.error("Unable to retrieve windows version information: ", err); osVersion += ".unknown"; } // add UBR if on Windows 10 if ( Services.vc.compare(Services.sysinfo.getProperty("version"), "10") >= 0 ) { const WINDOWS_UBR_KEY_PATH = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; let ubr = lazy.WindowsRegistry.readRegKey( Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, WINDOWS_UBR_KEY_PATH, "UBR", Ci.nsIWindowsRegKey.WOW64_64 ); if (ubr !== undefined) { osVersion += `.${ubr}`; } else { osVersion += ".unknown"; } } // Add processor architecture osVersion += " (" + lazy.gWinCPUArch + ")"; } try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; });