Bug 1920197 - Add prefs to allow updating Widevine directly from the Chromium update service. a=dmeehan

This patch allows us to download Widevine updates directly from the
Chromium update service, bypassing balrog and fallback configuration
options. This will be useful in the early stages of testing Widevine
updates, allowing us to use whatever Google has pushed to its own users,
including beta updates, before we engage release engineering.

Relevant Widevine L3 prefs have been added:
- media.gmp-widevinecdm.force-chromium-update
- media.gmp-widevinecdm.force-chromium-beta

which force the use of the Chromium update service, and requesting beta
versions (if available) respectively.

Original Revision: https://phabricator.services.mozilla.com/D223017

Differential Revision: https://phabricator.services.mozilla.com/D227283
This commit is contained in:
Andrew Osmond 2024-11-04 19:25:57 +00:00
parent b50bec4f89
commit 6c929b3dbc
3 changed files with 255 additions and 85 deletions

View File

@ -1822,8 +1822,12 @@ pref("media.gmp.trial-create.enabled", true);
// unsupported.
#ifdef MOZ_WIDEVINE_EME
pref("media.gmp-manager.chromium-update-url", "https://update.googleapis.com/service/update2/crx?response=redirect&x=id%3D%GUID%%26uc&acceptformat=crx3&updaterversion=999");
pref("media.gmp-widevinecdm.visible", true);
pref("media.gmp-widevinecdm.enabled", true);
pref("media.gmp-widevinecdm.chromium-guid", "oimompecagnajdejgnnjijobebaeigek");
pref("media.gmp-widevinecdm.force-chromium-update", false);
pref("media.gmp-widevinecdm.force-chromium-beta", false);
#endif
pref("media.gmp-gmpopenh264.visible", true);

View File

