зеркало из https://github.com/mozilla/gecko-dev.git
601 строка
20 KiB
JavaScript
601 строка
20 KiB
JavaScript
/* 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/. */
|
|
|
|
var EXPORTED_SYMBOLS = ["UpdateUtils"];
|
|
|
|
const { AppConstants } = ChromeUtils.import(
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
const { FileUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/FileUtils.jsm"
|
|
);
|
|
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
|
const { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"WindowsVersionInfo",
|
|
"resource://gre/modules/components-utils/WindowsVersionInfo.jsm"
|
|
);
|
|
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]); /* globals fetch */
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"WindowsRegistry",
|
|
"resource://gre/modules/WindowsRegistry.jsm"
|
|
);
|
|
|
|
// 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";
|
|
// Do not use the PREF_APP_UPDATE_AUTO preference directly!
|
|
// Call getAppUpdateAutoEnabled or setAppUpdateAutoEnabled instead.
|
|
const PREF_APP_UPDATE_AUTO = "app.update.auto";
|
|
const PREF_APP_UPDATE_AUTO_MIGRATED = "app.update.auto.migrated";
|
|
// The setting name in the FILE_UPDATE_CONFIG_JSON file for whether the
|
|
// Application Update Service automatically downloads and installs updates.
|
|
const CONFIG_APP_UPDATE_AUTO = "app.update.auto";
|
|
// The default value for the CONFIG_APP_UPDATE_AUTO setting and the
|
|
// PREF_APP_UPDATE_AUTO preference.
|
|
const DEFAULT_APP_UPDATE_AUTO = true;
|
|
|
|
var UpdateUtils = {
|
|
_locale: 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) {
|
|
Cu.reportError(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());
|
|
}
|
|
}
|
|
|
|
Cu.reportError(
|
|
FILE_UPDATE_LOCALE +
|
|
" file doesn't exist in either the " +
|
|
"application or GRE directories"
|
|
);
|
|
|
|
return (this._locale = null);
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
getAppUpdateAutoEnabled() {
|
|
if (Services.policies) {
|
|
if (!Services.policies.isAllowed("app-auto-updates-off")) {
|
|
// We aren't allowed to turn off auto-update - it is forced on.
|
|
return Promise.resolve(true);
|
|
}
|
|
if (!Services.policies.isAllowed("app-auto-updates-on")) {
|
|
// We aren't allowed to turn on auto-update - it is forced off.
|
|
return Promise.resolve(false);
|
|
}
|
|
}
|
|
if (AppConstants.platform != "win") {
|
|
// On platforms other than Windows the setting is stored in a preference.
|
|
let prefValue = Services.prefs.getBoolPref(
|
|
PREF_APP_UPDATE_AUTO,
|
|
DEFAULT_APP_UPDATE_AUTO
|
|
);
|
|
return Promise.resolve(prefValue);
|
|
}
|
|
// Justification for the empty catch statement below:
|
|
// All promises returned by (get|set)AutoUpdateIsEnabled are part of a
|
|
// single promise chain in order to serialize disk operations. We don't want
|
|
// the entire promise chain to reject when one operation fails.
|
|
//
|
|
// There is only one situation when a promise in this chain should ever
|
|
// reject, which is when writing fails and the error is logged and
|
|
// re-thrown. All other possible exceptions are wrapped in try blocks, which
|
|
// also log any exception that may occur.
|
|
let readPromise = updateAutoIOPromise
|
|
.catch(() => {})
|
|
.then(async () => {
|
|
try {
|
|
let configValue = await readUpdateAutoConfig();
|
|
// If we read a value out of this file, don't later perform migration.
|
|
// If the file is deleted, we don't want some stale pref getting
|
|
// written to it just because a different profile performed migration.
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO_MIGRATED, true);
|
|
return configValue;
|
|
} catch (e) {
|
|
// Not being able to read from the app update configuration file is not
|
|
// a serious issue so use logStringMessage to avoid concern from users.
|
|
Services.console.logStringMessage(
|
|
"UpdateUtils.getAppUpdateAutoEnabled - Unable to read app update " +
|
|
"configuration file. Exception: " +
|
|
e
|
|
);
|
|
let valueMigrated = Services.prefs.getBoolPref(
|
|
PREF_APP_UPDATE_AUTO_MIGRATED,
|
|
false
|
|
);
|
|
if (!valueMigrated) {
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO_MIGRATED, true);
|
|
let prefValue = Services.prefs.getBoolPref(
|
|
PREF_APP_UPDATE_AUTO,
|
|
DEFAULT_APP_UPDATE_AUTO
|
|
);
|
|
try {
|
|
let writtenValue = await writeUpdateAutoConfig(prefValue);
|
|
Services.prefs.clearUserPref(PREF_APP_UPDATE_AUTO);
|
|
return writtenValue;
|
|
} catch (e) {
|
|
Cu.reportError(
|
|
"UpdateUtils.getAppUpdateAutoEnabled - Migration " +
|
|
"failed. Exception: " +
|
|
e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// Fallthrough for if the value could not be read or migrated.
|
|
return DEFAULT_APP_UPDATE_AUTO;
|
|
})
|
|
.then(maybeUpdateAutoConfigChanged);
|
|
updateAutoIOPromise = readPromise;
|
|
return readPromise;
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
setAppUpdateAutoEnabled(enabledValue) {
|
|
if (this.appUpdateAutoSettingIsLocked()) {
|
|
return Promise.reject(
|
|
"setAppUpdateAutoEnabled: Unable to change value of setting because " +
|
|
"it is locked by policy"
|
|
);
|
|
}
|
|
if (AppConstants.platform != "win") {
|
|
// Only in Windows do we store the update config in the update directory
|
|
let prefValue = !!enabledValue;
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, prefValue);
|
|
// Rather than call maybeUpdateAutoConfigChanged, a pref observer has
|
|
// been connected to PREF_APP_UPDATE_AUTO. This allows us to catch direct
|
|
// changes to the pref (which Firefox shouldn't be doing, but the user
|
|
// might do in about:config).
|
|
return Promise.resolve(prefValue);
|
|
}
|
|
// Justification for the empty catch statement below:
|
|
// All promises returned by (get|set)AutoUpdateIsEnabled are part of a
|
|
// single promise chain in order to serialize disk operations. We don't want
|
|
// the entire promise chain to reject when one operation fails.
|
|
//
|
|
// There is only one situation when a promise in this chain should ever
|
|
// reject, which is when writing fails and the error is logged and
|
|
// re-thrown. All other possible exceptions are wrapped in try blocks, which
|
|
// also log any exception that may occur.
|
|
let writePromise = updateAutoIOPromise
|
|
.catch(() => {})
|
|
.then(async () => {
|
|
try {
|
|
return await writeUpdateAutoConfig(enabledValue);
|
|
} catch (e) {
|
|
Cu.reportError(
|
|
"UpdateUtils.setAppUpdateAutoEnabled - App update " +
|
|
"configuration file write failed. Exception: " +
|
|
e
|
|
);
|
|
// Rethrow the error so the caller knows that writing the value in the
|
|
// app update config file failed.
|
|
throw e;
|
|
}
|
|
})
|
|
.then(maybeUpdateAutoConfigChanged);
|
|
updateAutoIOPromise = writePromise;
|
|
return writePromise;
|
|
},
|
|
|
|
/**
|
|
* 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 (
|
|
Services.policies &&
|
|
(!Services.policies.isAllowed("app-auto-updates-off") ||
|
|
!Services.policies.isAllowed("app-auto-updates-on"))
|
|
);
|
|
},
|
|
};
|
|
|
|
// 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 updateAutoIOPromise = Promise.resolve();
|
|
var updateAutoSettingCachedVal = null;
|
|
|
|
async function readUpdateAutoConfig() {
|
|
let configFile = FileUtils.getDir("UpdRootD", [], true);
|
|
configFile.append(FILE_UPDATE_CONFIG_JSON);
|
|
let binaryData = await OS.File.read(configFile.path);
|
|
let jsonData = new TextDecoder().decode(binaryData);
|
|
let configData = JSON.parse(jsonData);
|
|
return !!configData[CONFIG_APP_UPDATE_AUTO];
|
|
}
|
|
|
|
async function writeUpdateAutoConfig(enabledValue) {
|
|
let enabledBoolValue = !!enabledValue;
|
|
let configFile = FileUtils.getDir("UpdRootD", [], true);
|
|
configFile.append(FILE_UPDATE_CONFIG_JSON);
|
|
let configObject = { [CONFIG_APP_UPDATE_AUTO]: enabledBoolValue };
|
|
await OS.File.writeAtomic(configFile.path, JSON.stringify(configObject));
|
|
return enabledBoolValue;
|
|
}
|
|
|
|
// Notifies observers if the value of app.update.auto has changed and returns
|
|
// the value for app.update.auto.
|
|
function maybeUpdateAutoConfigChanged(newValue) {
|
|
if (newValue !== updateAutoSettingCachedVal) {
|
|
updateAutoSettingCachedVal = newValue;
|
|
Services.obs.notifyObservers(
|
|
null,
|
|
"auto-update-config-change",
|
|
newValue.toString()
|
|
);
|
|
}
|
|
return newValue;
|
|
}
|
|
// On non-Windows platforms, the Update Auto Config is still stored as a pref.
|
|
// On those platforms, the best way to notify observers of this setting is
|
|
// just to propagate it from a pref observer
|
|
if (AppConstants.platform != "win") {
|
|
Services.prefs.addObserver(
|
|
PREF_APP_UPDATE_AUTO,
|
|
async (subject, topic, data) => {
|
|
let value = await UpdateUtils.getAppUpdateAutoEnabled();
|
|
maybeUpdateAutoConfigChanged(value);
|
|
}
|
|
);
|
|
}
|
|
|
|
/* 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:" + 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) {
|
|
Cu.reportError(
|
|
"Error getting system info memsize property. Exception: " + e
|
|
);
|
|
}
|
|
return memoryMB;
|
|
}
|
|
|
|
/**
|
|
* Gets the supported CPU instruction set.
|
|
*/
|
|
XPCOMUtils.defineLazyGetter(this, "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(this, "gWinCPUArch", function aus_gWinCPUArch() {
|
|
// Get processor architecture
|
|
let arch = "unknown";
|
|
|
|
const WORD = ctypes.uint16_t;
|
|
const DWORD = 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 ctypes.StructType("SYSTEM_INFO", [
|
|
{ wProcessorArchitecture: WORD },
|
|
{ wReserved: WORD },
|
|
{ dwPageSize: DWORD },
|
|
{ lpMinimumApplicationAddress: ctypes.voidptr_t },
|
|
{ lpMaximumApplicationAddress: ctypes.voidptr_t },
|
|
{ dwActiveProcessorMask: DWORD.ptr },
|
|
{ dwNumberOfProcessors: DWORD },
|
|
{ dwProcessorType: DWORD },
|
|
{ dwAllocationGranularity: DWORD },
|
|
{ wProcessorLevel: WORD },
|
|
{ wProcessorRevision: WORD },
|
|
]);
|
|
|
|
let kernel32 = false;
|
|
try {
|
|
kernel32 = ctypes.open("Kernel32");
|
|
} catch (e) {
|
|
Cu.reportError("Unable to open kernel32! Exception: " + e);
|
|
}
|
|
|
|
if (kernel32) {
|
|
try {
|
|
let GetNativeSystemInfo = kernel32.declare(
|
|
"GetNativeSystemInfo",
|
|
ctypes.winapi_abi,
|
|
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) {
|
|
Cu.reportError("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) {
|
|
Cu.reportError("XPCOM ABI unknown");
|
|
}
|
|
|
|
if (AppConstants.platform == "win") {
|
|
// Windows build should report the CPU architecture that it's running on.
|
|
abi += "-" + 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) {
|
|
Cu.reportError("OS Version unknown.");
|
|
}
|
|
|
|
if (osVersion) {
|
|
if (AppConstants.platform == "win") {
|
|
// Add service pack and build number
|
|
try {
|
|
const {
|
|
servicePackMajor,
|
|
servicePackMinor,
|
|
buildNumber,
|
|
} = WindowsVersionInfo.get();
|
|
osVersion += `.${servicePackMajor}.${servicePackMinor}.${buildNumber}`;
|
|
} catch (err) {
|
|
Cu.reportError(
|
|
"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 = 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 += " (" + 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;
|
|
});
|