Completed
Push — development ( 1fb984...7ea9e3 )
by Nils
08:29
created

platform.js ➔ ... ➔ parse   F

Complexity

Conditions 242
Paths > 20000

Size

Total Lines 936

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 242
nc 500170752
nop 1
dl 0
loc 936
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like platform.js ➔ ... ➔ parse often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * Platform.js <https://mths.be/platform>
3
 * Copyright 2014-2016 Benjamin Tan <https://demoneaux.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':  'Server 2008 R2 / 7',
88
      '6.0':  'Server 2008 / Vista',
89
      '5.2':  'Server 2003 / XP 64-bit',
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) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
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
0 ignored issues
show
Documentation introduced by
The parameter ua=navigator.userAgent does not exist. Did you maybe forget to remove this comment?
Loading history...
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 ends with "/ 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
      'Electron',
361
      'Epiphany',
362
      'Fennec',
363
      'Flock',
364
      'Galeon',
365
      'GreenBrowser',
366
      'iCab',
367
      'Iceweasel',
368
      'K-Meleon',
369
      'Konqueror',
370
      'Lunascape',
371
      'Maxthon',
372
      { 'label': 'Microsoft Edge', 'pattern': 'Edge' },
373
      'Midori',
374
      'Nook Browser',
375
      'PaleMoon',
376
      'PhantomJS',
377
      'Raven',
378
      'Rekonq',
379
      'RockMelt',
380
      { 'label': 'Samsung Internet', 'pattern': 'SamsungBrowser' },
381
      'SeaMonkey',