@ -53,6 +53,27 @@ function getLocalSources() {
return [];
}
function redirectChromiumUpdateService(uri) {
let log = getScopedLogger("GMPInstallManager.checkForAddons");
log.info("fetching redirect from: " + uri);
return new Promise((resolve, reject) => {
let xmlHttp = new lazy.ServiceRequest({ mozAnon: true });
xmlHttp.onload = function () {
resolve(this.responseURL);
};
xmlHttp.onerror = function (e) {
reject("Fetching " + uri + " results in error code: " + e.target.status);
};
xmlHttp.open("GET", uri);
xmlHttp.overrideMimeType("*/*");
xmlHttp.setRequestHeader("Range", "bytes=0-0");
xmlHttp.send();
});
}
function downloadJSON(uri) {
let log = getScopedLogger("GMPInstallManager.checkForAddons");
log.info("fetching config from: " + uri);
@ -332,6 +353,7 @@ GMPInstallManager.prototype = {
}
this._deferred = PromiseUtils.defer();
let deferredPromise = this._deferred.promise;
// Should content signature checking of Balrog replies be used? If so this
// will be done instead of the older cert pinning method.
@ -363,46 +385,131 @@ GMPInstallManager.prototype = {
log.info(
`Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}, trustedContentSignatureRoot=${trustedContentSignatureRoot}`
);
let addonPromise = ProductAddonChecker.getProductAddonList(
let success = true;
let res;
try {
res = await ProductAddonChecker.getProductAddonList(
url,
allowNonBuiltIn,
certs,
checkContentSignature,
trustedContentSignatureRoot
)
.then(res => {
);
if (checkContentSignature) {
this.recordUpdateXmlTelemetryForContentSignature(true);
} else {
this.recordUpdateXmlTelemetryForCertPinning(true);
}
return res;
})
.catch(err => {
} catch (err) {
success = false;
if (checkContentSignature) {
this.recordUpdateXmlTelemetryForContentSignature(false, err);
} else {
this.recordUpdateXmlTelemetryForCertPinning(false, err);
}
return downloadLocalConfig();
});
}
addonPromise.then(
res => {
if (!res || !res.addons) {
this._deferred.resolve({ addons: [] });
let localSources = getLocalSources();
try {
if (!success) {
log.info("Falling back to local config");
let fallbackSources = localSources.filter(function (gmpSource) {
return gmpSource.installByDefault;
});
res = await downloadLocalConfig(fallbackSources);
}
} catch (err) {
this._deferred.reject(err);
delete this._deferred;
return deferredPromise;
}
let addons;
if (res && res.addons) {
addons = res.addons.map(a => new GMPAddon(a));
} else {
res.addons = res.addons.map(a => new GMPAddon(a));
this._deferred.resolve(res);
addons = [];
}
delete this._deferred;
},
ex => {
this._deferred.reject(ex);
delete this._deferred;
let usedFallback;
if (res && res.usedFallback !== undefined) {
usedFallback = res.usedFallback;
} else {
usedFallback = true;
}
// Now let's check the addons that we are configured to override to go
// directly to the Chromium component update service.
try {
for (let gmpAddon of addons) {
if (
!GMPPrefs.getBool(
GMPPrefs.KEY_PLUGIN_FORCE_CHROMIUM_UPDATE,
false,
gmpAddon.id
)
) {
continue;
}
const guid = GMPPrefs.getString(
GMPPrefs.KEY_PLUGIN_CHROMIUM_GUID,
"",
gmpAddon.id
);
return this._deferred.promise;
if (guid === "") {
log.warn("Skipping chromium update, missing GUID for ", gmpAddon.id);
continue;
}
const params = GMPUtils._getChromiumUpdateParameters(gmpAddon);
const serviceUrl = GMPPrefs.getString(
GMPPrefs.KEY_CHROMIUM_UPDATE_URL,
""
);
const redirectUrl = await redirectChromiumUpdateService(
serviceUrl.replace("%GUID%", guid) + params
);
const versionMatch = redirectUrl.match(/_(\d+\.\d+\.\d+\.\d+)\//);
if (!versionMatch || versionMatch.length !== 2) {
log.warn(
"Skipping chromium update, no version from URL: ",
redirectUrl
);
continue;
}
const version = versionMatch[1];
log.info(
"Forcing " +
gmpAddon.id +
" to version " +
version +
" from chromium update " +
redirectUrl
);
// Update the addon with the final URL and the extracted version.
gmpAddon.URL = redirectUrl;
gmpAddon.version = version;
gmpAddon.usedChromiumUpdate = true;
// Delete these properties to avoid verifying the addon against our
// balrog configuration, which may or may not match.
delete gmpAddon.size;
delete gmpAddon.hash;
delete gmpAddon.hashFunction;
}
} catch (err) {
log.info("Failed to switch addons to Chromium update service: " + err);
}
this._deferred.resolve({ usedFallback, addons });
delete this._deferred;
return deferredPromise;
},
/**
* Installs the specified addon and calls a callback when done.
@ -621,6 +728,7 @@ GMPInstallManager.prototype = {
*/
export function GMPAddon(addon) {
let log = getScopedLogger("GMPAddon.constructor");
this.usedChromiumUpdate = false;
for (let name of Object.keys(addon)) {
this[name] = addon[name];
}
@ -656,18 +764,18 @@ GMPAddon.prototype = {
this.id &&
this.URL &&
this.version &&
this.hashFunction &&
!!this.hashValue
(this.usedChromiumUpdate || (this.hashFunction && !!this.hashValue))
);
},
get isInstalled() {
return (
this.version &&
!!this.hashValue &&
GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) ===
this.version &&
(this.usedChromiumUpdate ||
(!!this.hashValue &&
GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_HASHVALUE, "", this.id) ===
this.hashValue
this.hashValue))
);
},
get isEME() {
@ -783,11 +891,16 @@ GMPDownloader.prototype = {
gmpAddon.version,
]);
let installPromise = gmpInstaller.install();
return installPromise.then(
return installPromise
.then(
extractedPaths => {
// Success, set the prefs
let now = Math.round(Date.now() / 1000);
GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
GMPPrefs.setInt(
GMPPrefs.KEY_PLUGIN_LAST_UPDATE,
now,
gmpAddon.id
);
// Remember our ABI, so that if the profile is migrated to another
// platform or from 32 -> 64 bit, we notice and don't try to load the
// unexecutable plugin library.
@ -795,12 +908,16 @@ GMPDownloader.prototype = {
log.info("Setting ABI to '" + abi + "' for " + gmpAddon.id);
GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, abi, gmpAddon.id);
// We use the combination of the hash and version to ensure we are
// up to date.
// up to date. Ignored if we used the Chromium update service directly.
if (!gmpAddon.usedChromiumUpdate) {
GMPPrefs.setString(
GMPPrefs.KEY_PLUGIN_HASHVALUE,
gmpAddon.hashValue,
gmpAddon.id
);
} else {
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_HASHVALUE, gmpAddon.id);
}
// Setting the version pref signals installation completion to consumers,
// if you need to set other prefs etc. do it before this.
GMPPrefs.setString(
@ -824,7 +941,16 @@ GMPDownloader.prototype = {
);
throw reason;
}
);
)
.finally(() => {
log.info(`Deleting ${gmpAddon.id} temporary zip file ${zipPath}`);
// We need to send out an observer event to ensure the nsZipReaderCache
// clears its cache entries associated with our temporary file. Otherwise
// if the addons downloader reuses the temporary file path, then we may hit
// the cache and get different contents than expected.
Services.obs.notifyObservers(null, "flush-cache-entry", zipPath);
IOUtils.remove(zipPath);
});
},
reason => {
GMPPrefs.setString(

View File

@ -92,6 +92,42 @@ export var GMPUtils = {
);
},
_getChromiumUpdateParameters(aPlugin) {
let params = "";
if (AppConstants.platform === "win") {
params += "&os=win";
} else if (AppConstants.platform === "macosx") {
params += "&os=mac";
} else if (AppConstants.platform === "linux") {
params += "&os=Linux";
} else {
throw new Error("Unknown platform " + AppConstants.platform);
}
const abi = Services.appinfo.XPCOMABI;
if (abi.match(/aarch64/)) {
params += "&arch=arm64&os_arch=arm64";
} else if (abi.match(/x86_64/)) {
params += "&arch=x64&os_arch=x64";
} else if (abi.match(/x86/)) {
params += "&arch=x86&os_arch=x86";
} else {
throw new Error("Unknown ABI " + abi);
}
if (
GMPPrefs.getBool(
GMPPrefs.KEY_PLUGIN_FORCE_CHROMIUM_BETA,
false,
aPlugin.id
)
) {
params += "&testrequest=1";
}
return params;
},
_expectedABI(aPlugin) {
let defaultABI = lazy.UpdateUtils.ABI;
let expectedABIs = [defaultABI];
@ -124,9 +160,13 @@ export var GMPPrefs = {
KEY_PLUGIN_ABI: "media.{0}.abi",
KEY_PLUGIN_FORCE_SUPPORTED: "media.{0}.forceSupported",
KEY_PLUGIN_ALLOW_X64_ON_ARM64: "media.{0}.allow-x64-plugin-on-arm64",
KEY_PLUGIN_CHROMIUM_GUID: "media.{0}.chromium-guid",
KEY_PLUGIN_FORCE_CHROMIUM_UPDATE: "media.{0}.force-chromium-update",
KEY_PLUGIN_FORCE_CHROMIUM_BETA: "media.{0}.force-chromium-beta",
KEY_ALLOW_LOCAL_SOURCES: "media.gmp-manager.allowLocalSources",
KEY_URL: "media.gmp-manager.url",
KEY_URL_OVERRIDE: "media.gmp-manager.url.override",
KEY_CHROMIUM_UPDATE_URL: "media.gmp-manager.chromium-update-url",
KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes",
KEY_CERT_REQUIREBUILTIN: "media.gmp-manager.cert.requireBuiltIn",
KEY_CHECK_CONTENT_SIGNATURE: "media.gmp-manager.checkContentSignature",