API Docs for: 3.10.3
Show:

File: yui/js/yui-ua.js

  1. /**
  2. * The YUI module contains the components required for building the YUI seed
  3. * file. This includes the script loading mechanism, a simple queue, and the
  4. * core utilities for the library.
  5. * @module yui
  6. * @submodule yui-base
  7. */
  8.  
  9. /**
  10. * YUI user agent detection.
  11. * Do not fork for a browser if it can be avoided. Use feature detection when
  12. * you can. Use the user agent as a last resort. For all fields listed
  13. * as @type float, UA stores a version number for the browser engine,
  14. * 0 otherwise. This value may or may not map to the version number of
  15. * the browser using the engine. The value is presented as a float so
  16. * that it can easily be used for boolean evaluation as well as for
  17. * looking for a particular range of versions. Because of this,
  18. * some of the granularity of the version info may be lost. The fields that
  19. * are @type string default to null. The API docs list the values that
  20. * these fields can have.
  21. * @class UA
  22. * @static
  23. */
  24.  
  25. /**
  26. * Static method on `YUI.Env` for parsing a UA string. Called at instantiation
  27. * to populate `Y.UA`.
  28. *
  29. * @static
  30. * @method parseUA
  31. * @param {String} [subUA=navigator.userAgent] UA string to parse
  32. * @return {Object} The Y.UA object
  33. */
  34. YUI.Env.parseUA = function(subUA) {
  35.  
  36. var numberify = function(s) {
  37. var c = 0;
  38. return parseFloat(s.replace(/\./g, function() {
  39. return (c++ === 1) ? '' : '.';
  40. }));
  41. },
  42.  
  43. win = Y.config.win,
  44.  
  45. nav = win && win.navigator,
  46.  
  47. o = {
  48.  
  49. /**
  50. * Internet Explorer version number or 0. Example: 6
  51. * @property ie
  52. * @type float
  53. * @static
  54. */
  55. ie: 0,
  56.  
  57. /**
  58. * Opera version number or 0. Example: 9.2
  59. * @property opera
  60. * @type float
  61. * @static
  62. */
  63. opera: 0,
  64.  
  65. /**
  66. * Gecko engine revision number. Will evaluate to 1 if Gecko
  67. * is detected but the revision could not be found. Other browsers
  68. * will be 0. Example: 1.8
  69. * <pre>
  70. * Firefox 1.0.0.4: 1.7.8 <-- Reports 1.7
  71. * Firefox 1.5.0.9: 1.8.0.9 <-- 1.8
  72. * Firefox 2.0.0.3: 1.8.1.3 <-- 1.81
  73. * Firefox 3.0 <-- 1.9
  74. * Firefox 3.5 <-- 1.91
  75. * </pre>
  76. * @property gecko
  77. * @type float
  78. * @static
  79. */
  80. gecko: 0,
  81.  
  82. /**
  83. * AppleWebKit version. KHTML browsers that are not WebKit browsers
  84. * will evaluate to 1, other browsers 0. Example: 418.9
  85. * <pre>
  86. * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
  87. * latest available for Mac OSX 10.3.
  88. * Safari 2.0.2: 416 <-- hasOwnProperty introduced
  89. * Safari 2.0.4: 418 <-- preventDefault fixed
  90. * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
  91. * different versions of webkit
  92. * Safari 2.0.4 (419.3): 419 <-- Tiger installations that have been
  93. * updated, but not updated
  94. * to the latest patch.
  95. * Webkit 212 nightly: 522+ <-- Safari 3.0 precursor (with native
  96. * SVG and many major issues fixed).
  97. * Safari 3.0.4 (523.12) 523.12 <-- First Tiger release - automatic
  98. * update from 2.x via the 10.4.11 OS patch.
  99. * Webkit nightly 1/2008:525+ <-- Supports DOMContentLoaded event.
  100. * yahoo.com user agent hack removed.
  101. * </pre>
  102. * http://en.wikipedia.org/wiki/Safari_version_history
  103. * @property webkit
  104. * @type float
  105. * @static
  106. */
  107. webkit: 0,
  108.  
  109. /**
  110. * Safari will be detected as webkit, but this property will also
  111. * be populated with the Safari version number
  112. * @property safari
  113. * @type float
  114. * @static
  115. */
  116. safari: 0,
  117.  
  118. /**
  119. * Chrome will be detected as webkit, but this property will also
  120. * be populated with the Chrome version number
  121. * @property chrome
  122. * @type float
  123. * @static
  124. */
  125. chrome: 0,
  126.  
  127. /**
  128. * The mobile property will be set to a string containing any relevant
  129. * user agent information when a modern mobile browser is detected.
  130. * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
  131. * devices with the WebKit-based browser, and Opera Mini.
  132. * @property mobile
  133. * @type string
  134. * @default null
  135. * @static
  136. */
  137. mobile: null,
  138.  
  139. /**
  140. * Adobe AIR version number or 0. Only populated if webkit is detected.
  141. * Example: 1.0
  142. * @property air
  143. * @type float
  144. */
  145. air: 0,
  146. /**
  147. * PhantomJS version number or 0. Only populated if webkit is detected.
  148. * Example: 1.0
  149. * @property phantomjs
  150. * @type float
  151. */
  152. phantomjs: 0,
  153. /**
  154. * Detects Apple iPad's OS version
  155. * @property ipad
  156. * @type float
  157. * @static
  158. */
  159. ipad: 0,
  160. /**
  161. * Detects Apple iPhone's OS version
  162. * @property iphone
  163. * @type float
  164. * @static
  165. */
  166. iphone: 0,
  167. /**
  168. * Detects Apples iPod's OS version
  169. * @property ipod
  170. * @type float
  171. * @static
  172. */
  173. ipod: 0,
  174. /**
  175. * General truthy check for iPad, iPhone or iPod
  176. * @property ios
  177. * @type Boolean
  178. * @default null
  179. * @static
  180. */
  181. ios: null,
  182. /**
  183. * Detects Googles Android OS version
  184. * @property android
  185. * @type float
  186. * @static
  187. */
  188. android: 0,
  189. /**
  190. * Detects Kindle Silk
  191. * @property silk
  192. * @type float
  193. * @static
  194. */
  195. silk: 0,
  196. /**
  197. * Detects Kindle Silk Acceleration
  198. * @property accel
  199. * @type Boolean
  200. * @static
  201. */
  202. accel: false,
  203. /**
  204. * Detects Palms WebOS version
  205. * @property webos
  206. * @type float
  207. * @static
  208. */
  209. webos: 0,
  210.  
  211. /**
  212. * Google Caja version number or 0.
  213. * @property caja
  214. * @type float
  215. */
  216. caja: nav && nav.cajaVersion,
  217.  
  218. /**
  219. * Set to true if the page appears to be in SSL
  220. * @property secure
  221. * @type boolean
  222. * @static
  223. */
  224. secure: false,
  225.  
  226. /**
  227. * The operating system. Currently only detecting windows or macintosh
  228. * @property os
  229. * @type string
  230. * @default null
  231. * @static
  232. */
  233. os: null,
  234.  
  235. /**
  236. * The Nodejs Version
  237. * @property nodejs
  238. * @type float
  239. * @default 0
  240. * @static
  241. */
  242. nodejs: 0,
  243. /**
  244. * Window8/IE10 Application host environment
  245. * @property winjs
  246. * @type Boolean
  247. * @static
  248. */
  249. winjs: !!((typeof Windows !== "undefined") && Windows.System),
  250. /**
  251. * Are touch/msPointer events available on this device
  252. * @property touchEnabled
  253. * @type Boolean
  254. * @static
  255. */
  256. touchEnabled: false
  257. },
  258.  
  259. ua = subUA || nav && nav.userAgent,
  260.  
  261. loc = win && win.location,
  262.  
  263. href = loc && loc.href,
  264.  
  265. m;
  266.  
  267. /**
  268. * The User Agent string that was parsed
  269. * @property userAgent
  270. * @type String
  271. * @static
  272. */
  273. o.userAgent = ua;
  274.  
  275.  
  276. o.secure = href && (href.toLowerCase().indexOf('https') === 0);
  277.  
  278. if (ua) {
  279.  
  280. if ((/windows|win32/i).test(ua)) {
  281. o.os = 'windows';
  282. } else if ((/macintosh|mac_powerpc/i).test(ua)) {
  283. o.os = 'macintosh';
  284. } else if ((/android/i).test(ua)) {
  285. o.os = 'android';
  286. } else if ((/symbos/i).test(ua)) {
  287. o.os = 'symbos';
  288. } else if ((/linux/i).test(ua)) {
  289. o.os = 'linux';
  290. } else if ((/rhino/i).test(ua)) {
  291. o.os = 'rhino';
  292. }
  293.  
  294. // Modern KHTML browsers should qualify as Safari X-Grade
  295. if ((/KHTML/).test(ua)) {
  296. o.webkit = 1;
  297. }
  298. if ((/IEMobile|XBLWP7/).test(ua)) {
  299. o.mobile = 'windows';
  300. }
  301. if ((/Fennec/).test(ua)) {
  302. o.mobile = 'gecko';
  303. }
  304. // Modern WebKit browsers are at least X-Grade
  305. m = ua.match(/AppleWebKit\/([^\s]*)/);
  306. if (m && m[1]) {
  307. o.webkit = numberify(m[1]);
  308. o.safari = o.webkit;
  309.  
  310. if (/PhantomJS/.test(ua)) {
  311. m = ua.match(/PhantomJS\/([^\s]*)/);
  312. if (m && m[1]) {
  313. o.phantomjs = numberify(m[1]);
  314. }
  315. }
  316.  
  317. // Mobile browser check
  318. if (/ Mobile\//.test(ua) || (/iPad|iPod|iPhone/).test(ua)) {
  319. o.mobile = 'Apple'; // iPhone or iPod Touch
  320.  
  321. m = ua.match(/OS ([^\s]*)/);
  322. if (m && m[1]) {
  323. m = numberify(m[1].replace('_', '.'));
  324. }
  325. o.ios = m;
  326. o.os = 'ios';
  327. o.ipad = o.ipod = o.iphone = 0;
  328.  
  329. m = ua.match(/iPad|iPod|iPhone/);
  330. if (m && m[0]) {
  331. o[m[0].toLowerCase()] = o.ios;
  332. }
  333. } else {
  334. m = ua.match(/NokiaN[^\/]*|webOS\/\d\.\d/);
  335. if (m) {
  336. // Nokia N-series, webOS, ex: NokiaN95
  337. o.mobile = m[0];
  338. }
  339. if (/webOS/.test(ua)) {
  340. o.mobile = 'WebOS';
  341. m = ua.match(/webOS\/([^\s]*);/);
  342. if (m && m[1]) {
  343. o.webos = numberify(m[1]);
  344. }
  345. }
  346. if (/ Android/.test(ua)) {
  347. if (/Mobile/.test(ua)) {
  348. o.mobile = 'Android';
  349. }
  350. m = ua.match(/Android ([^\s]*);/);
  351. if (m && m[1]) {
  352. o.android = numberify(m[1]);
  353. }
  354.  
  355. }
  356. if (/Silk/.test(ua)) {
  357. m = ua.match(/Silk\/([^\s]*)\)/);
  358. if (m && m[1]) {
  359. o.silk = numberify(m[1]);
  360. }
  361. if (!o.android) {
  362. o.android = 2.34; //Hack for desktop mode in Kindle
  363. o.os = 'Android';
  364. }
  365. if (/Accelerated=true/.test(ua)) {
  366. o.accel = true;
  367. }
  368. }
  369. }
  370.  
  371. m = ua.match(/(Chrome|CrMo|CriOS)\/([^\s]*)/);
  372. if (m && m[1] && m[2]) {
  373. o.chrome = numberify(m[2]); // Chrome
  374. o.safari = 0; //Reset safari back to 0
  375. if (m[1] === 'CrMo') {
  376. o.mobile = 'chrome';
  377. }
  378. } else {
  379. m = ua.match(/AdobeAIR\/([^\s]*)/);
  380. if (m) {
  381. o.air = m[0]; // Adobe AIR 1.0 or better
  382. }
  383. }
  384. }
  385.  
  386. if (!o.webkit) { // not webkit
  387. // @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
  388. if (/Opera/.test(ua)) {
  389. m = ua.match(/Opera[\s\/]([^\s]*)/);
  390. if (m && m[1]) {
  391. o.opera = numberify(m[1]);
  392. }
  393. m = ua.match(/Version\/([^\s]*)/);
  394. if (m && m[1]) {
  395. o.opera = numberify(m[1]); // opera 10+
  396. }
  397.  
  398. if (/Opera Mobi/.test(ua)) {
  399. o.mobile = 'opera';
  400. m = ua.replace('Opera Mobi', '').match(/Opera ([^\s]*)/);
  401. if (m && m[1]) {
  402. o.opera = numberify(m[1]);
  403. }
  404. }
  405. m = ua.match(/Opera Mini[^;]*/);
  406.  
  407. if (m) {
  408. o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
  409. }
  410. } else { // not opera or webkit
  411. m = ua.match(/MSIE\s([^;]*)/);
  412. if (m && m[1]) {
  413. o.ie = numberify(m[1]);
  414. } else { // not opera, webkit, or ie
  415. m = ua.match(/Gecko\/([^\s]*)/);
  416. if (m) {
  417. o.gecko = 1; // Gecko detected, look for revision
  418. m = ua.match(/rv:([^\s\)]*)/);
  419. if (m && m[1]) {
  420. o.gecko = numberify(m[1]);
  421. if (/Mobile|Tablet/.test(ua)) {
  422. o.mobile = "ffos";
  423. }
  424. }
  425. }
  426. }
  427. }
  428. }
  429. }
  430.  
  431. //Check for known properties to tell if touch events are enabled on this device or if
  432. //the number of MSPointer touchpoints on this device is greater than 0.
  433. if (win && nav && !(o.chrome && o.chrome < 6)) {
  434. o.touchEnabled = (("ontouchstart" in win) || (("msMaxTouchPoints" in nav) && (nav.msMaxTouchPoints > 0)));
  435. }
  436.  
  437. //It was a parsed UA, do not assign the global value.
  438. if (!subUA) {
  439.  
  440. if (typeof process === 'object') {
  441.  
  442. if (process.versions && process.versions.node) {
  443. //NodeJS
  444. o.os = process.platform;
  445. o.nodejs = numberify(process.versions.node);
  446. }
  447. }
  448.  
  449. YUI.Env.UA = o;
  450.  
  451. }
  452.  
  453. return o;
  454. };
  455.  
  456.  
  457. Y.UA = YUI.Env.UA || YUI.Env.parseUA();
  458.  
  459. /**
  460. Performs a simple comparison between two version numbers, accounting for
  461. standard versioning logic such as the fact that "535.8" is a lower version than
  462. "535.24", even though a simple numerical comparison would indicate that it's
  463. greater. Also accounts for cases such as "1.1" vs. "1.1.0", which are
  464. considered equivalent.
  465.  
  466. Returns -1 if version _a_ is lower than version _b_, 0 if they're equivalent,
  467. 1 if _a_ is higher than _b_.
  468.  
  469. Versions may be numbers or strings containing numbers and dots. For example,
  470. both `535` and `"535.8.10"` are acceptable. A version string containing
  471. non-numeric characters, like `"535.8.beta"`, may produce unexpected results.
  472.  
  473. @method compareVersions
  474. @param {Number|String} a First version number to compare.
  475. @param {Number|String} b Second version number to compare.
  476. @return -1 if _a_ is lower than _b_, 0 if they're equivalent, 1 if _a_ is
  477. higher than _b_.
  478. **/
  479. Y.UA.compareVersions = function (a, b) {
  480. var aPart, aParts, bPart, bParts, i, len;
  481.  
  482. if (a === b) {
  483. return 0;
  484. }
  485.  
  486. aParts = (a + '').split('.');
  487. bParts = (b + '').split('.');
  488.  
  489. for (i = 0, len = Math.max(aParts.length, bParts.length); i < len; ++i) {
  490. aPart = parseInt(aParts[i], 10);
  491. bPart = parseInt(bParts[i], 10);
  492.  
  493. /*jshint expr: true*/
  494. isNaN(aPart) && (aPart = 0);
  495. isNaN(bPart) && (bPart = 0);
  496.  
  497. if (aPart < bPart) {
  498. return -1;
  499. }
  500.  
  501. if (aPart > bPart) {
  502. return 1;
  503. }
  504. }
  505.  
  506. return 0;
  507. };
  508.