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