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
24goog.provide('goog.userAgent');
25
26goog.require('goog.labs.userAgent.browser');
27goog.require('goog.labs.userAgent.engine');
28goog.require('goog.labs.userAgent.platform');
29goog.require('goog.labs.userAgent.util');
30goog.require('goog.string');
31
32
33/**
34 * @define {boolean} Whether we know at compile-time that the browser is IE.
35 */
36goog.define('goog.userAgent.ASSUME_IE', false);
37
38
39/**
40 * @define {boolean} Whether we know at compile-time that the browser is EDGE.
41 */
42goog.define('goog.userAgent.ASSUME_EDGE', false);
43
44
45/**
46 * @define {boolean} Whether we know at compile-time that the browser is GECKO.
47 */
48goog.define('goog.userAgent.ASSUME_GECKO', false);
49
50
51/**
52 * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
53 */
54goog.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 */
61goog.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 */
67goog.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 */
75goog.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 */
83goog.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 */
97goog.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 */
106goog.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 */
118goog.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 */
127goog.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 */
136goog.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 */
145goog.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 */
153goog.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 */
163goog.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 */
178goog.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 */
192goog.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 */
202goog.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 */
211goog.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 */
223goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
224
225
226/**
227 * @define {boolean} Whether the user agent is running on a Macintosh operating
228 * system.
229 */
230goog.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 */
237goog.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 */
244goog.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 */
251goog.define('goog.userAgent.ASSUME_X11', false);
252
253
254/**
255 * @define {boolean} Whether the user agent is running on Android.
256 */
257goog.define('goog.userAgent.ASSUME_ANDROID', false);
258
259
260/**
261 * @define {boolean} Whether the user agent is running on an iPhone.
262 */
263goog.define('goog.userAgent.ASSUME_IPHONE', false);
264
265
266/**
267 * @define {boolean} Whether the user agent is running on an iPad.
268 */
269goog.define('goog.userAgent.ASSUME_IPAD', false);
270
271
272/**
273 * @type {boolean}
274 * @private
275 */
276goog.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 */
290goog.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 */
298goog.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 */
310goog.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 */
325goog.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 */
334goog.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 */
345goog.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 */
354goog.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 */
363goog.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 */
372goog.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 */
383goog.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 */
398goog.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 */
437goog.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 */
459goog.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 */
471goog.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 */
485goog.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 */
497goog.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 */
514goog.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 */
529goog.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 */
541goog.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 */
553goog.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 */
564goog.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})();