# 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 |
24 | goog.provide('goog.userAgent');
25 |
26 | goog.require('goog.labs.userAgent.browser');
27 | goog.require('goog.labs.userAgent.engine');
28 | goog.require('goog.labs.userAgent.platform');
29 | goog.require('goog.labs.userAgent.util');
30 | goog.require('goog.string');
31 |
32 |
33 | /**
34 | * @define {boolean} Whether we know at compile-time that the browser is IE.
35 | */
36 | goog.define('goog.userAgent.ASSUME_IE', false);
37 |
38 |
39 | /**
40 | * @define {boolean} Whether we know at compile-time that the browser is GECKO.
41 | */
42 | goog.define('goog.userAgent.ASSUME_GECKO', false);
43 |
44 |
45 | /**
46 | * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
47 | */
48 | goog.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 | */
55 | goog.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 | */
61 | goog.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 | */
69 | goog.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 | */
77 | goog.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 | */
90 | goog.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 | */
99 | goog.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 | */
111 | goog.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 | */
120 | goog.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 | */
130 | goog.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 | */
140 | goog.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 | */
155 | goog.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 | */
169 | goog.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 | */
179 | goog.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 | */
188 | goog.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 | */
200 | goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
201 |
202 |
203 | /**
204 | * @define {boolean} Whether the user agent is running on a Macintosh operating
205 | * system.
206 | */
207 | goog.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 | */
214 | goog.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 | */
221 | goog.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 | */
228 | goog.define('goog.userAgent.ASSUME_X11', false);
229 |
230 |
231 | /**
232 | * @define {boolean} Whether the user agent is running on Android.
233 | */
234 | goog.define('goog.userAgent.ASSUME_ANDROID', false);
235 |
236 |
237 | /**
238 | * @define {boolean} Whether the user agent is running on an iPhone.
239 | */
240 | goog.define('goog.userAgent.ASSUME_IPHONE', false);
241 |
242 |
243 | /**
244 | * @define {boolean} Whether the user agent is running on an iPad.
245 | */
246 | goog.define('goog.userAgent.ASSUME_IPAD', false);
247 |
248 |
249 | /**
250 | * @type {boolean}
251 | * @private
252 | */
253 | goog.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 | */
267 | goog.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 | */
275 | goog.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 | */
287 | goog.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 | */
302 | goog.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 | */
311 | goog.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 | */
322 | goog.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 | */
331 | goog.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 | */
340 | goog.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 | */
349 | goog.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 | */
359 | goog.userAgent.determineVersion_ = function() {
360 | // All browsers have different ways to detect the version and they all have
361 | // different naming schemes.
362 |
363 | // version is a string rather than a number because it may contain 'b', 'a',
364 | // and so on.
365 | var version = '', re;
366 |
367 | if (goog.userAgent.OPERA && goog.global['opera']) {
368 | var operaVersion = goog.global['opera'].version;
369 | return goog.isFunction(operaVersion) ? operaVersion() : operaVersion;
370 | }
371 |
372 | if (goog.userAgent.GECKO) {
373 | re = /rv\:([^\);]+)(\)|;)/;
374 | } else if (goog.userAgent.IE) {
375 | re = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/;
376 | } else if (goog.userAgent.WEBKIT) {
377 | // WebKit/125.4
378 | re = /WebKit\/(\S+)/;
379 | }
380 |
381 | if (re) {
382 | var arr = re.exec(goog.userAgent.getUserAgentString());
383 | version = arr ? arr[1] : '';
384 | }
385 |
386 | if (goog.userAgent.IE) {
387 | // IE9 can be in document mode 9 but be reporting an inconsistent user agent
388 | // version. If it is identifying as a version lower than 9 we take the
389 | // documentMode as the version instead. IE8 has similar behavior.
390 | // It is recommended to set the X-UA-Compatible header to ensure that IE9
391 | // uses documentMode 9.
392 | var docMode = goog.userAgent.getDocumentMode_();
393 | if (docMode > parseFloat(version)) {
394 | return String(docMode);
395 | }
396 | }
397 |
398 | return version;
399 | };
400 |
401 |
402 | /**
403 | * @return {number|undefined} Returns the document mode (for testing).
404 | * @private
405 | */
406 | goog.userAgent.getDocumentMode_ = function() {
407 | // NOTE(user): goog.userAgent may be used in context where there is no DOM.
408 | var doc = goog.global['document'];
409 | return doc ? doc['documentMode'] : undefined;
410 | };
411 |
412 |
413 | /**
414 | * The version of the user agent. This is a string because it might contain
415 | * 'b' (as in beta) as well as multiple dots.
416 | * @type {string}
417 | */
418 | goog.userAgent.VERSION = goog.userAgent.determineVersion_();
419 |
420 |
421 | /**
422 | * Compares two version numbers.
423 | *
424 | * @param {string} v1 Version of first item.
425 | * @param {string} v2 Version of second item.
426 | *
427 | * @return {number} 1 if first argument is higher
428 | * 0 if arguments are equal
429 | * -1 if second argument is higher.
430 | * @deprecated Use goog.string.compareVersions.
431 | */
432 | goog.userAgent.compare = function(v1, v2) {
433 | return goog.string.compareVersions(v1, v2);
434 | };
435 |
436 |
437 | /**
438 | * Cache for {@link goog.userAgent.isVersionOrHigher}.
439 | * Calls to compareVersions are surprisingly expensive and, as a browser's
440 | * version number is unlikely to change during a session, we cache the results.
441 | * @const
442 | * @private
443 | */
444 | goog.userAgent.isVersionOrHigherCache_ = {};
445 |
446 |
447 | /**
448 | * Whether the user agent version is higher or the same as the given version.
449 | * NOTE: When checking the version numbers for Firefox and Safari, be sure to
450 | * use the engine's version, not the browser's version number. For example,
451 | * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
452 | * Opera and Internet Explorer versions match the product release number.<br>
453 | * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
454 | * Webkit</a>
455 | * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
456 | *
457 | * @param {string|number} version The version to check.
458 | * @return {boolean} Whether the user agent version is higher or the same as
459 | * the given version.
460 | */
461 | goog.userAgent.isVersionOrHigher = function(version) {
462 | return goog.userAgent.ASSUME_ANY_VERSION ||
463 | goog.userAgent.isVersionOrHigherCache_[version] ||
464 | (goog.userAgent.isVersionOrHigherCache_[version] =
465 | goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
466 | };
467 |
468 |
469 | /**
470 | * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
471 | * @param {string|number} version The version to check.
472 | * @return {boolean} Whether the user agent version is higher or the same as
473 | * the given version.
474 | * @deprecated Use goog.userAgent.isVersionOrHigher().
475 | */
476 | goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
477 |
478 |
479 | /**
480 | * Whether the IE effective document mode is higher or the same as the given
481 | * document mode version.
482 | * NOTE: Only for IE, return false for another browser.
483 | *
484 | * @param {number} documentMode The document mode version to check.
485 | * @return {boolean} Whether the IE effective document mode is higher or the
486 | * same as the given version.
487 | */
488 | goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
489 | return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
490 | };
491 |
492 |
493 | /**
494 | * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
495 | * @param {number} version The version to check.
496 | * @return {boolean} Whether the IE effective document mode is higher or the
497 | * same as the given version.
498 | * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
499 | */
500 | goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
501 |
502 |
503 | /**
504 | * For IE version < 7, documentMode is undefined, so attempt to use the
505 | * CSS1Compat property to see if we are in standards mode. If we are in
506 | * standards mode, treat the browser version as the document mode. Otherwise,
507 | * IE is emulating version 5.
508 | * @type {number|undefined}
509 | * @const
510 | */
511 | goog.userAgent.DOCUMENT_MODE = (function() {
512 | var doc = goog.global['document'];
513 | if (!doc || !goog.userAgent.IE) {
514 | return undefined;
515 | }
516 | var mode = goog.userAgent.getDocumentMode_();
517 | return mode || (doc['compatMode'] == 'CSS1Compat' ?
518 | parseInt(goog.userAgent.VERSION, 10) : 5);
519 | })();