blob: 782b1853a5dd69bf2579d8bc6bfea18affabd6e3 [file] [log] [blame]
Akron7524be12016-06-01 17:31:33 +02001/*!
2 * Platform.js v1.3.1 <https://mths.be/platform>
3 * Copyright 2014-2016 Benjamin Tan <https://d10.github.io/>
4 * Copyright 2011-2013 John-David Dalton <http://allyoucanleet.com/>
5 * Available under MIT license <https://mths.be/mit>
6 */
7;(function() {
8 'use strict';
9
10 /** Used to determine if values are of the language type `Object`. */
11 var objectTypes = {
12 'function': true,
13 'object': true
14 };
15
16 /** Used as a reference to the global object. */
17 var root = (objectTypes[typeof window] && window) || this;
18
19 /** Backup possible global object. */
20 var oldRoot = root;
21
22 /** Detect free variable `exports`. */
23 var freeExports = objectTypes[typeof exports] && exports;
24
25 /** Detect free variable `module`. */
26 var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
27
28 /** Detect free variable `global` from Node.js or Browserified code and use it as `root`. */
29 var freeGlobal = freeExports && freeModule && typeof global == 'object' && global;
30 if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) {
31 root = freeGlobal;
32 }
33
34 /**
35 * Used as the maximum length of an array-like object.
36 * See the [ES6 spec](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength)
37 * for more details.
38 */
39 var maxSafeInteger = Math.pow(2, 53) - 1;
40
41 /** Regular expression to detect Opera. */
42 var reOpera = /\bOpera/;
43
44 /** Possible global object. */
45 var thisBinding = this;
46
47 /** Used for native method references. */
48 var objectProto = Object.prototype;
49
50 /** Used to check for own properties of an object. */
51 var hasOwnProperty = objectProto.hasOwnProperty;
52
53 /** Used to resolve the internal `[[Class]]` of values. */
54 var toString = objectProto.toString;
55
56 /*--------------------------------------------------------------------------*/
57
58 /**
59 * Capitalizes a string value.
60 *
61 * @private
62 * @param {string} string The string to capitalize.
63 * @returns {string} The capitalized string.
64 */
65 function capitalize(string) {
66 string = String(string);
67 return string.charAt(0).toUpperCase() + string.slice(1);
68 }
69
70 /**
71 * A utility function to clean up the OS name.
72 *
73 * @private
74 * @param {string} os The OS name to clean up.
75 * @param {string} [pattern] A `RegExp` pattern matching the OS name.
76 * @param {string} [label] A label for the OS.
77 */
78 function cleanupOS(os, pattern, label) {
79 // Platform tokens are defined at:
80 // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
81 // http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
82 var data = {
83 '10.0': '10',
84 '6.4': '10 Technical Preview',
85 '6.3': '8.1',
86 '6.2': '8',
87 '6.1': '7 / Server 2008 R2',
88 '6.0': 'Vista / Server 2008',
89 '5.2': 'XP 64-bit / Server 2003',
90 '5.1': 'XP',
91 '5.01': '2000 SP1',
92 '5.0': '2000',
93 '4.0': 'NT',
94 '4.90': 'ME'
95 };
96 // Detect Windows version from platform tokens.
97 if (pattern && label && /^Win/i.test(os) && !/^Windows Phone /i.test(os) &&
98 (data = data[/[\d.]+$/.exec(os)])) {
99 os = 'Windows ' + data;
100 }
101 // Correct character case and cleanup string.
102 os = String(os);
103
104 if (pattern && label) {
105 os = os.replace(RegExp(pattern, 'i'), label);
106 }
107
108 os = format(
109 os.replace(/ ce$/i, ' CE')
110 .replace(/\bhpw/i, 'web')
111 .replace(/\bMacintosh\b/, 'Mac OS')
112 .replace(/_PowerPC\b/i, ' OS')
113 .replace(/\b(OS X) [^ \d]+/i, '$1')
114 .replace(/\bMac (OS X)\b/, '$1')
115 .replace(/\/(\d)/, ' $1')
116 .replace(/_/g, '.')
117 .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
118 .replace(/\bx86\.64\b/gi, 'x86_64')
119 .replace(/\b(Windows Phone) OS\b/, '$1')
120 .replace(/\b(Chrome OS \w+) [\d.]+\b/, '$1')
121 .split(' on ')[0]
122 );
123
124 return os;
125 }
126
127 /**
128 * An iteration utility for arrays and objects.
129 *
130 * @private
131 * @param {Array|Object} object The object to iterate over.
132 * @param {Function} callback The function called per iteration.
133 */
134 function each(object, callback) {
135 var index = -1,
136 length = object ? object.length : 0;
137
138 if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) {
139 while (++index < length) {
140 callback(object[index], index, object);
141 }
142 } else {
143 forOwn(object, callback);
144 }
145 }
146
147 /**
148 * Trim and conditionally capitalize string values.
149 *
150 * @private
151 * @param {string} string The string to format.
152 * @returns {string} The formatted string.
153 */
154 function format(string) {
155 string = trim(string);
156 return /^(?:webOS|i(?:OS|P))/.test(string)
157 ? string
158 : capitalize(string);
159 }
160
161 /**
162 * Iterates over an object's own properties, executing the `callback` for each.
163 *
164 * @private
165 * @param {Object} object The object to iterate over.
166 * @param {Function} callback The function executed per own property.
167 */
168 function forOwn(object, callback) {
169 for (var key in object) {
170 if (hasOwnProperty.call(object, key)) {
171 callback(object[key], key, object);
172 }
173 }
174 }
175
176 /**
177 * Gets the internal `[[Class]]` of a value.
178 *
179 * @private
180 * @param {*} value The value.
181 * @returns {string} The `[[Class]]`.
182 */
183 function getClassOf(value) {
184 return value == null
185 ? capitalize(value)
186 : toString.call(value).slice(8, -1);
187 }
188
189 /**
190 * Host objects can return type values that are different from their actual
191 * data type. The objects we are concerned with usually return non-primitive
192 * types of "object", "function", or "unknown".
193 *
194 * @private
195 * @param {*} object The owner of the property.
196 * @param {string} property The property to check.
197 * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`.
198 */
199 function isHostType(object, property) {
200 var type = object != null ? typeof object[property] : 'number';
201 return !/^(?:boolean|number|string|undefined)$/.test(type) &&
202 (type == 'object' ? !!object[property] : true);
203 }
204
205 /**
206 * Prepares a string for use in a `RegExp` by making hyphens and spaces optional.
207 *
208 * @private
209 * @param {string} string The string to qualify.
210 * @returns {string} The qualified string.
211 */
212 function qualify(string) {
213 return String(string).replace(/([ -])(?!$)/g, '$1?');
214 }
215
216 /**
217 * A bare-bones `Array#reduce` like utility function.
218 *
219 * @private
220 * @param {Array} array The array to iterate over.
221 * @param {Function} callback The function called per iteration.
222 * @returns {*} The accumulated result.
223 */
224 function reduce(array, callback) {
225 var accumulator = null;
226 each(array, function(value, index) {
227 accumulator = callback(accumulator, value, index, array);
228 });
229 return accumulator;
230 }
231
232 /**
233 * Removes leading and trailing whitespace from a string.
234 *
235 * @private
236 * @param {string} string The string to trim.
237 * @returns {string} The trimmed string.
238 */
239 function trim(string) {
240 return String(string).replace(/^ +| +$/g, '');
241 }
242
243 /*--------------------------------------------------------------------------*/
244
245 /**
246 * Creates a new platform object.
247 *
248 * @memberOf platform
249 * @param {Object|string} [ua=navigator.userAgent] The user agent string or
250 * context object.
251 * @returns {Object} A platform object.
252 */
253 function parse(ua) {
254
255 /** The environment context object. */
256 var context = root;
257
258 /** Used to flag when a custom context is provided. */
259 var isCustomContext = ua && typeof ua == 'object' && getClassOf(ua) != 'String';
260
261 // Juggle arguments.
262 if (isCustomContext) {
263 context = ua;
264 ua = null;
265 }
266
267 /** Browser navigator object. */
268 var nav = context.navigator || {};
269
270 /** Browser user agent string. */
271 var userAgent = nav.userAgent || '';
272
273 ua || (ua = userAgent);
274
275 /** Used to flag when `thisBinding` is the [ModuleScope]. */
276 var isModuleScope = isCustomContext || thisBinding == oldRoot;
277
278 /** Used to detect if browser is like Chrome. */
279 var likeChrome = isCustomContext
280 ? !!nav.likeChrome
281 : /\bChrome\b/.test(ua) && !/internal|\n/i.test(toString.toString());
282
283 /** Internal `[[Class]]` value shortcuts. */
284 var objectClass = 'Object',
285 airRuntimeClass = isCustomContext ? objectClass : 'ScriptBridgingProxyObject',
286 enviroClass = isCustomContext ? objectClass : 'Environment',
287 javaClass = (isCustomContext && context.java) ? 'JavaPackage' : getClassOf(context.java),
288 phantomClass = isCustomContext ? objectClass : 'RuntimeObject';
289
290 /** Detect Java environments. */
291 var java = /\bJava/.test(javaClass) && context.java;
292
293 /** Detect Rhino. */
294 var rhino = java && getClassOf(context.environment) == enviroClass;
295
296 /** A character to represent alpha. */
297 var alpha = java ? 'a' : '\u03b1';
298
299 /** A character to represent beta. */
300 var beta = java ? 'b' : '\u03b2';
301
302 /** Browser document object. */
303 var doc = context.document || {};
304
305 /**
306 * Detect Opera browser (Presto-based).
307 * http://www.howtocreate.co.uk/operaStuff/operaObject.html
308 * http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini
309 */
310 var opera = context.operamini || context.opera;
311
312 /** Opera `[[Class]]`. */
313 var operaClass = reOpera.test(operaClass = (isCustomContext && opera) ? opera['[[Class]]'] : getClassOf(opera))
314 ? operaClass
315 : (opera = null);
316
317 /*------------------------------------------------------------------------*/
318
319 /** Temporary variable used over the script's lifetime. */
320 var data;
321
322 /** The CPU architecture. */
323 var arch = ua;
324
325 /** Platform description array. */
326 var description = [];
327
328 /** Platform alpha/beta indicator. */
329 var prerelease = null;
330
331 /** A flag to indicate that environment features should be used to resolve the platform. */
332 var useFeatures = ua == userAgent;
333
334 /** The browser/environment version. */
335 var version = useFeatures && opera && typeof opera.version == 'function' && opera.version();
336
337 /** A flag to indicate if the OS begins with "Name Version /". */
338 var isSpecialCasedOS;
339
340 /* Detectable layout engines (order is important). */
341 var layout = getLayout([
342 { 'label': 'EdgeHTML', 'pattern': 'Edge' },
343 'Trident',
344 { 'label': 'WebKit', 'pattern': 'AppleWebKit' },
345 'iCab',
346 'Presto',
347 'NetFront',
348 'Tasman',
349 'KHTML',
350 'Gecko'
351 ]);
352
353 /* Detectable browser names (order is important). */
354 var name = getName([
355 'Adobe AIR',
356 'Arora',
357 'Avant Browser',
358 'Breach',
359 'Camino',
360 'Epiphany',
361 'Fennec',
362 'Flock',
363 'Galeon',
364 'GreenBrowser',
365 'iCab',
366 'Iceweasel',
367 'K-Meleon',
368 'Konqueror',
369 'Lunascape',
370 'Maxthon',
371 { 'label': 'Microsoft Edge', 'pattern': 'Edge' },
372 'Midori',
373 'Nook Browser',
374 'PaleMoon',
375 'PhantomJS',
376 'Raven',
377 'Rekonq',
378 'RockMelt',
379 'SeaMonkey',
380 { 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
381 'Sleipnir',
382 'SlimBrowser',
383 { 'label': 'SRWare Iron', 'pattern': 'Iron' },
384 'Sunrise',
385 'Swiftfox',
386 'WebPositive',
387 'Opera Mini',
388 { 'label': 'Opera Mini', 'pattern': 'OPiOS' },
389 'Opera',
390 { 'label': 'Opera', 'pattern': 'OPR' },
391 'Chrome',
392 { 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
393 { 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
394 { 'label': 'Firefox Mobile', 'pattern': 'FxiOS' },
395 { 'label': 'IE', 'pattern': 'IEMobile' },
396 { 'label': 'IE', 'pattern': 'MSIE' },
397 'Safari'
398 ]);
399
400 /* Detectable products (order is important). */
401 var product = getProduct([
402 { 'label': 'BlackBerry', 'pattern': 'BB10' },
403 'BlackBerry',
404 { 'label': 'Galaxy S', 'pattern': 'GT-I9000' },
405 { 'label': 'Galaxy S2', 'pattern': 'GT-I9100' },
406 { 'label': 'Galaxy S3', 'pattern': 'GT-I9300' },
407 { 'label': 'Galaxy S4', 'pattern': 'GT-I9500' },
408 'Google TV',
409 'Lumia',
410 'iPad',
411 'iPod',
412 'iPhone',
413 'Kindle',
414 { 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
415 'Nexus',
416 'Nook',
417 'PlayBook',
418 'PlayStation 3',
419 'PlayStation 4',
420 'PlayStation Vita',
421 'TouchPad',
422 'Transformer',
423 { 'label': 'Wii U', 'pattern': 'WiiU' },
424 'Wii',
425 'Xbox One',
426 { 'label': 'Xbox 360', 'pattern': 'Xbox' },
427 'Xoom'
428 ]);
429
430 /* Detectable manufacturers. */
431 var manufacturer = getManufacturer({
432 'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 },
433 'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 },
434 'Asus': { 'Transformer': 1 },
435 'Barnes & Noble': { 'Nook': 1 },
436 'BlackBerry': { 'PlayBook': 1 },
437 'Google': { 'Google TV': 1, 'Nexus': 1 },
438 'HP': { 'TouchPad': 1 },
439 'HTC': {},
440 'LG': {},
441 'Microsoft': { 'Xbox': 1, 'Xbox One': 1 },
442 'Motorola': { 'Xoom': 1 },
443 'Nintendo': { 'Wii U': 1, 'Wii': 1 },
444 'Nokia': { 'Lumia': 1 },
445 'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1, 'Galaxy S3': 1, 'Galaxy S4': 1 },
446 'Sony': { 'PlayStation 4': 1, 'PlayStation 3': 1, 'PlayStation Vita': 1 }
447 });
448
449 /* Detectable operating systems (order is important). */
450 var os = getOS([
451 'Windows Phone ',
452 'Android',
453 'CentOS',
454 { 'label': 'Chrome OS', 'pattern': 'CrOS' },
455 'Debian',
456 'Fedora',
457 'FreeBSD',
458 'Gentoo',
459 'Haiku',
460 'Kubuntu',
461 'Linux Mint',
462 'OpenBSD',
463 'Red Hat',
464 'SuSE',
465 'Ubuntu',
466 'Xubuntu',
467 'Cygwin',
468 'Symbian OS',
469 'hpwOS',
470 'webOS ',
471 'webOS',
472 'Tablet OS',
473 'Linux',
474 'Mac OS X',
475 'Macintosh',
476 'Mac',
477 'Windows 98;',
478 'Windows '
479 ]);
480
481 /*------------------------------------------------------------------------*/
482
483 /**
484 * Picks the layout engine from an array of guesses.
485 *
486 * @private
487 * @param {Array} guesses An array of guesses.
488 * @returns {null|string} The detected layout engine.
489 */
490 function getLayout(guesses) {
491 return reduce(guesses, function(result, guess) {
492 return result || RegExp('\\b' + (
493 guess.pattern || qualify(guess)
494 ) + '\\b', 'i').exec(ua) && (guess.label || guess);
495 });
496 }
497
498 /**
499 * Picks the manufacturer from an array of guesses.
500 *
501 * @private
502 * @param {Array} guesses An object of guesses.
503 * @returns {null|string} The detected manufacturer.
504 */
505 function getManufacturer(guesses) {
506 return reduce(guesses, function(result, value, key) {
507 // Lookup the manufacturer by product or scan the UA for the manufacturer.
508 return result || (
509 value[product] ||
510 value[/^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] ||
511 RegExp('\\b' + qualify(key) + '(?:\\b|\\w*\\d)', 'i').exec(ua)
512 ) && key;
513 });
514 }
515
516 /**
517 * Picks the browser name from an array of guesses.
518 *
519 * @private
520 * @param {Array} guesses An array of guesses.
521 * @returns {null|string} The detected browser name.
522 */
523 function getName(guesses) {
524 return reduce(guesses, function(result, guess) {
525 return result || RegExp('\\b' + (
526 guess.pattern || qualify(guess)
527 ) + '\\b', 'i').exec(ua) && (guess.label || guess);
528 });
529 }
530
531 /**
532 * Picks the OS name from an array of guesses.
533 *
534 * @private
535 * @param {Array} guesses An array of guesses.
536 * @returns {null|string} The detected OS name.
537 */
538 function getOS(guesses) {
539 return reduce(guesses, function(result, guess) {
540 var pattern = guess.pattern || qualify(guess);
541 if (!result && (result =
542 RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua)
543 )) {
544 result = cleanupOS(result, pattern, guess.label || guess);
545 }
546 return result;
547 });
548 }
549
550 /**
551 * Picks the product name from an array of guesses.
552 *
553 * @private
554 * @param {Array} guesses An array of guesses.
555 * @returns {null|string} The detected product name.
556 */
557 function getProduct(guesses) {
558 return reduce(guesses, function(result, guess) {
559 var pattern = guess.pattern || qualify(guess);
560 if (!result && (result =
561 RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) ||
562 RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua)
563 )) {
564 // Split by forward slash and append product version if needed.
565 if ((result = String((guess.label && !RegExp(pattern, 'i').test(guess.label)) ? guess.label : result).split('/'))[1] && !/[\d.]+/.test(result[0])) {
566 result[0] += ' ' + result[1];
567 }
568 // Correct character case and cleanup string.
569 guess = guess.label || guess;
570 result = format(result[0]
571 .replace(RegExp(pattern, 'i'), guess)
572 .replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ')
573 .replace(RegExp('(' + guess + ')[-_.]?(\\w)', 'i'), '$1 $2'));
574 }
575 return result;
576 });
577 }
578
579 /**
580 * Resolves the version using an array of UA patterns.
581 *
582 * @private
583 * @param {Array} patterns An array of UA patterns.
584 * @returns {null|string} The detected version.
585 */
586 function getVersion(patterns) {
587 return reduce(patterns, function(result, pattern) {
588 return result || (RegExp(pattern +
589 '(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null;
590 });
591 }
592
593 /**
594 * Returns `platform.description` when the platform object is coerced to a string.
595 *
596 * @name toString
597 * @memberOf platform
598 * @returns {string} Returns `platform.description` if available, else an empty string.
599 */
600 function toStringPlatform() {
601 return this.description || '';
602 }
603
604 /*------------------------------------------------------------------------*/
605
606 // Convert layout to an array so we can add extra details.
607 layout && (layout = [layout]);
608
609 // Detect product names that contain their manufacturer's name.
610 if (manufacturer && !product) {
611 product = getProduct([manufacturer]);
612 }
613 // Clean up Google TV.
614 if ((data = /\bGoogle TV\b/.exec(product))) {
615 product = data[0];
616 }
617 // Detect simulators.
618 if (/\bSimulator\b/i.test(ua)) {
619 product = (product ? product + ' ' : '') + 'Simulator';
620 }
621 // Detect Opera Mini 8+ running in Turbo/Uncompressed mode on iOS.
622 if (name == 'Opera Mini' && /\bOPiOS\b/.test(ua)) {
623 description.push('running in Turbo/Uncompressed mode');
624 }
625 // Detect iOS.
626 if (/^iP/.test(product)) {
627 name || (name = 'Safari');
628 os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua))
629 ? ' ' + data[1].replace(/_/g, '.')
630 : '');
631 }
632 // Detect Kubuntu.
633 else if (name == 'Konqueror' && !/buntu/i.test(os)) {
634 os = 'Kubuntu';
635 }
636 // Detect Android browsers.
637 else if (manufacturer && manufacturer != 'Google' &&
638 ((/Chrome/.test(name) && !/\bMobile Safari\b/i.test(ua)) || /\bVita\b/.test(product))) {
639 name = 'Android Browser';
640 os = /\bAndroid\b/.test(os) ? os : 'Android';
641 }
642 // Detect Silk desktop/accelerated modes.
643 else if (name == 'Silk') {
644 if (!/\bMobi/i.test(ua)) {
645 os = 'Android';
646 description.unshift('desktop mode');
647 }
648 if (/Accelerated *= *true/i.test(ua)) {
649 description.unshift('accelerated');
650 }
651 }
652 // Detect PaleMoon identifying as Firefox.
653 else if (name == 'PaleMoon' && (data = /\bFirefox\/([\d.]+)\b/.exec(ua))) {
654 description.push('identifying as Firefox ' + data[1]);
655 }
656 // Detect Firefox OS and products running Firefox.
657 else if (name == 'Firefox' && (data = /\b(Mobile|Tablet|TV)\b/i.exec(ua))) {
658 os || (os = 'Firefox OS');
659 product || (product = data[1]);
660 }
661 // Detect false positives for Firefox/Safari.
662 else if (!name || (data = !/\bMinefield\b/i.test(ua) && /\b(?:Firefox|Safari)\b/.exec(name))) {
663 // Escape the `/` for Firefox 1.
664 if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
665 // Clear name of false positives.
666 name = null;
667 }
668 // Reassign a generic name.
669 if ((data = product || manufacturer || os) &&
670 (product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) {
671 name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser';
672 }
673 }
674 // Detect non-Opera (Presto-based) versions (order is important).
675 if (!version) {
676 version = getVersion([
677 '(?:Cloud9|CriOS|CrMo|Edge|FxiOS|IEMobile|Iron|Opera ?Mini|OPiOS|OPR|Raven|Silk(?!/[\\d.]+$))',
678 'Version',
679 qualify(name),
680 '(?:Firefox|Minefield|NetFront)'
681 ]);
682 }
683 // Detect stubborn layout engines.
684 if ((data =
685 layout == 'iCab' && parseFloat(version) > 3 && 'WebKit' ||
686 /\bOpera\b/.test(name) && (/\bOPR\b/.test(ua) ? 'Blink' : 'Presto') ||
687 /\b(?:Midori|Nook|Safari)\b/i.test(ua) && !/^(?:Trident|EdgeHTML)$/.test(layout) && 'WebKit' ||
688 !layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident') ||
689 layout == 'WebKit' && /\bPlayStation\b(?! Vita\b)/i.test(name) && 'NetFront'
690 )) {
691 layout = [data];
692 }
693 // Detect Windows Phone 7 desktop mode.
694 if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) {
695 name += ' Mobile';
696 os = 'Windows Phone ' + (/\+$/.test(data) ? data : data + '.x');
697 description.unshift('desktop mode');
698 }
699 // Detect Windows Phone 8.x desktop mode.
700 else if (/\bWPDesktop\b/i.test(ua)) {
701 name = 'IE Mobile';
702 os = 'Windows Phone 8.x';
703 description.unshift('desktop mode');
704 version || (version = (/\brv:([\d.]+)/.exec(ua) || 0)[1]);
705 }
706 // Detect IE 11.
707 else if (name != 'IE' && layout == 'Trident' && (data = /\brv:([\d.]+)/.exec(ua))) {
708 if (name) {
709 description.push('identifying as ' + name + (version ? ' ' + version : ''));
710 }
711 name = 'IE';
712 version = data[1];
713 }
714 // Leverage environment features.
715 if (useFeatures) {
716 // Detect server-side environments.
717 // Rhino has a global function while others have a global object.
718 if (isHostType(context, 'global')) {
719 if (java) {
720 data = java.lang.System;
721 arch = data.getProperty('os.arch');
722 os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version');
723 }
724 if (isModuleScope && isHostType(context, 'system') && (data = [context.system])[0]) {
725 os || (os = data[0].os || null);
726 try {
727 data[1] = context.require('ringo/engine').version;
728 version = data[1].join('.');
729 name = 'RingoJS';
730 } catch(e) {
731 if (data[0].global.system == context.system) {
732 name = 'Narwhal';
733 }
734 }
735 }
736 else if (typeof context.process == 'object' && (data = context.process)) {
737 name = 'Node.js';
738 arch = data.arch;
739 os = data.platform;
740 version = /[\d.]+/.exec(data.version)[0];
741 }
742 else if (rhino) {
743 name = 'Rhino';
744 }
745 }
746 // Detect Adobe AIR.
747 else if (getClassOf((data = context.runtime)) == airRuntimeClass) {
748 name = 'Adobe AIR';
749 os = data.flash.system.Capabilities.os;
750 }
751 // Detect PhantomJS.
752 else if (getClassOf((data = context.phantom)) == phantomClass) {
753 name = 'PhantomJS';
754 version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch);
755 }
756 // Detect IE compatibility modes.
757 else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) {
758 // We're in compatibility mode when the Trident version + 4 doesn't
759 // equal the document mode.
760 version = [version, doc.documentMode];
761 if ((data = +data[1] + 4) != version[1]) {
762 description.push('IE ' + version[1] + ' mode');
763 layout && (layout[1] = '');
764 version[1] = data;
765 }
766 version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
767 }
768 os = os && format(os);
769 }
770 // Detect prerelease phases.
771 if (version && (data =
772 /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) ||
773 /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) ||
774 /\bMinefield\b/i.test(ua) && 'a'
775 )) {
776 prerelease = /b/i.test(data) ? 'beta' : 'alpha';
777 version = version.replace(RegExp(data + '\\+?$'), '') +
778 (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
779 }
780 // Detect Firefox Mobile.
781 if (name == 'Fennec' || name == 'Firefox' && /\b(?:Android|Firefox OS)\b/.test(os)) {
782 name = 'Firefox Mobile';
783 }
784 // Obscure Maxthon's unreliable version.
785 else if (name == 'Maxthon' && version) {
786 version = version.replace(/\.[\d.]+/, '.x');
787 }
788 // Detect Xbox 360 and Xbox One.
789 else if (/\bXbox\b/i.test(product)) {
790 os = null;
791 if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) {
792 description.unshift('mobile mode');
793 }
794 }
795 // Add mobile postfix.
796 else if ((/^(?:Chrome|IE|Opera)$/.test(name) || name && !product && !/Browser|Mobi/.test(name)) &&
797 (os == 'Windows CE' || /Mobi/i.test(ua))) {
798 name += ' Mobile';
799 }
800 // Detect IE platform preview.
801 else if (name == 'IE' && useFeatures && context.external === null) {
802 description.unshift('platform preview');
803 }
804 // Detect BlackBerry OS version.
805 // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
806 else if ((/\bBlackBerry\b/.test(product) || /\bBB10\b/.test(ua)) && (data =
807 (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
808 version
809 )) {
810 data = [data, /BB10/.test(ua)];
811 os = (data[1] ? (product = null, manufacturer = 'BlackBerry') : 'Device Software') + ' ' + data[0];
812 version = null;
813 }
814 // Detect Opera identifying/masking itself as another browser.
815 // http://www.opera.com/support/kb/view/843/
816 else if (this != forOwn && product != 'Wii' && (
817 (useFeatures && opera) ||
818 (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
819 (name == 'Firefox' && /\bOS X (?:\d+\.){2,}/.test(os)) ||
820 (name == 'IE' && (
821 (os && !/^Win/.test(os) && version > 5.5) ||
822 /\bWindows XP\b/.test(os) && version > 8 ||
823 version == 8 && !/\bTrident\b/.test(ua)
824 ))
825 ) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) {
826 // When "identifying", the UA contains both Opera and the other browser's name.
827 data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
828 if (reOpera.test(name)) {
829 if (/\bIE\b/.test(data) && os == 'Mac OS') {
830 os = null;
831 }
832 data = 'identify' + data;
833 }
834 // When "masking", the UA contains only the other browser's name.
835 else {
836 data = 'mask' + data;
837 if (operaClass) {
838 name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2'));
839 } else {
840 name = 'Opera';
841 }
842 if (/\bIE\b/.test(data)) {
843 os = null;
844 }
845 if (!useFeatures) {
846 version = null;
847 }
848 }
849 layout = ['Presto'];
850 description.push(data);
851 }
852 // Detect WebKit Nightly and approximate Chrome/Safari versions.
853 if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
854 // Correct build number for numeric comparison.
855 // (e.g. "532.5" becomes "532.05")
856 data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data];
857 // Nightly builds are postfixed with a "+".
858 if (name == 'Safari' && data[1].slice(-1) == '+') {
859 name = 'WebKit Nightly';
860 prerelease = 'alpha';
861 version = data[1].slice(0, -1);
862 }
863 // Clear incorrect browser versions.
864 else if (version == data[1] ||
865 version == (data[2] = (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
866 version = null;
867 }
868 // Use the full Chrome version when available.
869 data[1] = (/\bChrome\/([\d.]+)/i.exec(ua) || 0)[1];
870 // Detect Blink layout engine.
871 if (data[0] == 537.36 && data[2] == 537.36 && parseFloat(data[1]) >= 28 && layout == 'WebKit') {
872 layout = ['Blink'];
873 }
874 // Detect JavaScriptCore.
875 // http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi
876 if (!useFeatures || (!likeChrome && !data[1])) {
877 layout && (layout[1] = 'like Safari');
878 data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : data < 537 ? 6 : data < 538 ? 7 : data < 601 ? 8 : '8');
879 } else {
880 layout && (layout[1] = 'like Chrome');
881 data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 536.05 ? 18 : data < 536.10 ? 19 : data < 537.01 ? 20 : data < 537.11 ? '21+' : data < 537.13 ? 23 : data < 537.18 ? 24 : data < 537.24 ? 25 : data < 537.36 ? 26 : layout != 'Blink' ? '27' : '28');
882 }
883 // Add the postfix of ".x" or "+" for approximate versions.
884 layout && (layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+'));
885 // Obscure version for some Safari 1-2 releases.
886 if (name == 'Safari' && (!version || parseInt(version) > 45)) {
887 version = data;
888 }
889 }
890 // Detect Opera desktop modes.
891 if (name == 'Opera' && (data = /\bzbov|zvav$/.exec(os))) {
892 name += ' ';
893 description.unshift('desktop mode');
894 if (data == 'zvav') {
895 name += 'Mini';
896 version = null;
897 } else {
898 name += 'Mobile';
899 }
900 os = os.replace(RegExp(' *' + data + '$'), '');
901 }
902 // Detect Chrome desktop mode.
903 else if (name == 'Safari' && /\bChrome\b/.exec(layout && layout[1])) {
904 description.unshift('desktop mode');
905 name = 'Chrome Mobile';
906 version = null;
907
908 if (/\bOS X\b/.test(os)) {
909 manufacturer = 'Apple';
910 os = 'iOS 4.3+';
911 } else {
912 os = null;
913 }
914 }
915 // Strip incorrect OS versions.
916 if (version && version.indexOf((data = /[\d.]+$/.exec(os))) == 0 &&
917 ua.indexOf('/' + data + '-') > -1) {
918 os = trim(os.replace(data, ''));
919 }
920 // Add layout engine.
921 if (layout && !/\b(?:Avant|Nook)\b/.test(name) && (
922 /Browser|Lunascape|Maxthon/.test(name) ||
923 name != 'Safari' && /^iOS/.test(os) && /\bSafari\b/.test(layout[1]) ||
924 /^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Sleipnir|Web)/.test(name) && layout[1])) {
925 // Don't add layout details to description if they are falsey.
926 (data = layout[layout.length - 1]) && description.push(data);
927 }
928 // Combine contextual information.
929 if (description.length) {
930 description = ['(' + description.join('; ') + ')'];
931 }
932 // Append manufacturer to description.
933 if (manufacturer && product && product.indexOf(manufacturer) < 0) {
934 description.push('on ' + manufacturer);
935 }
936 // Append product to description.
937 if (product) {
938 description.push((/^on /.test(description[description.length - 1]) ? '' : 'on ') + product);
939 }
940 // Parse the OS into an object.
941 if (os) {
942 data =
943 / ([\d.+]+)$/.exec(os) ||
944 (isSpecialCasedOS = /^[a-z]+ ([\d.+]+) \//i.exec(os));
945 os = {
946 'architecture': 32,
947 'family': (data && !isSpecialCasedOS) ? os.replace(data[0], '') : os,
948 'version': data ? data[1] : null,
949 'toString': function() {
950 var version = this.version;
951 return this.family + ((version && !isSpecialCasedOS) ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : '');
952 }
953 };
954 }
955 // Add browser/OS architecture.
956 if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
957 if (os) {
958 os.architecture = 64;
959 os.family = os.family.replace(RegExp(' *' + data), '');
960 }
961 if (
962 name && (/\bWOW64\b/i.test(ua) ||
963 (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/\bWin64; x64\b/i.test(ua)))
964 ) {
965 description.unshift('32-bit');
966 }
967 }
968
969 ua || (ua = null);
970
971 /*------------------------------------------------------------------------*/
972
973 /**
974 * The platform object.
975 *
976 * @name platform
977 * @type Object
978 */
979 var platform = {};
980
981 /**
982 * The platform description.
983 *
984 * @memberOf platform
985 * @type string|null
986 */
987 platform.description = ua;
988
989 /**
990 * The name of the browser's layout engine.
991 *
992 * @memberOf platform
993 * @type string|null
994 */
995 platform.layout = layout && layout[0];
996
997 /**
998 * The name of the product's manufacturer.
999 *
1000 * @memberOf platform
1001 * @type string|null
1002 */
1003 platform.manufacturer = manufacturer;
1004
1005 /**
1006 * The name of the browser/environment.
1007 *
1008 * @memberOf platform
1009 * @type string|null
1010 */
1011 platform.name = name;
1012
1013 /**
1014 * The alpha/beta release indicator.
1015 *
1016 * @memberOf platform
1017 * @type string|null
1018 */
1019 platform.prerelease = prerelease;
1020
1021 /**
1022 * The name of the product hosting the browser.
1023 *
1024 * @memberOf platform
1025 * @type string|null
1026 */
1027 platform.product = product;
1028
1029 /**
1030 * The browser's user agent string.
1031 *
1032 * @memberOf platform
1033 * @type string|null
1034 */
1035 platform.ua = ua;
1036
1037 /**
1038 * The browser/environment version.
1039 *
1040 * @memberOf platform
1041 * @type string|null
1042 */
1043 platform.version = name && version;
1044
1045 /**
1046 * The name of the operating system.
1047 *
1048 * @memberOf platform
1049 * @type Object
1050 */
1051 platform.os = os || {
1052
1053 /**
1054 * The CPU architecture the OS is built for.
1055 *
1056 * @memberOf platform.os
1057 * @type number|null
1058 */
1059 'architecture': null,
1060
1061 /**
1062 * The family of the OS.
1063 *
1064 * Common values include:
1065 * "Windows", "Windows 7 / Server 2008 R2", "Windows Vista / Server 2008",
1066 * "Windows XP", "OS X", "Ubuntu", "Debian", "Fedora", "Red Hat", "SuSE",
1067 * "Android", "iOS" and "Windows Phone"
1068 *
1069 * @memberOf platform.os
1070 * @type string|null
1071 */
1072 'family': null,
1073
1074 /**
1075 * The version of the OS.
1076 *
1077 * @memberOf platform.os
1078 * @type string|null
1079 */
1080 'version': null,
1081
1082 /**
1083 * Returns the OS string.
1084 *
1085 * @memberOf platform.os
1086 * @returns {string} The OS string.
1087 */
1088 'toString': function() { return 'null'; }
1089 };
1090
1091 platform.parse = parse;
1092 platform.toString = toStringPlatform;
1093
1094 if (platform.version) {
1095 description.unshift(version);
1096 }
1097 if (platform.name) {
1098 description.unshift(name);
1099 }
1100 if (os && name && !(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product))) {
1101 description.push(product ? '(' + os + ')' : 'on ' + os);
1102 }
1103 if (description.length) {
1104 platform.description = description.join(' ');
1105 }
1106 return platform;
1107 }
1108
1109 /*--------------------------------------------------------------------------*/
1110
1111 // Export platform.
1112 // Some AMD build optimizers, like r.js, check for condition patterns like the following:
1113 if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
1114 // Define as an anonymous module so platform can be aliased through path mapping.
1115 define(function() {
1116 return parse();
1117 });
1118 }
1119 // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
1120 else if (freeExports && freeModule) {
1121 // Export for CommonJS support.
1122 forOwn(parse(), function(value, key) {
1123 freeExports[key] = value;
1124 });
1125 }
1126 else {
1127 // Export to the global object.
1128 root.platform = parse();
1129 }
1130}.call(this));