382
      { 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
383
      'Sleipnir',
384
      'SlimBrowser',
385
      { 'label': 'SRWare Iron', 'pattern': 'Iron' },
386
      'Sunrise',
387
      'Swiftfox',
388
      'Waterfox',
389
      'WebPositive',
390
      'Opera Mini',
391
      { 'label': 'Opera Mini', 'pattern': 'OPiOS' },
392
      'Opera',
393
      { 'label': 'Opera', 'pattern': 'OPR' },
394
      'Chrome',
395
      { 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
396
      { 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
397
      { 'label': 'Firefox for iOS', 'pattern': 'FxiOS' },
398
      { 'label': 'IE', 'pattern': 'IEMobile' },
399
      { 'label': 'IE', 'pattern': 'MSIE' },
400
      'Safari'
401
    ]);
402
403
    /* Detectable products (order is important). */
404
    var product = getProduct([
405
      { 'label': 'BlackBerry', 'pattern': 'BB10' },
406
      'BlackBerry',
407
      { 'label': 'Galaxy S', 'pattern': 'GT-I9000' },
408
      { 'label': 'Galaxy S2', 'pattern': 'GT-I9100' },
409
      { 'label': 'Galaxy S3', 'pattern': 'GT-I9300' },
410
      { 'label': 'Galaxy S4', 'pattern': 'GT-I9500' },
411
      { 'label': 'Galaxy S5', 'pattern': 'SM-G900' },
412
      { 'label': 'Galaxy S6', 'pattern': 'SM-G920' },
413
      { 'label': 'Galaxy S6 Edge', 'pattern': 'SM-G925' },
414
      { 'label': 'Galaxy S7', 'pattern': 'SM-G930' },
415
      { 'label': 'Galaxy S7 Edge', 'pattern': 'SM-G935' },
416
      'Google TV',
417
      'Lumia',
418
      'iPad',
419
      'iPod',
420
      'iPhone',
421
      'Kindle',
422
      { 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
423
      'Nexus',
424
      'Nook',
425
      'PlayBook',
426
      'PlayStation Vita',
427
      'PlayStation',
428
      'TouchPad',
429
      'Transformer',
430
      { 'label': 'Wii U', 'pattern': 'WiiU' },
431
      'Wii',
432
      'Xbox One',
433
      { 'label': 'Xbox 360', 'pattern': 'Xbox' },
434
      'Xoom'
435
    ]);
436
437
    /* Detectable manufacturers. */
438
    var manufacturer = getManufacturer({
439
      'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 },
440
      'Archos': {},
441
      'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 },
442
      'Asus': { 'Transformer': 1 },
443
      'Barnes & Noble': { 'Nook': 1 },
444
      'BlackBerry': { 'PlayBook': 1 },
445
      'Google': { 'Google TV': 1, 'Nexus': 1 },
446
      'HP': { 'TouchPad': 1 },
447
      'HTC': {},
448
      'LG': {},
449
      'Microsoft': { 'Xbox': 1, 'Xbox One': 1 },
450
      'Motorola': { 'Xoom': 1 },
451
      'Nintendo': { 'Wii U': 1,  'Wii': 1 },
452
      'Nokia': { 'Lumia': 1 },
453
      'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1, 'Galaxy S3': 1, 'Galaxy S4': 1 },
454
      'Sony': { 'PlayStation': 1, 'PlayStation Vita': 1 }
455
    });
456
457
    /* Detectable operating systems (order is important). */
458
    var os = getOS([
459
      'Windows Phone',
460
      'Android',
461
      'CentOS',
462
      { 'label': 'Chrome OS', 'pattern': 'CrOS' },
463
      'Debian',
464
      'Fedora',
465
      'FreeBSD',
466
      'Gentoo',
467
      'Haiku',
468
      'Kubuntu',
469
      'Linux Mint',
470
      'OpenBSD',
471
      'Red Hat',
472
      'SuSE',
473
      'Ubuntu',
474
      'Xubuntu',
475
      'Cygwin',
476
      'Symbian OS',
477
      'hpwOS',
478
      'webOS ',
479
      'webOS',
480
      'Tablet OS',
481
      'Tizen',
482
      'Linux',
483
      'Mac OS X',
484
      'Macintosh',
485
      'Mac',
486
      'Windows 98;',
487
      'Windows '
488
    ]);
489
490
    /*------------------------------------------------------------------------*/
491
492
    /**
493
     * Picks the layout engine from an array of guesses.
494
     *
495
     * @private
496
     * @param {Array} guesses An array of guesses.
497
     * @returns {null|string} The detected layout engine.
498
     */
499
    function getLayout(guesses) {
500
      return reduce(guesses, function(result, guess) {
501
        return result || RegExp('\\b' + (
502
          guess.pattern || qualify(guess)
503
        ) + '\\b', 'i').exec(ua) && (guess.label || guess);
504
      });
505
    }
506
507
    /**
508
     * Picks the manufacturer from an array of guesses.
509
     *
510
     * @private
511
     * @param {Array} guesses An object of guesses.
512
     * @returns {null|string} The detected manufacturer.
513
     */
514
    function getManufacturer(guesses) {
515
      return reduce(guesses, function(result, value, key) {
516
        // Lookup the manufacturer by product or scan the UA for the manufacturer.
517
        return result || (
518
          value[product] ||
519
          value[/^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] ||
520
          RegExp('\\b' + qualify(key) + '(?:\\b|\\w*\\d)', 'i').exec(ua)
521
        ) && key;
522
      });
523
    }
524
525
    /**
526
     * Picks the browser name from an array of guesses.
527
     *
528
     * @private
529
     * @param {Array} guesses An array of guesses.
530
     * @returns {null|string} The detected browser name.
531
     */
532
    function getName(guesses) {
533
      return reduce(guesses, function(result, guess) {
534
        return result || RegExp('\\b' + (
535
          guess.pattern || qualify(guess)
536
        ) + '\\b', 'i').exec(ua) && (guess.label || guess);
537
      });
538
    }
539
540
    /**
541
     * Picks the OS name from an array of guesses.
542
     *
543
     * @private
544
     * @param {Array} guesses An array of guesses.
545
     * @returns {null|string} The detected OS name.
546
     */
547
    function getOS(guesses) {
548
      return reduce(guesses, function(result, guess) {
549
        var pattern = guess.pattern || qualify(guess);
550
        if (!result && (result =
551
              RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua)
552
            )) {
553
          result = cleanupOS(result, pattern, guess.label || guess);
554
        }
555
        return result;
556
      });
557
    }
558
559
    /**
560
     * Picks the product name from an array of guesses.
561
     *
562
     * @private
563
     * @param {Array} guesses An array of guesses.
564
     * @returns {null|string} The detected product name.
565
     */
566
    function getProduct(guesses) {
567
      return reduce(guesses, function(result, guess) {
568
        var pattern = guess.pattern || qualify(guess);
569
        if (!result && (result =
570
              RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) ||
571
              RegExp('\\b' + pattern + ' *\\w+-[\\w]*', 'i').exec(ua) ||
572
              RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua)
573
            )) {
574
          // Split by forward slash and append product version if needed.
575
          if ((result = String((guess.label && !RegExp(pattern, 'i').test(guess.label)) ? guess.label : result).split('/'))[1] && !/[\d.]+/.test(result[0])) {
576
            result[0] += ' ' + result[1];
577
          }
578
          // Correct character case and cleanup string.
579
          guess = guess.label || guess;
580
          result = format(result[0]
581
            .replace(RegExp(pattern, 'i'), guess)
582
            .replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ')
583
            .replace(RegExp('(' + guess + ')[-_.]?(\\w)', 'i'), '$1 $2'));
584
        }
585
        return result;
586
      });
