    /**
     * @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
     */
    Ext.supports = {
        init : function() {
            var doc = document,
                div = doc.createElement('div'),
                tests = this.tests,
                ln = tests.length,
                i, test;

            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="float:left; background-color:transparent;"></div>'
            ].join('');

            doc.body.appendChild(div);

            for (i = 0; i < ln; i++) {
                test = tests[i];
                this[test.identity] = test.fn.call(this, doc, div);
            }

            doc.body.removeChild(div);
        },

        /**
         * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
         * @type {Boolean}
         */
        CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),

        /**
         * @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),

        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;
                    div = Ext.get(div);
                    for (; i < ln; i++) {
                        if (div.getStyle(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, view) {
                    view = doc.defaultView;
                    return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
                }
            },

            /**
             * @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() {
                    return !!(window.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,
                        options  = [property + webkit, property + w3c, property + moz];

                    div.style.cssText = options.join(';');

                    return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
                }
            },

            /**
             * @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() {
                    return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (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);
                }
            },
            /**
             * @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);
                }
            },
            {
                identity: 'IncludePaddingInWidthCalculation',
                fn: function(doc, div){
                    var el = Ext.get(div.childNodes[1].firstChild);
                    return el.getWidth() == 210;
                }
            },
            {
                identity: 'IncludePaddingInHeightCalculation',
                fn: function(doc, div){
                    var el = Ext.get(div.childNodes[1].firstChild);
                    return el.getHeight() == 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;
                }
            }

        ]
    };