Path: blob/trunk/third_party/closure/goog/labs/useragent/browser.js
4140 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Closure user agent detection (Browser).8* @see <a href="http://www.useragentstring.com/">User agent strings</a>9* For more information on rendering engine, platform, or device see the other10* sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,11* goog.labs.userAgent.device respectively.)12*/1314goog.module('goog.labs.userAgent.browser');15goog.module.declareLegacyNamespace();1617const util = goog.require('goog.labs.userAgent.util');18const {AsyncValue, Version} = goog.require('goog.labs.userAgent.highEntropy.highEntropyValue');19const {assert, assertExists} = goog.require('goog.asserts');20const {compareVersions} = goog.require('goog.string.internal');21const {fullVersionList} = goog.require('goog.labs.userAgent.highEntropy.highEntropyData');22const {useClientHints} = goog.require('goog.labs.userAgent');2324// TODO(nnaze): Refactor to remove excessive exclusion logic in matching25// functions.2627/**28* A browser brand represents an opaque string that is used for making29* brand-specific version checks in userAgentData.30* @enum {string}31*/32const Brand = {33/**34* The browser brand for Android Browser.35* Do not depend on the value of this string. Because Android Browser has not36* implemented userAgentData yet, the value of this string is not guaranteed37* to stay the same in future revisions.38*/39ANDROID_BROWSER: 'Android Browser',40/**41* The browser brand for Chromium, including Chromium-based Edge and Opera.42*/43CHROMIUM: 'Chromium',44/**45* The browser brand for Edge.46* This brand can be used to get the version of both EdgeHTML and47* Chromium-based Edge.48*/49EDGE: 'Microsoft Edge',50/**51* The browser brand for Firefox.52* Do not depend on the value of this string. Because Firefox has not53* implemented userAgentData yet, the value of this string is not guaranteed54* to stay the same in future revisions.55*/56FIREFOX: 'Firefox',57/**58* The browser brand for Internet Explorer.59* Do not depend on the value of this string. Because IE will never support60* userAgentData, the value of this string should be treated as opaque (it's61* used internally for legacy-userAgent fallback).62*/63IE: 'Internet Explorer',64/**65* The browser brand for Opera.66* This brand can be used to get the version of both Presto- and67* Chromium-based Opera.68*/69OPERA: 'Opera',70/**71* The browser brand for Safari.72* Do not depend on the value of this string. Because Safari has not73* implemented userAgentData yet, the value of this string is not guaranteed74* to stay the same in future revisions.75*/76SAFARI: 'Safari',77/**78* The browser brand for Silk.79* See80* https://docs.aws.amazon.com/silk/latest/developerguide/what-is-silk.html81* Do not depend on the value of this string. Because Silk does not82* identify itself in userAgentData yet, the value of this string is not83* guaranteed to stay the same in future revisions.84*/85SILK: 'Silk',86};87exports.Brand = Brand;8889/**90* @param {boolean=} ignoreClientHintsFlag Iff truthy, the `useClientHints`91* function will not be called when evaluating if User-Agent Client Hints92* Brand data can be used. For existing labs.userAgent API surfaces with93* widespread use, this should be a falsy value so that usage of the Client94* Hints APIs can be gated behind flags / experiment rollouts.95* @return {boolean} Whether to use navigator.userAgentData to determine96* browser's brand.97*/98function useUserAgentDataBrand(ignoreClientHintsFlag = false) {99if (util.ASSUME_CLIENT_HINTS_SUPPORT) return true;100// High-entropy API surfaces should not be gated behind the useClientHints101// check (as in production it is gated behind a define).102if (!ignoreClientHintsFlag && !useClientHints()) return false;103const userAgentData = util.getUserAgentData();104return !!userAgentData && userAgentData.brands.length > 0;105}106107/**108* @return {boolean} Whether this browser is likely to have the fullVersionList109* high-entropy Client Hint.110*/111function hasFullVersionList() {112// https://chromiumdash.appspot.com/commits?commit=1eb643c3057e64ff4d22468432ad16c4cab12879&platform=Linux113// indicates that for all platforms Chromium 98 shipped this feature.114// See also115// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version-List#browser_compatibility116return isAtLeast(Brand.CHROMIUM, 98);117}118119/**120* @return {boolean} Whether the user's browser is Opera. Note: Chromium based121* Opera (Opera 15+) is detected as Chrome to avoid unnecessary special122* casing.123*/124function matchOpera() {125if (useUserAgentDataBrand()) {126// Pre-Chromium Edge doesn't support navigator.userAgentData.127return false;128}129return util.matchUserAgent('Opera');130}131132/** @return {boolean} Whether the user's browser is IE. */133function matchIE() {134if (useUserAgentDataBrand()) {135// IE doesn't support navigator.userAgentData.136return false;137}138return util.matchUserAgent('Trident') || util.matchUserAgent('MSIE');139}140141/**142* @return {boolean} Whether the user's browser is Edge. This refers to143* EdgeHTML based Edge.144*/145function matchEdgeHtml() {146if (useUserAgentDataBrand()) {147// Pre-Chromium Edge doesn't support navigator.userAgentData.148return false;149}150return util.matchUserAgent('Edge');151}152153/** @return {boolean} Whether the user's browser is Chromium based Edge. */154function matchEdgeChromium() {155if (useUserAgentDataBrand()) {156return util.matchUserAgentDataBrand(Brand.EDGE);157}158return util.matchUserAgent('Edg/');159}160161/** @return {boolean} Whether the user's browser is Chromium based Opera. */162function matchOperaChromium() {163if (useUserAgentDataBrand()) {164return util.matchUserAgentDataBrand(Brand.OPERA);165}166return util.matchUserAgent('OPR');167}168169/** @return {boolean} Whether the user's browser is Firefox. */170function matchFirefox() {171// Firefox doesn't support navigator.userAgentData yet, so use172// navigator.userAgent.173return util.matchUserAgent('Firefox') || util.matchUserAgent('FxiOS');174}175176/** @return {boolean} Whether the user's browser is Safari. */177function matchSafari() {178// Apple-based browsers don't support navigator.userAgentData yet, so use179// navigator.userAgent.180return util.matchUserAgent('Safari') &&181!(matchChrome() || matchCoast() || matchOpera() || matchEdgeHtml() ||182matchEdgeChromium() || matchOperaChromium() || matchFirefox() ||183isSilk() || util.matchUserAgent('Android'));184}185186/**187* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based188* iOS browser).189*/190function matchCoast() {191if (useUserAgentDataBrand()) {192// Coast doesn't support navigator.userAgentData.193return false;194}195return util.matchUserAgent('Coast');196}197198/** @return {boolean} Whether the user's browser is iOS Webview. */199function matchIosWebview() {200// Apple-based browsers don't support navigator.userAgentData yet, so use201// navigator.userAgent.202// iOS Webview does not show up as Chrome or Safari.203return (util.matchUserAgent('iPad') || util.matchUserAgent('iPhone')) &&204!matchSafari() && !matchChrome() && !matchCoast() && !matchFirefox() &&205util.matchUserAgent('AppleWebKit');206}207208/**209* @return {boolean} Whether the user's browser is any Chromium browser. This210* returns true for Chrome, Opera 15+, and Edge Chromium.211*/212function matchChrome() {213if (useUserAgentDataBrand()) {214return util.matchUserAgentDataBrand(Brand.CHROMIUM);215}216return ((util.matchUserAgent('Chrome') || util.matchUserAgent('CriOS')) &&217!matchEdgeHtml()) ||218isSilk();219}220221/** @return {boolean} Whether the user's browser is the Android browser. */222function matchAndroidBrowser() {223// Android can appear in the user agent string for Chrome on Android.224// This is not the Android standalone browser if it does.225return util.matchUserAgent('Android') &&226!(isChrome() || isFirefox() || isOpera() || isSilk());227}228229/** @return {boolean} Whether the user's browser is Opera. */230const isOpera = matchOpera;231exports.isOpera = isOpera;232233/** @return {boolean} Whether the user's browser is IE. */234const isIE = matchIE;235exports.isIE = isIE;236237/** @return {boolean} Whether the user's browser is EdgeHTML based Edge. */238const isEdge = matchEdgeHtml;239exports.isEdge = isEdge;240241/** @return {boolean} Whether the user's browser is Chromium based Edge. */242const isEdgeChromium = matchEdgeChromium;243exports.isEdgeChromium = isEdgeChromium;244245/** @return {boolean} Whether the user's browser is Chromium based Opera. */246const isOperaChromium = matchOperaChromium;247exports.isOperaChromium = isOperaChromium;248249/** @return {boolean} Whether the user's browser is Firefox. */250const isFirefox = matchFirefox;251exports.isFirefox = isFirefox;252253/** @return {boolean} Whether the user's browser is Safari. */254const isSafari = matchSafari;255exports.isSafari = isSafari;256257/**258* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based259* iOS browser).260*/261const isCoast = matchCoast;262exports.isCoast = isCoast;263264/** @return {boolean} Whether the user's browser is iOS Webview. */265const isIosWebview = matchIosWebview;266exports.isIosWebview = isIosWebview;267268/**269* @return {boolean} Whether the user's browser is any Chromium based browser (270* Chrome, Blink-based Opera (15+) and Edge Chromium).271*/272const isChrome = matchChrome;273exports.isChrome = isChrome;274275/** @return {boolean} Whether the user's browser is the Android browser. */276const isAndroidBrowser = matchAndroidBrowser;277exports.isAndroidBrowser = isAndroidBrowser;278279/**280* For more information, see:281* http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html282* @return {boolean} Whether the user's browser is Silk.283*/284function isSilk() {285// As of Silk 93, Silk does not identify itself in userAgentData.brands.286// When Silk changes this behavior, update this method to call287// matchUserAgentDataBrand (akin to isChrome, etc.)288return util.matchUserAgent('Silk');289}290exports.isSilk = isSilk;291292/**293* A helper function that returns a function mapping a list of candidate294* version tuple keys to the first version string present under a key.295* Ex:296* <code>297* // Arg extracted from "Foo/1.2.3 Bar/0.2021"298* const mapVersion = createVersionMap([["Foo", "1.2.3"], ["Bar", "0.2021"]]);299* mapVersion(["Bar", "Foo"]); // returns "0.2021"300* mapVersion(["Baz", "Foo"]); // returns "1.2.3"301* mapVersion(["Baz", "???"]); // returns ""302* </code>303* @param {!Array<!Array<string>>} versionTuples Version tuples pre-extracted304* from a user agent string.305* @return {function(!Array<string>): string} The version string, or empty306* string if it doesn't exist under the given key.307*/308function createVersionMap(versionTuples) {309// Construct a map for easy lookup.310const versionMap = {};311versionTuples.forEach((tuple) => {312// Note that the tuple is of length three, but we only care about the313// first two.314const key = tuple[0];315const value = tuple[1];316versionMap[key] = value;317});318319// Gives the value with the first key it finds, otherwise empty string.320return (keys) => versionMap[keys.find((key) => key in versionMap)] || '';321}322323/**324* Returns the browser version.325*326* Note that for browsers with multiple brands, this function assumes a primary327* brand and returns the version for that brand.328*329* Additionally, this function is not userAgentData-aware and will return330* incorrect values when the User Agent string is frozen. The current status of331* User Agent string freezing is available here:332* https://www.chromestatus.com/feature/5704553745874944333*334* To mitigate both of these potential issues, use335* getVersionStringForLogging() or fullVersionOf() instead.336*337* @return {string} The browser version or empty string if version cannot be338* determined. Note that for Internet Explorer, this returns the version of339* the browser, not the version of the rendering engine. (IE 8 in340* compatibility mode will return 8.0 rather than 7.0. To determine the341* rendering engine version, look at document.documentMode instead. See342* http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more343* details.)344*/345function getVersion() {346const userAgentString = util.getUserAgent();347348// Special case IE since IE's version is inside the parenthesis and349// without the '/'.350if (isIE()) {351return getIEVersion(userAgentString);352}353354const versionTuples = util.extractVersionTuples(userAgentString);355const lookUpValueWithKeys = createVersionMap(versionTuples);356357// Check Opera before Chrome since Opera 15+ has "Chrome" in the string.358// See359// http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond360if (isOpera()) {361// Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.362// Opera uses 'OPR' for more recent UAs.363return lookUpValueWithKeys(['Version', 'Opera']);364}365366// Check Edge before Chrome since it has Chrome in the string.367if (isEdge()) {368return lookUpValueWithKeys(['Edge']);369}370371// Check Chromium Edge before Chrome since it has Chrome in the string.372if (isEdgeChromium()) {373return lookUpValueWithKeys(['Edg']);374}375376// Check Silk before Chrome since it may have Chrome in its string and be377// treated as Chrome.378if (isSilk()) {379return lookUpValueWithKeys(['Silk']);380}381382if (isChrome()) {383return lookUpValueWithKeys(['Chrome', 'CriOS', 'HeadlessChrome']);384}385386// Usually products browser versions are in the third tuple after "Mozilla"387// and the engine.388const tuple = versionTuples[2];389return tuple && tuple[1] || '';390}391exports.getVersion = getVersion;392393/**394* Returns whether the current browser's version is at least as high as the395* given one.396*397* Note that for browsers with multiple brands, this function assumes a primary398* brand and checks the version for that brand.399*400* Additionally, this function is not userAgentData-aware and will return401* incorrect values when the User Agent string is frozen. The current status of402* User Agent string freezing is available here:403* https://www.chromestatus.com/feature/5704553745874944404*405* To mitigate both of these potential issues, use isAtLeast()/isAtMost() or406* fullVersionOf() instead.407*408* @param {string|number} version The version to check.409* @return {boolean} Whether the browser version is higher or the same as the410* given version.411* @deprecated Use isAtLeast()/isAtMost() instead.412*/413function isVersionOrHigher(version) {414return compareVersions(getVersion(), version) >= 0;415}416exports.isVersionOrHigher = isVersionOrHigher;417418/**419* A helper function to determine IE version. More information:420* http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString421* http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx422* http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx423* http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx424* @param {string} userAgent the User-Agent.425* @return {string}426*/427function getIEVersion(userAgent) {428// IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade429// bug. Example UA:430// Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)431// like Gecko.432// See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.433const rv = /rv: *([\d\.]*)/.exec(userAgent);434if (rv && rv[1]) {435return rv[1];436}437438let version = '';439const msie = /MSIE +([\d\.]+)/.exec(userAgent);440if (msie && msie[1]) {441// IE in compatibility mode usually identifies itself as MSIE 7.0; in this442// case, use the Trident version to determine the version of IE. For more443// details, see the links above.444const tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);445if (msie[1] == '7.0') {446if (tridentVersion && tridentVersion[1]) {447switch (tridentVersion[1]) {448case '4.0':449version = '8.0';450break;451case '5.0':452version = '9.0';453break;454case '6.0':455version = '10.0';456break;457case '7.0':458version = '11.0';459break;460}461} else {462version = '7.0';463}464} else {465version = msie[1];466}467}468return version;469}470471/**472* A helper function to return the navigator.userAgent-supplied full version473* number of the current browser or an empty string, based on whether the474* current browser is the one specified.475* @param {string} browser The brand whose version should be returned.476* @return {string}477*/478function getFullVersionFromUserAgentString(browser) {479const userAgentString = util.getUserAgent();480// Special case IE since IE's version is inside the parenthesis and481// without the '/'.482if (browser === Brand.IE) {483return isIE() ? getIEVersion(userAgentString) : '';484}485486const versionTuples = util.extractVersionTuples(userAgentString);487const lookUpValueWithKeys = createVersionMap(versionTuples);488switch (browser) {489case Brand.OPERA:490// Opera 10 has Version/10.0 but Opera/9.8, so look for "Version"491// first. Opera uses 'OPR' for more recent UAs.492if (isOpera()) {493return lookUpValueWithKeys(['Version', 'Opera']);494} else if (isOperaChromium()) {495return lookUpValueWithKeys(['OPR']);496}497break;498case Brand.EDGE:499if (isEdge()) {500return lookUpValueWithKeys(['Edge']);501} else if (isEdgeChromium()) {502return lookUpValueWithKeys(['Edg']);503}504break;505case Brand.CHROMIUM:506if (isChrome()) {507return lookUpValueWithKeys(['Chrome', 'CriOS', 'HeadlessChrome']);508}509break;510}511512// For the following browsers, the browser version is in the third tuple after513// "Mozilla" and the engine.514if ((browser === Brand.FIREFOX && isFirefox()) ||515(browser === Brand.SAFARI && isSafari()) ||516(browser === Brand.ANDROID_BROWSER && isAndroidBrowser()) ||517(browser === Brand.SILK && isSilk())) {518const tuple = versionTuples[2];519return tuple && tuple[1] || '';520}521522return '';523}524525/**526* Returns the major version of the given browser brand, or NaN if the current527* browser is not the given brand.528* Note that the major version number may be different depending on which529* browser is specified. The returned value can be used to make browser version530* comparisons using comparison operators.531* @private532* @param {!Brand} browser The brand whose version should be returned.533* @return {number} The major version number associated with the current534* browser under the given brand, or NaN if the current browser doesn't match535* the given brand.536*/537function versionOf_(browser) {538let versionParts;539// Silk currently does not identify itself in its userAgentData.brands array,540// so if checking its version, always fall back to the user agent string.541if (useUserAgentDataBrand() && browser !== Brand.SILK) {542const data = util.getUserAgentData();543const matchingBrand = data.brands.find(({brand}) => brand === browser);544if (!matchingBrand || !matchingBrand.version) {545return NaN;546}547versionParts = matchingBrand.version.split('.');548} else {549const fullVersion = getFullVersionFromUserAgentString(browser);550if (fullVersion === '') {551return NaN;552}553versionParts = fullVersion.split('.');554}555if (versionParts.length === 0) {556return NaN;557}558const majorVersion = versionParts[0];559return Number(majorVersion); // Returns NaN if it is not parseable.560}561562/**563* Returns true if the current browser matches the given brand and is at least564* the given major version. The major version must be a whole number (i.e.565* decimals should not be used to represent a minor version).566* @param {!Brand} brand The brand whose version should be returned.567* @param {number} majorVersion The major version number to compare against.568* This must be a whole number.569* @return {boolean} Whether the current browser both matches the given brand570* and is at least the given version.571*/572function isAtLeast(brand, majorVersion) {573assert(574Math.floor(majorVersion) === majorVersion,575'Major version must be an integer');576return versionOf_(brand) >= majorVersion;577}578exports.isAtLeast = isAtLeast;579580/**581* Returns true if the current browser matches the given brand and is at most582* the given version. The major version must be a whole number (i.e. decimals583* should not be used to represent a minor version).584* @param {!Brand} brand The brand whose version should be returned.585* @param {number} majorVersion The major version number to compare against.586* This must be a whole number.587* @return {boolean} Whether the current browser both matches the given brand588* and is at most the given version.589*/590function isAtMost(brand, majorVersion) {591assert(592Math.floor(majorVersion) === majorVersion,593'Major version must be an integer');594return versionOf_(brand) <= majorVersion;595}596exports.isAtMost = isAtMost;597598/**599* Loads the high-entropy browser brand/version data and wraps the correct600* version string in a Version object.601* @implements {AsyncValue<!Version>}602*/603class HighEntropyBrandVersion {604/**605* @param {string} brand The brand whose version is retrieved in this606* container.607* @param {boolean} useUach Whether to attempt to use the User-Agent Client608* Hints (UACH) API surface.609* @param {string} fallbackVersion The fallback version derived from the610* userAgent string.611*/612constructor(brand, useUach, fallbackVersion) {613/** @private @const {string} */614this.brand_ = brand;615616/** @private @const {!Version} */617this.version_ = new Version(fallbackVersion);618619/** @private @const {boolean} */620this.useUach_ = useUach;621}622623/**624* @return {!Version|undefined}625* @override626*/627getIfLoaded() {628if (this.useUach_) {629const loadedVersionList = fullVersionList.getIfLoaded();630if (loadedVersionList !== undefined) {631const matchingBrand =632loadedVersionList.find(({brand}) => this.brand_ === brand);633// We assumed in fullVersionOf that if the fullVersionList is defined634// the brands must match. Double-check this here.635assertExists(matchingBrand);636return new Version(matchingBrand.version);637}638// Fallthrough to fallback on Pre-UACH implementation639}640// We want to make sure the loading semantics of the Pre-UACH implementation641// match those of the UACH implementation. Loading must happen before any642// data can be retrieved from getIfLoaded.643// For HighEntropyBrandVersion, loading can either be done by calling #load644// or by calling the module-local loadFullVersions function.645if (preUachHasLoaded) {646return this.version_;647}648return;649}650651/**652* @return {!Promise<!Version>}653* @override654*/655async load() {656if (this.useUach_) {657const loadedVersionList = await fullVersionList.load();658if (loadedVersionList !== undefined) {659const matchingBrand =660loadedVersionList.find(({brand}) => this.brand_ === brand);661assertExists(matchingBrand);662return new Version(matchingBrand.version);663}664// Fallthrough to fallback on Pre-UACH implementation665} else {666// Await something so that calling load with or without UACH API667// availability results in waiting at least one macrotask before allowing668// access to the cached version information.669await 0;670}671// Regardless of whether we are using UACH APIs, we can now allow access to672// the fallback case673preUachHasLoaded = true;674return this.version_;675}676}677678/**679* Whether full version data should be considered available when using UACH680* fallback implementations. This is flipped to true when either681* loadFullVersions or HighEntropyBrandVersion.prototype.load are called,682* matching the global singleton semantics of the UACH codepaths.683*/684let preUachHasLoaded = false;685686/**687* Requests all full browser versions to be cached. When the returned promise688* resolves, subsequent calls to `fullVersionOf(...).getIfLoaded()` will return689* a value.690*691* This method should be avoided in favor of directly awaiting692* `fullVersionOf(...).load()` where it is used.693*694* @return {!Promise<void>}695*/696async function loadFullVersions() {697if (useUserAgentDataBrand(true)) {698await fullVersionList.load();699}700preUachHasLoaded = true;701}702exports.loadFullVersions = loadFullVersions;703704/**705* Resets module-local caches used by functionality in this module.706* This is only for use by goog.labs.userAgent.testUtil.resetUserAgent (and707* labs.userAgent tests).708* @package709*/710exports.resetForTesting = () => {711preUachHasLoaded = false;712fullVersionList.resetForTesting();713};714715716/**717* Returns an object that provides access to the full version string of the718* current browser -- or undefined, based on whether the current browser matches719* the requested browser brand. Note that the full version string is a720* high-entropy value, and must be asynchronously loaded before it can be721* accessed synchronously.722* @param {!Brand} browser The brand whose version should be returned.723* @return {!AsyncValue<!Version>|undefined} An object that can be used724* to get or load the full version string as a high-entropy value, or725* undefined if the current browser doesn't match the given brand.726*/727function fullVersionOf(browser) {728let fallbackVersionString = '';729// If we are reasonably certain now that the browser we are on has the730// fullVersionList high-entropy hint, then we can skip computing the fallback731// value as we won't end up using it.732if (!hasFullVersionList()) {733fallbackVersionString = getFullVersionFromUserAgentString(browser);734}735// Silk has the UACH API surface, but currently does not identify itself in736// the userAgentData.brands array. Fallback to using userAgent string version737// for Silk.738const useUach = browser !== Brand.SILK && useUserAgentDataBrand(true);739if (useUach) {740const data = util.getUserAgentData();741// Operate under the assumption that the low-entropy and high-entropy lists742// of brand/version pairs contain an identical set of brands. Therefore, if743// the low-entropy list doesn't contain the given brand, return undefined.744if (!data.brands.find(({brand}) => brand === browser)) {745return undefined;746}747} else if (fallbackVersionString === '') {748return undefined;749}750return new HighEntropyBrandVersion(browser, useUach, fallbackVersionString);751}752exports.fullVersionOf = fullVersionOf;753754755/**756* Returns a version string for the current browser or undefined, based on757* whether the current browser is the one specified.758* This value should ONLY be used for logging/debugging purposes. Do not use it759* to branch code paths. For comparing versions, use isAtLeast()/isAtMost() or760* fullVersionOf() instead.761* @param {!Brand} browser The brand whose version should be returned.762* @return {string} The version as a string.763*/764function getVersionStringForLogging(browser) {765if (useUserAgentDataBrand(true)) {766const fullVersionObj = fullVersionOf(browser);767if (fullVersionObj) {768const fullVersion = fullVersionObj.getIfLoaded();769if (fullVersion) {770return fullVersion.toVersionStringForLogging();771}772// No full version, return the major version instead.773const data = util.getUserAgentData();774const matchingBrand = data.brands.find(({brand}) => brand === browser);775// Checking for the existence of matchingBrand is not necessary because776// the existence of fullVersionObj implies that there is already a777// matching brand.778assertExists(matchingBrand);779return matchingBrand.version;780}781// If fullVersionObj is undefined, this doesn't mean that the full version782// is unavailable, but rather that the current browser doesn't match the783// input `browser` argument.784return '';785} else {786return getFullVersionFromUserAgentString(browser);787}788}789exports.getVersionStringForLogging = getVersionStringForLogging;790791792