587
    }
588
589
    /**
590
     * Resolves the version using an array of UA patterns.
591
     *
592
     * @private
593
     * @param {Array} patterns An array of UA patterns.
594
     * @returns {null|string} The detected version.
595
     */
596
    function getVersion(patterns) {
597
      return reduce(patterns, function(result, pattern) {
598
        return result || (RegExp(pattern +
599
          '(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null;
600
      });
601
    }
602
603
    /**
604
     * Returns `platform.description` when the platform object is coerced to a string.
605
     *
606
     * @name toString
607
     * @memberOf platform
608
     * @returns {string} Returns `platform.description` if available, else an empty string.
609
     */
610
    function toStringPlatform() {
611
      return this.description || '';
612
    }
613
614
    /*------------------------------------------------------------------------*/
615
616
    // Convert layout to an array so we can add extra details.
617
    layout && (layout = [layout]);
618
619
    // Detect product names that contain their manufacturer's name.
620
    if (manufacturer && !product) {
621
      product = getProduct([manufacturer]);
622
    }
623
    // Clean up Google TV.
624
    if ((data = /\bGoogle TV\b/.exec(product))) {
625
      product = data[0];
626
    }
627
    // Detect simulators.
628
    if (/\bSimulator\b/i.test(ua)) {
629
      product = (product ? product + ' ' : '') + 'Simulator';
630
    }
631
    // Detect Opera Mini 8+ running in Turbo/Uncompressed mode on iOS.
632
    if (name == 'Opera Mini' && /\bOPiOS\b/.test(ua)) {
633
      description.push('running in Turbo/Uncompressed mode');
634
    }
635
    // Detect IE Mobile 11.
636
    if (name == 'IE' && /\blike iPhone OS\b/.test(ua)) {
637
      data = parse(ua.replace(/like iPhone OS/, ''));
638
      manufacturer = data.manufacturer;
639
      product = data.product;
640
    }
641
    // Detect iOS.
642
    else if (/^iP/.test(product)) {
643
      name || (name = 'Safari');
644
      os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua))
645
        ? ' ' + data[1].replace(/_/g, '.')
646
        : '');
647
    }
648
    // Detect Kubuntu.
649
    else if (name == 'Konqueror' && !/buntu/i.test(os)) {
650
      os = 'Kubuntu';
651
    }
652
    // Detect Android browsers.
