/*! * Platform.js v1.0.0-pre * Copyright 2010-2012 John-David Dalton * Available under MIT license */ ;(function(window) { /** Backup possible window/global object */ var oldWin = window; /** Detect free variable `exports` */ var freeExports = typeof exports == 'object' && exports; /** Detect free variable `global` */ var freeGlobal = typeof global == 'object' && global && (global == global.global ? (window = global) : global); /** Opera regexp */ var reOpera = /Opera/; /** Used to resolve a value's internal [[Class]] */ var toString = {}.toString; /** Detect Java environment */ var java = /Java/.test(getClassOf(window.java)) && window.java; /** A character to represent alpha */ var alpha = java ? 'a' : '\u03b1'; /** A character to represent beta */ var beta = java ? 'b' : '\u03b2'; /** Browser document object */ var doc = window.document || {}; /** Used to check for own properties of an object */ var hasOwnProperty = {}.hasOwnProperty; /** Browser navigator object */ var nav = window.navigator || {}; /** * Detect Opera browser * http://www.howtocreate.co.uk/operaStuff/operaObject.html * http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini */ var opera = window.operamini || window.opera; /** Opera [[Class]] */ var operaClass = reOpera.test(operaClass = getClassOf(opera)) ? operaClass : (opera = null); /** Possible global object */ var thisBinding = this; /** Browser user agent string */ var userAgent = nav.userAgent || ''; /*--------------------------------------------------------------------------*/ /** * Capitalizes a string value. * @private * @param {String} string The string to capitalize. * @returns {String} The capitalized string. */ function capitalize(string) { string = String(string); return string.charAt(0).toUpperCase() + string.slice(1); } /** * An iteration utility for arrays and objects. * @private * @param {Array|Object} object The object to iterate over. * @param {Function} callback The function called per iteration. */ function each(object, callback) { var index = -1, length = object.length; if (length == length >>> 0) { while (++index < length) { callback(object[index], index, object); } } else { forOwn(object, callback); } } /** * Iterates over an object's own properties, executing the `callback` for each. * @private * @param {Object} object The object to iterate over. * @param {Function} callback The function executed per own property. */ function forOwn(object, callback) { for (var key in object) { hasKey(object, key) && callback(object[key], key, object); } } /** * Trim and conditionally capitalize string values. * @private * @param {String} string The string to format. * @returns {String} The formatted string. */ function format(string) { string = trim(string); return /^(?:webOS|i(?:OS|P))/.test(string) ? string : capitalize(string); } /** * Gets the internal [[Class]] of a value. * @private * @param {Mixed} value The value. * @returns {String} The [[Class]]. */ function getClassOf(value) { return value == null ? capitalize(value) : toString.call(value).slice(8, -1); } /** * Checks if an object has the specified key as a direct property. * @private * @param {Object} object The object to check. * @param {String} key The key to check for. * @returns {Boolean} Returns `true` if key is a direct property, else `false`. */ function hasKey() { // lazy define for others (not as accurate) hasKey = function(object, key) { var parent = object != null && (object.constructor || Object).prototype; return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]); }; // for modern browsers if (getClassOf(hasOwnProperty) == 'Function') { hasKey = function(object, key) { return object != null && hasOwnProperty.call(object, key); }; } // for Safari 2 else if ({}.__proto__ == Object.prototype) { hasKey = function(object, key) { var result = false; if (object != null) { object = Object(object); object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0]; } return result; }; } return hasKey.apply(this, arguments); } /** * Host objects can return type values that are different from their actual * data type. The objects we are concerned with usually return non-primitive * types of object, function, or unknown. * @private * @param {Mixed} object The owner of the property. * @param {String} property The property to check. * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`. */ function isHostType(object, property) { var type = object != null ? typeof object[property] : 'number'; return !/^(?:boolean|number|string|undefined)$/.test(type) && (type == 'object' ? !!object[property] : true); } /** * Prepares a string for use in a RegExp constructor by making hyphens and spaces optional. * @private * @param {String} string The string to qualify. * @returns {String} The qualified string. */ function qualify(string) { return String(string).replace(/([ -])(?!$)/g, '$1?'); } /** * A bare-bones` Array#reduce` like utility function. * @private * @param {Array} array The array to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} accumulator Initial value of the accumulator. * @returns {Mixed} The accumulator. */ function reduce(array, callback) { var accumulator = null; each(array, function(value, index) { accumulator = callback(accumulator, value, index, array); }); return accumulator; } /** * Removes leading and trailing whitespace from a string. * @private * @param {String} string The string to trim. * @returns {String} The trimmed string. */ function trim(string) { return String(string).replace(/^ +| +$/g, ''); } /*--------------------------------------------------------------------------*/ /** * Creates a new platform object. * @memberOf platform * @param {String} [ua = navigator.userAgent] The user agent string. * @returns {Object} A platform object. */ function parse(ua) { ua || (ua = userAgent); /** Temporary variable used over the script's lifetime */ var data; /** The CPU architecture */ var arch = ua; /** Platform description array */ var description = []; /** Platform alpha/beta indicator */ var prerelease = null; /** A flag to indicate that environment features should be used to resolve the platform */ var useFeatures = ua == userAgent; /** The browser/environment version */ var version = useFeatures && opera && typeof opera.version == 'function' && opera.version(); /* Detectable layout engines (order is important) */ var layout = getLayout([ { 'label': 'WebKit', 'pattern': 'AppleWebKit' }, 'iCab', 'Presto', 'NetFront', 'Tasman', 'Trident', 'KHTML', 'Gecko' ]); /* Detectable browser names (order is important) */ var name = getName([ 'Adobe AIR', 'Arora', 'Avant Browser', 'Camino', 'Epiphany', 'Fennec', 'Flock', 'Galeon', 'GreenBrowser', 'iCab', 'Iceweasel', 'Iron', 'K-Meleon', 'Konqueror', 'Lunascape', 'Maxthon', 'Midori', 'Nook Browser', 'PhantomJS', 'Raven', 'Rekonq', 'RockMelt', 'SeaMonkey', { 'label': 'Silk', 'pattern': '(?:Cloud9|Silk)' }, 'Sleipnir', 'SlimBrowser', 'Sunrise', 'Swiftfox', 'WebPositive', 'Opera Mini', 'Opera', 'Chrome', { 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' }, { 'label': 'IE', 'pattern': 'MSIE' }, 'Safari' ]); /* Detectable products (order is important) */ var product = getProduct([ 'BlackBerry', { 'label': 'Galaxy S', 'pattern': 'GT-I9000' }, { 'label': 'Galaxy S2', 'pattern': 'GT-I9100' }, 'iPad', 'iPod', 'iPhone', 'Kindle', { 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk)' }, 'Nook', 'PlayBook', 'TouchPad', 'Transformer', 'Xoom' ]); /* Detectable manufacturers */ var manufacturer = getManufacturer({ 'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 }, 'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 }, 'Asus': { 'Transformer': 1 }, 'Barnes & Noble': { 'Nook': 1 }, 'BlackBerry': { 'PlayBook': 1 }, 'HP': { 'TouchPad': 1 }, 'LG': { }, 'Motorola': { 'Xoom': 1 }, 'Nokia': { }, 'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1 } }); /* Detectable OSes (order is important) */ var os = getOS([ 'Android', 'CentOS', 'Debian', 'Fedora', 'FreeBSD', 'Gentoo', 'Haiku', 'Kubuntu', 'Linux Mint', 'Red Hat', 'SuSE', 'Ubuntu', 'Xubuntu', 'Cygwin', 'Symbian OS', 'hpwOS', 'webOS ', 'webOS', 'Tablet OS', 'Linux', 'Mac OS X', 'Macintosh', 'Mac', 'Windows 98;', 'Windows ' ]); /*------------------------------------------------------------------------*/ /** * Picks the layout engine from an array of guesses. * @private * @param {Array} guesses An array of guesses. * @returns {String|Null} The detected layout engine. */ function getLayout(guesses) { return reduce(guesses, function(result, guess) { return result || RegExp('\\b' + ( guess.pattern || qualify(guess) ) + '\\b', 'i').exec(ua) && (guess.label || guess); }); } /** * Picks the manufacturer from an array of guesses. * @private * @param {Array} guesses An array of guesses. * @returns {String|Null} The detected manufacturer. */ function getManufacturer(guesses) { return reduce(guesses, function(result, value, key) { // lookup the manufacturer by product or scan the UA for the manufacturer return result || ( value[product] || value[0/*Opera 9.25 fix*/, /^[a-z]+/i.exec(product)] || RegExp('\\b' + (key.pattern || qualify(key)) + '(?:\\b|\\w*\\d)', 'i').exec(ua) ) && (key.label || key); }); } /** * Picks the browser name from an array of guesses. * @private * @param {Array} guesses An array of guesses. * @returns {String|Null} The detected browser name. */ function getName(guesses) { return reduce(guesses, function(result, guess) { return result || RegExp('\\b' + ( guess.pattern || qualify(guess) ) + '\\b', 'i').exec(ua) && (guess.label || guess); }); } /** * Picks the OS name from an array of guesses. * @private * @param {Array} guesses An array of guesses. * @returns {String|Null} The detected OS name. */ function getOS(guesses) { return reduce(guesses, function(result, guess) { var pattern = guess.pattern || qualify(guess); if (!result && (result = RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua))) { // platform tokens defined at // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx // http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx data = { '6.2': '8', '6.1': 'Server 2008 R2 / 7', '6.0': 'Server 2008 / Vista', '5.2': 'Server 2003 / XP 64-bit', '5.1': 'XP', '5.01': '2000 SP1', '5.0': '2000', '4.0': 'NT', '4.90': 'ME' }; // detect Windows version from platform tokens if (/^Win/i.test(result) && (data = data[0/*Opera 9.25 fix*/, /[\d.]+$/.exec(result)])) { result = 'Windows ' + data; } // correct character case and cleanup result = format(String(result) .replace(RegExp(pattern, 'i'), guess.label || guess) .replace(/ ce$/i, ' CE') .replace(/hpw/i, 'web') .replace(/Macintosh/, 'Mac OS') .replace(/_PowerPC/i, ' OS') .replace(/(OS X) [^ \d]+/i, '$1') .replace(/\/(\d)/, ' $1') .replace(/_/g, '.') .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '') .replace(/x86\.64/gi, 'x86_64') .split(' on ')[0]); } return result; }); } /** * Picks the product name from an array of guesses. * @private * @param {Array} guesses An array of guesses. * @returns {String|Null} The detected product name. */ function getProduct(guesses) { return reduce(guesses, function(result, guess) { var pattern = guess.pattern || qualify(guess); if (!result && (result = RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) || RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua) )) { // split by forward slash and append product version if needed if ((result = String(guess.label || result).split('/'))[1] && !/[\d.]+/.test(result[0])) { result[0] += ' ' + result[1]; } // correct character case and cleanup guess = guess.label || guess; result = format(result[0] .replace(RegExp(pattern, 'i'), guess) .replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ') .replace(RegExp('(' + guess + ')(\\w)', 'i'), '$1 $2')); } return result; }); } /** * Resolves the version using an array of UA patterns. * @private * @param {Array} patterns An array of UA patterns. * @returns {String|Null} The detected version. */ function getVersion(patterns) { return reduce(patterns, function(result, pattern) { return result || (RegExp(pattern + '(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/-]*)', 'i').exec(ua) || 0)[1] || null; }); } /*------------------------------------------------------------------------*/ /** * Return platform description when the platform object is coerced to a string. * @name toString * @memberOf platform * @type Function * @returns {String} The platform description. */ function toStringPlatform() { return this.description || ''; } /*------------------------------------------------------------------------*/ // convert layout to an array so we can add extra details layout && (layout = [layout]); // detect product names that contain their manufacturer's name if (manufacturer && !product) { product = getProduct([manufacturer]); } // detect simulators if (/\bSimulator\b/i.test(ua)) { product = (product ? product + ' ' : '') + 'Simulator'; } // detect iOS if (/^iP/.test(product)) { name || (name = 'Safari'); os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua)) ? ' ' + data[1].replace(/_/g, '.') : ''); } // detect Kubuntu else if (name == 'Konqueror' && !/buntu/i.test(os)) { os = 'Kubuntu'; } // detect Android browsers else if (name == 'Chrome' && manufacturer) { name = 'Android Browser'; os = /Android/.test(os) ? os : 'Android'; } // detect false positives for Firefox/Safari else if (!name || (data = !/\bMinefield\b/i.test(ua) && /Firefox|Safari/.exec(name))) { // escape the `/` for Firefox 1 if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) { // clear name of false positives name = null; } // reassign a generic name if ((data = product || manufacturer || os) && (product || manufacturer || /Android|Symbian OS|Tablet OS|webOS/.test(os))) { name = /[a-z]+(?: Hat)?/i.exec(/Android/.test(os) ? os : data) + ' Browser'; } } // detect non-Opera versions (order is important) if (!version) { version = getVersion([ '(?:Cloud9|Opera ?Mini|Raven|Silk)', 'Version', qualify(name), '(?:Firefox|Minefield|NetFront)' ]); } // detect stubborn layout engines if (layout == 'iCab' && parseFloat(version) > 3) { layout = ['WebKit']; } else if (name == 'Konqueror' && /\bKHTML\b/i.test(ua)) { layout = ['KHTML']; } else if (data = /Opera/.test(name) && 'Presto' || /\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' || !layout && /\bMSIE\b/i.test(ua) && (/^Mac/.test(os) ? 'Tasman' : 'Trident')) { layout = [data]; } // leverage environment features if (useFeatures) { // detect server-side environments // Rhino has a global function while others have a global object if (isHostType(thisBinding, 'global')) { if (java) { data = java.lang.System; arch = data.getProperty('os.arch'); os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version'); } if (typeof exports == 'object' && exports) { // if `thisBinding` is the [ModuleScope] if (thisBinding == oldWin && typeof system == 'object' && (data = [system])[0]) { os || (os = data[0].os || null); try { data[1] = require('ringo/engine').version; version = data[1].join('.'); name = 'RingoJS'; } catch(e) { if (data[0].global == freeGlobal) { name = 'Narwhal'; } } } else if (typeof process == 'object' && (data = process)) { name = 'Node.js'; arch = data.arch; os = data.platform; version = /[\d.]+/.exec(data.version)[0]; } } else if (getClassOf(window.environment) == 'Environment') { name = 'Rhino'; } } // detect Adobe AIR else if (getClassOf(data = window.runtime) == 'ScriptBridgingProxyObject') { name = 'Adobe AIR'; os = data.flash.system.Capabilities.os; } // detect PhantomJS else if (getClassOf(data = window.phantom) == 'RuntimeObject') { name = 'PhantomJS'; version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch); } // detect IE compatibility modes else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) { // we're in compatibility mode when the Trident version + 4 doesn't // equal the document mode version = [version, doc.documentMode]; if ((data = +data[1] + 4) != version[1]) { description.push('IE ' + version[1] + ' mode'); layout[1] = ''; version[1] = data; } version = name == 'IE' ? String(version[1].toFixed(1)) : version[0]; } os = os && format(os); } // detect prerelease phases if (version && (data = /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) || /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) || /\bMinefield\b/i.test(ua) && 'a')) { prerelease = /b/i.test(data) ? 'beta' : 'alpha'; version = version.replace(RegExp(data + '\\+?$'), '') + (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || ''); } // obscure Maxthon's unreliable version if (name == 'Maxthon' && version) { version = version.replace(/\.[\d.]+/, '.x'); } // detect Silk desktop/accelerated modes else if (name == 'Silk') { if (!/Mobi/i.test(ua)) { os = 'Android'; description.unshift('desktop mode'); } if (/Accelerated *= *true/i.test(ua)) { description.unshift('accelerated'); } } // detect Windows Phone desktop mode else if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) { name += ' Mobile'; os = 'Windows Phone OS ' + data + '.x'; description.unshift('desktop mode'); } // add mobile postfix else if ((name == 'IE' || name && !product && !/Browser/.test(name)) && (os == 'Windows CE' || /Mobi/i.test(ua))) { name += ' Mobile'; } // detect IE platform preview else if (name == 'IE' && useFeatures && typeof external == 'object' && !external) { description.unshift('platform preview'); } // detect BlackBerry OS version // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp else if (/BlackBerry/.test(product) && (data = (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] || version)) { os = 'Device Software ' + data; version = null; } // detect Opera identifying/masking itself as another browser // http://www.opera.com/support/kb/view/843/ else if (this != forOwn && ( (useFeatures && opera) || (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) || (name == 'Firefox' && /OS X (?:\d+\.){2,}/.test(os)) || (name == 'IE' && ( (os && !/^Win/.test(os) && version > 5.5) || /Windows XP/.test(os) && version > 8 || version == 8 && !/Trident/.test(ua) )) ) && !reOpera.test(data = parse.call(forOwn, ua.replace(reOpera, '') + ';')) && data.name) { // when "indentifying" the UA contains both Opera and the other browser's name data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : ''); if (reOpera.test(name)) { if (/IE/.test(data) && os == 'Mac OS') { os = null; } data = 'identify' + data; } // when "masking" the UA contains only the other browser's name else { data = 'mask' + data; if (operaClass) { name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2')); } else { name = 'Opera'; } if (/IE/.test(data)) { os = null; } if (!useFeatures) { version = null; } } layout = ['Presto']; description.push(data); } // detect WebKit Nightly and approximate Chrome/Safari versions if ((data = (/AppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) { // correct build for numeric comparison // (e.g. "532.5" becomes "532.05") data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data]; // nightly builds are postfixed with a `+` if (name == 'Safari' && data[1].slice(-1) == '+') { name = 'WebKit Nightly'; prerelease = 'alpha'; version = data[1].slice(0, -1); } // clear incorrect browser versions else if (version == data[1] || version == (/Safari\/([\d.]+\+?)/i.exec(ua) || 0)[1]) { version = null; } // use the full Chrome version when available data = [data[0], (/Chrome\/([\d.]+)/i.exec(ua) || 0)[1]]; // detect JavaScriptCore // http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi if (!useFeatures || (/internal|\n/i.test(toString.toString()) && !data[1])) { layout[1] = 'like Safari'; data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : '5'); } else { layout[1] = 'like Chrome'; 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 < 535.21 ? 18 : '19'); } // add the postfix of ".x" or "+" for approximate versions layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+'); // obscure version for some Safari 1-2 releases if (name == 'Safari' && (!version || parseInt(version) > 45)) { version = data; } } // strip incorrect OS versions if (version && version.indexOf(data = /[\d.]+$/.exec(os)) == 0 && ua.indexOf('/' + data + '-') > -1) { os = trim(os.replace(data, '')); } // add layout engine if (layout && !/Avant|Nook/.test(name) && ( /Browser|Lunascape|Maxthon/.test(name) || /^(?:Adobe|Arora|Midori|Phantom|Rekonq|Rock|Sleipnir|Web)/.test(name) && layout[1])) { // don't add layout details to description if they are falsey (data = layout[layout.length - 1]) && description.push(data); } // combine contextual information if (description.length) { description = ['(' + description.join('; ') + ')']; } // append manufacturer if (manufacturer && product && product.indexOf(manufacturer) < 0) { description.push('on ' + manufacturer); } // append product if (product) { description.push((/^on /.test(description[description.length -1]) ? '' : 'on ') + product); } // add browser/OS architecture if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i).test(arch) && !/\bi686\b/i.test(arch)) { os = os && os + (data.test(os) ? '' : ' 64-bit'); if (name && (/WOW64/i.test(ua) || (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform)))) { description.unshift('32-bit'); } } ua || (ua = null); /*------------------------------------------------------------------------*/ /** * The platform object. * @name platform * @type Object */ return { /** * The browser/environment version. * @memberOf platform * @type String|Null */ 'version': name && version && (description.unshift(version), version), /** * The name of the browser/environment. * @memberOf platform * @type String|Null */ 'name': name && (description.unshift(name), name), /** * The name of the operating system. * @memberOf platform * @type String|Null */ 'os': os && (name && !(os == os.split(' ')[0] && (os == name.split(' ')[0] || product)) && description.push(product ? '(' + os + ')' : 'on ' + os), os), /** * The platform description. * @memberOf platform * @type String|Null */ 'description': description.length ? description.join(' ') : ua, /** * The name of the browser layout engine. * @memberOf platform * @type String|Null */ 'layout': layout && layout[0], /** * The name of the product's manufacturer. * @memberOf platform * @type String|Null */ 'manufacturer': manufacturer, /** * The alpha/beta release indicator. * @memberOf platform * @type String|Null */ 'prerelease': prerelease, /** * The name of the product hosting the browser. * @memberOf platform * @type String|Null */ 'product': product, /** * The browser's user agent string. * @memberOf platform * @type String|Null */ 'ua': ua, // parses a user agent string into a platform object 'parse': parse, // returns the platform description 'toString': toStringPlatform }; } /*--------------------------------------------------------------------------*/ // expose platform // in Narwhal, Node.js, or RingoJS if (freeExports) { forOwn(parse(), function(value, key) { freeExports[key] = value; }); } // via an AMD loader else if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { define('platform', function() { return parse(); }); } // in a browser or Rhino else { // use square bracket notation so Closure Compiler won't munge `platform` // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export window['platform'] = parse(); } }(this));