    /*

    Siesta 5.3.0
    Copyright(c) 2009-2019 Bryntum AB
    https://bryntum.com/contact
    https://bryntum.com/products/siesta/license

    */
    Singleton('Siesta.Project.Browser.FeatureSupport', {

        does : [
            Siesta.Util.Role.CanCalculatePageScroll
        ],

        has     : {
            isConfigured        : false,

            supports    : Joose.I.Object,

            simulator   : {
                lazy        : 'this.buildWebSimulator'
            },

            tests       : {
                init        : [
                    {
                        id  : 'TouchEvents',
                        fn  : function() {
                            return this.isEventSupported('touchend', window);
                        }
                    },
                    {
                        id  : 'PointerEvents',
                        fn  : function () {
                            return Boolean(window.PointerEvent)
                        }
                    },
                    {
                        id  : 'MSPointerEvents',
                        fn  : function() {
                            return Boolean(window.MSPointerEvent)
                        }
                    },
                    {
                        id  : 'PointerEventsGeneric',
                        fn  : function () {
                            return Boolean(window.PointerEvent || window.MSPointerEvent)
                        }
                    },
                    // "fn"s are called as methods of the "Siesta.Project.Browser.FeatureSupport" singleton
                    {
                        id : "mouseEnterLeave",
                        fn : function() {
                            var el = document.createElement("div");
                            return 'onmouseenter' in el && 'onmouseleave' in el;
                        }
                    },

                    {
                        id : "enterOnAnchorTriggersClick",
                        fn : function() {
                            var sim     = this.getSimulator(),
                                E       = Siesta.Test.UserAgent.KeyCodes().keys.ENTER,
                                result  = false;

                            var anchor = $('<a href="foo" style="display:none">test me</a>');
                            $('body').append(anchor);

                            anchor.focus({ preventScroll : true });
                            anchor.click(function(e) {
                                result = true;
                                return false;
                            });

                            sim.simulateEvent(anchor, 'keypress', { keyCode : E, charCode : 0 });

                            anchor.remove();
                            return result;
                        }
                    },

                    {
                        id : "canSimulateKeyCharacters",
                        fn : function() {
                            var sim     = this.getSimulator();

                            var input   = $('<input class="siesta-hidden" type="text" />'),
                                A       = Siesta.Test.UserAgent.KeyCodes().keys.A;

                            $('body').append(input);

                            input.focus({ preventScroll : true });
                            sim.simulateEvent(input, 'keypress', { keyCode : A, charCode : A });
                            sim.simulateEvent(input, 'textInput', { text : "A" });

                            var result  = input.val() === 'A';

                            input.remove();

                            return result;
                        }
                    },

                    {
                        id : "canSimulateBackspace",
                        fn : function() {
                            var sim     = this.getSimulator();

                            var input   = $('<input class="siesta-hidden" type="text" />'),
                                BS      = Siesta.Test.UserAgent.KeyCodes().keys.BACKSPACE,
                                A       = Siesta.Test.UserAgent.KeyCodes().keys.A;

                            $('body').append(input);

                            input.focus({ preventScroll : true });
                            sim.simulateEvent(input, 'keypress', { keyCode : A, charCode : A });
                            sim.simulateEvent(input, 'keypress', { keyCode : A, charCode : A });
                            sim.simulateEvent(input, 'keypress', { keyCode : BS, charCode : BS });

                            var result  = input.val() === 'A';

                            input.remove();

                            return result;
                        }
                    },

                    {
                        id : "enterSubmitsForm",
                        fn : function() {
                            var sim     = this.getSimulator(),
                                E       = Siesta.Test.UserAgent.KeyCodes().keys.ENTER,
                                result  = false;

                            var form = $('<form method="post"><input type="text"/></form>');
                            var input = $(form).find('input');
                            $('body').append(form);

                            form[0].onsubmit = function(e) {
                                result = true;
                                return false;
                            };

                            input.focus({ preventScroll : true });
                            sim.simulateEvent(input, 'keypress', { keyCode : E, charCode : 0 });

                            form.remove();
                            return result;
                        }
                    },
                    // remove after https://bugzilla.mozilla.org/show_bug.cgi?id=959992 will be fixed
                    {
                        id : "imageWithIdCreatesGlobalEnumerable",
                        fn : function () {
                            var img     = $('<img id="test_img_id"/>');

                            $('body').append(img);

                            var hasImgId    = false

                            for (var i in window) {
                                if (i == 'test_img_id') hasImgId = true
                            }

                            img.remove();

                            return hasImgId;
                        }
                    },

                    // Safari triggers native 'change' event when an INPUT is blurred
                    {
                        id : "inputFiresChangeAfterLosingFocus",
                        fn : function() {
                            var sim     = this.getSimulator();
                            var input   = document.createElement('input'),
                                result  = false,
                                A       = Siesta.Test.UserAgent.KeyCodes().keys.A;

                            input.className = "siesta-hidden";

                            document.body.appendChild(input);

                            input.addEventListener('change', function (ev) {
                                result = ev.isTrusted;
                            });

                            input.focus({ preventScroll : true });

                            sim.simulateEvent(input, 'keydown', { keyCode : A, charCode : A });
                            sim.simulateEvent(input, 'keypress', { keyCode : A, charCode : A });
                            sim.simulateEvent(input, 'keyup', { keyCode : A, charCode : A });
                            sim.simulateEvent(input, 'textInput', { text : "A" });

                            input.blur();

                            document.body.removeChild(input);

                            return result;
                        }
                    }
                ]
            }

        },

        methods     : {

            buildWebSimulator : function () {
                return new Siesta.Test.Simulator({
                    global  : window,
                    test    : {
                        normalizeElement    : function(a) { return a[ 0 ] || a; }
                    }
                });
            },

            configure : function() {
                // Already configured
                if (this.isConfigured) return

                this.isConfigured       = true

                this.maintainScrollPositionDuring(function () {
                    for (var i = 0; i < this.tests.length; i++) {
                        var test            = this.tests[i];
                        var testId          = test.id;
                        var detectorFn      = test.fn;

                        try {
                            // also save the results to "results" property - we'll use this in our own test suite
                            // where we copy the feature testing results from the outer scope to inner
                            this.supports[ testId ] = detectorFn.call(this);
                        } catch (e) {
                            this.supports[ testId ] = false
                        }
                    }
                })
            },

            // from Modernizr
            isEventSupported: function (eventName, element) {
                var isSupported;

                if (!eventName) return false
                if (!element || typeof element === 'string') element = document.createElement(element || 'div');

                // Testing via the `in` operator is sufficient for modern browsers and IE.
                // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and
                // "resize", whereas `in` "catches" those.
                eventName       = 'on' + eventName;
                isSupported     = eventName in element;

                // Fallback technique for old Firefox - bit.ly/event-detection
                if (!isSupported) {
                    if (!element.setAttribute) {
                        // Switch to generic element if it lacks `setAttribute`.
                        // It could be the `document`, `window`, or something else.
                        element = document.createElement('div');
                    }

                    element.setAttribute(eventName, '');
                    isSupported = typeof element[ eventName ] === 'function';

                    if (element[ eventName ] !== undefined ) {
                        // If property was created, "remove it" by setting value to `undefined`.
                        element[ eventName ] = undefined;
                    }
                    element.removeAttribute(eventName);
                }

                return isSupported;
            }

        }
    })