653
    else if ((manufacturer && manufacturer != 'Google' &&
654
        ((/Chrome/.test(name) && !/\bMobile Safari\b/i.test(ua)) || /\bVita\b/.test(product))) ||
655
        (/\bAndroid\b/.test(os) && /^Chrome/.test(name) && /\bVersion\//i.test(ua))) {
656
      name = 'Android Browser';
657
      os = /\bAndroid\b/.test(os) ? os : 'Android';
658
    }
659
    // Detect Silk desktop/accelerated modes.
660
    else if (name == 'Silk') {
661
      if (!/\bMobi/i.test(ua)) {
662
        os = 'Android';
663
        description.unshift('desktop mode');
664
      }
665
      if (/Accelerated *= *true/i.test(ua)) {
666
        description.unshift('accelerated');
667
      }
668
    }
669
    // Detect PaleMoon identifying as Firefox.
670
    else if (name == 'PaleMoon' && (data = /\bFirefox\/([\d.]+)\b/.exec(ua))) {
671
      description.push('identifying as Firefox ' + data[1]);
672
    }
673
    // Detect Firefox OS and products running Firefox.
674
    else if (name == 'Firefox' && (data = /\b(Mobile|Tablet|TV)\b/i.exec(ua))) {
675
      os || (os = 'Firefox OS');
676
      product || (product = data[1]);
677
    }
678
    // Detect false positives for Firefox/Safari.
679
    else if (!name || (data = !/\bMinefield\b/i.test(ua) && /\b(?:Firefox|Safari)\b/.exec(name))) {
680
      // Escape the `/` for Firefox 1.
681
      if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
682
        // Clear name of false positives.
683
        name = null;
684
      }
685
      // Reassign a generic name.
686
      if ((data = product || manufacturer || os) &&
687
          (product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) {
688
        name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser';
689
      }
690
    }
691
    // Add Chrome version to description for Electron.
692
    else if (name == 'Electron' && (data = (/\bChrome\/([\d.]+)\b/.exec(ua) || 0)[1])) {
693
      description.push('Chromium ' + data);
694
    }
695
    // Detect non-Opera (Presto-based) versions (order is important).
696
    if (!version) {
697
      version = getVersion([
698
        '(?:Cloud9|CriOS|CrMo|Edge|FxiOS|IEMobile|Iron|Opera ?Mini|OPiOS|OPR|Raven|SamsungBrowser|Silk(?!/[\\d.]+$))',
699
        'Version',
700
        qualify(name),
701
        '(?:Firefox|Minefield|NetFront)'
702
      ]);
703
    }
704
    // Detect stubborn layout engines.
705
    if ((data =
706
          layout == 'iCab' && parseFloat(version) > 3 && 'WebKit' ||
707
          /\bOpera\b/.test(name) && (/\bOPR\b/.test(ua) ? 'Blink' : 'Presto') ||
708
          /\b(?:Midori|Nook|Safari)\b/i.test(ua) && !/^(?:Trident|EdgeHTML)$/.test(layout) && 'WebKit' ||
709
          !layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident') ||
710
          layout == 'WebKit' && /\bPlayStation\b(?! Vita\b)/i.test(name) && 'NetFront'
711
        )) {
712
      layout = [data];
713
    }
714
    // Detect Windows Phone 7 desktop mode.
715
    if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) {
716
      name += ' Mobile';
717
      os = 'Windows Phone ' + (/\+$/.test(data) ? data : data + '.x');
718
      description.unshift('desktop mode');
719
    }
720
    // Detect Windows Phone 8.x desktop mode.
721
    else if (/\bWPDesktop\b/i.test(ua)) {
722
      name = 'IE Mobile';
723
      os = 'Windows Phone 8.x';
724
      description.unshift('desktop mode');
725
      version || (version = (/\brv:([\d.]+)/.exec(ua) || 0)[1]);
726
    }
727
    // Detect IE 11 identifying as other browsers.
728
    else if (name != 'IE' && layout == 'Trident' && (data = /\brv:([\d.]+)/.exec(ua))) {
729
      if (name) {
730
        description.push('identifying as ' + name + (version ? ' ' + version : ''));
731
      }
732
      name = 'IE';
733
      version = data[1];
734
    }
735
    // Leverage environment features.
