Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/labs/useragent/platform.js
4147 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 platform detection.
9
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
10
* For more information on browser brand, rendering engine, or device see the
11
* other sub-namespaces in goog.labs.userAgent (browser, engine, and device
12
* respectively).
13
*/
14
15
goog.module('goog.labs.userAgent.platform');
16
goog.module.declareLegacyNamespace();
17
18
const googString = goog.require('goog.string.internal');
19
const util = goog.require('goog.labs.userAgent.util');
20
const {AsyncValue, Version} = goog.require('goog.labs.userAgent.highEntropy.highEntropyValue');
21
const {platformVersion} = goog.require('goog.labs.userAgent.highEntropy.highEntropyData');
22
const {useClientHints} = goog.require('goog.labs.userAgent');
23
24
/**
25
* @param {boolean=} ignoreClientHintsFlag Iff truthy, the `useClientHints`
26
* function will not be called when evaluating if User-Agent Client Hints
27
* Brand data can be used. For existing labs.userAgent API surfaces with
28
* widespread use, this should be a falsy value so that usage of the Client
29
* Hints APIs can be gated behind flags / experiment rollouts.
30
* @return {boolean} Whether to use navigator.userAgentData to determine
31
* the current platform.
32
* userAgentData.platform was enabled by default in Chrome 93:
33
* https://www.chromestatus.com/feature/5733498725859328
34
* TODO(user): Skip this check with FEATURESET_YEAR once userAgentData is
35
* present in all major browsers (may not be until 2024).
36
* See https://caniuse.com/mdn-api_navigator_useragentdata.
37
*/
38
function useUserAgentDataPlatform(ignoreClientHintsFlag = false) {
39
if (util.ASSUME_CLIENT_HINTS_SUPPORT) return true;
40
// High-entropy API surfaces should not be gated behind the useClientHints
41
// check (as in production it is gated behind a define).
42
if (!ignoreClientHintsFlag && !useClientHints()) return false;
43
const userAgentData = util.getUserAgentData();
44
return !!userAgentData && !!userAgentData.platform;
45
}
46
47
/**
48
* @return {boolean} Whether the platform is Android.
49
*/
50
function isAndroid() {
51
if (useUserAgentDataPlatform()) {
52
return util.getUserAgentData().platform === 'Android';
53
}
54
return util.matchUserAgent('Android');
55
}
56
57
/**
58
* @return {boolean} Whether the platform is iPod.
59
* TODO(user): Combine iPod/iPhone detection since they may become
60
* indistinguishable if we begin relying on userAgentdata in iOS.
61
*/
62
function isIpod() {
63
// navigator.userAgentData is currently not supported on any iOS browser, so
64
// rely only on navigator.userAgent.
65
return util.matchUserAgent('iPod');
66
}
67
68
/**
69
* @return {boolean} Whether the platform is iPhone.
70
*/
71
function isIphone() {
72
// navigator.userAgentData is currently not supported on any iOS browser, so
73
// rely only on navigator.userAgent.
74
return util.matchUserAgent('iPhone') && !util.matchUserAgent('iPod') &&
75
!util.matchUserAgent('iPad');
76
}
77
78
/**
79
* Returns whether the platform is iPad.
80
* Note that iPadOS 13+ spoofs macOS Safari by default in its user agent, and in
81
* this scenario the platform will not be recognized as iPad. If you must have
82
* iPad-specific behavior, use
83
* {@link goog.labs.userAgent.extra.isSafariDesktopOnMobile}.
84
* @return {boolean} Whether the platform is iPad.
85
*/
86
function isIpad() {
87
// navigator.userAgentData is currently not supported on any iOS browser, so
88
// rely only on navigator.userAgent.
89
return util.matchUserAgent('iPad');
90
}
91
92
/**
93
* Returns whether the platform is iOS.
94
* Note that iPadOS 13+ spoofs macOS Safari by default in its user agent, and in
95
* this scenario the platform will not be recognized as iOS. If you must have
96
* iPad-specific behavior, use
97
* {@link goog.labs.userAgent.extra.isSafariDesktopOnMobile}.
98
* @return {boolean} Whether the platform is iOS.
99
*/
100
function isIos() {
101
return isIphone() || isIpad() || isIpod();
102
}
103
104
/**
105
* @return {boolean} Whether the platform is Mac.
106
*/
107
function isMacintosh() {
108
if (useUserAgentDataPlatform()) {
109
return util.getUserAgentData().platform === 'macOS';
110
}
111
return util.matchUserAgent('Macintosh');
112
}
113
114
/**
115
* Note: ChromeOS is not considered to be Linux as it does not report itself
116
* as Linux in the user agent string.
117
* @return {boolean} Whether the platform is Linux.
118
*/
119
function isLinux() {
120
if (useUserAgentDataPlatform()) {
121
return util.getUserAgentData().platform === 'Linux';
122
}
123
return util.matchUserAgent('Linux');
124
}
125
126
/**
127
* @return {boolean} Whether the platform is Windows.
128
*/
129
function isWindows() {
130
if (useUserAgentDataPlatform()) {
131
return util.getUserAgentData().platform === 'Windows';
132
}
133
return util.matchUserAgent('Windows');
134
}
135
136
/**
137
* @return {boolean} Whether the platform is ChromeOS.
138
*/
139
function isChromeOS() {
140
if (useUserAgentDataPlatform()) {
141
return util.getUserAgentData().platform === 'Chrome OS';
142
}
143
return util.matchUserAgent('CrOS');
144
}
145
146
/**
147
* @return {boolean} Whether the platform is Chromecast.
148
*/
149
function isChromecast() {
150
// TODO(user): Check against util.getUserAgentData().platform once the
151
// OS string for Chromecast is known.
152
return util.matchUserAgent('CrKey');
153
}
154
155
/**
156
* @return {boolean} Whether the platform is KaiOS.
157
*/
158
function isKaiOS() {
159
// navigator.userAgentData is currently not supported on any KaiOS browser, so
160
// rely only on navigator.userAgent.
161
return util.matchUserAgentIgnoreCase('KaiOS');
162
}
163
164
/**
165
* The version of the platform. We only determine the version for Windows,
166
* Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
167
* look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
168
* version 0.0.
169
*
170
* @return {string} The platform version or empty string if version cannot be
171
* determined.
172
*/
173
function getVersion() {
174
const userAgentString = util.getUserAgent();
175
let version = '', re;
176
if (isWindows()) {
177
re = /Windows (?:NT|Phone) ([0-9.]+)/;
178
const match = re.exec(userAgentString);
179
if (match) {
180
version = match[1];
181
} else {
182
version = '0.0';
183
}
184
} else if (isIos()) {
185
re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
186
const match = re.exec(userAgentString);
187
// Report the version as x.y.z and not x_y_z
188
version = match && match[1].replace(/_/g, '.');
189
} else if (isMacintosh()) {
190
re = /Mac OS X ([0-9_.]+)/;
191
const match = re.exec(userAgentString);
192
// Note: some old versions of Camino do not report an OSX version.
193
// Default to 10.
194
version = match ? match[1].replace(/_/g, '.') : '10';
195
} else if (isKaiOS()) {
196
re = /(?:KaiOS)\/(\S+)/i;
197
const match = re.exec(userAgentString);
198
version = match && match[1];
199
} else if (isAndroid()) {
200
re = /Android\s+([^\);]+)(\)|;)/;
201
const match = re.exec(userAgentString);
202
version = match && match[1];
203
} else if (isChromeOS()) {
204
re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
205
const match = re.exec(userAgentString);
206
version = match && match[1];
207
}
208
return version || '';
209
}
210
211
/**
212
* @param {string|number} version The version to check.
213
* @return {boolean} Whether the browser version is higher or the same as the
214
* given version.
215
*/
216
function isVersionOrHigher(version) {
217
return googString.compareVersions(getVersion(), version) >= 0;
218
}
219
220
/**
221
* Represents a high-entropy version string.
222
* @implements {AsyncValue<!Version>}
223
*/
224
class PlatformVersion {
225
constructor() {
226
/** @private {boolean} */
227
this.preUachHasLoaded_ = false;
228
}
229
230
/**
231
* @return {!Version|undefined}
232
* @override
233
*/
234
getIfLoaded() {
235
if (useUserAgentDataPlatform(true)) {
236
const loadedPlatformVersion = platformVersion.getIfLoaded();
237
if (loadedPlatformVersion === undefined) {
238
// No platform data has been cached
239
return undefined;
240
}
241
return new Version(loadedPlatformVersion);
242
} else if (!this.preUachHasLoaded_) {
243
// Nobody ever called `load` on this class instance, so we should return
244
// nothing to match the semantics of the class when using the Client Hint
245
// APIs.
246
return undefined;
247
} else {
248
// `load` has been called, so we can return a Version derived from the
249
// useragent string.
250
return new Version(getVersion());
251
}
252
}
253
254
/**
255
* @return {!Promise<!Version>}
256
* @override
257
*/
258
async load() {
259
if (useUserAgentDataPlatform(true)) {
260
return new Version(await platformVersion.load());
261
} else {
262
this.preUachHasLoaded_ = true;
263
return new Version(getVersion());
264
}
265
}
266
267
/** @package */
268
resetForTesting() {
269
platformVersion.resetForTesting();
270
this.preUachHasLoaded_ = false;
271
}
272
}
273
274
/**
275
* The platform version, a high-entropy value.
276
* @type {!PlatformVersion}
277
*/
278
const version = new PlatformVersion();
279
280
exports = {
281
getVersion,
282
isAndroid,
283
isChromeOS,
284
isChromecast,
285
isIos,
286
isIpad,
287
isIphone,
288
isIpod,
289
isKaiOS,
290
isLinux,
291
isMacintosh,
292
isVersionOrHigher,
293
isWindows,
294
version,
295
};
296
297