//@tag extras,core //@require perf/Monitor.js //@define Ext.Supports /** * @class Ext.is * * Determines information about the current platform the application is running on. * * @singleton */ Ext.is = { init : function(navigator) { var platforms = this.platforms, ln = platforms.length, i, platform; navigator = navigator || window.navigator; for (i = 0; i < ln; i++) { platform = platforms[i]; this[platform.identity] = platform.regex.test(navigator[platform.property]); } /** * @property Desktop True if the browser is running on a desktop machine * @type {Boolean} */ this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android); /** * @property Tablet True if the browser is running on a tablet (iPad) */ this.Tablet = this.iPad; /** * @property Phone True if the browser is running on a phone. * @type {Boolean} */ this.Phone = !this.Desktop && !this.Tablet; /** * @property iOS True if the browser is running on iOS * @type {Boolean} */ this.iOS = this.iPhone || this.iPad || this.iPod; /** * @property Standalone Detects when application has been saved to homescreen. * @type {Boolean} */ this.Standalone = !!window.navigator.standalone; }, /** * @property iPhone True when the browser is running on a iPhone * @type {Boolean} */ platforms: [{ property: 'platform', regex: /iPhone/i, identity: 'iPhone' }, /** * @property iPod True when the browser is running on a iPod * @type {Boolean} */ { property: 'platform', regex: /iPod/i, identity: 'iPod' }, /** * @property iPad True when the browser is running on a iPad * @type {Boolean} */ { property: 'userAgent', regex: /iPad/i, identity: 'iPad' }, /** * @property Blackberry True when the browser is running on a Blackberry * @type {Boolean} */ { property: 'userAgent', regex: /Blackberry/i, identity: 'Blackberry' }, /** * @property Android True when the browser is running on an Android device * @type {Boolean} */ { property: 'userAgent', regex: /Android/i, identity: 'Android' }, /** * @property Mac True when the browser is running on a Mac * @type {Boolean} */ { property: 'platform', regex: /Mac/i, identity: 'Mac' }, /** * @property Windows True when the browser is running on Windows * @type {Boolean} */ { property: 'platform', regex: /Win/i, identity: 'Windows' }, /** * @property Linux True when the browser is running on Linux * @type {Boolean} */ { property: 'platform', regex: /Linux/i, identity: 'Linux' }] }; Ext.is.init(); /** * @class Ext.supports * * Determines information about features are supported in the current environment * * @singleton */ (function(){ // this is a local copy of certain logic from (Abstract)Element.getStyle // to break a dependancy between the supports mechanism and Element // use this instead of element references to check for styling info var getStyle = function(element, styleName){ var view = element.ownerDocument.defaultView, style = (view ? view.getComputedStyle(element, null) : element.currentStyle) || element.style; return style[styleName]; }, supportsVectors = { 'IE6-quirks': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,1,0,1,0,0], 'IE6-strict': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,1,1,0,0,1,0,1,0,0], 'IE7-quirks': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,1,0,1,0,0], 'IE7-strict': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,1,0,0,1,0,1,0,0], 'IE8-quirks': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,1,0,1,0,0], 'IE8-strict': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,0,0,1,0,1,0,0], 'IE9-quirks': [0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,1,0,0,1,0,1,0,0], 'IE9-strict': [0,1,0,0,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,0], 'IE10-quirks': [1,1,0,0,1,1,1,1,0,1,1,1,0,0,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,0,0], 'IE10-strict': [1,1,0,0,1,1,1,1,0,1,1,1,0,0,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,0,0] }; function getBrowserKey() { var browser = Ext.isIE6 ? 'IE6' : Ext.isIE7 ? 'IE7' : Ext.isIE8 ? 'IE8' : Ext.isIE9 ? 'IE9': Ext.isIE10 ? 'IE10' : ''; return browser ? browser + (Ext.isStrict ? '-strict' : '-quirks') : ''; } Ext.supports = { /** * Runs feature detection routines and sets the various flags. This is called when * the scripts loads (very early) and again at {@link Ext#onReady}. Some detections * are flagged as `early` and run immediately. Others that require the document body * will not run until ready. * * Each test is run only once, so calling this method from an onReady function is safe * and ensures that all flags have been set. * @markdown * @private */ init : function() { var me = this, doc = document, toRun = me.toRun || me.tests, n = toRun.length, div = n && Ext.isReady && doc.createElement('div'), notRun = [], browserKey = getBrowserKey(), test, vector, value; if (div) { div.innerHTML = [ '<div style="height:30px;width:50px;">', '<div style="height:20px;width:20px;"></div>', '</div>', '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">', '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>', '</div>', '<div style="position: absolute; left: 10%; top: 10%;"></div>', '<div style="float:left; background-color:transparent;"></div>' ].join(''); doc.body.appendChild(div); } vector = supportsVectors[browserKey]; while (n--) { test = toRun[n]; value = vector && vector[n]; if (value !== undefined) { me[test.identity] = value; } else if (div || test.early) { me[test.identity] = test.fn.call(me, doc, div); } else { notRun.push(test); } } if (div) { doc.body.removeChild(div); } me.toRun = notRun; }, //<debug> /** * Generates a support vector for the current browser/mode. The result can be * added to supportsVectors to eliminate feature detection at startup time. * @private */ generateVector: function() { var tests = this.tests, vector = [], i = 0, ln = tests.length, test; for (; i < ln; i++) { test = tests[i]; vector.push(this[test.identity] ? 1 : 0); } return vector; }, //</debug> /** * @property PointerEvents True if document environment supports the CSS3 pointer-events style. * @type {Boolean} */ PointerEvents: 'pointerEvents' in document.documentElement.style, // IE10/Win8 throws "Access Denied" accessing window.localStorage, so this test // needs to have a try/catch /** * @property LocalStorage True if localStorage is supported */ LocalStorage: (function() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } })(), /** * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style. * @type {Boolean} */ CSS3BoxShadow: 'boxShadow' in document.documentElement.style || 'WebkitBoxShadow' in document.documentElement.style || 'MozBoxShadow' in document.documentElement.style, /** * @property ClassList True if document environment supports the HTML5 classList API. * @type {Boolean} */ ClassList: !!document.documentElement.classList, /** * @property OrientationChange True if the device supports orientation change * @type {Boolean} */ OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)), /** * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate) * @type {Boolean} */ DeviceMotion: ('ondevicemotion' in window), /** * @property Touch True if the device supports touch * @type {Boolean} */ // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2 // and Safari 4.0 (they all have 'ontouchstart' in the window object). Touch: ('ontouchstart' in window) && (!Ext.is.Desktop), /** * @property TimeoutActualLateness True if the browser passes the "actualLateness" parameter to * setTimeout. See: https://developer.mozilla.org/en/DOM/window.setTimeout * @type {Boolean} */ TimeoutActualLateness: (function(){ setTimeout(function(){ Ext.supports.TimeoutActualLateness = arguments.length !== 0; }, 0); }()), tests: [ /** * @property Transitions True if the device supports CSS3 Transitions * @type {Boolean} */ { identity: 'Transitions', fn: function(doc, div) { var prefix = [ 'webkit', 'Moz', 'o', 'ms', 'khtml' ], TE = 'TransitionEnd', transitionEndName = [ prefix[0] + TE, 'transitionend', //Moz bucks the prefixing convention prefix[2] + TE, prefix[3] + TE, prefix[4] + TE ], ln = prefix.length, i = 0, out = false; for (; i < ln; i++) { if (getStyle(div, prefix[i] + "TransitionProperty")) { Ext.supports.CSS3Prefix = prefix[i]; Ext.supports.CSS3TransitionEnd = transitionEndName[i]; out = true; break; } } return out; } }, /** * @property RightMargin True if the device supports right margin. * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed. * @type {Boolean} */ { identity: 'RightMargin', fn: function(doc, div) { var view = doc.defaultView; return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px'); } }, /** * @property DisplayChangeInputSelectionBug True if INPUT elements lose their * selection when their display style is changed. Essentially, if a text input * has focus and its display style is changed, the I-beam disappears. * * This bug is encountered due to the work around in place for the {@link #RightMargin} * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html). */ { identity: 'DisplayChangeInputSelectionBug', early: true, fn: function() { var webKitVersion = Ext.webKitVersion; // WebKit but older than Safari 5 or Chrome 6: return 0 < webKitVersion && webKitVersion < 533; } }, /** * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their * selection when their display style is changed. Essentially, if a text area has * focus and its display style is changed, the I-beam disappears. * * This bug is encountered due to the work around in place for the {@link #RightMargin} * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to * be fixed in Chrome 11. */ { identity: 'DisplayChangeTextAreaSelectionBug', early: true, fn: function() { var webKitVersion = Ext.webKitVersion; /* Has bug w/textarea: (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1 No bug: (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57 Safari/534.24 */ return 0 < webKitVersion && webKitVersion < 534.24; } }, /** * @property TransparentColor True if the device supports transparent color * @type {Boolean} */ { identity: 'TransparentColor', fn: function(doc, div, view) { view = doc.defaultView; return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent'); } }, /** * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle() * @type {Boolean} */ { identity: 'ComputedStyle', fn: function(doc, div, view) { view = doc.defaultView; return view && view.getComputedStyle; } }, /** * @property Svg True if the device supports SVG * @type {Boolean} */ { identity: 'Svg', fn: function(doc) { return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect; } }, /** * @property Canvas True if the device supports Canvas * @type {Boolean} */ { identity: 'Canvas', fn: function(doc) { return !!doc.createElement('canvas').getContext; } }, /** * @property Vml True if the device supports VML * @type {Boolean} */ { identity: 'Vml', fn: function(doc) { var d = doc.createElement("div"); d.innerHTML = "<!--[if vml]><br/><br/><![endif]-->"; return (d.childNodes.length == 2); } }, /** * @property Float True if the device supports CSS float * @type {Boolean} */ { identity: 'Float', fn: function(doc, div) { return !!div.lastChild.style.cssFloat; } }, /** * @property AudioTag True if the device supports the HTML5 audio tag * @type {Boolean} */ { identity: 'AudioTag', fn: function(doc) { return !!doc.createElement('audio').canPlayType; } }, /** * @property History True if the device supports HTML5 history * @type {Boolean} */ { identity: 'History', fn: function() { var history = window.history; return !!(history && history.pushState); } }, /** * @property CSS3DTransform True if the device supports CSS3DTransform * @type {Boolean} */ { identity: 'CSS3DTransform', fn: function() { return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41')); } }, /** * @property CSS3LinearGradient True if the device supports CSS3 linear gradients * @type {Boolean} */ { identity: 'CSS3LinearGradient', fn: function(doc, div) { var property = 'background-image:', webkit = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))', w3c = 'linear-gradient(left top, black, white)', moz = '-moz-' + w3c, ms = '-ms-' + w3c, opera = '-o-' + w3c, options = [property + webkit, property + w3c, property + moz, property + ms, property + opera]; div.style.cssText = options.join(';'); return (("" + div.style.backgroundImage).indexOf('gradient') !== -1) && !Ext.isIE9; } }, /** * @property CSS3BorderRadius True if the device supports CSS3 border radius * @type {Boolean} */ { identity: 'CSS3BorderRadius', fn: function(doc, div) { var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'], pass = false, i; for (i = 0; i < domPrefixes.length; i++) { if (document.body.style[domPrefixes[i]] !== undefined) { return true; } } return pass; } }, /** * @property GeoLocation True if the device supports GeoLocation * @type {Boolean} */ { identity: 'GeoLocation', fn: function() { // Use the in check for geolocation, see https://github.com/Modernizr/Modernizr/issues/513 return (typeof navigator != 'undefined' && 'geolocation' in navigator) || (typeof google != 'undefined' && typeof google.gears != 'undefined'); } }, /** * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events * @type {Boolean} */ { identity: 'MouseEnterLeave', fn: function(doc, div){ return ('onmouseenter' in div && 'onmouseleave' in div); } }, /** * @property MouseWheel True if the browser supports the mousewheel event * @type {Boolean} */ { identity: 'MouseWheel', fn: function(doc, div) { return ('onmousewheel' in div); } }, /** * @property Opacity True if the browser supports normal css opacity * @type {Boolean} */ { identity: 'Opacity', fn: function(doc, div){ // Not a strict equal comparison in case opacity can be converted to a number. if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) { return false; } div.firstChild.style.cssText = 'opacity:0.73'; return div.firstChild.style.opacity == '0.73'; } }, /** * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs * @type {Boolean} */ { identity: 'Placeholder', fn: function(doc) { return 'placeholder' in doc.createElement('input'); } }, /** * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel. * @type {Boolean} */ { identity: 'Direct2DBug', fn: function() { return Ext.isString(document.body.style.msTransformOrigin) && Ext.isIE10m; } }, /** * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements * @type {Boolean} */ { identity: 'BoundingClientRect', fn: function(doc, div) { return Ext.isFunction(div.getBoundingClientRect); } }, /** * @property RotatedBoundingClientRect True if the BoundingClientRect is * rotated when the element is rotated using a CSS transform. * @type {Boolean} */ { identity: 'RotatedBoundingClientRect', fn: function() { var body = document.body, supports = false, el = document.createElement('div'), style = el.style; if (el.getBoundingClientRect) { style.WebkitTransform = style.MozTransform = style.OTransform = style.transform = 'rotate(90deg)'; style.width = '100px'; style.height = '30px'; body.appendChild(el) supports = el.getBoundingClientRect().height !== 100; body.removeChild(el); } return supports; } }, { identity: 'IncludePaddingInWidthCalculation', fn: function(doc, div){ return div.childNodes[1].firstChild.offsetWidth == 210; } }, { identity: 'IncludePaddingInHeightCalculation', fn: function(doc, div){ return div.childNodes[1].firstChild.offsetHeight == 210; } }, /** * @property ArraySort True if the Array sort native method isn't bugged. * @type {Boolean} */ { identity: 'ArraySort', fn: function() { var a = [1,2,3,4,5].sort(function(){ return 0; }); return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5; } }, /** * @property Range True if browser support document.createRange native method. * @type {Boolean} */ { identity: 'Range', fn: function() { return !!document.createRange; } }, /** * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods. * @type {Boolean} */ { identity: 'CreateContextualFragment', fn: function() { var range = Ext.supports.Range ? document.createRange() : false; return range && !!range.createContextualFragment; } }, /** * @property WindowOnError True if browser supports window.onerror. * @type {Boolean} */ { identity: 'WindowOnError', fn: function () { // sadly, we cannot feature detect this... return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+ } }, /** * @property TextAreaMaxLength True if the browser supports maxlength on textareas. * @type {Boolean} */ { identity: 'TextAreaMaxLength', fn: function(){ var el = document.createElement('textarea'); return ('maxlength' in el); } }, /** * @property GetPositionPercentage True if the browser will return the left/top/right/bottom * position as a percentage when explicitly set as a percentage value. * @type {Boolean} */ // Related bug: https://bugzilla.mozilla.org/show_bug.cgi?id=707691#c7 { identity: 'GetPositionPercentage', fn: function(doc, div){ return getStyle(div.childNodes[2], 'left') == '10%'; } }, /** * @property {Boolean} PercentageHeightOverflowBug * In some browsers (IE quirks, IE6, IE7, IE9, chrome, safari and opera at the time * of this writing) a percentage-height element ignores the horizontal scrollbar * of its parent element. This method returns true if the browser is affected * by this bug. * * @private */ { identity: 'PercentageHeightOverflowBug', fn: function(doc) { var hasBug = false, style, el; if (Ext.getScrollbarSize().height) { // must have space-consuming scrollbars for bug to be possible el = doc.createElement('div'); style = el.style; style.height = '50px'; style.width = '50px'; style.overflow = 'auto'; style.position = 'absolute'; el.innerHTML = [ '<div style="display:table;height:100%;">', // The element that causes the horizontal overflow must be // a child of the element with the 100% height, otherwise // horizontal overflow is not triggered in webkit quirks mode '<div style="width:51px;"></div>', '</div>' ].join(''); doc.body.appendChild(el); if (el.firstChild.offsetHeight === 50) { hasBug = true; } doc.body.removeChild(el); } return hasBug; } }, /** * @property {Boolean} xOriginBug * In Chrome 24.0, an RTL element which has vertical overflow positions its right X origin incorrectly. * It skips a non-existent scrollbar which has been moved to the left edge due to the RTL setting. * * http://code.google.com/p/chromium/issues/detail?id=174656 * * This method returns true if the browser is affected by this bug. * * @private */ { identity: 'xOriginBug', fn: function(doc, div) { div.innerHTML = '<div id="b1" style="height:100px;width:100px;direction:rtl;position:relative;overflow:scroll">' + '<div id="b2" style="position:relative;width:100%;height:20px;"></div>' + '<div id="b3" style="position:absolute;width:20px;height:20px;top:0px;right:0px"></div>' + '</div>'; var outerBox = document.getElementById('b1').getBoundingClientRect(), b2 = document.getElementById('b2').getBoundingClientRect(), b3 = document.getElementById('b3').getBoundingClientRect(); return (b2.left !== outerBox.left && b3.right !== outerBox.right); } } ] }; }()); Ext.supports.init(); // run the "early" detections now