736
    if (useFeatures) {
737
      // Detect server-side environments.
738
      // Rhino has a global function while others have a global object.
739
      if (isHostType(context, 'global')) {
740
        if (java) {
741
          data = java.lang.System;
742
          arch = data.getProperty('os.arch');
743
          os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version');
744
        }
745
        if (isModuleScope && isHostType(context, 'system') && (data = [context.system])[0]) {
746
          os || (os = data[0].os || null);
747
          try {
748
            data[1] = context.require('ringo/engine').version;
749
            version = data[1].join('.');
750
            name = 'RingoJS';
751
          } catch(e) {
752
            if (data[0].global.system == context.system) {
753
              name = 'Narwhal';
754
            }
755
          }
756
        }
757
        else if (
758
          typeof context.process == 'object' && !context.process.browser &&
759
          (data = context.process)
760
        ) {
761
          if (typeof data.versions == 'object') {
762
            if (typeof data.versions.electron == 'string') {
763
              description.push('Node ' + data.versions.node);
764
              name = 'Electron';
765
              version = data.versions.electron;
766
            } else if (typeof data.versions.nw == 'string') {
767
              description.push('Chromium ' + version, 'Node ' + data.versions.node);
768
              name = 'NW.js';
769
              version = data.versions.nw;
770
            }
771
          }
772
          if (!name) {
773
            name = 'Node.js';
774
            arch = data.arch;
775
            os = data.platform;
776
            version = /[\d.]+/.exec(data.version)
777
            version = version ? version[0] : 'unknown';
778
          }
779
        }
780
        else if (rhino) {
781
          name = 'Rhino';
782
        }
783
      }
784
      // Detect Adobe AIR.
785
      else if (getClassOf((data = context.runtime)) == airRuntimeClass) {
786
        name = 'Adobe AIR';
787
        os = data.flash.system.Capabilities.os;
788
      }
789
      // Detect PhantomJS.
790
      else if (getClassOf((data = context.phantom)) == phantomClass) {
791
        name = 'PhantomJS';
792
        version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch);
793
      }
794
      // Detect IE compatibility modes.
795
      else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) {
796
        // We're in compatibility mode when the Trident version + 4 doesn't
797
        // equal the document mode.
798
        version = [version, doc.documentMode];
799
        if ((data = +data[1] + 4) != version[1]) {
800
          description.push('IE ' + version[1] + ' mode');
801
          layout && (layout[1] = '');
802
          version[1] = data;
803
        }
804
        version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
805
      }
806
      // Detect IE 11 masking as other browsers.
807
      else if (typeof doc.documentMode == 'number' && /^(?:Chrome|Firefox)\b/.test(name)) {
808
        description.push('masking as ' + name + ' ' + version);
809
        name = 'IE';
810
        version = '11.0';
811
        layout = ['Trident'];
812
        os = 'Windows';
813
      }
814
      os = os && format(os);
815
    }
816
    // Detect prerelease phases.
817
    if (version && (data =
818
          /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) ||
819
          /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) ||
820
          /\bMinefield\b/i.test(ua) && 'a'
821
        )) {
822
      prerelease = /b/i.test(data) ? 'beta' : 'alpha';
823
      version = version.replace(RegExp(data + '\\+?$'), '') +
824
        (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
825
    }
826
    // Detect Firefox Mobile.
827
    if (name == 'Fennec' || name == 'Firefox' && /\b(?:Android|Firefox OS)\b/.test(os)) {
828
      name = 'Firefox Mobile';
829
    }
830
    // Obscure Maxthon's unreliable version.
831
    else if (name == 'Maxthon' && version) {
832
      version = version.replace(/\.[\d.]+/, '.x');
833
    }
834
    // Detect Xbox 360 and Xbox One.
835
    else if (/\bXbox\b/i.test(product)) {
836
      if (product == 'Xbox 360') {
837
        os = null;
838
      }
839
      if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) {
840
        description.unshift('mobile mode');
841
      }
842
    }
843
    // Add mobile postfix.
844
    else if ((/^(?:Chrome|IE|Opera)$/.test(name) || name && !product && !/Browser|Mobi/.test(name)) &&
845
        (os == 'Windows CE' || /Mobi/i.test(ua))) {
846
      name += ' Mobile';
847
    }
848
    // Detect IE platform preview.
849
    else if (name == 'IE' && useFeatures) {
850
      try {
851
        if (context.external === null) {
852
          description.unshift('platform preview');
853
        }
854
      } catch(e) {
855
        description.unshift('embedded');
856
      }
857
    }
858
    // Detect BlackBerry OS version.
859
    // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
