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