diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/modules/GMPInstallManager.sys.mjs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/modules/GMPInstallManager.sys.mjs')
-rw-r--r-- | toolkit/modules/GMPInstallManager.sys.mjs | 850 |
1 files changed, 850 insertions, 0 deletions
diff --git a/toolkit/modules/GMPInstallManager.sys.mjs b/toolkit/modules/GMPInstallManager.sys.mjs new file mode 100644 index 0000000000..41f57b9a63 --- /dev/null +++ b/toolkit/modules/GMPInstallManager.sys.mjs @@ -0,0 +1,850 @@ +/* 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/. */ + +// 1 day default +const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; +import { + GMPPrefs, + GMPUtils, + GMP_PLUGIN_IDS, + WIDEVINE_L1_ID, + WIDEVINE_L3_ID, +} from "resource://gre/modules/GMPUtils.sys.mjs"; + +import { ProductAddonChecker } from "resource://gre/modules/addons/ProductAddonChecker.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CertUtils: "resource://gre/modules/CertUtils.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs", + UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs", +}); + +function getScopedLogger(prefix) { + // `PARENT_LOGGER_ID.` being passed here effectively links this logger + // to the parentLogger. + return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " "); +} + +const LOCAL_GMP_SOURCES = [ + { + id: "gmp-gmpopenh264", + src: "chrome://global/content/gmp-sources/openh264.json", + installByDefault: true, + }, + { + id: "gmp-widevinecdm", + src: "chrome://global/content/gmp-sources/widevinecdm.json", + installByDefault: true, + }, + { + id: "gmp-widevinecdm-l1", + src: "chrome://global/content/gmp-sources/widevinecdm_l1.json", + installByDefault: false, + }, +]; + +function downloadJSON(uri) { + let log = getScopedLogger("GMPInstallManager.checkForAddons"); + log.info("fetching config from: " + uri); + return new Promise((resolve, reject) => { + let xmlHttp = new lazy.ServiceRequest({ mozAnon: true }); + + xmlHttp.onload = function (aResponse) { + resolve(JSON.parse(this.responseText)); + }; + + xmlHttp.onerror = function (e) { + reject("Fetching " + uri + " results in error code: " + e.target.status); + }; + + xmlHttp.open("GET", uri); + xmlHttp.overrideMimeType("application/json"); + xmlHttp.send(); + }); +} + +/** + * If downloading from the network fails (AUS server is down), + * load the sources from local build configuration. + */ +function downloadLocalConfig(sources) { + if (!sources.length) { + return Promise.resolve({ addons: [] }); + } + + let log = getScopedLogger("GMPInstallManager.downloadLocalConfig"); + return Promise.all( + sources.map(conf => { + return downloadJSON(conf.src).then(addons => { + let platforms = addons.vendors[conf.id].platforms; + let target = Services.appinfo.OS + "_" + lazy.UpdateUtils.ABI; + let details = null; + + while (!details) { + if (!(target in platforms)) { + // There was no matching platform so return false, this addon + // will be filtered from the results below + log.info("no details found for: " + target); + return false; + } + // Field either has the details of the binary or is an alias + // to another build target key that does + if (platforms[target].alias) { + target = platforms[target].alias; + } else { + details = platforms[target]; + } + } + + log.info("found plugin: " + conf.id); + return { + id: conf.id, + URL: details.fileUrl, + hashFunction: addons.hashFunction, + hashValue: details.hashValue, + version: addons.vendors[conf.id].version, + size: details.filesize, + usedFallback: true, + }; + }); + }) + ).then(addons => { + // Some filters may not match this platform so + // filter those out + return { addons: addons.filter(x => x !== false) }; + }); +} + +/** + * Provides an easy API for downloading and installing GMP Addons + */ +export function GMPInstallManager() {} + +/** + * Temp file name used for downloading + */ +GMPInstallManager.prototype = { + /** + * Obtains a URL with replacement of vars + */ + async _getURL() { + let log = getScopedLogger("GMPInstallManager._getURL"); + // Use the override URL if it is specified. The override URL is just like + // the normal URL but it does not check the cert. + let url = GMPPrefs.getString(GMPPrefs.KEY_URL_OVERRIDE, ""); + if (url) { + log.info("Using override url: " + url); + } else { + url = GMPPrefs.getString(GMPPrefs.KEY_URL); + log.info("Using url: " + url); + } + + url = await lazy.UpdateUtils.formatUpdateURL(url); + + log.info("Using url (with replacement): " + url); + return url; + }, + + /** + * Records telemetry results on if fetching update.xml from Balrog succeeded + * when content signature was used to verify the response from Balrog. + * @param didGetAddonList + * A boolean indicating if an update.xml containing the addon list was + * successfully fetched (true) or not (false). + * @param err + * The error that was thrown (if it exists) for the failure case. This + * is expected to have a addonCheckerErr member which provides further + * information on why the addon checker failed. + */ + recordUpdateXmlTelemetryForContentSignature(didGetAddonList, err = null) { + let log = getScopedLogger( + "GMPInstallManager.recordUpdateXmlTelemetryForContentSignature" + ); + try { + let updateResultHistogram = Services.telemetry.getHistogramById( + "MEDIA_GMP_UPDATE_XML_FETCH_RESULT" + ); + + // The non-glean telemetry used here will be removed in future and just + // the glean data will be gathered. + if (didGetAddonList) { + updateResultHistogram.add("content_sig_ok"); + Glean.gmp.updateXmlFetchResult.content_sig_success.add(1); + return; + } + // All remaining cases are failure cases. + updateResultHistogram.add("content_sig_fail"); + if (!err?.addonCheckerErr) { + // Unknown error case. If this is happening we should audit error paths + // to identify why we're not getting an error, or not getting it + // labelled. + Glean.gmp.updateXmlFetchResult.content_sig_unknown_error.add(1); + return; + } + const errorToHistogramMap = { + [ProductAddonChecker.NETWORK_REQUEST_ERR]: + "content_sig_net_request_error", + [ProductAddonChecker.NETWORK_TIMEOUT_ERR]: "content_sig_net_timeout", + [ProductAddonChecker.ABORT_ERR]: "content_sig_abort", + [ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR]: + "content_sig_missing_data", + [ProductAddonChecker.VERIFICATION_FAILED_ERR]: "content_sig_failed", + [ProductAddonChecker.VERIFICATION_INVALID_ERR]: "content_sig_invalid", + [ProductAddonChecker.XML_PARSE_ERR]: "content_sig_xml_parse_error", + }; + let metricID = + errorToHistogramMap[err.addonCheckerErr] ?? "content_sig_unknown_error"; + let metric = Glean.gmp.updateXmlFetchResult[metricID]; + metric.add(1); + } catch (e) { + // We don't expect this path to be hit, but we don't want telemetry + // failures to break GMP updates, so catch any issues here and let the + // update machinery continue. + log.error( + `Failed to record telemetry result of getProductAddonList, got error: ${e}` + ); + } + }, + + /** + * Records telemetry results on if fetching update.xml from Balrog succeeded + * when cert pinning was used to verify the response from Balrog. This + * should be removed once we're no longer using cert pinning. + * @param didGetAddonList + * A boolean indicating if an update.xml containing the addon list was + * successfully fetched (true) or not (false). + * @param err + * The error that was thrown (if it exists) for the failure case. This + * is expected to have a addonCheckerErr member which provides further + * information on why the addon checker failed. + */ + recordUpdateXmlTelemetryForCertPinning(didGetAddonList, err = null) { + let log = getScopedLogger( + "GMPInstallManager.recordUpdateXmlTelemetryForCertPinning" + ); + try { + let updateResultHistogram = Services.telemetry.getHistogramById( + "MEDIA_GMP_UPDATE_XML_FETCH_RESULT" + ); + + // The non-glean telemetry used here will be removed in future and just + // the glean data will be gathered. + if (didGetAddonList) { + updateResultHistogram.add("cert_pinning_ok"); + Glean.gmp.updateXmlFetchResult.cert_pin_success.add(1); + return; + } + // All remaining cases are failure cases. + updateResultHistogram.add("cert_pinning_fail"); + if (!err?.addonCheckerErr) { + // Unknown error case. If this is happening we should audit error paths + // to identify why we're not getting an error, or not getting it + // labelled. + Glean.gmp.updateXmlFetchResult.cert_pin_unknown_error.add(1); + return; + } + const errorToHistogramMap = { + [ProductAddonChecker.NETWORK_REQUEST_ERR]: "cert_pin_net_request_error", + [ProductAddonChecker.NETWORK_TIMEOUT_ERR]: "cert_pin_net_timeout", + [ProductAddonChecker.ABORT_ERR]: "cert_pin_abort", + [ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR]: + "cert_pin_missing_data", + [ProductAddonChecker.VERIFICATION_FAILED_ERR]: "cert_pin_failed", + [ProductAddonChecker.VERIFICATION_INVALID_ERR]: "cert_pin_invalid", + [ProductAddonChecker.XML_PARSE_ERR]: "cert_pin_xml_parse_error", + }; + let metricID = + errorToHistogramMap[err.addonCheckerErr] ?? "cert_pin_unknown_error"; + let metric = Glean.gmp.updateXmlFetchResult[metricID]; + metric.add(1); + } catch (e) { + // We don't expect this path to be hit, but we don't want telemetry + // failures to break GMP updates, so catch any issues here and let the + // update machinery continue. + log.error( + `Failed to record telemetry result of getProductAddonList, got error: ${e}` + ); + } + }, + + /** + * Performs an addon check. + * @return a promise which will be resolved or rejected. + * The promise is resolved with an object with properties: + * addons: array of addons + * usedFallback: whether the data was collected from online or + * from fallback data within the build + * The promise is rejected with an object with properties: + * target: The XHR request object + * status: The HTTP status code + * type: Sometimes specifies type of rejection + */ + async checkForAddons() { + let log = getScopedLogger("GMPInstallManager.checkForAddons"); + if (this._deferred) { + log.error("checkForAddons already called"); + return Promise.reject({ type: "alreadycalled" }); + } + + if (!GMPPrefs.getBool(GMPPrefs.KEY_UPDATE_ENABLED, true)) { + log.info("Updates are disabled via media.gmp-manager.updateEnabled"); + return { usedFallback: true, addons: [] }; + } + + this._deferred = Promise.withResolvers(); + 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. + let checkContentSignature = GMPPrefs.getBool( + GMPPrefs.KEY_CHECK_CONTENT_SIGNATURE, + true + ); + + let allowNonBuiltIn = true; + let certs = null; + // Only check certificates if we're not using a custom URL, and only if + // we're not checking a content signature. + if ( + !Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) && + !checkContentSignature + ) { + allowNonBuiltIn = !GMPPrefs.getString( + GMPPrefs.KEY_CERT_REQUIREBUILTIN, + true + ); + if (GMPPrefs.getBool(GMPPrefs.KEY_CERT_CHECKATTRS, true)) { + certs = lazy.CertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH); + } + } + + let url = await this._getURL(); + + log.info( + `Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}` + ); + + let success = true; + let res; + try { + res = await ProductAddonChecker.getProductAddonList( + url, + allowNonBuiltIn, + certs, + checkContentSignature + ); + + if (checkContentSignature) { + this.recordUpdateXmlTelemetryForContentSignature(true); + } else { + this.recordUpdateXmlTelemetryForCertPinning(true); + } + } catch (err) { + success = false; + if (checkContentSignature) { + this.recordUpdateXmlTelemetryForContentSignature(false, err); + } else { + this.recordUpdateXmlTelemetryForCertPinning(false, err); + } + } + + try { + if (!success) { + log.info("Falling back to local config"); + let fallbackSources = LOCAL_GMP_SOURCES.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 { + addons = []; + } + + // We need to merge in the addons that are only available via fallback that + // the user has requested be forced installed regardless of our update + // server configuration. + try { + let forcedSources = LOCAL_GMP_SOURCES.filter(function (gmpSource) { + return GMPPrefs.getBool( + GMPPrefs.KEY_PLUGIN_FORCE_INSTALL, + false, + gmpSource.id + ); + }); + + let forcedConfigs = await downloadLocalConfig( + forcedSources.filter(function (gmpSource) { + return !addons.find(gmpAddon => gmpAddon.id == gmpSource.id); + }) + ); + + let forcedAddons = forcedConfigs.addons.map( + config => new GMPAddon(config) + ); + + log.info("Forced " + forcedAddons.length + " addons."); + addons = addons.concat(forcedAddons); + } catch (err) { + log.info("Failed to force addons: " + err); + } + + this._deferred.resolve({ addons }); + delete this._deferred; + return deferredPromise; + }, + /** + * Installs the specified addon and calls a callback when done. + * @param gmpAddon The GMPAddon object to install + * @return a promise which will be resolved or rejected + * The promise will resolve with an array of paths that were extracted + * The promise will reject with an error object: + * target: The XHR request object + * status: The HTTP status code + * type: A string to represent the type of error + * downloaderr, verifyerr or previouserrorencountered + */ + installAddon(gmpAddon) { + if (this._deferred) { + let log = getScopedLogger("GMPInstallManager.installAddon"); + log.error("previous error encountered"); + return Promise.reject({ type: "previouserrorencountered" }); + } + this.gmpDownloader = new GMPDownloader(gmpAddon); + return this.gmpDownloader.start(); + }, + _getTimeSinceLastCheck() { + let now = Math.round(Date.now() / 1000); + // Default to 0 here because `now - 0` will be returned later if that case + // is hit. We want a large value so a check will occur. + let lastCheck = GMPPrefs.getInt(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0); + // Handle clock jumps, return now since we want it to represent + // a lot of time has passed since the last check. + if (now < lastCheck) { + return now; + } + return now - lastCheck; + }, + get _isEMEEnabled() { + return GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true); + }, + _isAddonEnabled(aAddon) { + return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_ENABLED, true, aAddon); + }, + _isAddonUpdateEnabled(aAddon) { + return ( + this._isAddonEnabled(aAddon) && + GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, aAddon) + ); + }, + _updateLastCheck() { + let now = Math.round(Date.now() / 1000); + GMPPrefs.setInt(GMPPrefs.KEY_UPDATE_LAST_CHECK, now); + }, + _versionchangeOccurred() { + let savedBuildID = GMPPrefs.getString(GMPPrefs.KEY_BUILDID, ""); + let buildID = Services.appinfo.platformBuildID || ""; + if (savedBuildID == buildID) { + return false; + } + GMPPrefs.setString(GMPPrefs.KEY_BUILDID, buildID); + return true; + }, + /** + * Wrapper for checkForAddons and installAddon. + * Will only install if not already installed and will log the results. + * This will only install/update the OpenH264 and EME plugins + * @return a promise which will be resolved if all addons could be installed + * successfully, rejected otherwise. + */ + async simpleCheckAndInstall() { + let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall"); + + if (this._versionchangeOccurred()) { + log.info( + "A version change occurred. Ignoring " + + "media.gmp-manager.lastCheck to check immediately for " + + "new or updated GMPs." + ); + } else { + let secondsBetweenChecks = GMPPrefs.getInt( + GMPPrefs.KEY_SECONDS_BETWEEN_CHECKS, + DEFAULT_SECONDS_BETWEEN_CHECKS + ); + let secondsSinceLast = this._getTimeSinceLastCheck(); + log.info( + "Last check was: " + + secondsSinceLast + + " seconds ago, minimum seconds: " + + secondsBetweenChecks + ); + if (secondsBetweenChecks > secondsSinceLast) { + log.info("Will not check for updates."); + return { status: "too-frequent-no-check" }; + } + } + + try { + let { addons } = await this.checkForAddons(); + this._updateLastCheck(); + log.info("Found " + addons.length + " addons advertised."); + let addonsToInstall = addons.filter(function (gmpAddon) { + log.info("Found addon: " + gmpAddon.toString()); + + if (!gmpAddon.isValid) { + log.info("Addon |" + gmpAddon.id + "| is invalid."); + return false; + } + + if (GMPUtils.isPluginHidden(gmpAddon)) { + log.info("Addon |" + gmpAddon.id + "| has been hidden."); + return false; + } + + if (gmpAddon.isInstalled) { + log.info("Addon |" + gmpAddon.id + "| already installed."); + return false; + } + + // Do not install from fallback if already installed as it + // may be a downgrade + if (gmpAddon.usedFallback && gmpAddon.isUpdate) { + log.info( + "Addon |" + + gmpAddon.id + + "| not installing updates based " + + "on fallback." + ); + return false; + } + + let addonUpdateEnabled = false; + if (GMP_PLUGIN_IDS.includes(gmpAddon.id)) { + if (!this._isAddonEnabled(gmpAddon.id)) { + log.info( + "GMP |" + gmpAddon.id + "| has been disabled; skipping check." + ); + } else if (!this._isAddonUpdateEnabled(gmpAddon.id)) { + log.info( + "Auto-update is off for " + gmpAddon.id + ", skipping check." + ); + } else { + addonUpdateEnabled = true; + } + } else { + // Currently, we only support installs of OpenH264 and EME plugins. + log.info( + "Auto-update is off for unknown plugin '" + + gmpAddon.id + + "', skipping check." + ); + } + + return addonUpdateEnabled; + }, this); + + if (!addonsToInstall.length) { + let now = Math.round(Date.now() / 1000); + GMPPrefs.setInt(GMPPrefs.KEY_UPDATE_LAST_EMPTY_CHECK, now); + log.info("No new addons to install, returning"); + return { status: "nothing-new-to-install" }; + } + + let installResults = []; + let failureEncountered = false; + for (let addon of addonsToInstall) { + try { + await this.installAddon(addon); + installResults.push({ + id: addon.id, + result: "succeeded", + }); + } catch (e) { + failureEncountered = true; + installResults.push({ + id: addon.id, + result: "failed", + }); + } + } + if (failureEncountered) { + // eslint-disable-next-line no-throw-literal + throw { status: "failed", results: installResults }; + } + return { status: "succeeded", results: installResults }; + } catch (e) { + log.error("Could not check for addons", e); + throw e; + } + }, + + /** + * Makes sure everything is cleaned up + */ + uninit() { + let log = getScopedLogger("GMPInstallManager.uninit"); + if (this._request) { + log.info("Aborting request"); + this._request.abort(); + } + if (this._deferred) { + log.info("Rejecting deferred"); + this._deferred.reject({ type: "uninitialized" }); + } + log.info("Done cleanup"); + }, + + /** + * If set to true, specifies to leave the temporary downloaded zip file. + * This is useful for tests. + */ + overrideLeaveDownloadedZip: false, +}; + +/** + * Used to construct a single GMP addon + * GMPAddon objects are returns from GMPInstallManager.checkForAddons + * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon + * + * @param addon The ProductAddonChecker `addon` object + */ +export function GMPAddon(addon) { + let log = getScopedLogger("GMPAddon.constructor"); + this.usedFallback = false; + for (let name of Object.keys(addon)) { + this[name] = addon[name]; + } + log.info("Created new addon: " + this.toString()); +} + +GMPAddon.prototype = { + /** + * Returns a string representation of the addon + */ + toString() { + return ( + this.id + + " (" + + "isValid: " + + this.isValid + + ", isInstalled: " + + this.isInstalled + + ", hashFunction: " + + this.hashFunction + + ", hashValue: " + + this.hashValue + + (this.size !== undefined ? ", size: " + this.size : "") + + ")" + ); + }, + /** + * If all the fields aren't specified don't consider this addon valid + * @return true if the addon is parsed and valid + */ + get isValid() { + return ( + this.id && + this.URL && + this.version && + this.hashFunction && + !!this.hashValue + ); + }, + get isInstalled() { + return ( + this.version && + !!this.hashValue && + GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) === + this.version && + GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_HASHVALUE, "", this.id) === + this.hashValue + ); + }, + get isEME() { + return this.id == WIDEVINE_L1_ID || this.id == WIDEVINE_L3_ID; + }, + get isOpenH264() { + return this.id == "gmp-gmpopenh264"; + }, + /** + * @return true if the addon has been previously installed and this is + * a new version, if this is a fresh install return false + */ + get isUpdate() { + return ( + this.version && + GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_VERSION, false, this.id) + ); + }, +}; + +/** + * Constructs a GMPExtractor object which is used to extract a GMP zip + * into the specified location. + * @param zipPath The path on disk of the zip file to extract + * @param relativePath The relative path components inside the profile directory + * to extract the zip to. + */ +export function GMPExtractor(zipPath, relativeInstallPath) { + this.zipPath = zipPath; + this.relativeInstallPath = relativeInstallPath; +} + +GMPExtractor.prototype = { + /** + * Installs the this.zipPath contents into the directory used to store GMP + * addons for the current platform. + * + * @return a promise which will be resolved or rejected + * See GMPInstallManager.installAddon for resolve/rejected info + */ + install() { + this._deferred = Promise.withResolvers(); + let deferredPromise = this._deferred; + let { zipPath, relativeInstallPath } = this; + // Escape the zip path since the worker will use it as a URI + let zipFile = new lazy.FileUtils.File(zipPath); + let zipURI = Services.io.newFileURI(zipFile).spec; + let worker = new ChromeWorker( + "resource://gre/modules/GMPExtractor.worker.js" + ); + worker.onmessage = function (msg) { + let log = getScopedLogger("GMPExtractor"); + worker.terminate(); + if (msg.data.result != "success") { + log.error("Failed to extract zip file: " + zipURI); + log.error("Exception: " + msg.data.exception); + return deferredPromise.reject({ + target: this, + status: msg.data.exception, + type: "exception", + }); + } + log.info("Successfully extracted zip file: " + zipURI); + return deferredPromise.resolve(msg.data.extractedPaths); + }; + worker.postMessage({ zipURI, relativeInstallPath }); + return this._deferred.promise; + }, +}; + +/** + * Constructs an object which downloads and initiates an install of + * the specified GMPAddon object. + * @param gmpAddon The addon to install. + */ +export function GMPDownloader(gmpAddon) { + this._gmpAddon = gmpAddon; +} + +GMPDownloader.prototype = { + /** + * Starts the download process for an addon. + * @return a promise which will be resolved or rejected + * See GMPInstallManager.installAddon for resolve/rejected info + */ + start() { + let log = getScopedLogger("GMPDownloader"); + let gmpAddon = this._gmpAddon; + let now = Math.round(Date.now() / 1000); + GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_INSTALL_START, now, gmpAddon.id); + + if (!gmpAddon.isValid) { + log.info("gmpAddon is not valid, will not continue"); + return Promise.reject({ + target: this, + type: "downloaderr", + }); + } + // If the HTTPS-Only Mode is enabled, every insecure request gets upgraded + // by default. This upgrade has to be prevented for openh264 downloads since + // the server doesn't support https:// + const downloadOptions = { + httpsOnlyNoUpgrade: gmpAddon.isOpenH264, + }; + return ProductAddonChecker.downloadAddon(gmpAddon, downloadOptions).then( + zipPath => { + let now = Math.round(Date.now() / 1000); + GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_DOWNLOAD, now, gmpAddon.id); + log.info( + `install to directory path: ${gmpAddon.id}/${gmpAddon.version}` + ); + let gmpInstaller = new GMPExtractor(zipPath, [ + gmpAddon.id, + gmpAddon.version, + ]); + let installPromise = gmpInstaller.install(); + 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); + // 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. + let abi = GMPUtils._expectedABI(gmpAddon); + 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. + GMPPrefs.setString( + GMPPrefs.KEY_PLUGIN_HASHVALUE, + gmpAddon.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( + GMPPrefs.KEY_PLUGIN_VERSION, + gmpAddon.version, + gmpAddon.id + ); + return extractedPaths; + }, + reason => { + GMPPrefs.setString( + GMPPrefs.KEY_PLUGIN_LAST_INSTALL_FAIL_REASON, + reason, + gmpAddon.id + ); + let now = Math.round(Date.now() / 1000); + GMPPrefs.setInt( + GMPPrefs.KEY_PLUGIN_LAST_INSTALL_FAILED, + now, + gmpAddon.id + ); + throw reason; + } + ); + }, + reason => { + GMPPrefs.setString( + GMPPrefs.KEY_PLUGIN_LAST_DOWNLOAD_FAIL_REASON, + reason, + gmpAddon.id + ); + let now = Math.round(Date.now() / 1000); + GMPPrefs.setInt( + GMPPrefs.KEY_PLUGIN_LAST_DOWNLOAD_FAILED, + now, + gmpAddon.id + ); + throw reason; + } + ); + }, +}; |