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