File: yui/js/yui-ua.js
/**
* The YUI module contains the components required for building the YUI seed
* file. This includes the script loading mechanism, a simple queue, and the
* core utilities for the library.
* @module yui
* @submodule yui-base
*/
/**
* YUI user agent detection.
* Do not fork for a browser if it can be avoided. Use feature detection when
* you can. Use the user agent as a last resort. For all fields listed
* as @type float, UA stores a version number for the browser engine,
* 0 otherwise. This value may or may not map to the version number of
* the browser using the engine. The value is presented as a float so
* that it can easily be used for boolean evaluation as well as for
* looking for a particular range of versions. Because of this,
* some of the granularity of the version info may be lost. The fields that
* are @type string default to null. The API docs list the values that
* these fields can have.
* @class UA
* @static
*/
/**
* Static method on `YUI.Env` for parsing a UA string. Called at instantiation
* to populate `Y.UA`.
*
* @static
* @method parseUA
* @param {String} [subUA=navigator.userAgent] UA string to parse
* @return {Object} The Y.UA object
*/
YUI.Env.parseUA = function(subUA) {
var numberify = function(s) {
var c = 0;
return parseFloat(s.replace(/\./g, function() {
return (c++ === 1) ? '' : '.';
}));
},
win = Y.config.win,
nav = win && win.navigator,
o = {
/**
* Internet Explorer version number or 0. Example: 6
* @property ie
* @type float
* @static
*/
ie: 0,
/**
* Opera version number or 0. Example: 9.2
* @property opera
* @type float
* @static
*/
opera: 0,
/**
* Gecko engine revision number. Will evaluate to 1 if Gecko
* is detected but the revision could not be found. Other browsers
* will be 0. Example: 1.8
* <pre>
* Firefox 1.0.0.4: 1.7.8 <-- Reports 1.7
* Firefox 1.5.0.9: 1.8.0.9 <-- 1.8
* Firefox 2.0.0.3: 1.8.1.3 <-- 1.81
* Firefox 3.0 <-- 1.9
* Firefox 3.5 <-- 1.91
* </pre>
* @property gecko
* @type float
* @static
*/
gecko: 0,
/**
* AppleWebKit version. KHTML browsers that are not WebKit browsers
* will evaluate to 1, other browsers 0. Example: 418.9
* <pre>
* Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
* latest available for Mac OSX 10.3.
* Safari 2.0.2: 416 <-- hasOwnProperty introduced
* Safari 2.0.4: 418 <-- preventDefault fixed
* Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
* different versions of webkit
* Safari 2.0.4 (419.3): 419 <-- Tiger installations that have been
* updated, but not updated
* to the latest patch.
* Webkit 212 nightly: 522+ <-- Safari 3.0 precursor (with native
* SVG and many major issues fixed).
* Safari 3.0.4 (523.12) 523.12 <-- First Tiger release - automatic
* update from 2.x via the 10.4.11 OS patch.
* Webkit nightly 1/2008:525+ <-- Supports DOMContentLoaded event.
* yahoo.com user agent hack removed.
* </pre>
* http://en.wikipedia.org/wiki/Safari_version_history
* @property webkit
* @type float
* @static
*/
webkit: 0,
/**
* Safari will be detected as webkit, but this property will also
* be populated with the Safari version number
* @property safari
* @type float
* @static
*/
safari: 0,
/**
* Chrome will be detected as webkit, but this property will also
* be populated with the Chrome version number
* @property chrome
* @type float
* @static
*/
chrome: 0,
/**
* The mobile property will be set to a string containing any relevant
* user agent information when a modern mobile browser is detected.
* Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
* devices with the WebKit-based browser, and Opera Mini.
* @property mobile
* @type string
* @default null
* @static
*/
mobile: null,
/**
* Adobe AIR version number or 0. Only populated if webkit is detected.
* Example: 1.0
* @property air
* @type float
*/
air: 0,
/**
* PhantomJS version number or 0. Only populated if webkit is detected.
* Example: 1.0
* @property phantomjs
* @type float
*/
phantomjs: 0,
/**
* Detects Apple iPad's OS version
* @property ipad
* @type float
* @static
*/
ipad: 0,
/**
* Detects Apple iPhone's OS version
* @property iphone
* @type float
* @static
*/
iphone: 0,
/**
* Detects Apples iPod's OS version
* @property ipod
* @type float
* @static
*/
ipod: 0,
/**
* General truthy check for iPad, iPhone or iPod
* @property ios
* @type Boolean
* @default null
* @static
*/
ios: null,
/**
* Detects Googles Android OS version
* @property android
* @type float
* @static
*/
android: 0,
/**
* Detects Kindle Silk
* @property silk
* @type float
* @static
*/
silk: 0,
/**
* Detects Ubuntu version
* @property ubuntu
* @type float
* @static
*/
ubuntu: 0,
/**
* Detects Kindle Silk Acceleration
* @property accel
* @type Boolean
* @static
*/
accel: false,
/**
* Detects Palms WebOS version
* @property webos
* @type float
* @static
*/
webos: 0,
/**
* Google Caja version number or 0.
* @property caja
* @type float
*/
caja: nav && nav.cajaVersion,
/**
* Set to true if the page appears to be in SSL
* @property secure
* @type boolean
* @static
*/
secure: false,
/**
* The operating system. Currently only detecting windows or macintosh
* @property os
* @type string
* @default null
* @static
*/
os: null,
/**
* The Nodejs Version
* @property nodejs
* @type float
* @default 0
* @static
*/
nodejs: 0,
/**
* Window8/IE10 Application host environment
* @property winjs
* @type Boolean
* @static
*/
winjs: !!((typeof Windows !== "undefined") && Windows.System),
/**
* Are touch/msPointer events available on this device
* @property touchEnabled
* @type Boolean
* @static
*/
touchEnabled: false
},
ua = subUA || nav && nav.userAgent,
loc = win && win.location,
href = loc && loc.href,
m;
/**
* The User Agent string that was parsed
* @property userAgent
* @type String
* @static
*/
o.userAgent = ua;
o.secure = href && (href.toLowerCase().indexOf('https') === 0);
if (ua) {
if ((/windows|win32/i).test(ua)) {
o.os = 'windows';
} else if ((/macintosh|mac_powerpc/i).test(ua)) {
o.os = 'macintosh';
} else if ((/android/i).test(ua)) {
o.os = 'android';
} else if ((/symbos/i).test(ua)) {
o.os = 'symbos';
} else if ((/linux/i).test(ua)) {
o.os = 'linux';
} else if ((/rhino/i).test(ua)) {
o.os = 'rhino';
}
// Modern KHTML browsers should qualify as Safari X-Grade
if ((/KHTML/).test(ua)) {
o.webkit = 1;
}
if ((/IEMobile|XBLWP7/).test(ua)) {
o.mobile = 'windows';
}
if ((/Fennec/).test(ua)) {
o.mobile = 'gecko';
}
// Modern WebKit browsers are at least X-Grade
m = ua.match(/AppleWebKit\/([^\s]*)/);
if (m && m[1]) {
o.webkit = numberify(m[1]);
o.safari = o.webkit;
if (/PhantomJS/.test(ua)) {
m = ua.match(/PhantomJS\/([^\s]*)/);
if (m && m[1]) {
o.phantomjs = numberify(m[1]);
}
}
// Mobile browser check
if (/ Mobile\//.test(ua) || (/iPad|iPod|iPhone/).test(ua)) {
o.mobile = 'Apple'; // iPhone or iPod Touch
m = ua.match(/OS ([^\s]*)/);
if (m && m[1]) {
m = numberify(m[1].replace('_', '.'));
}
o.ios = m;
o.os = 'ios';
o.ipad = o.ipod = o.iphone = 0;
m = ua.match(/iPad|iPod|iPhone/);
if (m && m[0]) {
o[m[0].toLowerCase()] = o.ios;
}
} else {
m = ua.match(/NokiaN[^\/]*|webOS\/\d\.\d/);
if (m) {
// Nokia N-series, webOS, ex: NokiaN95
o.mobile = m[0];
}
if (/webOS/.test(ua)) {
o.mobile = 'WebOS';
m = ua.match(/webOS\/([^\s]*);/);
if (m && m[1]) {
o.webos = numberify(m[1]);
}
}
if (/ Android/.test(ua)) {
if (/Mobile/.test(ua)) {
o.mobile = 'Android';
}
m = ua.match(/Android ([^\s]*);/);
if (m && m[1]) {
o.android = numberify(m[1]);
}
}
if (/Silk/.test(ua)) {
m = ua.match(/Silk\/([^\s]*)/);
if (m && m[1]) {
o.silk = numberify(m[1]);
}
if (!o.android) {
o.android = 2.34; //Hack for desktop mode in Kindle
o.os = 'Android';
}
if (/Accelerated=true/.test(ua)) {
o.accel = true;
}
}
}
m = ua.match(/OPR\/(\d+\.\d+)/);
if (m && m[1]) {
// Opera 15+ with Blink (pretends to be both Chrome and Safari)
o.opera = numberify(m[1]);
} else {
m = ua.match(/(Chrome|CrMo|CriOS)\/([^\s]*)/);
if (m && m[1] && m[2]) {
o.chrome = numberify(m[2]); // Chrome
o.safari = 0; //Reset safari back to 0
if (m[1] === 'CrMo') {
o.mobile = 'chrome';
}
} else {
m = ua.match(/AdobeAIR\/([^\s]*)/);
if (m) {
o.air = m[0]; // Adobe AIR 1.0 or better
}
}
}
}
m = ua.match(/Ubuntu\ (\d+\.\d+)/);
if (m && m[1]) {
o.os = 'linux';
o.ubuntu = numberify(m[1]);
m = ua.match(/\ WebKit\/([^\s]*)/);
if (m && m[1]) {
o.webkit = numberify(m[1]);
}
m = ua.match(/\ Chromium\/([^\s]*)/);
if (m && m[1]) {
o.chrome = numberify(m[1]);
}
if (/ Mobile$/.test(ua)) {
o.mobile = 'Ubuntu';
}
}
if (!o.webkit) { // not webkit
// @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
if (/Opera/.test(ua)) {
m = ua.match(/Opera[\s\/]([^\s]*)/);
if (m && m[1]) {
o.opera = numberify(m[1]);
}
m = ua.match(/Version\/([^\s]*)/);
if (m && m[1]) {
o.opera = numberify(m[1]); // opera 10+
}
if (/Opera Mobi/.test(ua)) {
o.mobile = 'opera';
m = ua.replace('Opera Mobi', '').match(/Opera ([^\s]*)/);
if (m && m[1]) {
o.opera = numberify(m[1]);
}
}
m = ua.match(/Opera Mini[^;]*/);
if (m) {
o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
}
} else { // not opera or webkit
m = ua.match(/MSIE ([^;]*)|Trident.*; rv:([0-9.]+)/);
if (m && (m[1] || m[2])) {
o.ie = numberify(m[1] || m[2]);
} else { // not opera, webkit, or ie
m = ua.match(/Gecko\/([^\s]*)/);
if (m) {
o.gecko = 1; // Gecko detected, look for revision
m = ua.match(/rv:([^\s\)]*)/);
if (m && m[1]) {
o.gecko = numberify(m[1]);
if (/Mobile|Tablet/.test(ua)) {
o.mobile = "ffos";
}
}
}
}
}
}
}
//Check for known properties to tell if touch events are enabled on this device or if
//the number of MSPointer touchpoints on this device is greater than 0.
if (win && nav && !(o.chrome && o.chrome < 6)) {
o.touchEnabled = (("ontouchstart" in win) || (("msMaxTouchPoints" in nav) && (nav.msMaxTouchPoints > 0)));
}
//It was a parsed UA, do not assign the global value.
if (!subUA) {
if (typeof process === 'object') {
if (process.versions && process.versions.node) {
//NodeJS
o.os = process.platform;
o.nodejs = numberify(process.versions.node);
}
}
YUI.Env.UA = o;
}
return o;
};
Y.UA = YUI.Env.UA || YUI.Env.parseUA();
/**
Performs a simple comparison between two version numbers, accounting for
standard versioning logic such as the fact that "535.8" is a lower version than
"535.24", even though a simple numerical comparison would indicate that it's
greater. Also accounts for cases such as "1.1" vs. "1.1.0", which are
considered equivalent.
Returns -1 if version _a_ is lower than version _b_, 0 if they're equivalent,
1 if _a_ is higher than _b_.
Versions may be numbers or strings containing numbers and dots. For example,
both `535` and `"535.8.10"` are acceptable. A version string containing
non-numeric characters, like `"535.8.beta"`, may produce unexpected results.
@method compareVersions
@param {Number|String} a First version number to compare.
@param {Number|String} b Second version number to compare.
@return -1 if _a_ is lower than _b_, 0 if they're equivalent, 1 if _a_ is
higher than _b_.
**/
Y.UA.compareVersions = function (a, b) {
var aPart, aParts, bPart, bParts, i, len;
if (a === b) {
return 0;
}
aParts = (a + '').split('.');
bParts = (b + '').split('.');
for (i = 0, len = Math.max(aParts.length, bParts.length); i < len; ++i) {
aPart = parseInt(aParts[i], 10);
bPart = parseInt(bParts[i], 10);
/*jshint expr: true*/
isNaN(aPart) && (aPart = 0);
isNaN(bPart) && (bPart = 0);
if (aPart < bPart) {
return -1;
}
if (aPart > bPart) {
return 1;
}
}
return 0;
};