Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/labs/useragent/browser.js
4140 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview Closure user agent detection (Browser).
9
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
10
* For more information on rendering engine, platform, or device see the other
11
* sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
12
* goog.labs.userAgent.device respectively.)
13
*/
14
15
goog.module('goog.labs.userAgent.browser');
16
goog.module.declareLegacyNamespace();
17
18
const util = goog.require('goog.labs.userAgent.util');
19
const {AsyncValue, Version} = goog.require('goog.labs.userAgent.highEntropy.highEntropyValue');
20
const {assert, assertExists} = goog.require('goog.asserts');
21
const {compareVersions} = goog.require('goog.string.internal');
22
const {fullVersionList} = goog.require('goog.labs.userAgent.highEntropy.highEntropyData');
23
const {useClientHints} = goog.require('goog.labs.userAgent');
24
25
// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
26
// functions.
27
28
/**
29
* A browser brand represents an opaque string that is used for making
30
* brand-specific version checks in userAgentData.
31
* @enum {string}
32
*/
33
const Brand = {
34
/**
35
* The browser brand for Android Browser.
36
* Do not depend on the value of this string. Because Android Browser has not
37
* implemented userAgentData yet, the value of this string is not guaranteed
38
* to stay the same in future revisions.
39
*/
40
ANDROID_BROWSER: 'Android Browser',
41
/**
42
* The browser brand for Chromium, including Chromium-based Edge and Opera.
43
*/
44
CHROMIUM: 'Chromium',
45
/**
46
* The browser brand for Edge.
47
* This brand can be used to get the version of both EdgeHTML and
48
* Chromium-based Edge.
49
*/
50
EDGE: 'Microsoft Edge',
51
/**
52
* The browser brand for Firefox.
53
* Do not depend on the value of this string. Because Firefox has not
54
* implemented userAgentData yet, the value of this string is not guaranteed
55
* to stay the same in future revisions.
56
*/
57
FIREFOX: 'Firefox',
58
/**
59
* The browser brand for Internet Explorer.
60
* Do not depend on the value of this string. Because IE will never support
61
* userAgentData, the value of this string should be treated as opaque (it's
62
* used internally for legacy-userAgent fallback).
63
*/
64
IE: 'Internet Explorer',
65
/**
66
* The browser brand for Opera.
67
* This brand can be used to get the version of both Presto- and
68
* Chromium-based Opera.
69
*/
70
OPERA: 'Opera',
71
/**
72
* The browser brand for Safari.
73
* Do not depend on the value of this string. Because Safari has not
74
* implemented userAgentData yet, the value of this string is not guaranteed
75
* to stay the same in future revisions.
76
*/
77
SAFARI: 'Safari',
78
/**
79
* The browser brand for Silk.
80
* See
81
* https://docs.aws.amazon.com/silk/latest/developerguide/what-is-silk.html
82
* Do not depend on the value of this string. Because Silk does not
83
* identify itself in userAgentData yet, the value of this string is not
84
* guaranteed to stay the same in future revisions.
85
*/
86
SILK: 'Silk',
87
};
88
exports.Brand = Brand;
89
90
/**
91
* @param {boolean=} ignoreClientHintsFlag Iff truthy, the `useClientHints`
92
* function will not be called when evaluating if User-Agent Client Hints
93
* Brand data can be used. For existing labs.userAgent API surfaces with
94
* widespread use, this should be a falsy value so that usage of the Client
95
* Hints APIs can be gated behind flags / experiment rollouts.
96
* @return {boolean} Whether to use navigator.userAgentData to determine
97
* browser's brand.
98
*/
99
function useUserAgentDataBrand(ignoreClientHintsFlag = false) {
100
if (util.ASSUME_CLIENT_HINTS_SUPPORT) return true;
101
// High-entropy API surfaces should not be gated behind the useClientHints
102
// check (as in production it is gated behind a define).
103
if (!ignoreClientHintsFlag && !useClientHints()) return false;
104
const userAgentData = util.getUserAgentData();
105
return !!userAgentData && userAgentData.brands.length > 0;
106
}
107
108
/**
109
* @return {boolean} Whether this browser is likely to have the fullVersionList
110
* high-entropy Client Hint.
111
*/
112
function hasFullVersionList() {
113
// https://chromiumdash.appspot.com/commits?commit=1eb643c3057e64ff4d22468432ad16c4cab12879&platform=Linux
114
// indicates that for all platforms Chromium 98 shipped this feature.
115
// See also
116
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version-List#browser_compatibility
117
return isAtLeast(Brand.CHROMIUM, 98);
118
}
119
120
/**
121
* @return {boolean} Whether the user's browser is Opera. Note: Chromium based
122
* Opera (Opera 15+) is detected as Chrome to avoid unnecessary special
123
* casing.
124
*/
125
function matchOpera() {
126
if (useUserAgentDataBrand()) {
127
// Pre-Chromium Edge doesn't support navigator.userAgentData.
128
return false;
129
}
130
return util.matchUserAgent('Opera');
131
}
132
133
/** @return {boolean} Whether the user's browser is IE. */
134
function matchIE() {
135
if (useUserAgentDataBrand()) {
136
// IE doesn't support navigator.userAgentData.
137
return false;
138
}
139
return util.matchUserAgent('Trident') || util.matchUserAgent('MSIE');
140
}
141
142
/**
143
* @return {boolean} Whether the user's browser is Edge. This refers to
144
* EdgeHTML based Edge.
145
*/
146
function matchEdgeHtml() {
147
if (useUserAgentDataBrand()) {
148
// Pre-Chromium Edge doesn't support navigator.userAgentData.
149
return false;
150
}
151
return util.matchUserAgent('Edge');
152
}
153
154
/** @return {boolean} Whether the user's browser is Chromium based Edge. */
155
function matchEdgeChromium() {
156
if (useUserAgentDataBrand()) {
157
return util.matchUserAgentDataBrand(Brand.EDGE);
158
}
159
return util.matchUserAgent('Edg/');
160
}
161
162
/** @return {boolean} Whether the user's browser is Chromium based Opera. */
163
function matchOperaChromium() {
164
if (useUserAgentDataBrand()) {
165
return util.matchUserAgentDataBrand(Brand.OPERA);
166
}
167
return util.matchUserAgent('OPR');
168
}
169
170
/** @return {boolean} Whether the user's browser is Firefox. */
171
function matchFirefox() {
172
// Firefox doesn't support navigator.userAgentData yet, so use
173
// navigator.userAgent.
174
return util.matchUserAgent('Firefox') || util.matchUserAgent('FxiOS');
175
}
176
177
/** @return {boolean} Whether the user's browser is Safari. */
178
function matchSafari() {
179
// Apple-based browsers don't support navigator.userAgentData yet, so use
180
// navigator.userAgent.
181
return util.matchUserAgent('Safari') &&
182
!(matchChrome() || matchCoast() || matchOpera() || matchEdgeHtml() ||
183
matchEdgeChromium() || matchOperaChromium() || matchFirefox() ||
184
isSilk() || util.matchUserAgent('Android'));
185
}
186
187
/**
188
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
189
* iOS browser).
190
*/
191
function matchCoast() {
192
if (useUserAgentDataBrand()) {
193
// Coast doesn't support navigator.userAgentData.
194
return false;
195
}
196
return util.matchUserAgent('Coast');
197
}
198
199
/** @return {boolean} Whether the user's browser is iOS Webview. */
200
function matchIosWebview() {
201
// Apple-based browsers don't support navigator.userAgentData yet, so use
202
// navigator.userAgent.
203
// iOS Webview does not show up as Chrome or Safari.
204
return (util.matchUserAgent('iPad') || util.matchUserAgent('iPhone')) &&
205
!matchSafari() && !matchChrome() && !matchCoast() && !matchFirefox() &&
206
util.matchUserAgent('AppleWebKit');
207
}
208
209
/**
210
* @return {boolean} Whether the user's browser is any Chromium browser. This
211
* returns true for Chrome, Opera 15+, and Edge Chromium.
212
*/
213
function matchChrome() {
214
if (useUserAgentDataBrand()) {
215
return util.matchUserAgentDataBrand(Brand.CHROMIUM);
216
}
217
return ((util.matchUserAgent('Chrome') || util.matchUserAgent('CriOS')) &&
218
!matchEdgeHtml()) ||
219
isSilk();
220
}
221
222
/** @return {boolean} Whether the user's browser is the Android browser. */
223
function matchAndroidBrowser() {
224
// Android can appear in the user agent string for Chrome on Android.
225
// This is not the Android standalone browser if it does.
226
return util.matchUserAgent('Android') &&
227
!(isChrome() || isFirefox() || isOpera() || isSilk());
228
}
229
230
/** @return {boolean} Whether the user's browser is Opera. */
231
const isOpera = matchOpera;
232
exports.isOpera = isOpera;
233
234
/** @return {boolean} Whether the user's browser is IE. */
235
const isIE = matchIE;
236
exports.isIE = isIE;
237
238
/** @return {boolean} Whether the user's browser is EdgeHTML based Edge. */
239
const isEdge = matchEdgeHtml;
240
exports.isEdge = isEdge;
241
242
/** @return {boolean} Whether the user's browser is Chromium based Edge. */
243
const isEdgeChromium = matchEdgeChromium;
244
exports.isEdgeChromium = isEdgeChromium;
245
246
/** @return {boolean} Whether the user's browser is Chromium based Opera. */
247
const isOperaChromium = matchOperaChromium;
248
exports.isOperaChromium = isOperaChromium;
249
250
/** @return {boolean} Whether the user's browser is Firefox. */
251
const isFirefox = matchFirefox;
252
exports.isFirefox = isFirefox;
253
254
/** @return {boolean} Whether the user's browser is Safari. */
255
const isSafari = matchSafari;
256
exports.isSafari = isSafari;
257
258
/**
259
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
260
* iOS browser).
261
*/
262
const isCoast = matchCoast;
263
exports.isCoast = isCoast;
264
265
/** @return {boolean} Whether the user's browser is iOS Webview. */
266
const isIosWebview = matchIosWebview;
267
exports.isIosWebview = isIosWebview;
268
269
/**
270
* @return {boolean} Whether the user's browser is any Chromium based browser (
271
* Chrome, Blink-based Opera (15+) and Edge Chromium).
272
*/
273
const isChrome = matchChrome;
274
exports.isChrome = isChrome;
275
276
/** @return {boolean} Whether the user's browser is the Android browser. */
277
const isAndroidBrowser = matchAndroidBrowser;
278
exports.isAndroidBrowser = isAndroidBrowser;
279
280
/**
281
* For more information, see:
282
* http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
283
* @return {boolean} Whether the user's browser is Silk.
284
*/
285
function isSilk() {
286
// As of Silk 93, Silk does not identify itself in userAgentData.brands.
287
// When Silk changes this behavior, update this method to call
288
// matchUserAgentDataBrand (akin to isChrome, etc.)
289
return util.matchUserAgent('Silk');
290
}
291
exports.isSilk = isSilk;
292
293
/**
294
* A helper function that returns a function mapping a list of candidate
295
* version tuple keys to the first version string present under a key.
296
* Ex:
297
* <code>
298
* // Arg extracted from "Foo/1.2.3 Bar/0.2021"
299
* const mapVersion = createVersionMap([["Foo", "1.2.3"], ["Bar", "0.2021"]]);
300
* mapVersion(["Bar", "Foo"]); // returns "0.2021"
301
* mapVersion(["Baz", "Foo"]); // returns "1.2.3"
302
* mapVersion(["Baz", "???"]); // returns ""
303
* </code>
304
* @param {!Array<!Array<string>>} versionTuples Version tuples pre-extracted
305
* from a user agent string.
306
* @return {function(!Array<string>): string} The version string, or empty
307
* string if it doesn't exist under the given key.
308
*/
309
function createVersionMap(versionTuples) {
310
// Construct a map for easy lookup.
311
const versionMap = {};
312
versionTuples.forEach((tuple) => {
313
// Note that the tuple is of length three, but we only care about the
314
// first two.
315
const key = tuple[0];
316
const value = tuple[1];
317
versionMap[key] = value;
318
});
319
320
// Gives the value with the first key it finds, otherwise empty string.
321
return (keys) => versionMap[keys.find((key) => key in versionMap)] || '';
322
}
323
324
/**
325
* Returns the browser version.
326
*
327
* Note that for browsers with multiple brands, this function assumes a primary
328
* brand and returns the version for that brand.
329
*
330
* Additionally, this function is not userAgentData-aware and will return
331
* incorrect values when the User Agent string is frozen. The current status of
332
* User Agent string freezing is available here:
333
* https://www.chromestatus.com/feature/5704553745874944
334
*
335
* To mitigate both of these potential issues, use
336
* getVersionStringForLogging() or fullVersionOf() instead.
337
*
338
* @return {string} The browser version or empty string if version cannot be
339
* determined. Note that for Internet Explorer, this returns the version of
340
* the browser, not the version of the rendering engine. (IE 8 in
341
* compatibility mode will return 8.0 rather than 7.0. To determine the
342
* rendering engine version, look at document.documentMode instead. See
343
* http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
344
* details.)
345
*/
346
function getVersion() {
347
const userAgentString = util.getUserAgent();
348
349
// Special case IE since IE's version is inside the parenthesis and
350
// without the '/'.
351
if (isIE()) {
352
return getIEVersion(userAgentString);
353
}
354
355
const versionTuples = util.extractVersionTuples(userAgentString);
356
const lookUpValueWithKeys = createVersionMap(versionTuples);
357
358
// Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
359
// See
360
// http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
361
if (isOpera()) {
362
// Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
363
// Opera uses 'OPR' for more recent UAs.
364
return lookUpValueWithKeys(['Version', 'Opera']);
365
}
366
367
// Check Edge before Chrome since it has Chrome in the string.
368
if (isEdge()) {
369
return lookUpValueWithKeys(['Edge']);
370
}
371
372
// Check Chromium Edge before Chrome since it has Chrome in the string.
373
if (isEdgeChromium()) {
374
return lookUpValueWithKeys(['Edg']);
375
}
376
377
// Check Silk before Chrome since it may have Chrome in its string and be
378
// treated as Chrome.
379
if (isSilk()) {
380
return lookUpValueWithKeys(['Silk']);
381
}
382
383
if (isChrome()) {
384
return lookUpValueWithKeys(['Chrome', 'CriOS', 'HeadlessChrome']);
385
}
386
387
// Usually products browser versions are in the third tuple after "Mozilla"
388
// and the engine.
389
const tuple = versionTuples[2];
390
return tuple && tuple[1] || '';
391
}
392
exports.getVersion = getVersion;
393
394
/**
395
* Returns whether the current browser's version is at least as high as the
396
* given one.
397
*
398
* Note that for browsers with multiple brands, this function assumes a primary
399
* brand and checks the version for that brand.
400
*
401
* Additionally, this function is not userAgentData-aware and will return
402
* incorrect values when the User Agent string is frozen. The current status of
403
* User Agent string freezing is available here:
404
* https://www.chromestatus.com/feature/5704553745874944
405
*
406
* To mitigate both of these potential issues, use isAtLeast()/isAtMost() or
407
* fullVersionOf() instead.
408
*
409
* @param {string|number} version The version to check.
410
* @return {boolean} Whether the browser version is higher or the same as the
411
* given version.
412
* @deprecated Use isAtLeast()/isAtMost() instead.
413
*/
414
function isVersionOrHigher(version) {
415
return compareVersions(getVersion(), version) >= 0;
416
}
417
exports.isVersionOrHigher = isVersionOrHigher;
418
419
/**
420
* A helper function to determine IE version. More information:
421
* http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
422
* http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
423
* http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
424
* http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
425
* @param {string} userAgent the User-Agent.
426
* @return {string}
427
*/
428
function getIEVersion(userAgent) {
429
// IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
430
// bug. Example UA:
431
// Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
432
// like Gecko.
433
// See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
434
const rv = /rv: *([\d\.]*)/.exec(userAgent);
435
if (rv && rv[1]) {
436
return rv[1];
437
}
438
439
let version = '';
440
const msie = /MSIE +([\d\.]+)/.exec(userAgent);
441
if (msie && msie[1]) {
442
// IE in compatibility mode usually identifies itself as MSIE 7.0; in this
443
// case, use the Trident version to determine the version of IE. For more
444
// details, see the links above.
445
const tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
446
if (msie[1] == '7.0') {
447
if (tridentVersion && tridentVersion[1]) {
448
switch (tridentVersion[1]) {
449
case '4.0':
450
version = '8.0';
451
break;
452
case '5.0':
453
version = '9.0';
454
break;
455
case '6.0':
456
version = '10.0';
457
break;
458
case '7.0':
459
version = '11.0';
460
break;
461
}
462
} else {
463
version = '7.0';
464
}
465
} else {
466
version = msie[1];
467
}
468
}
469
return version;
470
}
471
472
/**
473
* A helper function to return the navigator.userAgent-supplied full version
474
* number of the current browser or an empty string, based on whether the
475
* current browser is the one specified.
476
* @param {string} browser The brand whose version should be returned.
477
* @return {string}
478
*/
479
function getFullVersionFromUserAgentString(browser) {
480
const userAgentString = util.getUserAgent();
481
// Special case IE since IE's version is inside the parenthesis and
482
// without the '/'.
483
if (browser === Brand.IE) {
484
return isIE() ? getIEVersion(userAgentString) : '';
485
}
486
487
const versionTuples = util.extractVersionTuples(userAgentString);
488
const lookUpValueWithKeys = createVersionMap(versionTuples);
489
switch (browser) {
490
case Brand.OPERA:
491
// Opera 10 has Version/10.0 but Opera/9.8, so look for "Version"
492
// first. Opera uses 'OPR' for more recent UAs.
493
if (isOpera()) {
494
return lookUpValueWithKeys(['Version', 'Opera']);
495
} else if (isOperaChromium()) {
496
return lookUpValueWithKeys(['OPR']);
497
}
498
break;
499
case Brand.EDGE:
500
if (isEdge()) {
501
return lookUpValueWithKeys(['Edge']);
502
} else if (isEdgeChromium()) {
503
return lookUpValueWithKeys(['Edg']);
504
}
505
break;
506
case Brand.CHROMIUM:
507
if (isChrome()) {
508
return lookUpValueWithKeys(['Chrome', 'CriOS', 'HeadlessChrome']);
509
}
510
break;
511
}
512
513
// For the following browsers, the browser version is in the third tuple after
514
// "Mozilla" and the engine.
515
if ((browser === Brand.FIREFOX && isFirefox()) ||
516
(browser === Brand.SAFARI && isSafari()) ||
517
(browser === Brand.ANDROID_BROWSER && isAndroidBrowser()) ||
518
(browser === Brand.SILK && isSilk())) {
519
const tuple = versionTuples[2];
520
return tuple && tuple[1] || '';
521
}
522
523
return '';
524
}
525
526
/**
527
* Returns the major version of the given browser brand, or NaN if the current
528
* browser is not the given brand.
529
* Note that the major version number may be different depending on which
530
* browser is specified. The returned value can be used to make browser version
531
* comparisons using comparison operators.
532
* @private
533
* @param {!Brand} browser The brand whose version should be returned.
534
* @return {number} The major version number associated with the current
535
* browser under the given brand, or NaN if the current browser doesn't match
536
* the given brand.
537
*/
538
function versionOf_(browser) {
539
let versionParts;
540
// Silk currently does not identify itself in its userAgentData.brands array,
541
// so if checking its version, always fall back to the user agent string.
542
if (useUserAgentDataBrand() && browser !== Brand.SILK) {
543
const data = util.getUserAgentData();
544
const matchingBrand = data.brands.find(({brand}) => brand === browser);
545
if (!matchingBrand || !matchingBrand.version) {
546
return NaN;
547
}
548
versionParts = matchingBrand.version.split('.');
549
} else {
550
const fullVersion = getFullVersionFromUserAgentString(browser);
551
if (fullVersion === '') {
552
return NaN;
553
}
554
versionParts = fullVersion.split('.');
555
}
556
if (versionParts.length === 0) {
557
return NaN;
558
}
559
const majorVersion = versionParts[0];
560
return Number(majorVersion); // Returns NaN if it is not parseable.
561
}
562
563
/**
564
* Returns true if the current browser matches the given brand and is at least
565
* the given major version. The major version must be a whole number (i.e.
566
* decimals should not be used to represent a minor version).
567
* @param {!Brand} brand The brand whose version should be returned.
568
* @param {number} majorVersion The major version number to compare against.
569
* This must be a whole number.
570
* @return {boolean} Whether the current browser both matches the given brand
571
* and is at least the given version.
572
*/
573
function isAtLeast(brand, majorVersion) {
574
assert(
575
Math.floor(majorVersion) === majorVersion,
576
'Major version must be an integer');
577
return versionOf_(brand) >= majorVersion;
578
}
579
exports.isAtLeast = isAtLeast;
580
581
/**
582
* Returns true if the current browser matches the given brand and is at most
583
* the given version. The major version must be a whole number (i.e. decimals
584
* should not be used to represent a minor version).
585
* @param {!Brand} brand The brand whose version should be returned.
586
* @param {number} majorVersion The major version number to compare against.
587
* This must be a whole number.
588
* @return {boolean} Whether the current browser both matches the given brand
589
* and is at most the given version.
590
*/
591
function isAtMost(brand, majorVersion) {
592
assert(
593
Math.floor(majorVersion) === majorVersion,
594
'Major version must be an integer');
595
return versionOf_(brand) <= majorVersion;
596
}
597
exports.isAtMost = isAtMost;
598
599
/**
600
* Loads the high-entropy browser brand/version data and wraps the correct
601
* version string in a Version object.
602
* @implements {AsyncValue<!Version>}
603
*/
604
class HighEntropyBrandVersion {
605
/**
606
* @param {string} brand The brand whose version is retrieved in this
607
* container.
608
* @param {boolean} useUach Whether to attempt to use the User-Agent Client
609
* Hints (UACH) API surface.
610
* @param {string} fallbackVersion The fallback version derived from the
611
* userAgent string.
612
*/
613
constructor(brand, useUach, fallbackVersion) {
614
/** @private @const {string} */
615
this.brand_ = brand;
616
617
/** @private @const {!Version} */
618
this.version_ = new Version(fallbackVersion);
619
620
/** @private @const {boolean} */
621
this.useUach_ = useUach;
622
}
623
624
/**
625
* @return {!Version|undefined}
626
* @override
627
*/
628
getIfLoaded() {
629
if (this.useUach_) {
630
const loadedVersionList = fullVersionList.getIfLoaded();
631
if (loadedVersionList !== undefined) {
632
const matchingBrand =
633
loadedVersionList.find(({brand}) => this.brand_ === brand);
634
// We assumed in fullVersionOf that if the fullVersionList is defined
635
// the brands must match. Double-check this here.
636
assertExists(matchingBrand);
637
return new Version(matchingBrand.version);
638
}
639
// Fallthrough to fallback on Pre-UACH implementation
640
}
641
// We want to make sure the loading semantics of the Pre-UACH implementation
642
// match those of the UACH implementation. Loading must happen before any
643
// data can be retrieved from getIfLoaded.
644
// For HighEntropyBrandVersion, loading can either be done by calling #load
645
// or by calling the module-local loadFullVersions function.
646
if (preUachHasLoaded) {
647
return this.version_;
648
}
649
return;
650
}
651
652
/**
653
* @return {!Promise<!Version>}
654
* @override
655
*/
656
async load() {
657
if (this.useUach_) {
658
const loadedVersionList = await fullVersionList.load();
659
if (loadedVersionList !== undefined) {
660
const matchingBrand =
661
loadedVersionList.find(({brand}) => this.brand_ === brand);
662
assertExists(matchingBrand);
663
return new Version(matchingBrand.version);
664
}
665
// Fallthrough to fallback on Pre-UACH implementation
666
} else {
667
// Await something so that calling load with or without UACH API
668
// availability results in waiting at least one macrotask before allowing
669
// access to the cached version information.
670
await 0;
671
}
672
// Regardless of whether we are using UACH APIs, we can now allow access to
673
// the fallback case
674
preUachHasLoaded = true;
675
return this.version_;
676
}
677
}
678
679
/**
680
* Whether full version data should be considered available when using UACH
681
* fallback implementations. This is flipped to true when either
682
* loadFullVersions or HighEntropyBrandVersion.prototype.load are called,
683
* matching the global singleton semantics of the UACH codepaths.
684
*/
685
let preUachHasLoaded = false;
686
687
/**
688
* Requests all full browser versions to be cached. When the returned promise
689
* resolves, subsequent calls to `fullVersionOf(...).getIfLoaded()` will return
690
* a value.
691
*
692
* This method should be avoided in favor of directly awaiting
693
* `fullVersionOf(...).load()` where it is used.
694
*
695
* @return {!Promise<void>}
696
*/
697
async function loadFullVersions() {
698
if (useUserAgentDataBrand(true)) {
699
await fullVersionList.load();
700
}
701
preUachHasLoaded = true;
702
}
703
exports.loadFullVersions = loadFullVersions;
704
705
/**
706
* Resets module-local caches used by functionality in this module.
707
* This is only for use by goog.labs.userAgent.testUtil.resetUserAgent (and
708
* labs.userAgent tests).
709
* @package
710
*/
711
exports.resetForTesting = () => {
712
preUachHasLoaded = false;
713
fullVersionList.resetForTesting();
714
};
715
716
717
/**
718
* Returns an object that provides access to the full version string of the
719
* current browser -- or undefined, based on whether the current browser matches
720
* the requested browser brand. Note that the full version string is a
721
* high-entropy value, and must be asynchronously loaded before it can be
722
* accessed synchronously.
723
* @param {!Brand} browser The brand whose version should be returned.
724
* @return {!AsyncValue<!Version>|undefined} An object that can be used
725
* to get or load the full version string as a high-entropy value, or
726
* undefined if the current browser doesn't match the given brand.
727
*/
728
function fullVersionOf(browser) {
729
let fallbackVersionString = '';
730
// If we are reasonably certain now that the browser we are on has the
731
// fullVersionList high-entropy hint, then we can skip computing the fallback
732
// value as we won't end up using it.
733
if (!hasFullVersionList()) {
734
fallbackVersionString = getFullVersionFromUserAgentString(browser);
735
}
736
// Silk has the UACH API surface, but currently does not identify itself in
737
// the userAgentData.brands array. Fallback to using userAgent string version
738
// for Silk.
739
const useUach = browser !== Brand.SILK && useUserAgentDataBrand(true);
740
if (useUach) {
741
const data = util.getUserAgentData();
742
// Operate under the assumption that the low-entropy and high-entropy lists
743
// of brand/version pairs contain an identical set of brands. Therefore, if
744
// the low-entropy list doesn't contain the given brand, return undefined.
745
if (!data.brands.find(({brand}) => brand === browser)) {
746
return undefined;
747
}
748
} else if (fallbackVersionString === '') {
749
return undefined;
750
}
751
return new HighEntropyBrandVersion(browser, useUach, fallbackVersionString);
752
}
753
exports.fullVersionOf = fullVersionOf;
754
755
756
/**
757
* Returns a version string for the current browser or undefined, based on
758
* whether the current browser is the one specified.
759
* This value should ONLY be used for logging/debugging purposes. Do not use it
760
* to branch code paths. For comparing versions, use isAtLeast()/isAtMost() or
761
* fullVersionOf() instead.
762
* @param {!Brand} browser The brand whose version should be returned.
763
* @return {string} The version as a string.
764
*/
765
function getVersionStringForLogging(browser) {
766
if (useUserAgentDataBrand(true)) {
767
const fullVersionObj = fullVersionOf(browser);
768
if (fullVersionObj) {
769
const fullVersion = fullVersionObj.getIfLoaded();
770
if (fullVersion) {
771
return fullVersion.toVersionStringForLogging();
772
}
773
// No full version, return the major version instead.
774
const data = util.getUserAgentData();
775
const matchingBrand = data.brands.find(({brand}) => brand === browser);
776
// Checking for the existence of matchingBrand is not necessary because
777
// the existence of fullVersionObj implies that there is already a
778
// matching brand.
779
assertExists(matchingBrand);
780
return matchingBrand.version;
781
}
782
// If fullVersionObj is undefined, this doesn't mean that the full version
783
// is unavailable, but rather that the current browser doesn't match the
784
// input `browser` argument.
785
return '';
786
} else {
787
return getFullVersionFromUserAgentString(browser);
788
}
789
}
790
exports.getVersionStringForLogging = getVersionStringForLogging;
791
792