    /**
     * A class to detect if the current browser supports various features.
     *
     * Please refer to the documentation of {@link Ext.feature.has} on how to use it.
     *
     *     if (Ext.feature.has.Canvas) {
     *         // do some cool things with canvas here
     *     }
     */
    Ext.define('Ext.env.Feature', {

        requires: ['Ext.env.Browser', 'Ext.env.OS'],

        constructor: function() {
            this.testElements = {};

            this.has = function(name) {
                return !!this.has[name];
            };

            return this;
        },

        getTestElement: function(tag, createNew) {
            if (tag === undefined) {
                tag = 'div';
            }
            else if (typeof tag !== 'string') {
                return tag;
            }

            if (createNew) {
                return document.createElement(tag);
            }

            if (!this.testElements[tag]) {
                this.testElements[tag] = document.createElement(tag);
            }

            return this.testElements[tag];
        },

        isStyleSupported: function(name, tag) {
            var elementStyle = this.getTestElement(tag).style,
                cName = Ext.String.capitalize(name);

            if (typeof elementStyle[name] !== 'undefined'
                || typeof elementStyle[Ext.browser.getStylePrefix(name) + cName] !== 'undefined') {
                return true;
            }

            return false;
        },

        isEventSupported: function(name, tag) {
            if (tag === undefined) {
                tag = window;
            }

            var element = this.getTestElement(tag),
                eventName = 'on' + name.toLowerCase(),
                isSupported = (eventName in element);

            if (!isSupported) {
                if (element.setAttribute && element.removeAttribute) {
                    element.setAttribute(eventName, '');
                    isSupported = typeof element[eventName] === 'function';

                    if (typeof element[eventName] !== 'undefined') {
                        element[eventName] = undefined;
                    }

                    element.removeAttribute(eventName);
                }
            }

            return isSupported;
        },

        getSupportedPropertyName: function(object, name) {
            var vendorName = Ext.browser.getVendorProperyName(name);

            if (vendorName in object) {
                return vendorName;
            }
            else if (name in object) {
                return name;
            }

            return null;
        },

        registerTest: Ext.Function.flexSetter(function(name, fn) {
            this.has[name] = fn.call(this);

            return this;
        })

    }, function() {

        Ext.feature = new this;

        var has = Ext.feature.has;

        /**
         * @class Ext.feature.has
         * A simple class to verify if a browser feature exists or not on the current device.
         *
         *     if (Ext.feature.has.Canvas) {
         *         // do some cool things with canvas here
         *     }
         *
         * See the list of properties below too see which features are available for detection.
         */

        Ext.feature.registerTest({
            /**
             * @member Ext.feature.has
             * @property {Boolean} Canvas
             * True if the current device supports Canvas.
             */
            Canvas: function() {
                var element = this.getTestElement('canvas');
                return !!(element && element.getContext && element.getContext('2d'));
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Svg
             * True if the current device supports SVG.
             */
            Svg: function() {
                var doc = document;

                return !!(doc.createElementNS && !!doc.createElementNS("http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect);
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Vml
             * True if the current device supports VML.
             */
            Vml: function() {
                var element = this.getTestElement(),
                    ret = false;

                element.innerHTML = "<!--[if vml]><br><![endif]-->";
                ret = (element.childNodes.length === 1);
                element.innerHTML = "";

                return ret;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Touch
             * True if the current device supports touch events (`touchstart`).
             */
            Touch: function() {
                return this.isEventSupported('touchstart') && !(Ext.os && Ext.os.name.match(/Windows|MacOSX|Linux/));
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Orientation
             * True if the current device supports different orientations.
             */
            Orientation: function() {
                return ('orientation' in window) && this.isEventSupported('orientationchange');
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} OrientationChange
             * True if the current device supports the `orientationchange` event.
             */
            OrientationChange: function() {
                return this.isEventSupported('orientationchange');
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} DeviceMotion
             * True if the current device supports the `devicemotion` event.
             */
            DeviceMotion: function() {
                return this.isEventSupported('devicemotion');
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Geolocation
             * True if the current device supports Geolocation.
             */
            Geolocation: function() {
                return 'geolocation' in window.navigator;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} SqlDatabase
             * True if the current device supports SQL Databases.
             */
            SqlDatabase: function() {
                return 'openDatabase' in window;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} WebSockets
             */
            WebSockets: function() {
                return 'WebSocket' in window;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} History
             */
            History: function() {
                return ('history' in window && 'pushState' in window.history);
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} CssTransforms
             * True if the current device supports CSS Transform animations.
             */
            CssTransforms: function() {
                return this.isStyleSupported('transform');
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Css3dTransforms
             * True if the current device supports CSS 3D Transform animations.
             */
            Css3dTransforms: function() {
                // See https://sencha.jira.com/browse/TOUCH-1544
                return this.has('CssTransforms') && this.isStyleSupported('perspective') && !Ext.os.is.Android2;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} CssAnimations
             * True if the current device supports CSS Animations.
             */
            CssAnimations: function() {
                return this.isStyleSupported('animationName');
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} CssTransitions
             * True if the current device supports CSS Transitions.
             */
            CssTransitions: function() {
                return this.isStyleSupported('transitionProperty');
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Audio
             * True if the current device supports the `<audio>` tag.
             */
            Audio: function() {
                return !!this.getTestElement('audio').canPlayType;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} Video
             * True if the current device supports the `<video>` tag.
             */
            Video: function() {
                return !!this.getTestElement('video').canPlayType;
            },

            /**
             * @member Ext.feature.has
             * @property {Boolean} ClassList
             * True if document environment supports the HTML5 classList API.
             */
            ClassList: function() {
                return "classList" in this.getTestElement();
            }
        });

        //<deprecated product=touch since=2.0>
        /**
         * @class Ext.supports
         * @deprecated
         * Please use the {@link Ext.env.Browser}, {@link Ext.env.OS} and {@link Ext.feature.has} classes.
         */

        /**
         * @member Ext.supports
         * @property Transitions
         * @deprecated 2.0.0 Please use {@link Ext.feature.has#CssTransitions} instead
         */
        Ext.deprecatePropertyValue(has, 'Transitions', has.CssTransitions,
                              "Ext.supports.Transitions is deprecated, please use Ext.feature.has.CssTransitions instead");

        /**
         * @member Ext.supports
         * @property SVG
         * @deprecated 2.0.0 Please use {@link Ext.feature.has#Svg} instead
         */
        Ext.deprecatePropertyValue(has, 'SVG', has.Svg,
                              "Ext.supports.SVG is deprecated, please use Ext.feature.has.Svg instead");

        /**
         * @member Ext.supports
         * @property VML
         * @deprecated 2.0.0 Please use {@link Ext.feature.has#Vml} instead
         */
        Ext.deprecatePropertyValue(has, 'VML', has.Vml,
                              "Ext.supports.VML is deprecated, please use Ext.feature.has.Vml instead");

        /**
         * @member Ext.supports
         * @property AudioTag
         * @deprecated 2.0.0 Please use {@link Ext.feature.has#Audio} instead
         */
        Ext.deprecatePropertyValue(has, 'AudioTag', has.Audio,
                              "Ext.supports.AudioTag is deprecated, please use Ext.feature.has.Audio instead");

        /**
         * @member Ext.supports
         * @property GeoLocation
         * @deprecated 2.0.0 Please use {@link Ext.feature.has#Geolocation} instead
         */
        Ext.deprecatePropertyValue(has, 'GeoLocation', has.Geolocation,
                              "Ext.supports.GeoLocation is deprecated, please use Ext.feature.has.Geolocation instead");

        /**
         * @member Ext.supports
         * @property ClassList
         * @deprecated 2.0.0 Please use {@link Ext.feature.has#ClassList} instead
         */
        Ext.deprecatePropertyValue(has, 'ClassList', has.ClassList,
                              "Ext.supports.ClassList is deprecated, please use Ext.feature.has.ClassList instead");

        var name;

        if (!Ext.supports) {
            Ext.supports = {};
        }

        for (name in has) {
            if (has.hasOwnProperty(name)) {
                Ext.deprecatePropertyValue(Ext.supports, name, has[name], "Ext.supports." + name + " is deprecated, please use Ext.feature.has." + name + " instead");
            }
        }
        //</deprecated>
    });