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.labs.userAgent.browser'); |
26 | goog.require('goog.labs.userAgent.engine'); |
27 | goog.require('goog.labs.userAgent.util'); |
28 | goog.require('goog.string'); |
29 | |
30 | |
31 | /** |
32 | * @define {boolean} Whether we know at compile-time that the browser is IE. |
33 | */ |
34 | goog.define('goog.userAgent.ASSUME_IE', false); |
35 | |
36 | |
37 | /** |
38 | * @define {boolean} Whether we know at compile-time that the browser is GECKO. |
39 | */ |
40 | goog.define('goog.userAgent.ASSUME_GECKO', false); |
41 | |
42 | |
43 | /** |
44 | * @define {boolean} Whether we know at compile-time that the browser is WEBKIT. |
45 | */ |
46 | goog.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 | */ |
53 | goog.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 | */ |
59 | goog.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 | */ |
67 | goog.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 | */ |
75 | goog.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 | */ |
88 | goog.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 | */ |
97 | goog.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 | */ |
109 | goog.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 | */ |
118 | goog.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 | */ |
128 | goog.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 | */ |
138 | goog.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 | */ |
153 | goog.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 | */ |
167 | goog.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 | */ |
177 | goog.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 | */ |
186 | goog.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 | */ |
198 | goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_(); |
199 | |
200 | |
201 | /** |
202 | * @define {boolean} Whether the user agent is running on a Macintosh operating |
203 | * system. |
204 | */ |
205 | goog.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 | */ |
212 | goog.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 | */ |
219 | goog.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 | */ |
226 | goog.define('goog.userAgent.ASSUME_X11', false); |
227 | |
228 | |
229 | /** |
230 | * @define {boolean} Whether the user agent is running on Android. |
231 | */ |
232 | goog.define('goog.userAgent.ASSUME_ANDROID', false); |
233 | |
234 | |
235 | /** |
236 | * @define {boolean} Whether the user agent is running on an iPhone. |
237 | */ |
238 | goog.define('goog.userAgent.ASSUME_IPHONE', false); |
239 | |
240 | |
241 | /** |
242 | * @define {boolean} Whether the user agent is running on an iPad. |
243 | */ |
244 | goog.define('goog.userAgent.ASSUME_IPAD', false); |
245 | |
246 | |
247 | /** |
248 | * @type {boolean} |
249 | * @private |
250 | */ |
251 | goog.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 | */ |
266 | goog.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 | |
327 | if (!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 | */ |
336 | goog.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 | */ |
344 | goog.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 | */ |
352 | goog.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 | */ |
360 | goog.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 | */ |
368 | goog.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 | */ |
376 | goog.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 | */ |
384 | goog.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 | */ |
393 | goog.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 | */ |
440 | goog.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 | */ |
452 | goog.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 | */ |
466 | goog.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 | */ |
478 | goog.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 | */ |
495 | goog.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 | */ |
510 | goog.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 | */ |
522 | goog.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 | */ |
534 | goog.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 | */ |
545 | goog.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 | })(); |