# lib/goog/useragent/useragent.js

1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS-IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /**
16 | * @fileoverview Rendering engine detection.
17 | * @see <a href="http://www.useragentstring.com/">User agent strings</a>
18 | * For information on the browser brand (such as Safari versus Chrome), see
19 | * goog.userAgent.product.
20 | * @author arv@google.com (Erik Arvidsson)
21 | * @see ../demos/useragent.html
22 | */
23 |
24 | goog.provide('goog.userAgent');
25 |
26 | goog.require('goog.labs.userAgent.browser');
27 | goog.require('goog.labs.userAgent.engine');
28 | goog.require('goog.labs.userAgent.platform');
29 | goog.require('goog.labs.userAgent.util');
30 | goog.require('goog.string');
31 |
32 |
33 | /**
34 | * @define {boolean} Whether we know at compile-time that the browser is IE.
35 | */
36 | goog.define('goog.userAgent.ASSUME_IE', false);
37 |
38 |
39 | /**
40 | * @define {boolean} Whether we know at compile-time that the browser is EDGE.
41 | */
42 | goog.define('goog.userAgent.ASSUME_EDGE', false);
43 |
44 |
45 | /**
46 | * @define {boolean} Whether we know at compile-time that the browser is GECKO.
47 | */
48 | goog.define('goog.userAgent.ASSUME_GECKO', false);
49 |
50 |
51 | /**
52 | * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
53 | */
54 | goog.define('goog.userAgent.ASSUME_WEBKIT', false);
55 |
56 |
57 | /**
58 | * @define {boolean} Whether we know at compile-time that the browser is a
59 | * mobile device running WebKit e.g. iPhone or Android.
60 | */
61 | goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
62 |
63 |
64 | /**
65 | * @define {boolean} Whether we know at compile-time that the browser is OPERA.
66 | */
67 | goog.define('goog.userAgent.ASSUME_OPERA', false);
68 |
69 |
70 | /**
71 | * @define {boolean} Whether the
72 | * {@code goog.userAgent.isVersionOrHigher}
73 | * function will return true for any version.
74 | */
75 | goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
76 |
77 |
78 | /**
79 | * Whether we know the browser engine at compile-time.
80 | * @type {boolean}
81 | * @private
82 | */
83 | goog.userAgent.BROWSER_KNOWN_ =
84 | goog.userAgent.ASSUME_IE ||
85 | goog.userAgent.ASSUME_EDGE ||
86 | goog.userAgent.ASSUME_GECKO ||
87 | goog.userAgent.ASSUME_MOBILE_WEBKIT ||
88 | goog.userAgent.ASSUME_WEBKIT ||
89 | goog.userAgent.ASSUME_OPERA;
90 |
91 |
92 | /**
93 | * Returns the userAgent string for the current browser.
94 | *
95 | * @return {string} The userAgent string.
96 | */
97 | goog.userAgent.getUserAgentString = function() {
98 | return goog.labs.userAgent.util.getUserAgent();
99 | };
100 |
101 |
102 | /**
103 | * TODO(nnaze): Change type to "Navigator" and update compilation targets.
104 | * @return {Object} The native navigator object.
105 | */
106 | goog.userAgent.getNavigator = function() {
107 | // Need a local navigator reference instead of using the global one,
108 | // to avoid the rare case where they reference different objects.
109 | // (in a WorkerPool, for example).
110 | return goog.global['navigator'] || null;
111 | };
112 |
113 |
114 | /**
115 | * Whether the user agent is Opera.
116 | * @type {boolean}
117 | */
118 | goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
119 | goog.userAgent.ASSUME_OPERA :
120 | goog.labs.userAgent.browser.isOpera();
121 |
122 |
123 | /**
124 | * Whether the user agent is Internet Explorer.
125 | * @type {boolean}
126 | */
127 | goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
128 | goog.userAgent.ASSUME_IE :
129 | goog.labs.userAgent.browser.isIE();
130 |
131 |
132 | /**
133 | * Whether the user agent is Microsoft Edge.
134 | * @type {boolean}
135 | */
136 | goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ?
137 | goog.userAgent.ASSUME_EDGE :
138 | goog.labs.userAgent.engine.isEdge();
139 |
140 |
141 | /**
142 | * Whether the user agent is MS Internet Explorer or MS Edge.
143 | * @type {boolean}
144 | */
145 | goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;
146 |
147 |
148 | /**
149 | * Whether the user agent is Gecko. Gecko is the rendering engine used by
150 | * Mozilla, Firefox, and others.
151 | * @type {boolean}
152 | */
153 | goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
154 | goog.userAgent.ASSUME_GECKO :
155 | goog.labs.userAgent.engine.isGecko();
156 |
157 |
158 | /**
159 | * Whether the user agent is WebKit. WebKit is the rendering engine that
160 | * Safari, Android and others use.
161 | * @type {boolean}
162 | */
163 | goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
164 | goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
165 | goog.labs.userAgent.engine.isWebKit();
166 |
167 |
168 | /**
169 | * Whether the user agent is running on a mobile device.
170 | *
171 | * This is a separate function so that the logic can be tested.
172 | *
173 | * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
174 | *
175 | * @return {boolean} Whether the user agent is running on a mobile device.
176 | * @private
177 | */
178 | goog.userAgent.isMobile_ = function() {
179 | return goog.userAgent.WEBKIT &&
180 | goog.labs.userAgent.util.matchUserAgent('Mobile');
181 | };
182 |
183 |
184 | /**
185 | * Whether the user agent is running on a mobile device.
186 | *
187 | * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
188 | * is promoted as the gecko/webkit logic is likely inaccurate.
189 | *
190 | * @type {boolean}
191 | */
192 | goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
193 | goog.userAgent.isMobile_();
194 |
195 |
196 | /**
197 | * Used while transitioning code to use WEBKIT instead.
198 | * @type {boolean}
199 | * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
200 | * TODO(nicksantos): Delete this from goog.userAgent.
201 | */
202 | goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
203 |
204 |
205 | /**
206 | * @return {string} the platform (operating system) the user agent is running
207 | * on. Default to empty string because navigator.platform may not be defined
208 | * (on Rhino, for example).
209 | * @private
210 | */
211 | goog.userAgent.determinePlatform_ = function() {
212 | var navigator = goog.userAgent.getNavigator();
213 | return navigator && navigator.platform || '';
214 | };
215 |
216 |
217 | /**
218 | * The platform (operating system) the user agent is running on. Default to
219 | * empty string because navigator.platform may not be defined (on Rhino, for
220 | * example).
221 | * @type {string}
222 | */
223 | goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
224 |
225 |
226 | /**
227 | * @define {boolean} Whether the user agent is running on a Macintosh operating
228 | * system.
229 | */
230 | goog.define('goog.userAgent.ASSUME_MAC', false);
231 |
232 |
233 | /**
234 | * @define {boolean} Whether the user agent is running on a Windows operating
235 | * system.
236 | */
237 | goog.define('goog.userAgent.ASSUME_WINDOWS', false);
238 |
239 |
240 | /**
241 | * @define {boolean} Whether the user agent is running on a Linux operating
242 | * system.
243 | */
244 | goog.define('goog.userAgent.ASSUME_LINUX', false);
245 |
246 |
247 | /**
248 | * @define {boolean} Whether the user agent is running on a X11 windowing
249 | * system.
250 | */
251 | goog.define('goog.userAgent.ASSUME_X11', false);
252 |
253 |
254 | /**
255 | * @define {boolean} Whether the user agent is running on Android.
256 | */
257 | goog.define('goog.userAgent.ASSUME_ANDROID', false);
258 |
259 |
260 | /**
261 | * @define {boolean} Whether the user agent is running on an iPhone.
262 | */
263 | goog.define('goog.userAgent.ASSUME_IPHONE', false);
264 |
265 |
266 | /**
267 | * @define {boolean} Whether the user agent is running on an iPad.
268 | */
269 | goog.define('goog.userAgent.ASSUME_IPAD', false);
270 |
271 |
272 | /**
273 | * @type {boolean}
274 | * @private
275 | */
276 | goog.userAgent.PLATFORM_KNOWN_ =
277 | goog.userAgent.ASSUME_MAC ||
278 | goog.userAgent.ASSUME_WINDOWS ||
279 | goog.userAgent.ASSUME_LINUX ||
280 | goog.userAgent.ASSUME_X11 ||
281 | goog.userAgent.ASSUME_ANDROID ||
282 | goog.userAgent.ASSUME_IPHONE ||
283 | goog.userAgent.ASSUME_IPAD;
284 |
285 |
286 | /**
287 | * Whether the user agent is running on a Macintosh operating system.
288 | * @type {boolean}
289 | */
290 | goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
291 | goog.userAgent.ASSUME_MAC : goog.labs.userAgent.platform.isMacintosh();
292 |
293 |
294 | /**
295 | * Whether the user agent is running on a Windows operating system.
296 | * @type {boolean}
297 | */
298 | goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
299 | goog.userAgent.ASSUME_WINDOWS :
300 | goog.labs.userAgent.platform.isWindows();
301 |
302 |
303 | /**
304 | * Whether the user agent is Linux per the legacy behavior of
305 | * goog.userAgent.LINUX, which considered ChromeOS to also be
306 | * Linux.
307 | * @return {boolean}
308 | * @private
309 | */
310 | goog.userAgent.isLegacyLinux_ = function() {
311 | return goog.labs.userAgent.platform.isLinux() ||
312 | goog.labs.userAgent.platform.isChromeOS();
313 | };
314 |
315 |
316 | /**
317 | * Whether the user agent is running on a Linux operating system.
318 | *
319 | * Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
320 | * while goog.labs.userAgent.platform considers ChromeOS and
321 | * Linux to be different OSes.
322 | *
323 | * @type {boolean}
324 | */
325 | goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
326 | goog.userAgent.ASSUME_LINUX :
327 | goog.userAgent.isLegacyLinux_();
328 |
329 |
330 | /**
331 | * @return {boolean} Whether the user agent is an X11 windowing system.
332 | * @private
333 | */
334 | goog.userAgent.isX11_ = function() {
335 | var navigator = goog.userAgent.getNavigator();
336 | return !!navigator &&
337 | goog.string.contains(navigator['appVersion'] || '', 'X11');
338 | };
339 |
340 |
341 | /**
342 | * Whether the user agent is running on a X11 windowing system.
343 | * @type {boolean}
344 | */
345 | goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
346 | goog.userAgent.ASSUME_X11 :
347 | goog.userAgent.isX11_();
348 |
349 |
350 | /**
351 | * Whether the user agent is running on Android.
352 | * @type {boolean}
353 | */
354 | goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
355 | goog.userAgent.ASSUME_ANDROID :
356 | goog.labs.userAgent.platform.isAndroid();
357 |
358 |
359 | /**
360 | * Whether the user agent is running on an iPhone.
361 | * @type {boolean}
362 | */
363 | goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
364 | goog.userAgent.ASSUME_IPHONE :
365 | goog.labs.userAgent.platform.isIphone();
366 |
367 |
368 | /**
369 | * Whether the user agent is running on an iPad.
370 | * @type {boolean}
371 | */
372 | goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
373 | goog.userAgent.ASSUME_IPAD :
374 | goog.labs.userAgent.platform.isIpad();
375 |
376 |
377 | /**
378 | * @return {string} The string that describes the version number of the user
379 | * agent.
380 | * Assumes user agent is opera.
381 | * @private
382 | */
383 | goog.userAgent.operaVersion_ = function() {
384 | var version = goog.global.opera.version;
385 | try {
386 | return version();
387 | } catch (e) {
388 | return version;
389 | }
390 | };
391 |
392 |
393 | /**
394 | * @return {string} The string that describes the version number of the user
395 | * agent.
396 | * @private
397 | */
398 | goog.userAgent.determineVersion_ = function() {
399 | // All browsers have different ways to detect the version and they all have
400 | // different naming schemes.
401 |
402 | if (goog.userAgent.OPERA && goog.global['opera']) {
403 | return goog.userAgent.operaVersion_();
404 | }
405 |
406 | // version is a string rather than a number because it may contain 'b', 'a',
407 | // and so on.
408 | var version = '';
409 | var arr = goog.userAgent.getVersionRegexResult_();
410 | if (arr) {
411 | version = arr ? arr[1] : '';
412 | }
413 |
414 | if (goog.userAgent.IE) {
415 | // IE9 can be in document mode 9 but be reporting an inconsistent user agent
416 | // version. If it is identifying as a version lower than 9 we take the
417 | // documentMode as the version instead. IE8 has similar behavior.
418 | // It is recommended to set the X-UA-Compatible header to ensure that IE9
419 | // uses documentMode 9.
420 | var docMode = goog.userAgent.getDocumentMode_();
421 | if (docMode > parseFloat(version)) {
422 | return String(docMode);
423 | }
424 | }
425 |
426 | return version;
427 | };
428 |
429 |
430 | /**
431 | * @return {Array|undefined} The version regex matches from parsing the user
432 | * agent string. These regex statements must be executed inline so they can
433 | * be compiled out by the closure compiler with the rest of the useragent
434 | * detection logic when ASSUME_* is specified.
435 | * @private
436 | */
437 | goog.userAgent.getVersionRegexResult_ = function() {
438 | var userAgent = goog.userAgent.getUserAgentString();
439 | if (goog.userAgent.GECKO) {
440 | return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
441 | }
442 | if (goog.userAgent.EDGE) {
443 | return /Edge\/([\d\.]+)/.exec(userAgent);
444 | }
445 | if (goog.userAgent.IE) {
446 | return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
447 | }
448 | if (goog.userAgent.WEBKIT) {
449 | // WebKit/125.4
450 | return /WebKit\/(\S+)/.exec(userAgent);
451 | }
452 | };
453 |
454 |
455 | /**
456 | * @return {number|undefined} Returns the document mode (for testing).
457 | * @private
458 | */
459 | goog.userAgent.getDocumentMode_ = function() {
460 | // NOTE(user): goog.userAgent may be used in context where there is no DOM.
461 | var doc = goog.global['document'];
462 | return doc ? doc['documentMode'] : undefined;
463 | };
464 |
465 |
466 | /**
467 | * The version of the user agent. This is a string because it might contain
468 | * 'b' (as in beta) as well as multiple dots.
469 | * @type {string}
470 | */
471 | goog.userAgent.VERSION = goog.userAgent.determineVersion_();
472 |
473 |
474 | /**
475 | * Compares two version numbers.
476 | *
477 | * @param {string} v1 Version of first item.
478 | * @param {string} v2 Version of second item.
479 | *
480 | * @return {number} 1 if first argument is higher
481 | * 0 if arguments are equal
482 | * -1 if second argument is higher.
483 | * @deprecated Use goog.string.compareVersions.
484 | */
485 | goog.userAgent.compare = function(v1, v2) {
486 | return goog.string.compareVersions(v1, v2);
487 | };
488 |
489 |
490 | /**
491 | * Cache for {@link goog.userAgent.isVersionOrHigher}.
492 | * Calls to compareVersions are surprisingly expensive and, as a browser's
493 | * version number is unlikely to change during a session, we cache the results.
494 | * @const
495 | * @private
496 | */
497 | goog.userAgent.isVersionOrHigherCache_ = {};
498 |
499 |
500 | /**
501 | * Whether the user agent version is higher or the same as the given version.
502 | * NOTE: When checking the version numbers for Firefox and Safari, be sure to
503 | * use the engine's version, not the browser's version number. For example,
504 | * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
505 | * Opera and Internet Explorer versions match the product release number.<br>
506 | * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
507 | * Webkit</a>
508 | * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
509 | *
510 | * @param {string|number} version The version to check.
511 | * @return {boolean} Whether the user agent version is higher or the same as
512 | * the given version.
513 | */
514 | goog.userAgent.isVersionOrHigher = function(version) {
515 | return goog.userAgent.ASSUME_ANY_VERSION ||
516 | goog.userAgent.isVersionOrHigherCache_[version] ||
517 | (goog.userAgent.isVersionOrHigherCache_[version] =
518 | goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
519 | };
520 |
521 |
522 | /**
523 | * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
524 | * @param {string|number} version The version to check.
525 | * @return {boolean} Whether the user agent version is higher or the same as
526 | * the given version.
527 | * @deprecated Use goog.userAgent.isVersionOrHigher().
528 | */
529 | goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
530 |
531 |
532 | /**
533 | * Whether the IE effective document mode is higher or the same as the given
534 | * document mode version.
535 | * NOTE: Only for IE, return false for another browser.
536 | *
537 | * @param {number} documentMode The document mode version to check.
538 | * @return {boolean} Whether the IE effective document mode is higher or the
539 | * same as the given version.
540 | */
541 | goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
542 | return goog.userAgent.DOCUMENT_MODE >= documentMode;
543 | };
544 |
545 |
546 | /**
547 | * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
548 | * @param {number} version The version to check.
549 | * @return {boolean} Whether the IE effective document mode is higher or the
550 | * same as the given version.
551 | * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
552 | */
553 | goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
554 |
555 |
556 | /**
557 | * For IE version < 7, documentMode is undefined, so attempt to use the
558 | * CSS1Compat property to see if we are in standards mode. If we are in
559 | * standards mode, treat the browser version as the document mode. Otherwise,
560 | * IE is emulating version 5.
561 | * @type {number|undefined}
562 | * @const
563 | */
564 | goog.userAgent.DOCUMENT_MODE = (function() {
565 | var doc = goog.global['document'];
566 | var mode = goog.userAgent.getDocumentMode_();
567 | if (!doc || !goog.userAgent.IE) {
568 | return undefined;
569 | }
570 | return mode || (doc['compatMode'] == 'CSS1Compat' ?
571 | parseInt(goog.userAgent.VERSION, 10) : 5);
572 | })();