1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 window.SC = window.SC || { MODULE_INFO: {}, LAZY_INSTANTIATION: {} };
  9 
 10 
 11 /**
 12   The list of browsers that are automatically identified.
 13 
 14   @readonly
 15   @enum
 16 */
 17 SC.BROWSER = {
 18   android: 'android',
 19   blackberry: 'blackberry',
 20   chrome: 'chrome',
 21   firefox: 'firefox',
 22   ie: 'ie',
 23   opera: 'opera',
 24   safari: 'safari',
 25   unknown: 'unknown'
 26 };
 27 
 28 /**
 29   The list of browser specific object prefixes, these are matched to the
 30   browser engine.
 31 
 32   @readonly
 33   @enum
 34 */
 35 SC.CLASS_PREFIX = {
 36   gecko: 'Moz',
 37   opera: 'O',
 38   presto: 'O',
 39   trident: 'Ms', // Note the uppercase 'M'
 40   webkit: 'WebKit' // Note the uppercase 'K'
 41 };
 42 
 43 /**
 44   The list of browser specific CSS prefixes, these are matched to the
 45   browser engine.
 46 
 47   @readonly
 48   @enum
 49 */
 50 SC.CSS_PREFIX = {
 51   gecko: '-moz-',
 52   opera: '-o-',
 53   presto: '-o-',
 54   trident: '-ms-',
 55   webkit: '-webkit-'
 56 };
 57 
 58 /**
 59   The list of devices that are automatically identified.
 60 
 61   @readonly
 62   @enum
 63 */
 64 SC.DEVICE = {
 65   android: 'android',
 66   blackberry: 'blackberry',
 67   desktop: 'desktop',
 68   ipad: 'ipad',
 69   iphone: 'iphone',
 70   ipod: 'ipod',
 71   mobile: 'mobile'
 72 };
 73 
 74 /**
 75   The list of browser specific DOM prefixes, these are matched to the
 76   browser engine.
 77 
 78   @readonly
 79   @enum
 80 */
 81 SC.DOM_PREFIX = {
 82   gecko: 'Moz',
 83   opera: 'O',
 84   presto: 'O',
 85   trident: 'ms',
 86   webkit: 'Webkit'
 87 };
 88 
 89 /**
 90   The list of browser engines that are automatically identified.
 91 
 92   @readonly
 93   @enum
 94 */
 95 SC.ENGINE = {
 96   gecko: 'gecko',
 97   opera: 'opera',
 98   presto: 'presto',
 99   trident: 'trident',
100   webkit: 'webkit'
101 };
102 
103 /**
104   The list of operating systems that are automatically identified.
105 
106   @readonly
107   @enum
108 */
109 SC.OS = {
110   android: 'android',
111   blackberry: 'blackberry',
112   ios: 'ios',
113   linux: 'linux',
114   mac: 'mac',
115   win: 'windows'
116 };
117 
118 
119 /**
120   Detects browser properties based on the given userAgent and language.
121 
122   @private
123 */
124 SC.detectBrowser = function (userAgent, language) {
125   var browser = {},
126       device,
127       engineAndVersion,
128       isIOSDevice,
129       conExp = '(?:[\\/:\\::\\s:;])', // Match the connecting character
130       numExp = '(\\S+[^\\s:;:\\)]|)', // Match the "number"
131       nameAndVersion,
132       os, osAndVersion,
133       override;
134 
135   // Use the current values if none are provided.
136   userAgent = (userAgent || navigator.userAgent).toLowerCase();
137   language = language || navigator.language || navigator.browserLanguage;
138 
139   // Calculations to determine the device.  See SC.DEVICE.
140   device =
141     userAgent.match(new RegExp('(android|ipad|iphone|ipod|blackberry)')) ||
142     userAgent.match(new RegExp('(mobile)')) ||
143     ['', SC.DEVICE.desktop];
144 
145   /**
146     @name SC.browser.device
147     @type SC.DEVICE|SC.BROWSER.unknown
148   */
149   browser.device = device[1];
150 
151 
152   // It simplifies further matching by recognizing this group of devices.
153   isIOSDevice =
154     browser.device === SC.DEVICE.ipad ||
155     browser.device === SC.DEVICE.iphone ||
156     browser.device === SC.DEVICE.ipod;
157 
158 
159   // Calculations to determine the name and version.  See SC.BROWSER.
160 
161   nameAndVersion =
162     // Match the specific names first, avoiding commonly spoofed browsers.
163     userAgent.match(new RegExp('(opera|chrome|firefox|android|blackberry)' + conExp + numExp)) ||
164     userAgent.match(new RegExp('(ie|safari)' + conExp + numExp)) ||
165     userAgent.match(new RegExp('(trident)')) ||
166     ['', SC.BROWSER.unknown, '0'];
167 
168   // If the device is an iOS device, use SC.BROWSER.safari for browser.name.
169   if (isIOSDevice) { nameAndVersion[1] = SC.BROWSER.safari; }
170 
171   // If a `Version` number is found, use that over the `Name` number
172   override = userAgent.match(new RegExp('(version)' + conExp + numExp));
173   if (override) { nameAndVersion[2] = override[2]; }
174   // If there is no `Version` in Safari, don't use the Safari number since it is
175   // the Webkit number.
176   else if (nameAndVersion[1] === SC.BROWSER.safari) { nameAndVersion[2] = '0'; }
177   else if (nameAndVersion[1] === SC.ENGINE.trident) {
178     // Special handling for IE11 (no 'ie' component, only 'trident' + 'rv')
179     nameAndVersion[1] = SC.BROWSER.ie;
180     this._ieVersion = nameAndVersion[2];
181     nameAndVersion[2] = userAgent.match(new RegExp('(rv)' + conExp + numExp))[2];
182   }
183 
184   /**
185     @name SC.browser.name
186     @type SC.BROWSER|SC.BROWSER.unknown
187   */
188   browser.name = nameAndVersion[1];
189 
190   /**
191     @name SC.browser.version
192     @type String
193   */
194   browser.version = nameAndVersion[2];
195 
196 
197   // Calculations to determine the engine and version.  See SC.ENGINE.
198   engineAndVersion =
199     // Match the specific engines first, avoiding commonly spoofed browsers.
200     userAgent.match(new RegExp('(presto)' + conExp + numExp)) ||
201     userAgent.match(new RegExp('(opera|trident|webkit|gecko)' + conExp + numExp)) ||
202     ['', SC.BROWSER.unknown, '0'];
203 
204   // If the browser is SC.BROWSER.ie, use SC.ENGINE.trident.
205   override = browser.name === SC.BROWSER.ie ? SC.ENGINE.trident : false;
206   if (override) { engineAndVersion[1] = override; }
207 
208   // If the engineVersion is unknown and the browser is SC.BROWSER.ie, use
209   // browser.version for browser.engineVersion.
210   override = browser.name === SC.BROWSER.ie && engineAndVersion[2] === '0';
211   if (override) { engineAndVersion[2] = browser.version; }
212 
213   // If a `rv` number is found, use that over the engine number (except for IE11+ where 'rv' now indicates the browser version).
214   override = userAgent.match(new RegExp('(rv)' + conExp + numExp));
215   if (override && engineAndVersion[1] !== SC.ENGINE.trident) { engineAndVersion[2] = override[2]; }
216 
217 
218   /**
219     @name SC.browser.engine
220     @type SC.ENGINE|SC.BROWSER.unknown
221   */
222   browser.engine = engineAndVersion[1];
223 
224   /**
225     @name SC.browser.engineVersion
226     @type String
227   */
228   browser.engineVersion = engineAndVersion[2];
229 
230   /**
231     The prefix of browser specific methods on this platform.
232 
233     @name SC.browser.domPrefix
234     @type String
235   */
236   browser.domPrefix = SC.DOM_PREFIX[browser.engine];
237 
238   /**
239     The prefix of browser specific properties on this platform.
240 
241     @name SC.browser.classPrefix
242     @type String
243   */
244   browser.classPrefix = SC.CLASS_PREFIX[browser.engine];
245 
246   /**
247     The prefix of browser specific CSS properties on this platform.
248 
249     @name SC.browser.cssPrefix
250     @type String
251   */
252   browser.cssPrefix = SC.CSS_PREFIX[browser.engine];
253 
254 
255   // If we don't know the name of the browser, use the name of the engine.
256   if (browser.name === SC.BROWSER.unknown) { browser.name = browser.engine; }
257 
258   // Calculations to determine the os and version.  See SC.OS.
259   osAndVersion =
260     // Match the specific names first, avoiding commonly spoofed os's.
261     userAgent.match(new RegExp('(blackberry)')) ||
262     userAgent.match(new RegExp('(android|iphone(?: os)|windows(?: nt))' + conExp + numExp)) ||
263     userAgent.match(new RegExp('(os|mac(?: os)(?: x))' + conExp + numExp)) ||
264     userAgent.match(new RegExp('(linux)')) ||
265     [null, SC.BROWSER.unknown, '0'];
266 
267   // Normalize the os name.
268   if (isIOSDevice) { os = SC.OS.ios; }
269   else if (osAndVersion[1] === 'mac os x' || osAndVersion[1] === 'mac os') { os = SC.OS.mac; }
270   else if (osAndVersion[1] === 'windows nt') { os = SC.OS.win; }
271   else { os = osAndVersion[1]; }
272 
273   // Normalize the os version.
274   osAndVersion[2] = osAndVersion[2] ? osAndVersion[2].replace(/_/g, '.') : '0';
275 
276 
277   /**
278     @name SC.browser.os
279     @type SC.OS|SC.BROWSER.unknown
280   */
281   browser.os = os;
282 
283   /**
284     @name SC.browser.osVersion
285     @type String
286   */
287   browser.osVersion = osAndVersion[2];
288 
289 
290   // The following long list of properties have all been deprecated.  While they
291   // are a bit less verbose then the above constants, they lack standardization
292   // and can be prone to failure.  Rather than continuing to expand this list
293   // with more and more one-off comparisons, which often muddle the line between
294   // the browser, the engine, the os and the device, it seems more practical to
295   // only maintain the 7 identifiable properties listed above:  device, name,
296   // version, os, osVersion, engine and engineVersion.
297 
298   /** @deprecated Version 1.7. Use browser.os === SC.OS.win.
299     @name SC.browser.isWindows
300     @type Boolean
301   */
302   browser.windows = browser.isWindows = browser.os === SC.OS.win;
303 
304   /** @deprecated Version 1.7. Use browser.os === SC.OS.mac.
305     @name SC.browser.isMac
306     @type Boolean
307   */
308   browser.mac = browser.isMac = browser.os === SC.OS.mac;
309 
310   /** @deprecated Version 1.7. Use browser.os === SC.OS.mac && browser.compare(browser.osVersion, '10.7') == 0
311     @name SC.browser.isLion
312     @type Boolean
313   */
314   browser.lion = browser.isLion = !!(/mac os x 10_7/.test(userAgent) && !/like mac os x 10_7/.test(userAgent));
315 
316   /** @deprecated Version 1.7. Use browser.device === SC.DEVICE.iphone.
317     @name SC.browser.isiPhone
318     @type Boolean
319   */
320   browser.iPhone = browser.isiPhone = browser.device === SC.DEVICE.iphone;
321 
322   /** @deprecated Version 1.7. Use browser.device === SC.DEVICE.ipod.
323     @name SC.browser.isiPod
324     @type Boolean
325   */
326   browser.iPod = browser.isiPod = browser.device === SC.DEVICE.ipod;
327 
328   /** @deprecated Version 1.7. Use browser.device === SC.DEVICE.ipad.
329     @name SC.browser.isiPad
330     @type Boolean
331   */
332   browser.iPad = browser.isiPad = browser.device === SC.DEVICE.ipad;
333 
334   /** @deprecated Version 1.7. Use browser.os === SC.OS.ios.
335     @name SC.browser.isiOS
336     @type Boolean
337   */
338   browser.iOS = browser.isiOS = browser.os === SC.OS.ios;
339 
340   /** @deprecated Version 1.7. Use browser.os === SC.OS.android or browser.name === SC.BROWSER.android or browser.device === SC.DEVICE.android.
341     @name SC.browser.isAndroid
342     @type Boolean
343   */
344   browser.android = browser.isAndroid = browser.os === SC.OS.android;
345 
346   /** @deprecated Version 1.7. Use browser.version or browser.engineVersion.
347     @name SC.browser.opera
348     @type String
349   */
350   browser.opera = browser.name === SC.BROWSER.opera ? browser.version : '0';
351 
352   /** @deprecated Version 1.7. Use browser.name === SC.BROWSER.opera.
353     @name SC.browser.isOpera
354     @type Boolean
355   */
356   browser.isOpera = browser.name === SC.BROWSER.opera;
357 
358   /** @deprecated Version 1.7. Use browser.version or browser.engineVersion.
359     @name SC.browser.msie
360     @type String
361   */
362   browser.msie = browser.name === SC.BROWSER.ie ? browser.version : '0';
363 
364   /** @deprecated Version 1.7. Use browser.engine === SC.ENGINE.trident.
365     @name SC.browser.isIE
366     @type Boolean
367   */
368   browser.isIE = browser.engine === SC.ENGINE.trident;
369 
370   /** @deprecated Version 1.7. Use browser.compare(browser.version, '8') <= 0.
371     @name SC.browser.isIE8OrLower
372     @type Boolean
373   */
374   browser.isIE8OrLower = browser.name === SC.BROWSER.ie && browser.version <= 8;
375 
376   /** @deprecated Version 1.7. Use browser.version or browser.engineVersion.
377     @name SC.browser.mozilla
378     @type String
379   */
380   browser.mozilla = browser.engine === SC.ENGINE.gecko ? browser.version : '0';
381 
382   /** @deprecated Version 1.7. Use browser.name === SC.BROWSER.firefox or browser.engine === SC.ENGINE.gecko.
383     @name SC.browser.isMozilla
384     @type Boolean
385   */
386   browser.isMozilla = browser.engine === SC.ENGINE.gecko;
387 
388   /** @deprecated Version 1.7. Use browser.engineVersion.
389     @name SC.browser.webkit
390     @type String
391   */
392   browser.webkit = browser.engine === SC.ENGINE.webkit ? browser.engineVersion : '0';
393 
394   /** @deprecated Version 1.7. Use browser.engine === SC.ENGINE.webkit.
395     @name SC.browser.isWebkit
396     @type Boolean
397   */
398   browser.isWebkit = browser.engine === SC.ENGINE.webkit;
399 
400   /** @deprecated Version 1.7. Use browser.version.
401     @name SC.browser.chrome
402     @type String
403   */
404   browser.chrome = browser.name === SC.BROWSER.chrome ? browser.version : '0';
405 
406   /** @deprecated Version 1.7. Use browser.name === SC.BROWSER.chrome.
407     @name SC.browser.isChrome
408     @type Boolean
409   */
410   browser.isChrome = browser.name === SC.BROWSER.chrome;
411 
412   /** @deprecated Version 1.7. Use browser.version.
413     @name SC.browser.mobileSafari
414     @type String
415   */
416   browser.mobileSafari = browser.os === SC.OS.ios ? browser.version : '0';
417 
418   /** @deprecated Version 1.7. Use browser.name === SC.BROWSER.safari && browser.os === SC.OS.ios
419     @name SC.browser.isMobileSafari
420     @type Boolean
421   */
422   browser.isMobileSafari = browser.name === SC.BROWSER.safari && browser.os === SC.OS.ios;
423 
424   /** @deprecated Version 1.7. Use browser.version.
425     @name SC.browser.iPadSafari
426     @type String
427   */
428   browser.iPadSafari = browser.device === SC.DEVICE.ipad && browser.name === SC.BROWSER.safari ?
429     browser.version : 0;
430 
431   /** @deprecated Version 1.7. Use browser.device === SC.DEVICE.ipad && browser.name === SC.BROWSER.safari
432     @name SC.browser.isiPadSafari
433     @type Boolean
434   */
435   browser.isiPadSafari = browser.device === SC.DEVICE.ipad && browser.name === SC.BROWSER.safari;
436 
437   /** @deprecated Version 1.7. Use browser.version.
438     @name SC.browser.iPhoneSafari
439     @type String
440   */
441   browser.iPhoneSafari = browser.device === SC.DEVICE.iphone && browser.name === SC.BROWSER.safari ?
442     browser.version : 0;
443 
444   /** @deprecated Version 1.7. Use browser.device === SC.DEVICE.iphone && browser.name === SC.BROWSER.safari
445     @name SC.browser.isiPhoneSafari
446     @type Boolean
447   */
448   browser.isiPhoneSafari = browser.device === SC.DEVICE.iphone && browser.name === SC.BROWSER.safari;
449 
450   /** @deprecated Version 1.7. Use browser.version.
451     @name SC.browser.iPodSafari
452     @type String
453   */
454   browser.iPodSafari = browser.device === SC.DEVICE.ipod && browser.name === SC.BROWSER.safari ?
455     browser.version : 0;
456 
457   /** @deprecated Version 1.7. Use browser.device === SC.DEVICE.ipod && browser.name === SC.BROWSER.safari
458     @name SC.browser.isiPodSafari
459     @type Boolean
460   */
461   browser.isiPodSafari = browser.device === SC.DEVICE.ipod && browser.name === SC.BROWSER.safari;
462 
463   /** @deprecated Version 1.7. Use SC.platform.standalone.
464     @name SC.browser.isiOSHomeScreen
465     @type Boolean
466   */
467   browser.isiOSHomeScreen = browser.isMobileSafari && !(/apple.*mobile.*safari/.test(userAgent));
468 
469   /** @deprecated Version 1.7. Use browser.version.
470     @name SC.browser.safari
471     @type String
472   */
473   browser.safari = browser.name === SC.BROWSER.safari && browser.os === SC.OS.mac ?
474     browser.version : 0;
475 
476   /** @deprecated Version 1.7. Use browser.name === SC.BROWSER.safari && browser.os === SC.OS.mac.
477     @name SC.browser.isSafari
478     @type Boolean
479   */
480   browser.isSafari = browser.name === SC.BROWSER.safari && browser.os === SC.OS.mac;
481 
482   /**
483     @name SC.browser.language
484     @type String
485   */
486   browser.language = language.split('-', 1)[0];
487 
488   /**
489     @name SC.browser.countryCode
490     @type String
491   */
492   browser.countryCode = language.split('-')[1] ? language.split('-')[1].toLowerCase() : undefined;
493 
494   /** @deprecated Version 1.7. Use browser.name.  See SC.BROWSER for possible values.
495     @name SC.browser.current
496     @type String
497   */
498   browser.current = browser.name;
499 
500   return browser;
501 };
502 
503 
504 /** @class
505 
506   This object contains information about the browser environment SproutCore is
507   running in. This includes the following properties:
508 
509     - browser.device                  ex. SC.DEVICE.ipad
510     - browser.name                    ex. SC.BROWSER.chrome
511     - browser.version                 ex. '16.0.2.34'
512     - browser.os                      ex. SC.OS.mac
513     - browser.osVersion               ex. '10.6'
514     - browser.engine                  ex. SC.ENGINE.webkit
515     - browser.engineVersion           ex. '533.29'
516     - browser.cssPrefix               ex. '-webkit-'
517     - browser.classPrefix            ex. 'WebKit'
518     - browser.domPrefix               ex. 'webkit'
519 
520   Note: User agent sniffing does not provide guaranteed results and spoofing may
521   affect the accuracy.  Therefore, as a general rule, it is much better
522   to rely on the browser's verified capabilities in SC.platform. But if you must
523   write browser specific code, understand that SC.browser does an exceptional
524   job at identifying the current browser.
525 
526   Based on the unit test samples, the most stable browser properties appear to
527   be `engine` and `engineVersion`.
528 
529   @since Version 1.0
530 */
531 SC.browser = SC.detectBrowser();
532