860
    else if ((/\bBlackBerry\b/.test(product) || /\bBB10\b/.test(ua)) && (data =
861
          (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
862
          version
863
        )) {
864
      data = [data, /BB10/.test(ua)];
865
      os = (data[1] ? (product = null, manufacturer = 'BlackBerry') : 'Device Software') + ' ' + data[0];
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
866
      version = null;
867
    }
868
    // Detect Opera identifying/masking itself as another browser.
869
    // http://www.opera.com/support/kb/view/843/
870
    else if (this != forOwn && product != 'Wii' && (
871
          (useFeatures && opera) ||
872
          (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
873
          (name == 'Firefox' && /\bOS X (?:\d+\.){2,}/.test(os)) ||
874
          (name == 'IE' && (
875
            (os && !/^Win/.test(os) && version > 5.5) ||
876
            /\bWindows XP\b/.test(os) && version > 8 ||
877
            version == 8 && !/\bTrident\b/.test(ua)
878
          ))
879
        ) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) {
880
      // When "identifying", the UA contains both Opera and the other browser's name.
881
      data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
882
      if (reOpera.test(name)) {
883
        if (/\bIE\b/.test(data) && os == 'Mac OS') {
884
          os = null;
885
        }
886
        data = 'identify' + data;
887
      }
888
      // When "masking", the UA contains only the other browser's name.
889
      else {
890
        data = 'mask' + data;
891
        if (operaClass) {
892
          name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2'));
893
        } else {
894
          name = 'Opera';
895
        }
896
        if (/\bIE\b/.test(data)) {
897
          os = null;
898
        }
899
        if (!useFeatures) {
900
          version = null;
901
        }
902
      }
903
      layout = ['Presto'];
904
      description.push(data);
905
    }
906
    // Detect WebKit Nightly and approximate Chrome/Safari versions.
907
    if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
908
      // Correct build number for numeric comparison.
909
      // (e.g. "532.5" becomes "532.05")
910
      data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data];
911
      // Nightly builds are postfixed with a "+".
912
      if (name == 'Safari' && data[1].slice(-1) == '+') {
913
        name = 'WebKit Nightly';
914
        prerelease = 'alpha';
915
        version = data[1].slice(0, -1);
916
      }
917
      // Clear incorrect browser versions.
918
      else if (version == data[1] ||
919
          version == (data[2] = (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
920
        version = null;
921
      }
922
      // Use the full Chrome version when available.
923
      data[1] = (/\bChrome\/([\d.]+)/i.exec(ua) || 0)[1];
924
      // Detect Blink layout engine.
925
      if (data[0] == 537.36 && data[2] == 537.36 && parseFloat(data[1]) >= 28 && layout == 'WebKit') {
926
        layout = ['Blink'];
927
      }
928
      // Detect JavaScriptCore.
929
      // http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi
930
      if (!useFeatures || (!likeChrome && !data[1])) {
931
        layout && (layout[1] = 'like Safari');
932
        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');
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
933
      } else {
934
        layout && (layout[1] = 'like Chrome');
935
        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');
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
936
      }
937
      // Add the postfix of ".x" or "+" for approximate versions.
938
      layout && (layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+'));
939
      // Obscure version for some Safari 1-2 releases.
940
      if (name == 'Safari' && (!version || parseInt(version) > 45)) {
941
        version = data;
942
      }
943
    }
944
    // Detect Opera desktop modes.
945
    if (name == 'Opera' &&  (data = /\bzbov|zvav$/.exec(os))) {
946
      name += ' ';
947
      description.unshift('desktop mode');
948
      if (data == 'zvav') {
949
        name += 'Mini';
950
        version = null;
951
      } else {
952
        name += 'Mobile';
953
      }
954
      os = os.replace(RegExp(' *' + data + '$'), '');
955
    }
956
    // Detect Chrome desktop mode.
957
    else if (name == 'Safari' && /\bChrome\b/.exec(layout && layout[1])) {
958
      description.unshift('desktop mode');
959
      name = 'Chrome Mobile';
960
      version = null;
961
962
      if (/\bOS X\b/.test(os)) {
963
        manufacturer = 'Apple';
964
        os = 'iOS 4.3+';
965
      } else {
966
        os = null;
967
      }
968
    }
969
    // Strip incorrect OS versions.
970
    if (version && version.indexOf((data = /[\d.]+$/.exec(os))) == 0 &&
971
        ua.indexOf('/' + data + '-') > -1) {
972
      os = trim(os.replace(data, ''));
973
    }
974
    // Add layout engine.
975
    if (layout && !/\b(?:Avant|Nook)\b/.test(name) && (
976
        /Browser|Lunascape|Maxthon/.test(name) ||
977
        name != 'Safari' && /^iOS/.test(os) && /\bSafari\b/.test(layout[1]) ||
978
        /^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Samsung Internet|Sleipnir|Web)/.test(name) && layout[1])) {
979
      // Don't add layout details to description if they are falsey.
980
      (data = layout[layout.length - 1]) && description.push(data);
981
    }
982
    // Combine contextual information.
983
    if (description.length) {
984
      description = ['(' + description.join('; ') + ')'];
985
    }
986
    // Append manufacturer to description.
987
    if (manufacturer && product && product.indexOf(manufacturer) < 0) {
988
      description.push('on ' + manufacturer);
989
    }
990
    // Append product to description.
991
    if (product) {
992
      description.push((/^on /.test(description[description.length - 1]) ? '' : 'on ') + product);
993
    }
994
    // Parse the OS into an object.
995
    if (os) {
996
      data = / ([\d.+]+)$/.exec(os);
997
      isSpecialCasedOS = data && os.charAt(os.length - data[0].length - 1) == '/';
998
      os = {
999
        'architecture': 32,
1000
        'family': (data && !isSpecialCasedOS) ? os.replace(data[0], '') : os,
1001
        'version': data ? data[1] : null,
1002
        'toString': function() {
1003
          var version = this.version;
1004
          return this.family + ((version && !isSpecialCasedOS) ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : '');
1005
        }
1006
      };
1007
    }
1008
    // Add browser/OS architecture.
1009
    if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
1010
      if (os) {
1011
        os.architecture = 64;
1012
        os.family = os.family.replace(RegExp(' *' + data), '');
1013
      }
1014
      if (
1015
          name && (/\bWOW64\b/i.test(ua) ||
1016
          (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/\bWin64; x64\b/i.test(ua)))
1017
      ) {
1018
        description.unshift('32-bit');
1019
      }
1020
    }
1021
    // Chrome 39 and above on OS X is always 64-bit.
1022
    else if (
1023
        os && /^OS X/.test(os.family) &&
1024
        name == 'Chrome' && parseFloat(version) >= 39
1025
    ) {
1026
      os.architecture = 64;
1027
    }
1028
1029
    ua || (ua = null);
1030
1031
    /*------------------------------------------------------------------------*/
1032
1033
    /**
1034
     * The platform object.
1035
     *
1036
     * @name platform
1037
     * @type Object
1038
     */
1039
    var platform = {};
1040
1041
    /**
1042
     * The platform description.
1043
     *
1044
     * @memberOf platform
1045
     * @type string|null
1046
     */
1047
    platform.description = ua;
1048
1049
    /**
1050
     * The name of the browser's layout engine.
1051
     *
1052
     * The list of common layout engines include:
1053
     * "Blink", "EdgeHTML", "Gecko", "Trident" and "WebKit"
1054
     *
1055
     * @memberOf platform
1056
     * @type string|null
1057
     */
1058
    platform.layout = layout && layout[0];
1059
1060
    /**
1061
     * The name of the product's manufacturer.
1062
     *
1063
     * The list of manufacturers include:
1064
     * "Apple", "Archos", "Amazon", "Asus", "Barnes & Noble", "BlackBerry",
1065
     * "Google", "HP", "HTC", "LG", "Microsoft", "Motorola", "Nintendo",
1066
     * "Nokia", "Samsung" and "Sony"
1067
     *
1068
     * @memberOf platform
1069
     * @type string|null
1070
     */
1071
    platform.manufacturer = manufacturer;
1072
1073
    /**
1074
     * The name of the browser/environment.
1075
     *
1076
     * The list of common browser names include:
1077
     * "Chrome", "Electron", "Firefox", "Firefox for iOS", "IE",
1078
     * "Microsoft Edge", "PhantomJS", "Safari", "SeaMonkey", "Silk",
1079
     * "Opera Mini" and "Opera"
1080
     *
1081
     * Mobile versions of some browsers have "Mobile" appended to their name:
1082
     * eg. "Chrome Mobile", "Firefox Mobile", "IE Mobile" and "Opera Mobile"
1083
     *
1084
     * @memberOf platform
1085
     * @type string|null
1086
     */
1087
    platform.name = name;
1088
1089
    /**
1090
     * The alpha/beta release indicator.
1091
     *
1092
     * @memberOf platform
1093
     * @type string|null
1094
     */
1095
    platform.prerelease = prerelease;
1096
1097
    /**
1098
     * The name of the product hosting the browser.
1099
     *
1100
     * The list of common products include:
1101
     *
1102
     * "BlackBerry", "Galaxy S4", "Lumia", "iPad", "iPod", "iPhone", "Kindle",
1103
     * "Kindle Fire", "Nexus", "Nook", "PlayBook", "TouchPad" and "Transformer"
1104
     *
1105
     * @memberOf platform
1106
     * @type string|null
1107
     */
1108
    platform.product = product;
1109
1110
    /**
1111
     * The browser's user agent string.
1112
     *
1113
     * @memberOf platform
1114
     * @type string|null
1115
     */
1116
    platform.ua = ua;
1117
1118
    /**
1119
     * The browser/environment version.
1120
     *
1121
     * @memberOf platform
1122
     * @type string|null
1123
     */
1124
    platform.version = name && version;
1125
1126
    /**
1127
     * The name of the operating system.
1128
     *
1129
     * @memberOf platform
1130
     * @type Object
1131
     */
1132
    platform.os = os || {
1133
1134
      /**
1135
       * The CPU architecture the OS is built for.
1136
       *
1137
       * @memberOf platform.os
1138
       * @type number|null
1139
       */
1140
      'architecture': null,
1141
1142
      /**
1143
       * The family of the OS.
1144
       *
1145
       * Common values include:
1146
       * "Windows", "Windows Server 2008 R2 / 7", "Windows Server 2008 / Vista",
1147
       * "Windows XP", "OS X", "Ubuntu", "Debian", "Fedora", "Red Hat", "SuSE",
1148
       * "Android", "iOS" and "Windows Phone"
1149
       *
1150
       * @memberOf platform.os
1151
       * @type string|null
1152
       */
1153
      'family': null,
1154
1155
      /**
1156
       * The version of the OS.
1157
       *
1158
       * @memberOf platform.os
1159
       * @type string|null
1160
       */
1161
      'version': null,
1162
1163
      /**
1164
       * Returns the OS string.
1165
       *
1166
       * @memberOf platform.os
1167
       * @returns {string} The OS string.
1168
       */
1169
      'toString': function() { return 'null'; }
1170
    };
1171
1172
    platform.parse = parse;
1173
    platform.toString = toStringPlatform;
1174
1175
    if (platform.version) {
1176
      description.unshift(version);
1177
    }
1178
    if (platform.name) {
1179
      description.unshift(name);
1180
    }
1181
    if (os && name && !(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product))) {
1182
      description.push(product ? '(' + os + ')' : 'on ' + os);
1183
    }
1184
    if (description.length) {
1185
      platform.description = description.join(' ');
1186
    }
1187
    return platform;
1188
  }
1189
1190
  /*--------------------------------------------------------------------------*/
1191
1192
  // Export platform.
1193
  var platform = parse();
1194
1195
  // Some AMD build optimizers, like r.js, check for condition patterns like the following:
1196
  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
1197
    // Expose platform on the global object to prevent errors when platform is
1198
    // loaded by a script tag in the presence of an AMD loader.
1199
    // See http://requirejs.org/docs/errors.html#mismatch for more details.
1200
    root.platform = platform;
1201
1202
    // Define as an anonymous module so platform can be aliased through path mapping.
1203
    define(function() {
1204
      return platform;
1205
    });
1206
  }
1207
  // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
1208
  else if (freeExports && freeModule) {
1209
    // Export for CommonJS support.
1210
    forOwn(platform, function(value, key) {
1211
      freeExports[key] = value;
1212
    });
1213
  }
1214
  else {
1215
    // Export to the global object.
1216
    root.platform = platform;
1217
  }
1218
}.call(this));
1219