# Source: core/event/event-manager.js

    /*global Window, Document, Element, Event, Components, Touch, MontageElement */

    /**
     * @author Lea Verou
     * @license MIT
     * @see http://leaverou.github.com/chainvas/
     */

    /**
     * @module montage/core/event/event-manager
     * @requires montage/core/core
     * @requires montage/core/event/mutable-event
     * @requires montage/core/serialization
     * @requires montage/core/event/action-event-listener
     */

    var Montage = require("../core").Montage,
        MutableEvent = require("./mutable-event").MutableEvent,
        Serializer = require("../serialization/serializer/montage-serializer").MontageSerializer,
        Deserializer = require("../serialization/deserializer/montage-deserializer").MontageDeserializer,
        Map = require("collections/map"),
        WeakMap = require("collections/weak-map"),
        currentEnvironment = require("../environment").currentEnvironment;

    var defaultEventManager;

    //This is a quick polyfill for IE10 that is not exposing CustomEvent as a function.
    //From https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
    if (
        typeof document !== "undefined" &&
            typeof window !== "undefined" &&
                typeof Event !== "undefined" &&
                    typeof CustomEvent !== "function"
    ) {
        CustomEvent = function CustomEvent (event, params) { // jshint ignore:line
            params = params || { bubbles: false, cancelable: false, detail: undefined };
            var evt = document.createEvent( 'CustomEvent' );
            evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
            return evt;
        };

        CustomEvent.prototype = Event.prototype;
        window.CustomEvent = CustomEvent;
    }

    // jshint -W015
    /* This is to handle browsers that have TouchEvents but don't have the global constructor function Touch */
    if (
        typeof document !== "undefined" &&
            typeof window !== "undefined" &&
                typeof Touch === "undefined" &&
                    "ontouchstart" in window
    ) {
        Touch = function Touch() {}; // jshint ignore:line

        (function () {
            var onFirstTouchstart;
            document.addEventListener("touchstart", onFirstTouchstart = function onFirstTouchstart(event) {
                window.Touch = event.touches[0].constructor;
                if (document.nativeRemoveEventListener) {
                    document.nativeRemoveEventListener("touchstart", onFirstTouchstart, true);
                } else {
                    document.removeEventListener("touchstart", onFirstTouchstart, true);
                }
                if (defaultEventManager && defaultEventManager.isStoringPointerEvents) {
                    defaultEventManager.isStoringPointerEvents = false;
                    defaultEventManager.isStoringPointerEvents = true;
                }
            }, true);
        })();

        window.Touch = Touch;
    }

    var _PointerVelocity = Montage.specialize({
        _identifier: {
            enumerable: false,
            writable: true,
            value: null
        },
        initWithIdentifier: {
            value: function (identifier) {
                this._identifier = identifier;
                return this;
            }
        },
        clearCache: {
            value: function () {
                this._data = this._x = this._y = this._speed = this._angle = null;
                return this;
            }
        },
        _data: {
            enumerable: false,
            writable: true,
            value: null
        },
        _x: {
            enumerable: false,
            writable: true,
            value: null
        },
        _y: {
            enumerable: false,
            writable: true,
            value: null
        },
        _speed: {
            enumerable: false,
            writable: true,
            value: null
        },
        _angle: {
            enumerable: false,
            writable: true,
            value: null
        },
        x: {
            get: function () {
                if (this._x === null) {
                    if (this._data === null) {
                        this._data = defaultEventManager._getPointerVelocityData(this._identifier);
                    }
                    this._x = defaultEventManager._calculatePointerVelocity(this._data.time, this._data.x);
                }
                return this._x;
            },
            set: function () {
            }
        },
        y: {
            get: function () {
                if (this._y === null) {
                    if (this._data === null) {
                        this._data = defaultEventManager._getPointerVelocityData(this._identifier);
                    }
                    this._y = defaultEventManager._calculatePointerVelocity(this._data.time, this._data.y);
                }
                return this._y;
            },
            set: function () {
            }
        },
        speed: {
            get: function () {
                if (this._speed === null) {
                    this._speed = Math.sqrt(this.x * this.x + this.y * this.y);
                }
                return this._speed;
            },
            set: function () {
            }
        },
        angle: {
            get: function () {
                if (this._angle === null) {
                    this._angle = Math.atan2(this.y, this.x);
                }
                return this._angle;
            },
            set: function () {
            }
        }
    });

    var _PointerStorageMemoryEntry = Montage.specialize({
        constructor: {
            value: function (identifier) {
                this.data = new Array(32);
                this.velocity = {velocity: (new _PointerVelocity()).initWithIdentifier(identifier)};
                return this;
            }
        },
        data: {
            enumerable: false,
            writable: true,
            value: null
        },
        size: {
            enumerable: false,
            writable: true,
            value: 0
        },
        pos: {
            enumerable: false,
            writable: true,
            value: 0
        },
        velocity: {
            enumerable: false,
            writable: true,
            value: 0
        }

    });

    var _StoredEvent = Montage.specialize({
        constructor: {
            value: function (clientX, clientY, timeStamp) {
                this.clientX = clientX;
                this.clientY = clientY;
                this.timeStamp = timeStamp;
                return this;
            }
        },
        clientX: {
            enumerable: false,
            writable: true,
            value: null
        },
        clientY: {
            enumerable: false,
            writable: true,
            value: 0
        },
        timeStamp: {
            enumerable: false,
            writable: true,
            value: 0
        }
    });

    var _serializeObjectRegisteredEventListenersForPhase = function (serializer, object, registeredEventListeners, eventListenerDescriptors, capture) {
        var i, l, type, listenerRegistrations, listeners, aListener, mapIter;
        mapIter = registeredEventListeners.keys();

        while ((type = mapIter.next().value)) {
            listenerRegistrations = registeredEventListeners.get(type);
            listeners = listenerRegistrations && listenerRegistrations.get(object);
            if (Array.isArray(listeners) && listeners.length > 0) {
                for (i = 0, l = listeners.length; i < l; i++) {
                    aListener = listeners[i];
                    eventListenerDescriptors.push({
                        type: type,
                        listener: serializer.addObjectReference(aListener),
                        capture: capture
                    });
                }
            }
            else if (listeners){
                eventListenerDescriptors.push({
                    type: type,
                    listener: serializer.addObjectReference(listeners),
                    capture: capture
                });
            }
        }
    };

    Serializer.defineSerializationUnit("listeners", function listenersSerializationUnit(serializer, object) {
        var eventManager = defaultEventManager,
            eventListenerDescriptors = [],
            descriptors,
            descriptor,
            listener;

            _serializeObjectRegisteredEventListenersForPhase(serializer, object,eventManager._registeredCaptureEventListeners,eventListenerDescriptors,true);
            _serializeObjectRegisteredEventListenersForPhase(serializer, object,eventManager._registeredBubbleEventListeners,eventListenerDescriptors,false);

        if (eventListenerDescriptors.length > 0) {
            return eventListenerDescriptors;
        }
    });

    Deserializer.defineDeserializationUnit("listeners", function listenersDeserializationUnit(deserializer, object, listeners) {
        for (var i = 0, listener; (listener = listeners[i]); i++) {
            object.addEventListener(listener.type, listener.listener, listener.capture);
        }
    });

    /**
     * @class EventManager
     * @extends Montage
     */
    var EventManager = exports.EventManager = Montage.specialize(/** @lends EventManager.prototype # */ {
        /**
         * @constructs
         */
        constructor: {
            value: function EventManager () {
                this._trackingTouchStartList = new Map();
                this._trackingTouchEndList = new Map();
                this._findActiveTargetMap = new Map();
                this._eventPathForTargetMap = new Map();
                this._claimedPointers = new Map();
                this._registeredCaptureEventListeners = new Map();
                this._registeredBubbleEventListeners = new Map();
                this._observedTarget_byEventType_ = Object.create(null);
                this._currentDispatchedTargetListeners = new Map();
                this._elementEventHandlerByElement = new WeakMap();
                this.environment = currentEnvironment;
                this._trackingTouchTimeoutIDs = new Map();
                return this;
            }
        },
        spliceOne: {
            value: function spliceOne(arr, index) {
                var len = arr.length;
                if (len) {
                    while (index < len) {
                        arr[index] = arr[index + 1];
                        index++;
                    }
                    arr.length--;
                }
            }
        },
        /**
         * Utility
         * @see ClipboardEvent http://dev.w3.org/2006/webapi/clipops/clipops.html#event-types-and-details
         * @see DND http://www.w3.org/TR/2010/WD-html5-20101019/dnd.html
         * @see document.implementation.hasFeature("HTMLEvents", "2.0")
         * @see DOM2 http://www.w3.org/TR/DOM-Level-2-Events/events.html
         * @see DOM3 http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
         * @see DOM4 http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#events
         * @see GECKO https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
         * @see MSFT defacto standard
         * @see ProgressEvent http://www.w3.org/TR/progress-events/
         * @see TouchEvent http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
         * @see INPUT http://dev.w3.org/html5/spec/common-input-element-apis.html#common-event-behaviors
         * @see WEBSOCKETS http://www.w3.org/TR/html5/comms.html
         * @see http://www.quirksmode.org/dom/events/index.html
         * @see https://developer.mozilla.org/en/DOM/DOM_event_reference
         */
        eventDefinitions: {
            value: {
                abort: {bubbles: false, cancelable: false}, //ProgressEvent, DOM3, //DOM2 does bubble
                beforeunload: {bubbles: false}, //MSFT
                blur: {bubbles: false, cancelable: false}, //DOM2, DOM3
                change: {bubbles: true, cancelable: false}, //DOM2, INPUT
                click: {bubbles: true, cancelable: true}, //DOM3
                close: {bubbles: false, cancelable: false}, //WEBSOCKETS
                compositionend: {bubbles: true, cancelable: false}, //DOM3
                compositionstart: {bubbles: true, cancelable: true}, //DOM3
                compositionupdate: {bubbles: true, cancelable: false}, //DOM3
                contextmenu: {bubbles: true, cancelable: true}, //MSFT
                copy: {bubbles: true, cancelable: true}, //ClipboardEvent
                cut: {bubbles: true, cancelable: true}, //ClipboardEvent
                dblclick: {bubbles: true, cancelable: false}, //DOM3
                DOMActivate: {bubbles: true, cancelable: true, deprecated: true}, //DOM2, DOM3 deprecated
                DOMMouseScroll: {bubbles: true}, //GECKO
                drag: {bubbles: true, cancelable: true}, //DND
                dragend: {bubbles: true, cancelable: false}, //DND
                dragenter: {bubbles: true, cancelable: true}, //DND
                dragleave: {bubbles: true, cancelable: false}, //DND
                dragover: {bubbles: true, cancelable: true}, //DND
                dragstart: {bubbles: true, cancelable: true}, //DND
                drop: {bubbles: true, cancelable: true}, //DND
                error: {
                    bubbles: function (target) {
                        // error does not bubble when used as a ProgressEvent
                        return !(XMLHttpRequest.prototype.isPrototypeOf(target) ||
                        target.tagName && "VIDEO" === target.tagName.toUpperCase() ||
                        target.tagName && "AUDIO" === target.tagName.toUpperCase());
                    },
                    cancelable: false
                }, //DOM2, DOM3, ProgressEvent
                focus: {bubbles: false, cancelable: false}, //DOM2, DOM3
                focusin: {bubbles: true, cancelable: false}, //DOM3
                focusout: {bubbles: true, cancelable: false}, //DOM3
                input: {bubbles: true, cancelable: false}, // INPUT
                keydown: {bubbles: true, cancelable: false}, //DOM3
                keypress: {bubbles: true, cancelable: false}, //DOM3
                keyup: {bubbles: true, cancelable: false}, //DOM3
                load: {bubbles: false, cancelable: false}, //ProgressEvent, DOM2, DOM3
                loadend: {bubbles: false, cancelable: false}, //ProgressEvent
                loadstart: {bubbles: false, cancelable: false}, //ProgressEvent
                message: {bubbles: false, cancelable: false}, //WEBSOCKETS
                mousedown: {bubbles: true, cancelable: true}, //DOM3
                mouseenter: {bubbles: false, cancelable: false}, //DOM3
                mouseleave: {bubbles: false, cancelable: false}, //DOM3
                mousemove: {bubbles: true, cancelable: true}, //DOM3
                mouseout: {bubbles: true, cancelable: true}, //DOM3
                mouseover: {bubbles: true, cancelable: true}, //DOM3
                mouseup: {bubbles: true, cancelable: true}, //DOM3
                mousewheel: {bubbles: true},
                orientationchange: {bubbles: false},
                paste: {bubbles: true, cancelable: true}, //ClipboardEvent
                progress: {bubbles: false, cancelable: false}, //ProgressEvent
                reset: {bubbles: true, cancelable: false}, //DOM2
                resize: {bubbles: false, cancelable: false}, //DOM2 bubbles, DOM3

                scroll: {
                    bubbles: function (target) {
                        return /*isDocument*/!!target.defaultView;
                    },
                    cancelable: false
                }, //DOM2, DOM3 When dispatched on Document element must bubble to defaultView object

                select: {bubbles: true, cancelable: false}, //DOM2, DOM3

                submit: {bubbles: true, cancelable: true}, //DOM2
                touchcancel: {bubbles: true, cancelable: false}, //TouchEvent
                touchend: {bubbles: true, cancelable: true}, //TouchEvent
                touchmove: {bubbles: true, cancelable: true}, //TouchEvent
                touchstart: {bubbles: true, cancelable: true}, //TouchEvent
                unload: {bubbles: false, cancelable: false}, //DOM2, DOM3
                wheel: {bubbles: true, cancelable: true}, //DOM3
                pointerdown: {bubbles: true, cancelable: true}, //PointerEvent
                pointerup: {bubbles: true, cancelable: true}, //PointerEvent
                pointerenter: {bubbles: false, cancelable: true}, //PointerEvent
                pointercancel: {bubbles: true, cancelable: true}, //PointerEvent
                pointerout: {bubbles: true, cancelable: true}, //PointerEvent
                pointerover: {bubbles: true, cancelable: true}, //PointerEvent
                pointerleave: {bubbles: false, cancelable: true}, //PointerEvent
                pointermove: {bubbles: true, cancelable: true}, //PointerEvent
                MSPointerDown: {bubbles: true, cancelable: true}, //MSPointerEvent
                MSPointerMove: {bubbles: true, cancelable: true}, //PointerEvent
                MSPointerUp: {bubbles: true, cancelable: true}, //MSPointerEvent
                MSPointerOver: {bubbles: true, cancelable: true}, //MSPointerEvent
                MSPointerOut: {bubbles: true, cancelable: true}, //MSPointerEvent
                MSPointerHover: {bubbles: true, cancelable: true}//MSPointerEvent

            }
        },

        /**
         * @private
         */
        _DOMPasteboardElement: {
            value: null,
            enumerable: false
        },

        /**
         * @property {string} value
         * @private
         */
        _delegate: {
            value: null,
            enumerable: false
        },

        /**
         * @returns {string}
         * @param {string}
         * @default null
         */
        delegate: {
            enumerable: false,
            get: function () {
                return this._delegate;
            },
            set: function (delegate) {
                this._delegate = delegate;
            }
        },

        /**
         * @property {Application} value
         * @private
         */
        _application: {
            value: null,
            enumerable: false
        },

        /**
         * The application object associated with the event manager.
         * @returns {Application}
         * @param {Application}
         * @default null
         *
         * @todo if this changes...we probably need to unregister all the windows
         * we know about and frankly probably the components too
         */
        application: {
            enumerable: false,
            get: function () {
                return this._application;
            },
            set: function (application) {
                this._application = application;
            }
        },

        // Dictionary keyed by event types with the collection of handlers per event type
        // This dictates why the event manager observes events of a particular type

        /**
         * All windows this event Manager may be listening to
         *
         * @property {Array} value
         * @private
         */
        _registeredWindows: {
            value: null,
            enumerable: false
        },

        /**
         * @property {Object} value
         * @private
         */
        _windowsAwaitingFinalRegistration: {
            value: (new WeakMap),
            enumerable: false
        },

        // Initialization

        /**
         * @function
         * @param {external:window} aWindow
         * @returns {EventManager}
         */
        initWithWindow: {
            enumerable: false,
            value: function (aWindow) {
                if (!!this._registeredWindows) {
                    throw "EventManager has already been initialized";
                }

                // TODO do we also complain if no window is given?
                // Technically we don't need one until we start listening for events
                this.registerWindow(aWindow);
                return this;
            }
        },
        /**
         * @function
         * @param {external:window} aWindow
         */
        registerWindow: {
            enumerable: false,
            value: function (aWindow) {

                if (aWindow.defaultEventManager && aWindow.defaultEventManager !== this) {
                    throw Error("EventManager cannot register a window already registered to another EventManager");
                }

                if (this._registeredWindows && this._registeredWindows.indexOf(aWindow) >= 0) {
                    throw new Error("EventManager cannot register a window more than once");
                }

                if (!this._registeredWindows) {
                    this._registeredWindows = [];
                }

                if (this._windowsAwaitingFinalRegistration.has(aWindow)) {
                    return;
                }

                // Setup the window as much as possible now without knowing whether
                // the DOM is ready or not

                // Keep a reference to the original listener functions

                // Note I think it may be implementation specific how these are implemented
                // so I'd rather preserve any native optimizations a browser has for
                // adding listeners to the document versus and element etc.
                aWindow.Element.prototype.nativeAddEventListener = aWindow.Element.prototype.addEventListener;
                Object.defineProperty(aWindow, "nativeAddEventListener", {
                    configurable: true,
                    value: aWindow.addEventListener
                });

                aWindow.document.nativeAddEventListener = aWindow.document.addEventListener;
                aWindow.XMLHttpRequest.prototype.nativeAddEventListener = aWindow.XMLHttpRequest.prototype.addEventListener;

                if (aWindow.DocumentFragment) {
                    aWindow.DocumentFragment.prototype.nativeAddEventListener = aWindow.DocumentFragment.prototype.addEventListener;
                }

                if (aWindow.ShadowRoot) {
                    aWindow.ShadowRoot.prototype.nativeAddEventListener = aWindow.ShadowRoot.prototype.addEventListener;
                }

                if (aWindow.Worker) {
                    aWindow.Worker.prototype.nativeAddEventListener = aWindow.Worker.prototype.addEventListener;
                }
                if (aWindow.MediaController) {
                    aWindow.MediaController.prototype.nativeAddEventListener = aWindow.MediaController.prototype.addEventListener;
                }

                aWindow.Element.prototype.nativeRemoveEventListener = aWindow.Element.prototype.removeEventListener;
                Object.defineProperty(aWindow, "nativeRemoveEventListener", {
                    configurable: true,
                    value: aWindow.removeEventListener
                });

                aWindow.document.nativeRemoveEventListener = aWindow.document.removeEventListener;
                aWindow.XMLHttpRequest.prototype.nativeRemoveEventListener = aWindow.XMLHttpRequest.prototype.removeEventListener;

                if (aWindow.DocumentFragment) {
                    aWindow.DocumentFragment.prototype.nativeRemoveEventListener = aWindow.DocumentFragment.prototype.removeEventListener;
                }

                if (aWindow.ShadowRoot) {
                    aWindow.ShadowRoot.prototype.nativeRemoveEventListener = aWindow.ShadowRoot.prototype.removeEventListener;
                }

                if (aWindow.Worker) {
                    aWindow.Worker.prototype.nativeRemoveEventListener = aWindow.Worker.prototype.removeEventListener;
                }
                if (aWindow.MediaController) {
                    aWindow.MediaController.prototype.nativeRemoveEventListener = aWindow.MediaController.prototype.removeEventListener;
                }

                // Redefine listener functions

                Object.defineProperty(aWindow, "addEventListener", {
                    configurable: true,
                    value: (aWindow.XMLHttpRequest.prototype.addEventListener =
                        aWindow.Element.prototype.addEventListener =
                            aWindow.document.addEventListener =
                                function addEventListener(eventType, listener, useCapture) {
                                    return aWindow.defaultEventManager.registerEventListener(this, eventType, listener, !!useCapture);
                                })
                });

                if (aWindow.Worker) {
                    aWindow.Worker.prototype.addEventListener = aWindow.addEventListener;
                }
                if (aWindow.MediaController) {
                    aWindow.MediaController.prototype.addEventListener = aWindow.addEventListener;
                }

                Object.defineProperty(aWindow, "removeEventListener", {
                    configurable: true,
                    value: (aWindow.XMLHttpRequest.prototype.removeEventListener =
                        aWindow.Element.prototype.removeEventListener =
                            aWindow.document.removeEventListener =
                                function removeEventListener(eventType, listener, useCapture) {
                                    return aWindow.defaultEventManager.unregisterEventListener(this, eventType, listener, !!useCapture);
                                })
                });

                if (aWindow.Worker) {
                    aWindow.Worker.prototype.removeEventListener = aWindow.removeEventListener;
                }
                if (aWindow.MediaController) {
                    aWindow.MediaController.prototype.removeEventListener = aWindow.removeEventListener;
                }

                // In some browsers (Firefox) each element has their own addEventLister/removeEventListener
                // Methodology to find all elements found in Chainvas (now mostly gone from this)
                if (aWindow.HTMLDivElement.prototype.addEventListener !== aWindow.Element.prototype.nativeAddEventListener) {
                    if (aWindow.HTMLElement &&
                        'addEventListener' in aWindow.HTMLElement.prototype
                    ) {
                        var candidates = Object.getOwnPropertyNames(aWindow),
                            candidate, candidatePrototype,
                            i = 0, candidatesLength = candidates.length;
                        for (i; i < candidatesLength; i++) {
                            candidate = candidates[i];
                            if (candidate.match(/^HTML\w*Element$/) && typeof candidate === "function") {
                                candidatePrototype = candidate.prototype;
                                candidatePrototype.nativeAddEventListener = candidatePrototype.addEventListener;
                                candidatePrototype.addEventListener = aWindow.Element.prototype.addEventListener;
                                candidatePrototype.nativeRemoveEventListener = candidatePrototype.removeEventListener;
                                candidatePrototype.removeEventListener = aWindow.Element.prototype.removeEventListener;
                            }
                        }
                    }
                }

                /**
                 * The component instance directly associated with the specified element.
                 *
                 * @member external:Element#component
                 */
                Montage.defineProperty(aWindow.Element.prototype, "component", {
                    get: function () {
                        return defaultEventManager._elementEventHandlerByElement.get(this);
                    },
                    enumerable: false
                });

                /**
                 * @namespace EventManager
                 * @instance
                 * @global
                 */
                defaultEventManager = aWindow.defaultEventManager = exports.defaultEventManager = this;
                this._registeredWindows.push(aWindow);

                this._windowsAwaitingFinalRegistration.set(aWindow,aWindow);

                // Some registration demands the window's dom be accessible
                // only finalize registration when that's true
                if (/loaded|complete|interactive/.test(aWindow.document.readyState)) {
                    this._finalizeWindowRegistration(aWindow);
                } else {
                    aWindow.document.addEventListener("DOMContentLoaded", this, true);
                }

                this._evaluateShouldDispatchEventCondition();

            }
        },

        _finalizeWindowRegistration: {
            enumerable: false,
            value: function (aWindow) {

                if (!this._windowsAwaitingFinalRegistration.has(aWindow)) {
                    throw "EventManager wasn't expecting to register this window";
                }

                this._windowsAwaitingFinalRegistration.delete(aWindow);

                this._listenToWindow(aWindow);
                // TODO uninstall DOMContentLoaded listener if all windows finalized
            }
        },
        /**
         * @function
         * @param {external:window} aWindow
         */
        unregisterWindow: {
            enumerable: false,
            value: function (aWindow) {
                if (this._registeredWindows.indexOf(aWindow) < 0) {
                    throw "EventManager cannot unregister an unregistered window";
                }

                this._registeredWindows = this._registeredWindows.filter(function (element) {
                    return (aWindow !== element);
                });

                delete aWindow.defaultEventManager;

                // Restore existing listener functions

                aWindow.Element.prototype.addEventListener = aWindow.Element.prototype.nativeAddEventListener;
                Object.defineProperty(aWindow, "addEventListener", {
                    configurable: true,
                    value: aWindow.nativeAddEventListener
                });

                aWindow.document.addEventListener = aWindow.document.nativeAddEventListener;
                aWindow.XMLHttpRequest.prototype.addEventListener = aWindow.XMLHttpRequest.prototype.nativeAddEventListener;
                if (aWindow.Worker) {
                    aWindow.Worker.prototype.addEventListener = aWindow.Worker.prototype.nativeAddEventListener;
                }

                aWindow.Element.prototype.removeEventListener = aWindow.Element.prototype.nativeRemoveEventListener;
                Object.defineProperty(aWindow, "removeEventListener", {
                    configurable: true,
                    value: aWindow.nativeRemoveEventListener
                });

                aWindow.document.removeEventListener = aWindow.document.nativeRemoveEventListener;
                aWindow.XMLHttpRequest.prototype.removeEventListener = aWindow.XMLHttpRequest.prototype.nativeRemoveEventListener;
                if (aWindow.Worker) {
                    aWindow.Worker.prototype.removeEventListener = aWindow.Worker.prototype.nativeRemoveEventListener;
                }

                // In some browsers (Firefox) each element has their own addEventLister/removeEventListener
                // Methodology to find all elements found in Chainvas
                if (aWindow.HTMLDivElement.prototype.nativeAddEventListener !== aWindow.Element.prototype.addEventListener) {
                    if (aWindow.HTMLElement &&
                        'addEventListener' in aWindow.HTMLElement.prototype &&
                        aWindow.Components &&
                        aWindow.Components.interfaces
                    ) {
                        var candidate, candidatePrototype;

                        for (candidate in Components.interfaces) {
                            if (candidate.match(/^nsIDOMHTML\w*Element$/)) {
                                candidate = candidate.replace(/^nsIDOM/, '');
                                if ((candidate = window[candidate])) {
                                    candidatePrototype = candidate.prototype;
                                    candidatePrototype.addEventListener = candidatePrototype.nativeAddEventListener;
                                    delete candidatePrototype.nativeAddEventListener;
                                    candidatePrototype.removeEventListener = candidatePrototype.nativeRemoveEventListener;
                                    delete candidatePrototype.nativeRemoveEventListener;
                                }
                            }
                        }
                    }
                }

                // Delete our references

                delete aWindow.Element.prototype.nativeAddEventListener;
                delete aWindow.nativeAddEventListener;

                delete aWindow.document.nativeAddEventListener;
                delete aWindow.XMLHttpRequest.prototype.nativeAddEventListener;
                if (aWindow.Worker) {
                    delete aWindow.Worker.prototype.nativeAddEventListener;
                }

                delete aWindow.Element.prototype.nativeRemoveEventListener;
                delete aWindow.nativeRemoveEventListener;

                delete aWindow.document.nativeRemoveEventListener;
                delete aWindow.XMLHttpRequest.prototype.nativeRemoveEventListener;
                if (aWindow.Worker) {
                    delete aWindow.Worker.prototype.nativeRemoveEventListener;
                }

                delete aWindow.Element.prototype.component;

                this._stopListeningToWindow(aWindow);
            }
        },

        /**
         * @function
         */
        unregisterWindows: {
            enumerable: false,
            value: function () {
                this._registeredWindows.forEach(this.unregisterWindow);
            }
        },

        // Event Handler Registration

        /**
         * Registered event listeners.
         *
         * @example
         * ```json         * ```
         *
         * @property {Listeners} value
         * @default {}
         */
        registeredEventListeners: {
            enumerable: false,
            value: {}
        },

        /**
         * Returns a dictionary of all listeners registered for the specified eventType,
         * regardless of the target being observed.
         *
         * @function
         * @param {Event} eventType The event type.
         * @returns null || listeners
         */
        registeredEventListenersForEventType_: {
            value: function (eventType) {
                var captureRegistration = this._registeredCaptureEventListeners.get(eventType),
                    bubbleRegistration = this._registeredBubbleEventListeners.get(eventType),
                    result = null;

                if (captureRegistration) {
                    captureRegistration.forEach(function(listeners, target, map) {
                        if (listeners && listeners.length > 0) {
                            result = result || [];
                            listeners.forEach(function(aListener) {
                                result.push(aListener);
                            });
                        }
                    });
                }

                if (bubbleRegistration) {
                    bubbleRegistration.forEach(function(listeners, target, map) {
                        if (listeners && listeners.length > 0) {
                            result = result || [];
                            listeners.forEach(function(aListener) {
                                result.push(aListener);
                            });
                        }
                    });
                }

                return result;
            }
        },

        /**
         * Returns the list of all listeners registered for
         * the specified eventType on the specified target, regardless of the phase.
         *
         * @function
         * @param {Event} eventType - The event type.
         * @param {Event} target - The event target.
         * @returns {?ActionEventListener}
         */
        registeredEventListenersForEventType_onTarget_: {
            enumerable: false,
            value: function (eventType, target) {

                var captureRegistration = this._registeredCaptureEventListeners.get(eventType),
                    bubbleRegistration = this._registeredBubbleEventListeners.get(eventType),
                    listeners,
                    result = null;

                if (!eventType || !target || (!captureRegistration && !bubbleRegistration)) {
                    return null;
                } else {
                    listeners = captureRegistration ? captureRegistration.get(target) : null;
                    if (listeners) {
                        if (!result) {
                            result = listeners;
                        }
                    }
                    listeners = bubbleRegistration ? bubbleRegistration.get(target) : null;
                    if (listeners) {
                        if (!result) {
                            result = listeners;
                        } else {
                            result = result.union(listeners);
                        }
                    }
                    return result;
                }
            }
        },

        /**
         * Returns all listeners registered for
         * the specified eventType on the specified target and specified phase.
         *
         * @function
         * @param {Event} eventType - The event type.
         * @param {Event} target - The event target.
         * @returns {?ActionEventListener}
         */
        registeredEventListenersForEventType_onTarget_phase_: {
            enumerable: false,
            value: function (eventType, target, capture) {
                if (!target) {
                    return null;
                }
                return this._registeredEventListenersForEventType_onTarget_registeredEventListeners_(eventType, target, (capture ? this._registeredCaptureEventListeners : this._registeredBubbleEventListeners));
            }
        },
        _registeredEventListenersForEventType_onTarget_registeredEventListeners_: {
            value: function (eventType, target, registeredEventListeners) {

                // 0.02224230716159459 on Samsung Galaxy Tab3 7"
                var result = registeredEventListeners.get(eventType);
                return result ? result.get(target) : null;
            },
            enumerable: false
        },

        /**
         * Returns the dictionary of all listeners registered on
         * the specified target, keyed by eventType.
         *
         * @function
         * @param {Event} target The event target.
         * @returns observedEventListeners
         */
        registeredEventListenersOnTarget_: {
            value: function (target) {

                var eventType,
                    eventRegistration,
                    _registeredCaptureEventListeners = this._registeredCaptureEventListeners,
                    _registeredBubbleEventListeners = this._registeredBubbleEventListeners,
                    observedEventListeners = [],
                    mapIter;

                mapIter = _registeredCaptureEventListeners.keys();
                while ((eventType = mapIter.next().value)) {
                    eventRegistration = _registeredCaptureEventListeners.get(eventType);
                    if (eventRegistration.has(target)) {
                        observedEventListeners.push(eventType);
                    }
                }

                mapIter = _registeredBubbleEventListeners.keys();
                while ((eventType = mapIter.next().value)) {
                    eventRegistration = _registeredBubbleEventListeners.get(eventType);
                    if (eventRegistration.has(target)) {
                        observedEventListeners.push(eventType);
                    }
                }

                return observedEventListeners;
            }
        },

        /**
         * This adds the listener to the definitive collection of
         * what targets are being observed for what eventTypes by whom and in what phases.
         * This collection maintained by the EventManager is used throughout
         * the discovery and distribution steps of the event handling system.
         *
         * @function
         * @param {Event} target - The event target.
         * @param {Event} eventType - The event type.
         * @param {Event} listener - The event listener.
         * @param {Event} useCapture - The event capture.
         * @returns returnResult
         */
         _registeredCaptureEventListeners: {
           value:null
        },
        _registeredBubbleEventListeners: {
          value:null
       },
         registerEventListener: {
             enumerable: false,
             value: function registerEventListener(target, eventType, listener, useCapture) {
                 var result;
                 result = this._registerEventListener(target, eventType, listener, useCapture ? this._registeredCaptureEventListeners : this._registeredBubbleEventListeners);
                 return result;

            }
        },
        _registerEventListener: {
            enumerable: false,
            value: function _registerEventListener(target, eventType, listener, registeredEventListeners) {
                var eventTypeRegistration = registeredEventListeners.get(eventType),
                    isNewTarget = false,
                    returnResult = false,
                    listeners;

                if (!eventTypeRegistration) {
                    // First time this eventType has been requested
                    registeredEventListeners.set(eventType, (eventTypeRegistration = new Map()));
                    // listeners = [listener];
                    // eventTypeRegistration.set(target,listeners);
                    eventTypeRegistration.set(target,listener);

                    isNewTarget = true;
                    returnResult = true;
                } else {

                    // Or, the event type was already observed; install this new listener (or at least any new parts)
                    if (!eventTypeRegistration.has(target)) {
                        // listeners = [];
                        // eventTypeRegistration.set(target,listeners);
                        eventTypeRegistration.set(target,listener);

                        isNewTarget = true;
                    }
                    else {
                      listeners = eventTypeRegistration.get(target);
                      if (Array.isArray(listeners)) {
                          if (listeners.indexOf(listener) !== -1) {
                              returnResult = true;
                          } else {
                              listeners.push(listener);
                              returnResult = true;
                          }
                      } else {
                          if (listeners !== listener) {
                              listeners = [listeners,listener];
                              eventTypeRegistration.set(target,listeners);
                          }

                          returnResult = true;
                      }
                    }
                }

                if (isNewTarget && typeof target.nativeAddEventListener === "function") {
                    this._observeTarget_forEventType_(target, eventType);
                }

                return returnResult;
            }
        },
        /**
         * This unregisters the listener.
         *
         * @function
         * @param {Event} target - The event target.
         * @param {Event} eventType - The event type.
         * @param {Event} listener - The event listener.
         * @param {Event} useCapture - The event capture.
         */
         unregisterEventListener: {
             enumerable: false,
             value: function unregisterEventListener(target, eventType, listener, useCapture) {
                 //console.log("EventManager.unregisterEventListener", target, eventType, listener, useCapture);

                 return useCapture ?
                    this._unregisterEventListener(target, eventType, listener, this._registeredCaptureEventListeners, this._registeredBubbleEventListeners) :
                        this._unregisterEventListener(target, eventType, listener, this._registeredBubbleEventListeners, this._registeredCaptureEventListeners);
            }
        },
        _unregisterEventListener: {
            enumerable: false,
            value: function _unregisterEventListener (target, eventType, listener, registeredEventListeners, otherPhaseRegisteredEventListeners) {

                var eventTypeRegistration = registeredEventListeners.get(eventType),
                    listeners,
                    map;

                if (!eventTypeRegistration) {
                    // this eventType wasn't being observed at all
                    return;
                }

                // the event type was observed; see if the target was registered
                listeners = eventTypeRegistration.get(target);
                if (!listeners) {
                    return;
                }

                // the target was being observed for this eventType; see if the specified listener was registered
                if (listeners === listener) {
                    eventTypeRegistration.set(target,null);
                    this._unregisterTargetForEventTypeIfNeeded(target, eventType, listeners, registeredEventListeners, otherPhaseRegisteredEventListeners);
                    return;
                }
                else if (Array.isArray(listeners)) {
                    if (listeners.indexOf(listener) === -1) {
                        return;
                    }
                    if (this._currentDispatchedTargetListeners.has(listeners)) {
                        map = this._currentDispatchedTargetListeners.get(listeners);
                        if (!map) {
                            this._currentDispatchedTargetListeners.set(listeners,(map = new Map()));
                        }
                        map.set(listener,true);

                    } else {
                        this.spliceOne(listeners,listeners.indexOf(listener));
                        // Done unregistering the listener for the specified phase
                        // Now see if we need to remove any registrations as a result of that
                        this._unregisterTargetForEventTypeIfNeeded(target, eventType, listeners, registeredEventListeners, otherPhaseRegisteredEventListeners);

                    }
                }
                else {
                //There's only one listener and it's no this one.
                    return;
                }

                // console.log("EventManager.unregisteredEventListener", this.registeredEventListeners)
            }
        },
        _unregisterTargetForEventTypeIfNeeded: {
            value: function(target, eventType, listeners, registeredEventListeners, otherPhaseRegisteredEventListeners) {
                if (!Array.isArray(listeners) || listeners.length === 0) {
                    var eventTypeRegistration = registeredEventListeners.get(eventType),
                        otherPhaseEventTypeRegistration = otherPhaseRegisteredEventListeners.get(eventType);

                    eventTypeRegistration.delete(target);

                    if (eventTypeRegistration.size === 0 && (!otherPhaseEventTypeRegistration || (otherPhaseEventTypeRegistration && otherPhaseEventTypeRegistration.size === 0))) {
                        // If no targets for this eventType; stop observing this event
                        //delete registeredEventListeners[eventType];
                        this._stopObservingTarget_forEventType_(target, eventType);
                    }
                }
            }
        },
        /**
         * Determines the actual target to observe given a target and an eventType.
         * This correctly decides whether to observe the element specified or
         * to observe some other element to leverage event delegation.
         * This should be consulted whenever starting or stopping the observation of
         * a target for a given eventType.
         *
         * @function
         * @param {Event} eventType
         * @param {Event} target
         * @returns null || target.screen ? target.document : target.ownerDocument
         */
        actualDOMTargetForEventTypeOnTarget: {
            value: function (eventType, target) {

                if (!target.nativeAddEventListener) {
                    return null;
                } else {

                    if (/*isDocument*/!!target.defaultView) {
                        return target;
                    }

                    var entry = this.eventDefinitions[eventType],
                        bubbles;

                    // For events we know we can safely delegate to handling at a higher level, listen on the document
                    // otherwise, be less surprising and listen on the specified target

                    if (!entry) {
                        return target;
                    }

                    // TODO allow eventTypes to describe a preferred delegation target window|document|none etc.
                    bubbles = (typeof entry.bubbles === "function") ? entry.bubbles(target) : entry.bubbles;

                    if (bubbles) {
                        // TODO why on the document and not the window?
                        var shadowRoot;
                        return /* isWindow*/target.screen ? target.document :
                            (shadowRoot = this.shawdowRootFromNode(target)) ?
                                shadowRoot : target.ownerDocument;
                    } else {
                        return target;
                    }
                }
            }
        },

        shawdowRootFromNode: {
            value: function isInShadow(node) {
                if (window.ShadowRoot) {
                    while (node) {
                        if (node.toString() === "[object ShadowRoot]") {
                            return node;
                        }
                        node = node.parentNode;
                    }
                }
            }
        },

        /**
         * @private
         */
        _observedTarget_byEventType_: {value: null},

        // Individual Event Registration

        _scrollBlockingEvents: {
            value: [
                'wheel',
                'mousewheel',
                'touchstart',
                'touchmove',
                'scroll'
            ]
        },

        /**
         * @private
         */
        _observeTarget_forEventType_: {
            enumerable: false,
            value: function (target, eventType) {

                var listenerTarget;

                if ((listenerTarget = this.actualDOMTargetForEventTypeOnTarget(eventType, target)) && (!this._observedTarget_byEventType_[eventType] || !this._observedTarget_byEventType_[eventType].has(listenerTarget))) {
                    if (!this._observedTarget_byEventType_[eventType]) {
                        this._observedTarget_byEventType_[eventType] = new Map();
                    }
                    this._observedTarget_byEventType_[eventType].set(listenerTarget,this);

                    var isScrollBlocking = this._scrollBlockingEvents.indexOf(eventType) !== -1,
                        eventOpts = isScrollBlocking ? {
                            passive: true
                        } : true;

                    listenerTarget.nativeAddEventListener(eventType, this, eventOpts);
                }
                // console.log("started listening: ", eventType, listenerTarget)
            }
        },

        /**
         * @private
         */
        _stopObservingTarget_forEventType_: {
            enumerable: false,
            value: function (target, eventType) {

                var listenerTarget;

                listenerTarget = this.actualDOMTargetForEventTypeOnTarget(eventType, target);
                if (listenerTarget) {
                    this._observedTarget_byEventType_[eventType].delete(listenerTarget);
                    listenerTarget.nativeRemoveEventListener(eventType, this, true);
                }
                // console.log("stopped listening: ", eventType, window)
            }
        },

        /**
         * @private
         */
        _activationHandler: {
            enumerable: true,
            value: null
        },

        // Toggle listening for EventManager

        /**
         * @private
         */
        _listenToWindow: {
            enumerable: false,
            value: function (aWindow) {

                // We use our own function to handle activation events so it's not inadvertently
                // removed as a listener when removing the last listener that may have also been observing
                // the same eventType of an activation event
                if (!this._activationHandler) {
                    var eventManager = this;
                    this._activationHandler = function _activationHandler(evt) {
                        var eventType = evt.type,
                            canBecomeActiveTarget = eventType !== "mouseenter" && eventType !== "pointerenter",
                            touchCount;

                        // Prepare any components associated with elements that may receive this event
                        // They need to registered there listeners before the next step, which is to find the components that
                        // observing for this type of event
                        if (evt.changedTouches) {
                            touchCount = evt.changedTouches.length;
                            for (var i = 0; i < touchCount; i++) {
                                eventManager._prepareComponentsForActivation(evt.changedTouches[i].target, canBecomeActiveTarget);
                            }
                        } else {
                            eventManager._prepareComponentsForActivation(evt.target, canBecomeActiveTarget);
                        }
                    };
                }

                this.registerTargetForActivation(aWindow);

                if (this.application) {

                    var applicationLevelEvents = this.registeredEventListenersOnTarget_(this.application),
                        eventType;

                    for (eventType in applicationLevelEvents) {
                        if (applicationLevelEvents.hasOwnProperty(eventType)) {
                            this._observeTarget_forEventType_(aWindow, eventType);
                        }
                    }
                }
            }
        },

        registerTargetForActivation: {
            value: function (target) {
                var _document = target instanceof Window ? target.document : target;
                // The EventManager needs to handle "gateway/pointer/activation events" that we
                // haven't let children listen for yet
                // when the EM handles them eventually it will need to allow
                // all components from the event target to the window to prepareForActivationEvents
                // before finding event handlers that were registered for these events
                if (window.PointerEvent) {
                    target.nativeAddEventListener("pointerdown", this._activationHandler, true);
                    _document.nativeAddEventListener("pointerenter", this._activationHandler, true);

                } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) {
                    target.nativeAddEventListener("MSPointerDown", this._activationHandler, true);
                    // IE10 has no support for pointerenter or pointerleave events.
                    _document.nativeAddEventListener("mouseenter", this._activationHandler, true);

                } else {
                    target.nativeAddEventListener("touchstart", this._activationHandler, true);
                    target.nativeAddEventListener("mousedown", this._activationHandler, true);
                    // mouseenter events are not dispatched from window under Chrome and Safari.
                    _document.nativeAddEventListener("mouseenter", this._activationHandler, true);
                }

                target.nativeAddEventListener("focus", this._activationHandler, true);
            }

        },

        /**
         * @private
         */
        _stopListeningToWindow: {
            enumerable: false,
            value: function (aWindow) {

                var applicationLevelEvents = this.registeredEventListenersOnTarget_(this.application),
                    windowLevelEvents = this.registeredEventListenersOnTarget_(aWindow),
                    eventType,
                    index;

                for (eventType in applicationLevelEvents) {
                    if (applicationLevelEvents.hasOwnProperty(eventType)) {
                        this._stopObservingTarget_forEventType_(aWindow, eventType);
                    }
                }

                for (eventType in windowLevelEvents) {
                    if (windowLevelEvents.hasOwnProperty(eventType)) {
                        this._stopObservingTarget_forEventType_(aWindow, eventType);
                    }
                }

                if ((index = this._listeningWindowOnTouchCancel.indexOf(aWindow)) > -1) {
                    this.spliceOne(this._listeningWindowOnTouchCancel,index);
                    // the listener on 'touchCancel' has already been removed by the previous step.
                }
            }
        },

        /**
         * @function
         */
        _resetRegisteredEventListeners: {
            enumerable: false,
            value: function (registeredEventListeners) {
                var i, l, target, eventType,
                    eventRegistration,
                    self = this;

                for (eventType in registeredEventListeners) {
                    if (registeredEventListeners.hasOwnProperty(eventType)) {
                        eventRegistration = registeredEventListeners.get(eventType);

                        if (eventRegistration && eventRegistration.length > 0) {
                            for (i = 0, l = eventRegistration.length; i < l; i++) {
                                target = eventRegistration[i];
                                self._stopObservingTarget_forEventType_(target, eventType);
                            }
                        }
                    }
                }
            }
        },

        reset: {
            enumerable: false,
            value: function () {
                this._resetRegisteredEventListeners(this._registeredCaptureEventListeners);
                this._resetRegisteredEventListeners(this._registeredBubbleEventListeners);

                // TODO for each component claiming a pointer, force them to surrender the pointer?
                this._claimedPointers = new Map();
                this._registeredCaptureEventListeners = new Map();
                this._registeredBubbleEventListeners = new Map();

            }
        },

        /**
         * @function
         */
        unload: {
            enumerable: false,
            value: function () {
                this._stopListening();
            }
        },
        _bubbleMethodNameByEventType_: {
            value: new Map()
        },
        _bubbleMethodNameByEventTypeIdentifier_: {
            value: new Map()
        },
        /**
         * @function
         */
        methodNameForBubblePhaseOfEventType: {
            enumerable: false,
            value: function methodNameForBubblePhaseOfEventType(eventType, identifier, capitalizedEventType, capitalizedIdentifier) {
                var eventTypeBucket;
                if (identifier) {
                    eventTypeBucket = this._bubbleMethodNameByEventTypeIdentifier_.get(eventType) || (this._bubbleMethodNameByEventTypeIdentifier_.set(eventType, new Map())).get(eventType);
                    return eventTypeBucket.get(identifier) || (eventTypeBucket.set(identifier, ("handle" + (capitalizedIdentifier || identifier.toCapitalized()) + (capitalizedEventType || eventType.toCapitalized())))).get(identifier);
                } else {
                    return this._bubbleMethodNameByEventType_.get(eventType) || (this._bubbleMethodNameByEventType_.set(eventType, ("handle" + (capitalizedEventType || eventType.toCapitalized())))).get(eventType);
                }
            }
        },

        /**
         * @private
         */
        _captureMethodNameByEventType_: {
            value: new Map()
        },
        _catptureMethodNameByEventTypeIdentifier_: {
            value: new Map()
        },

        methodNameForCapturePhaseOfEventType: {
            enumerable: false,
            value: function methodNameForCapturePhaseOfEventType(eventType, identifier, capitalizedEventType, capitalizedIdentifier) {
                var eventTypeBucket;
                if (identifier) {
                    eventTypeBucket = this._catptureMethodNameByEventTypeIdentifier_.get(eventType) || (this._catptureMethodNameByEventTypeIdentifier_.set(eventType,new Map())).get(eventType);
                    return eventTypeBucket.get(identifier) || (eventTypeBucket.set(identifier,("capture" + (capitalizedIdentifier || identifier.toCapitalized()) + (capitalizedEventType || eventType.toCapitalized())))).get(identifier);
                } else {
                    return this._captureMethodNameByEventType_.get(eventType) ||
                        (this._captureMethodNameByEventType_.set(eventType, ("capture" + (capitalizedEventType || eventType.toCapitalized())))).get(eventType);
                }
            }
        },

        // Claimed pointer information

        /**
         * @private
         */
        _claimedPointers: {
            enumerable: false,
            value: null
        },

        /**
         * The component claiming the specified pointer component
         *
         * @function
         * @param {string} pointer The pointer identifier in question
         * @returns component
         */
        componentClaimingPointer: {
            value: function (pointer) {
                return this._claimedPointers.get(pointer);
            }
        },

        /**
         * Whether or not the specified pointer identifier is claimed by the
         * specified component.
         *
         * @function
         * @param {string} pointer The pointer identifier in question
         * @param {string} component The component to interrogate regarding
         * ownership of the specified pointer
         * @returns {boolean}
         */
        isPointerClaimedByComponent: {
            value: function (pointer, component) {

                if (!component) {
                    throw "Must specify a valid component to see if it claims the specified pointer, '" + component + "' is not valid.";
                }

                return this._claimedPointers.get(pointer) === component;
            }
        },

        /**
         * Claims that a pointer, referred to by the specified pointer identifier,
         * is claimed by the specified component.  This does not give the component
         * exclusive use of the pointer per se, but does indicate that the
         * component is acting in a manner where it expects to be the only one
         * performing major actions in response to this pointer.  Other components
         * should respect the claimant's desire to react to this pointer in order
         * to prevent an entire hierarchy of components from reacting to a pointer
         * in potentially conflicting ways.
         *
         * If the pointer is currently claimed by another component that component
         * is asked to surrender the pointer, which is may or may not agree to do.
         *
         * @function
         * @param {string} pointer The pointer identifier to claim
         * @param {string} component The component that is claiming the specified
         * pointer.
         * @returns {boolean} - Whether or not the pointer was successfully claimed.
         */
        claimPointer: {
            value: function (pointer, component) {

                // if null, undefined, false: complain
                if (!pointer && pointer !== 0) {
                    throw "Must specify a valid pointer to claim, '" + pointer + "' is not valid.";
                }

                if (!component) {
                    throw "Must specify a valid component to claim a pointer, '" + component + "' is not valid.";
                }

                var claimant = this._claimedPointers.get(pointer);

                if (claimant === component) {
                    // Already claimed this pointer ourselves
                    return true;

                } else if (!claimant) {
                    //Nobody has claimed it; go for it
                    this._claimedPointers.set(pointer,component);
                    return true;

                } else {
                    //Somebody else has claimed it; ask them to surrender
                    if (claimant.surrenderPointer(pointer, component)) {
                        this._claimedPointers.set(pointer,component);
                        return true;
                    } else {
                        return false;
                    }
                }

            }
        },

        /**
         * Forfeits the specified pointer identifier from the specified component.
         * The specified component must be the current claimant.
         *
         * @function
         * @param {string} pointer The pointer identifier in question
         * @param {string} component The component that is trying to forfeit the
         * specified pointer
         */
        forfeitPointer: {
            value: function (pointer, component) {
                if (component === this._claimedPointers.get(pointer)) {
                    this._claimedPointers.delete(pointer);
                } else {
                    throw "Not allowed to forfeit pointer '" + pointer + "' claimed by another component";
                }

            }
        },

        /**
         * Forfeits all pointers from the specified component.
         *
         * @function
         * @param {Component} component
         */
        forfeitAllPointers: {
            value: function (component) {

                var mapIter  = this._claimedPointers.keys(),
                    pointerKey,
                    claimant;

                while ((pointerKey = mapIter.next().value)) {
                    claimant = this._claimedPointers.get(pointerKey);
                    if (component === claimant) {
                        // NOTE basically doing the work ofr freePointerFromComponent
                        this._claimedPointers.delete(pointerKey);
                    }
                }
            }
        },

        // Pointer Storage for calculating velocities

        /**
         * @private
         */
        _isStoringPointerEvents: {
            enumerable: false,
            value: false
        },

        /**
         * @returns {boolean}
         * @default false
         */
        isStoringPointerEvents: {
            enumerable: true,
            get: function () {
                return this._isStoringPointerEvents;
            },
            set: function (value) {
                if (value === true) {
                    if (!this._isStoringPointerEvents) {
                        this._isStoringPointerEvents = true;

                        if (
                            (typeof PointerEvent !== "undefined") ||
                                (typeof MSPointerEvent !== "undefined" && typeof navigator !== "undefined" && navigator.msPointerEnabled)
                        ) {
                            Object.defineProperty(MutableEvent.prototype, "velocity", {
                                get: function () {
                                    return defaultEventManager.pointerMotion(this.pointerId).velocity;
                                }
                            });
                        } else if (typeof Touch !== "undefined") {
                            Object.defineProperty(Touch.prototype, "velocity", {
                                get: function () {
                                    return defaultEventManager.pointerMotion(this.identifier).velocity;
                                }
                            });
                        }
                    }
                } else {
                    this._isStoringPointerEvents = false;
                    this._pointerStorage.memory = {};
                    this._isMouseDragging = false;
                }
            }
        },

        /**
         * @private
         */
        _isStoringMouseEventsWhileDraggingOnly: {
            enumerable: false,
            value: true
        },

        /**
         * @type {Function}
         * @default {boolean} true
         */
        isStoringMouseEventsWhileDraggingOnly: {
            enumerable: true,
            get: function () {
                return this._isStoringMouseEventsWhileDraggingOnly;
            },
            set: function (value) {
                this._isStoringMouseEventsWhileDraggingOnly = (value === true) ? true : false;
            }
        },

        /**
         * @private
         */
        _isMouseDragging: {
            enumerable: false,
            value: false
        },

        /**
         * @private
         */
        _pointerStorage: {
            enumerable: false,
            value: {
                memory: {},
                velocity: {},
                add: function (identifier, data) {
                    if (!this.memory[identifier]) {
                        this.memory[identifier] = {
                            data: new Array(32),
                            size: 0,
                            pos: 0
                        };
                    }
                    this.memory[identifier].data[this.memory[identifier].pos] = data;
                    if (this.memory[identifier].size < this.memory[identifier].data.length) {
                        this.memory[identifier].size++;
                    }
                    this.memory[identifier].pos = (this.memory[identifier].pos + 1) % this.memory[identifier].data.length;
                },
                remove: function (identifier) {
                    delete this.memory[identifier];
                },
                clear: function (identifier) {
                    if (this.memory[identifier]) {
                        this.memory[identifier].size = 0;
                    }
                },
                getMemory: function (identifier) {
                    return this.memory[identifier];
                },
                isStored: function (identifier) {
                    return (this.memory[identifier] && (this.memory[identifier].size > 0));
                },
                storeEvent: function (mutableEvent) {
                    var isBrowserSupportPointerEvents = currentEnvironment.isBrowserSupportPointerEvents,
                        event = mutableEvent instanceof MutableEvent ? mutableEvent._event : mutableEvent,
                        pointerType = event.pointerType;

                    if ((isBrowserSupportPointerEvents &&
                        (pointerType === "mouse" || (window.MSPointerEvent && pointerType === window.MSPointerEvent.MSPOINTER_TYPE_MOUSE))) ||
                        (!isBrowserSupportPointerEvents && event instanceof MouseEvent)) {

                        switch (mutableEvent.type) {
                            case "pointerdown":
                            case "MSPointerDown":
                            case "mousedown":
                                defaultEventManager._isMouseDragging = true;
                                /* falls through */
                            // roll into mousemove. break omitted intentionally.
                            case "pointermove":
                            case "MSPointerMove":
                            case "mousemove":
                                if (defaultEventManager._isStoringMouseEventsWhileDraggingOnly) {
                                    if (defaultEventManager._isMouseDragging) {
                                        this._storeMouse(mutableEvent);
                                    }
                                } else {
                                    this._storeMouse(mutableEvent);
                                }
                                break;

                            case "pointerup":
                            case "MSPointerUp":
                            case "mouseup":
                                this._storeMouse(mutableEvent);
                                break;
                        }
                    } else if ((isBrowserSupportPointerEvents &&
                        (pointerType === "touch" || (window.MSPointerEvent && pointerType === window.MSPointerEvent.MSPOINTER_TYPE_TOUCH))) ||
                        (window.TouchEvent !== void 0 && !isBrowserSupportPointerEvents && event instanceof TouchEvent)) {

                        switch (event.type) {
                            case "pointerdown":
                            case "MSPointerDown":
                            case "pointermove":
                            case "MSPointerMove":
                            case "pointerup":
                            case "MSPointerUp":
                                this._storeTouch(event.pointerId, event.clientX, event.clientY, event.timeStamp);
                                break;

                            case "touchstart":
                            case "touchmove":
                                this._storeTouches(event.touches, event.timeStamp);
                                break;

                            case "touchend":
                                this._storeTouches(event.changedTouches, event.timeStamp);
                                break;
                        }
                    }
                },

                removeEvent: function (mutableEvent) {
                    var isBrowserSupportPointerEvents = currentEnvironment.isBrowserSupportPointerEvents,
                        event = mutableEvent instanceof MutableEvent ? mutableEvent._event : mutableEvent;

                    if ((isBrowserSupportPointerEvents &&
                        (mutableEvent.pointerType === "mouse" || (window.MSPointerEvent && mutableEvent.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_MOUSE))) ||
                        (!isBrowserSupportPointerEvents && event instanceof MouseEvent)) {

                        if (mutableEvent.type === "mouseup" || mutableEvent.type === "pointerup" || mutableEvent.type === "MSPointerUp") {
                            defaultEventManager._isMouseDragging = false;

                            if (defaultEventManager._isStoringMouseEventsWhileDraggingOnly) {
                                this.clear("mouse");
                            }
                        }
                    } else if ((isBrowserSupportPointerEvents &&
                        (mutableEvent.pointerType === "touch" || (window.MSPointerEvent && mutableEvent.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_TOUCH))) ||
                        (window.TouchEvent !== void 0 && !isBrowserSupportPointerEvents && event instanceof TouchEvent)) {

                        if (mutableEvent.type === "touchend" || mutableEvent.type === "pointerup" || mutableEvent.type === "MSPointerUp") {
                            if (mutableEvent.changedTouches) {
                                for (var i = 0, changedTouches = mutableEvent.changedTouches, iChangedTouch; (iChangedTouch = changedTouches[i]); i++) {
                                    this.remove(iChangedTouch.identifier);
                                }
                            } else {
                                this.remove(mutableEvent.pointerId);
                            }
                        }
                    }
                },

                _storeMouse: function (event) {
                    this.add("mouse", {
                        clientX: event.clientX,
                        clientY: event.clientY,
                        timeStamp: event.timeStamp
                    });

                    Object.defineProperty(event, "velocity", {
                        get: function () {
                            return defaultEventManager.pointerMotion("mouse").velocity;
                        }
                    });
                },

                _storeTouches: function (touches, timeStamp) {
                    var touch;

                    for (var i = 0; (touch = touches[i]); i++) {
                        this._storeTouch(touch.identifier, touch.clientX, touch.clientY, timeStamp);
                    }
                },

                _storeTouch: function (identifier, clientX, clientY, timeStamp) {
                    this.add(identifier, {
                        clientX: clientX,
                        clientY: clientY,
                        timeStamp: timeStamp
                    });
                }
            }
        },

        /**
         @private
         */
        _getPointerVelocityData: {
            enumerable: false,
            value: function (identifier) {
                var i = 0,
                    memory,
                    memoryLength,
                    evt,
                    startTime,
                    iTime,
                    oldTime, oldX, oldY, squaredModule,
                    addData = true,
                    data = {
                        x: [],
                        y: [],
                        time: []
                    };
                memory = defaultEventManager._pointerStorage.getMemory(identifier);
                memoryLength = memory.data.length;
                evt = memory.data[((memory.pos - 1) + memoryLength) % memoryLength];
                startTime = iTime = oldTime = evt.timeStamp;
                oldX = evt.clientX;
                oldY = evt.clientY;
                while (addData && (iTime > startTime - 350) && (i < memory.size)) {
                    evt = memory.data[((memory.pos - i - 1) + memoryLength) % memoryLength];
                    iTime = evt.timeStamp;
                    squaredModule = oldX * oldX + oldY * oldY;
                    if ((squaredModule > 2) && ((oldTime - iTime) <= 50)) {
                        data.x.push(evt.clientX);
                        data.y.push(evt.clientY);
                        data.time.push(iTime);
                        oldTime = iTime;
                        oldX = evt.clientX;
                        oldY = evt.clientY;
                        i++;
                    } else {
                        addData = false;
                    }
                }
                return data;
            }
        },

        /**
         @private
         */
        _fitPointerCurve: {
            enumerable: false,
            value: function (bezier, data) {
                var a, b, c, d, epsilon = 0.0001,
                    dl = data.length, e, t, v, t2, t3, i,
                    f0, c0, d0, b0, a0, s0, e0,
                    f1, c1, d1, b1, a1, s1, e1,
                    f2, c2, d2, b2, a2, s2, e2,
                    f3, c3, d3, b3, a3, s3, e3;
                do {
                    f0 = c0 = d0 = b0 = a0 = s0 = f1 = c1 = d1 = b1 = a1 = s1 = f2 = c2 = d2 = b2 = a2 = s2 = f3 = c3 = d3 = b3 = a3 = s3 = 0;
                    for (i = 0; i < dl; i++) {
                        e = data[i];
                        t = e.t;
                        t2 = t * t;
                        t3 = t2 * t;
                        v = e.v;
                        e0 = epsilon * (6 * (t2 - t) - t3 + 2);
                        e1 = epsilon * 6 * (t3 - 2 * t2 + t);
                        e2 = epsilon * 6 * (t2 - t3);
                        e3 = epsilon * 2 * t3;
                        s0 += e0 * e0;
                        s1 += e1 * e1;
                        s2 += e2 * e2;
                        s3 += e3 * e3;
                        f0 += v * e0;
                        f1 += v * e1;
                        f2 += v * e2;
                        f3 += v * e3;
                        d0 -= e0;
                        d1 -= e1;
                        d2 -= e2;
                        d3 -= e3;
                        c0 -= e0 * t;
                        c1 -= e1 * t;
                        c2 -= e2 * t;
                        c3 -= e3 * t;
                        b0 -= e0 * t2;
                        b1 -= e1 * t2;
                        b2 -= e2 * t2;
                        b3 -= e3 * t2;
                        a0 -= e0 * t3;
                        a1 -= e1 * t3;
                        a2 -= e2 * t3;
                        a3 -= e3 * t3;
                    }
                    epsilon *= 2;
                } while (s0 === 0 || s1 === 0 || s2 === 0 || s3 === 0);
                t = epsilon / s0;
                f0 *= t;
                c0 *= t * 3;
                d0 *= t;
                b0 *= t * 3;
                a0 *= t;
                t = epsilon / s1;
                f1 *= t;
                c1 *= t * 3;
                d1 *= t;
                b1 *= t * 3;
                a1 *= t;
                t = epsilon / s2;
                f2 *= t;
                c2 *= t * 3;
                d2 *= t;
                b2 *= t * 3;
                a2 *= t;
                t = epsilon / s3;
                f3 *= t;
                c3 *= t * 3;
                d3 *= t;
                b3 *= t * 3;
                a3 *= t;
                s0 = bezier[0];
                s1 = bezier[1];
                s2 = bezier[2];
                s3 = bezier[3];
                a = (s1 - s2) * 3 + s3 - s0;
                b = s0 + s2 - 2 * s1;
                c = s1 - s0;
                d = s0;
                for (i = 0; i < 20; i++) {
                    t = f0 + d * d0 + c * c0 + b * b0 + a * a0;
                    s0 += t;
                    d += t;
                    a -= t;
                    b += t;
                    c -= t;
                    t = f1 + d * d1 + c * c1 + b * b1 + a * a1;
                    s1 += t;
                    a += t * 3;
                    b -= t + t;
                    c += t;
                    t = f2 + d * d2 + c * c2 + b * b2 + a * a2;
                    s2 += t;
                    a -= t * 3;
                    b += t;
                    t = f3 + d * d3 + c * c3 + b * b3 + a * a3;
                    s3 += t;
                    a += t;
                }
                bezier[0] = s0;
                bezier[1] = s1;
                bezier[2] = s2;
                bezier[3] = s3;
            }
        },

        /**
         @private
         */
        _pointerBezierValue: {
            enumerable: false,
            value: function (t, bezier) {
                var it = 1 - t;
                return it * it * it * bezier[0] + 3 * it * it * t * bezier[1] + 3 * it * t * t * bezier[2] + t * t * t * bezier[3];
            }
        },
        /**
         @private
         */
        _calculatePointerVelocity: {
            enumerable: false,
            value: function (time, position) {
                var length = time.length,
                    timeMin = time[0],
                    timeMax = time[0],
                    timeInterval,
                    iMin = 0, i;
                for (i = 1; i < length; i++) {
                    if (time[i] < timeMin) {
                        timeMin = time[i];
                        iMin = i;
                    }
                }
                timeInterval = timeMax - timeMin;
                if (timeInterval) {
                    if (length > 5) {
                        var s, e, bezier, data = [];
                        for (i = 0; i < length; i++) {
                            data[i] = {
                                v: position[i],
                                t: (time[i] - timeMin) / timeInterval
                            };
                        }
                        s = data[iMin].v;
                        e = data[0].v;
                        bezier = [s, (s * 2 + e) / 3, (s + e * 2) / 3, e];
                        this._fitPointerCurve(bezier, data);
                        return (this._pointerBezierValue(0.8, bezier) - this._pointerBezierValue(0.6, bezier)) * 5000 / timeInterval;
                    } else if (length > 1) {
                        return (position[0] - position[iMin]) * 1000 / timeInterval;
                    } else {
                        return 0;
                    }
                } else {
                    return 0;
                }
            }
        },

        /**
         @function
         @param {attribute} identifier
         */
        pointerMotion: {
            value: function (identifier) {
                if (defaultEventManager._pointerStorage.isStored(identifier)) {
                    return {velocity: (new _PointerVelocity()).initWithIdentifier(identifier)};
                } else {
                    return undefined;
                }
            }
        },

        monitorDOMModificationInEventHandling: {value: false},

        domModificationEventHandler: {
            value: Montage.specialize({
                handleEvent: {
                    value: function (event) {
                        throw "DOM Modified";
                    }
                },
                captureDOMSubtreeModified: {
                    value: function (event) {
                        throw "DOMSubtreeModified";
                    }
                },
                captureDOMAttrModified: {
                    value: function (event) {
                        throw "DOMAttrModified";
                    }
                },
                captureDOMCharacterDataModified: {
                    value: function (event) {
                        throw "DOMCharacterDataModified";
                    }
                }
            })
        },

        _trackingTouchStartList: {
            value: null
        },
        _trackingTouchEndList: {
            value: null
        },

        _trackingTouchTimeoutIDs: {
            value: null
        },

        _wouldTouchTriggerSimulatedEvent: {
            value: function (event) {

                switch (event.type) {
                    case "touchstart":
                    case "touchend":
                        return true;
                    default:
                        return false;
                }

            }
        },

        _couldEventBeSimulated: {
            value: function (event) {

                switch (event.type) {
                    case "mousedown":
                    case "mouseup":
                    case "click":
                        return true;
                    default:
                        return false;
                }

            }
        },

        _listeningWindowOnTouchCancel: {
            value: []
        },

        _findWindowFromEvent: {
            value: function (event) {
                var target = event.target,
                    aWindow;

                if (target) {
                    aWindow = target instanceof Window ? target : target.defaultView instanceof Window ?
                        target.defaultView : target.ownerDocument && target.ownerDocument.defaultView ?
                        target.ownerDocument.defaultView : null;
                }

                return aWindow;
            }
        },

        _isWindowListeningOnTouchCancel: {
            value: function (aWindow) {
                return this._registeredWindows.indexOf(aWindow) > -1 && this._listeningWindowOnTouchCancel.indexOf(aWindow) > -1;
            }
        },

        _listenToTouchCancelIfNeeded: {
            value: function (event) {
                var aWindow = this._findWindowFromEvent(event);

                if (aWindow && !this._isWindowListeningOnTouchCancel(aWindow)) {
                    var self = this;

                    aWindow.addEventListener("touchcancel", function touchcancelListener(event) {
                        var changedTouches = event.changedTouches,
                            touchesStartList = self._trackingTouchStartList,
                            identifier;

                        for (var i = 0, length = changedTouches.length; i < length; i++) {
                            identifier = changedTouches[i].identifier;

                            if (touchesStartList.has(identifier)) {
                                touchesStartList.delete(identifier);
                            }
                        }
                    }, true);

                    this._listeningWindowOnTouchCancel.push(aWindow);
                }
            }
        },

        _blocksEmulatedEvents: {
            value: true
        },
        blocksEmulatedEvents: {
            get: function() { return this._blocksEmulatedEvents; },
            set: function(value) {
                if (value !== this._blocksEmulatedEvents) {
                    this._blocksEmulatedEvents = value;
                    this._evaluateShouldDispatchEventCondition();
                }
            }
        },
        _shouldDispatchEventCondition: {
            value: undefined
        },

        _evaluateShouldDispatchEventCondition: {
            value: function() {
                this._shouldDispatchEventCondition = (this.blocksEmulatedEvents && !window.PointerEvent &&
                !(window.MSPointerEvent && window.navigator.msPointerEnabled));
            }
        },

        /**
         * @function
         * @param {Event} event
         * @description Decides if an event can be dispatched by the EventManager within a montage app.
         * Filter emulated mouse events (mousedown, mouseup, click) from touch events.
         *
         * @private
         */
        _shouldDispatchEvent: {
            value: function (event) {
                if (this._shouldDispatchEventCondition) {
                    /**
                     * Under IOS < 10.3.1, emulated mouse events have a timestamp set to 0. (Just WKWebView not UIWebView)
                     * starting with 10.3.1, timeStamp is non null. However, emulated events don't have the property movementX/Y
                     * that desktop Safari has, so we're adding logic to leverage that.
                     * Plus, this property can't be used for Firefox.
                     * Firefox has an open bug since 2004: the property timeStamp is not populated correctly.
                     * -> https://bugzilla.mozilla.org/show_bug.cgi?id=238041
                     */
                    if (this.environment.isIOSDevice && this.environment.isWKWebView) {
                        if (event.timeStamp === 0) {
                            return false;
                        }
                        if (this._couldEventBeSimulated(event) && !event.hasOwnProperty("movementX")) {
                            return false;
                        }
                    }

                    /**
                     * No needs do dispatch mouse events on android devices.
                     */
                    if (this.environment.isAndroidDevice && this._couldEventBeSimulated(event)) {
                        return false;
                    }

                    // Checks if the event may trigger simulated events.
                    if (this._wouldTouchTriggerSimulatedEvent(event)) {
                        var changedTouches = event.changedTouches;

                        // Needs to clean the tracking touches "start" when a touch event is canceled.
                        this._listenToTouchCancelIfNeeded(event);

                        for (var i = 0, length = changedTouches.length; i < length; i++) {
                            this._trackTouch(event, changedTouches[i]);
                        }

                    } else if (this._couldEventBeSimulated(event)) { // Determines if mouse events are simulated.
                        return !this._isEmulatedEvent(event);
                    }  // else -> Dispatches all the others.
                }

                return true;
            }
        },

        _trackTouch: {
            value: function (touchEvent, touch) {
                var timeoutIDs = this._trackingTouchTimeoutIDs,
                    trackingTouchStartList = this._trackingTouchStartList,
                    trackingTouchEndList = this._trackingTouchEndList,
                    touchIdentifier = touch.identifier;

                touch.timeStamp = touchEvent.timeStamp;

                if (touchEvent.type === "touchstart") {
                    /**
                     * Touch identifiers are not unique for Firefox or Chrome (they are re-used).
                     * So, we need to clear the timeout that was supposed to clean this tracking touch.
                     */
                    var timeoutID = timeoutIDs.get(touchIdentifier);

                    if (timeoutID) {
                        clearTimeout(timeoutID);
                        timeoutIDs.delete(touchIdentifier);
                    }

                    trackingTouchStartList.set(touchIdentifier,touch);

                } else { // touchend
                    timeoutIDs.set(touchIdentifier, setTimeout(function () {
                        trackingTouchEndList.delete(touchIdentifier);
                        delete timeoutIDs[touchIdentifier];
                    }, 400));
                    /**
                     * 400ms -> need a higher timeout than the click delay for UIWebViews.
                     * Probably related to the fact Apple pauses JavaScript execution during scrolls on UIWebViews.
                     * http://developer.telerik.com/featured/scroll-event-change-ios-8-big-deal/
                     */

                    trackingTouchStartList.delete(touchIdentifier);
                    trackingTouchEndList.set(touchIdentifier,touch);
                }
                // console.groupTimeEnd("_trackTouch");

            }
        },

        /**
         * @function
         * @param {Event} event
         * @description Decides if an event is an emualted mouse event.
         * Checks if a target has already been "activated" by a touch.
         *
         * @private
         */
        _isEmulatedEvent: {
            value: function (event) {
                /**
                 * Can't use the position, indeed the emulated mouse events are not at the same position
                 * than the touch events than triggered them. Doesn't work with a radius as well.
                 * (using a radius can make it fail with wide fast movements -> FF)
                 *
                 * Needs to check both maps for devices with multiples pointers
                 * (touchstart + mousedown -> mouseup -> click) or (touchstart + delay ≈ 600ms -> mousedown)
                 */
                var response = this._findEmulatedEventIdentifierWithEventAndTrackingTouchList(event, this._trackingTouchStartList) > -1;

                if (!response) {
                    var trackingTouchList = this._trackingTouchEndList,
                        identifier = this._findEmulatedEventIdentifierWithEventAndTrackingTouchList(event, trackingTouchList);

                    // Faster "awake", can be useful for devices with multiple pointers. (simultaneous click/touch)
                    if ((response = identifier > -1) && event.type === "click") {
                        var timeoutIDs = this._trackingTouchTimeoutIDs,
                            timeoutID = timeoutIDs.get(identifier);

                        if (timeoutID) {
                            clearTimeout(timeoutID);

                            trackingTouchList.delete(identifier);
                            timeoutIDs.delete(identifier);
                        }
                    }
                }

                return response;
            }
        },

        _emulatedEventTimestampThreshold: {
            value: 20 //ms
        },

        _emulatedEventRadiusThreshold: {
            value: 20 //px
        },

        /**
         * @function
         * @private
         *
         */
        _findEmulatedEventIdentifierWithEventAndTrackingTouchList: {
            value: function (mouseEvent, trackingTouchList) {
                var mouseTarget = mouseEvent.target,
                    identifier = -1,
                    key,
                    touch,
                    mapIter;

                mapIter = trackingTouchList.keys();
                while ((key = mapIter.next().value)) {
                    touch = trackingTouchList.get(key);

                    if (touch.target === mouseTarget ||
                        this._couldEmulatedEventHaveWrongTarget(
                            touch,
                            mouseEvent,
                            this._emulatedEventRadiusThreshold,
                            this._emulatedEventTimestampThreshold
                        )) {

                        identifier = key;
                        break;
                    }
                }
                return identifier;
            }
        },

        /**
         * @function
         * @private
         * @description Check if a mouse event can not be a simulated event even if the target is different.
         * Indeed, Touch Events and simulated Mouse Events can have a different target and not the same position on Chrome.
         *
         */
        _couldEmulatedEventHaveWrongTarget: {
            value: function (touch, mouseEvent, radiusThreshold, timestampThreshold) {

                if (/*dTimestamp*/(mouseEvent.timeStamp - touch.timeStamp) <= timestampThreshold) {
                    var dX = touch.clientX - mouseEvent.clientX,
                        dY = touch.clientY - mouseEvent.clientY;

                    return dX * dX + dY * dY <= radiusThreshold * radiusThreshold;
                }

                return false;
            }
        },

        // Event Handling
        /**
         * @property {Array}
         * @description the pointer to the current candidate event listeners.
         *
         * @private
         */
        _currentDispatchedTargetListeners: {
            value:null
        },
        /**
         * @function
         * @description the pointer to the current candidate event listeners.
         *
         * @private
         */
        _processCurrentDispatchedTargetListenersToRemove: {
            value: function(target, eventType, useCapture, listeners) {

                var registeredEventListeners,
                    otherPhaseRegisteredEventListeners,
                    currentDispatchedTargetListenersToRemove = this._currentDispatchedTargetListeners.get(listeners);

                if (currentDispatchedTargetListenersToRemove && currentDispatchedTargetListenersToRemove.size > 0) {
                    listeners.removeObjects(currentDispatchedTargetListenersToRemove);
                    registeredEventListeners = useCapture ? this._registeredCaptureEventListeners : this._registeredBubbleEventListeners;
                    otherPhaseRegisteredEventListeners = useCapture ? this._registeredBubbleEventListeners : this._registeredCaptureEventListeners;
                    this._unregisterTargetForEventTypeIfNeeded(target, eventType, listeners, registeredEventListeners, otherPhaseRegisteredEventListeners);
                }
            }
        },

        /**
         @function
         @param {Event} event The handled event.
         */
        handleEvent: {
            enumerable: false,
            value: function (event) {
                if ((window.MontageElement && event.target instanceof MontageElement) ||
                    (event instanceof UIEvent && !this._shouldDispatchEvent(event))) {
                    return void 0;
                }

                if (this.monitorDOMModificationInEventHandling) {
                    document.body.addEventListener("DOMSubtreeModified", this.domModificationEventHandler, true);
                    document.body.addEventListener("DOMAttrModified", this.domModificationEventHandler, true);
                    document.body.addEventListener("DOMCharacterDataModified", this.domModificationEventHandler, true);
                }

                var loadedWindow,
                    i,
                    j,
                    iTarget,
                    listenerEntries,
                    nextEntry,
                    eventPath,
                    eventType = event.type,
                    capitalizedEventType = eventType.toCapitalized(),
                    eventBubbles = event.bubbles,
                    captureMethodName,
                    bubbleMethodName,
                    identifierSpecificCaptureMethodName,
                    identifierSpecificBubbleMethodName,
                    capitalizedIdentifier,
                    mutableEvent,
                    mutableEventTarget,
                    _currentDispatchedTargetListeners = this._currentDispatchedTargetListeners,
                    registeredCaptureEventListeners = this._registeredCaptureEventListeners,
                    registeredBubbleEventListeners = this._registeredBubbleEventListeners;

                if ("DOMContentLoaded" === eventType) {
                    loadedWindow = event.target.defaultView;
                    if (loadedWindow && this._windowsAwaitingFinalRegistration.has(loadedWindow)) {
                        this._finalizeWindowRegistration(loadedWindow);
                        // Stop listening for DOMContentLoaded on this target
                        // Otherwise the eventManager's handleEvent will be called
                        // again from within here when the eventManager is found
                        // to be a listener for this event when we find the listeners
                        event.target.removeEventListener("DOMContentLoaded", this, true);
                    }
                }

                if (typeof event.propagationStopped !== "boolean") {
                    mutableEvent = MutableEvent.fromEvent(event);
                } else {
                    mutableEvent = event;
                }

                mutableEventTarget = mutableEvent.target;
                if (Element.isElement(mutableEventTarget) || mutableEventTarget instanceof Document || mutableEventTarget === window) {
                    eventPath = this._eventPathForDomTarget(mutableEventTarget);
                } else {
                    eventPath = this._eventPathForTarget(mutableEventTarget);
                }

                // use most specific handler method available, possibly based upon the identifier of the event target
                if (mutableEventTarget.identifier) {
                    capitalizedIdentifier = mutableEventTarget.identifier.toCapitalized();
                    identifierSpecificCaptureMethodName = this.methodNameForCapturePhaseOfEventType(eventType, mutableEventTarget.identifier, capitalizedEventType, capitalizedIdentifier);
                    identifierSpecificBubbleMethodName = this.methodNameForBubblePhaseOfEventType(eventType, mutableEventTarget.identifier, capitalizedEventType, capitalizedIdentifier);
                } else {
                    identifierSpecificCaptureMethodName = null;
                    identifierSpecificBubbleMethodName = null;
                }

                captureMethodName = this.methodNameForCapturePhaseOfEventType(eventType, null, capitalizedEventType);
                bubbleMethodName = this.methodNameForBubblePhaseOfEventType(eventType, null, capitalizedEventType);

                // Let the delegate handle the event first
                if (this.delegate && typeof this.delegate.willDistributeEvent === "function") {
                    this.delegate.willDistributeEvent(mutableEvent);
                }

                if (this._isStoringPointerEvents) {
                    this._pointerStorage.storeEvent(mutableEvent);
                }

                // Capture Phase Distribution
                mutableEvent.eventPhase = Event.CAPTURING_PHASE;
                // The event path we generate is from bottom to top, capture needs to traverse this backwards
                for (i = eventPath.length - 1; !mutableEvent.propagationStopped && (iTarget = eventPath[i]); i--) {
                    mutableEvent.currentTarget = iTarget;

                    listenerEntries = this._registeredEventListenersForEventType_onTarget_registeredEventListeners_(eventType, iTarget, registeredCaptureEventListeners);
                    if (!listenerEntries) {
                        continue;
                    }
                    if (Array.isArray(listenerEntries)) {
                        j=0;
                        _currentDispatchedTargetListeners.set(listenerEntries,null);
                        while ((nextEntry = listenerEntries[j++]) && !mutableEvent.immediatePropagationStopped) {
                            this._invokeTargetListenerForEvent(iTarget, nextEntry, mutableEvent, identifierSpecificCaptureMethodName, captureMethodName);
                        }
                        this._processCurrentDispatchedTargetListenersToRemove(iTarget, eventType, true, listenerEntries);
                        _currentDispatchedTargetListeners.delete(listenerEntries);
                    }
                    else {
                        this._invokeTargetListenerForEvent(iTarget, listenerEntries, mutableEvent, identifierSpecificCaptureMethodName, captureMethodName);
                    }

                }

                // At Target Distribution
                if (!mutableEvent.propagationStopped) {
                    mutableEvent.eventPhase = Event.AT_TARGET;
                    mutableEvent.currentTarget = iTarget = mutableEventTarget;
                    //Capture
                    listenerEntries = this._registeredEventListenersForEventType_onTarget_registeredEventListeners_(eventType, iTarget, registeredCaptureEventListeners);
                    if (listenerEntries) {
                        if (Array.isArray(listenerEntries)) {
                            j=0;
                            _currentDispatchedTargetListeners.set(listenerEntries,null);
                            while ((nextEntry = listenerEntries[j++]) && !mutableEvent.immediatePropagationStopped) {
                                this._invokeTargetListenerForEvent(iTarget, nextEntry, mutableEvent, identifierSpecificCaptureMethodName, captureMethodName);
                            }
                            this._processCurrentDispatchedTargetListenersToRemove(iTarget, eventType, true, listenerEntries);
                            _currentDispatchedTargetListeners.delete(listenerEntries);
                        }
                        else {
                            this._invokeTargetListenerForEvent(iTarget, listenerEntries, mutableEvent, identifierSpecificCaptureMethodName, captureMethodName);
                        }

                    }
                    //Bubble
                    listenerEntries = this._registeredEventListenersForEventType_onTarget_registeredEventListeners_(eventType, iTarget, registeredBubbleEventListeners);
                    if (listenerEntries) {
                        if (Array.isArray(listenerEntries)) {
                            j=0;
                            _currentDispatchedTargetListeners.set(listenerEntries,null);
                            while ((nextEntry = listenerEntries[j++]) && !mutableEvent.immediatePropagationStopped) {
                                this._invokeTargetListenerForEvent(iTarget, nextEntry, mutableEvent, identifierSpecificBubbleMethodName, bubbleMethodName);
                            }
                            this._processCurrentDispatchedTargetListenersToRemove(iTarget, eventType, false, listenerEntries);
                            _currentDispatchedTargetListeners.delete(listenerEntries);
                        }
                        else {
                            this._invokeTargetListenerForEvent(iTarget, listenerEntries, mutableEvent, identifierSpecificBubbleMethodName, bubbleMethodName);
                        }

                    }
                }

                // Bubble Phase Distribution
                mutableEvent.eventPhase = Event.BUBBLING_PHASE;
                for (i = 0; eventBubbles && !mutableEvent.propagationStopped && (iTarget = eventPath[i]); i++) {
                    mutableEvent.currentTarget = iTarget;

                    listenerEntries = this._registeredEventListenersForEventType_onTarget_registeredEventListeners_(eventType, iTarget, registeredBubbleEventListeners);
                    if (!listenerEntries) {
                        continue;
                    }

                    if (Array.isArray(listenerEntries)) {
                        j=0;
                        _currentDispatchedTargetListeners.set(listenerEntries,null);
                          while ((nextEntry = listenerEntries[j++]) && !mutableEvent.immediatePropagationStopped) {
                              this._invokeTargetListenerForEvent(iTarget, nextEntry, mutableEvent, identifierSpecificBubbleMethodName, bubbleMethodName);
                          }
                          this._processCurrentDispatchedTargetListenersToRemove(iTarget, eventType, false, listenerEntries);
                          _currentDispatchedTargetListeners.delete(listenerEntries);
                      }
                      else {
                          this._invokeTargetListenerForEvent(iTarget, listenerEntries, mutableEvent, identifierSpecificBubbleMethodName, bubbleMethodName);
                      }
                }

                mutableEvent.eventPhase = Event.NONE;
                mutableEvent.currentTarget = null;

                if (this._isStoringPointerEvents) {
                    this._pointerStorage.removeEvent(event);
                }

                if (this.monitorDOMModificationInEventHandling) {
                    document.body.removeEventListener("DOMSubtreeModified", this.domModificationEventHandler, true);
                    document.body.removeEventListener("DOMAttrModified", this.domModificationEventHandler, true);
                    document.body.removeEventListener("DOMCharacterDataModified", this.domModificationEventHandler, true);
                }
                //console.profileEnd("handleEvent "+event.type);
                //console.groupTimeEnd("handleEvent");
            }
        },

        /**
         * @private
         */
        _invokeTargetListenerForEvent: {
            value: function _invokeTargetListenerForEvent(iTarget, jListener, mutableEvent, identifierSpecificPhaseMethodName, phaseMethodName) {
                var functionType = "function";
                if (typeof jListener === functionType) {
                    jListener.call(iTarget, mutableEvent);
                }
                else if (identifierSpecificPhaseMethodName && typeof jListener[identifierSpecificPhaseMethodName] === functionType) {
                    jListener[identifierSpecificPhaseMethodName](mutableEvent);
                }
                else if (typeof jListener[phaseMethodName] === functionType) {
                    jListener[phaseMethodName](mutableEvent);
                }
                else if (typeof jListener.handleEvent === functionType) {
                    jListener.handleEvent(mutableEvent);
                }

            }
        },

        /**
         * Ensure that any components associated with DOM elements in the hierarchy between the
         * original activationEvent target and the window are preparedForActionEvents
         *
         * @function
         * @private
         */
        _prepareComponentsForActivation: {
            value: function (eventTarget, canBecomeActiveTarget) {
                var target = eventTarget,
                    previousTarget,
                    targetView = target && target.defaultView ? target.defaultView : window,
                    targetDocument = targetView.document ? targetView.document : document,
                    associatedComponent,
                    lookedForActiveTarget = false,
                    activeTarget = null;

                do {
                    if (target) {
                        associatedComponent = this.eventHandlerForElement(target);
                        if (associatedComponent) {

                            // Once we've found a component starting point,
                            // find the closest Target that accepts focus
                            if (!lookedForActiveTarget) {
                                lookedForActiveTarget = true;
                                activeTarget = this._findActiveTarget(associatedComponent);
                            }

                            if (!associatedComponent.preparedForActivationEvents) {
                                associatedComponent._prepareForActivationEvents();
                            }
                        }
                    }

                    previousTarget = target;

                    // We only need to go up to the window, and even that's debateable as the activationEvent system really
                    // only pertains to components, which are only ever associated with elements. The root element being the
                    // exception which is associated with the document.
                    switch (target) {
                        case targetView:
                            target = null;
                            break;
                        case targetDocument:
                            target = targetView;
                            break;
                        case targetDocument.documentElement:
                            target = targetDocument;
                            break;
                        default:
                            target = target.parentNode;
                            break;
                    }

                } while (target && previousTarget !== target);

                if (canBecomeActiveTarget) {
                    this.activeTarget = activeTarget;
                }
            }
        },
        _findActiveTargetMap : {
            value: undefined
        },
        /**
         * @private
         */
        _findActiveTarget: {
            value: function (target) {

                var foundTarget = null,
                    checkedTargetMap = this._findActiveTargetMap;

                checkedTargetMap.clear();
                //TODO report if a cycle is detected?
                while (!foundTarget && target && !(checkedTargetMap.has(target))) {

                    //TODO complain if a non-Target-alike is considered

                    checkedTargetMap.set(target,true);

                    if (target.acceptsActiveTarget) {
                        foundTarget = target;
                    } else {
                        target = target.nextTarget;
                    }
                }

                return foundTarget;
            }
        },

        _eventPathForTargetMap : {
            value: undefined
        },
        /**
         * Build the event target chain for the the specified Target
         * @private
         */
        _eventPathForTarget: {
            enumerable: false,
            value: function (target) {

                if (!target) {
                    return [];
                }

                var targetCandidate = target,
                    application = this.application,
                    eventPath = [],
                    discoveredTargets = this._eventPathForTargetMap;

                discoveredTargets.clear();

                // Consider the target "discovered" for less specialized detection of cycles
                discoveredTargets.set(target,true);

                do {
                    if (!discoveredTargets.has(targetCandidate)) {
                        eventPath.push(targetCandidate);
                        discoveredTargets.set(targetCandidate,true);
                    }

                    targetCandidate = targetCandidate.nextTarget;

                    if (!targetCandidate || discoveredTargets.has(targetCandidate)) {
                        targetCandidate = application;
                    }

                    if (targetCandidate && discoveredTargets.has(targetCandidate)) {
                        targetCandidate = null;
                    }
                }
                while (targetCandidate);

                return eventPath;
            }
        },

        /**
         * Build the event target chain for the the specified DOM target
         * @private
         */
        _eventPathForDomTarget: {
            enumerable: false,
            value: function (target) {

                if (!target) {
                    return [];
                }

                var targetCandidate = target,
                    targetView = targetCandidate && targetCandidate.defaultView ? targetCandidate.defaultView : window,
                    targetDocument = targetView.document ? targetView.document : document,
                    targetApplication = this.application,
                    previousBubblingTarget,
                    eventPath = [];

                do {
                    // Don't include the target itself as the root of the event path
                    if (targetCandidate !== target) {
                        eventPath.push(targetCandidate);
                    }

                    previousBubblingTarget = targetCandidate;
                    // use the structural DOM hierarchy until we run out of that and need
                    // to give listeners on document, window, and application a chance to respond
                    switch (targetCandidate) {
                        case targetApplication:
                            targetCandidate = targetCandidate.parentApplication;
                            if (targetCandidate) {
                                targetApplication = targetCandidate;
                            }
                            break;
                        case targetView:
                            targetCandidate = targetApplication;
                            break;
                        case targetDocument:
                            targetCandidate = targetView;
                            break;
                        case targetDocument.documentElement:
                            targetCandidate = targetDocument;
                            break;
                        default:
                            targetCandidate = targetCandidate.parentNode;

                            // Run out of hierarchy candidates? go up to the application
                            if (!targetCandidate) {
                                targetCandidate = targetApplication;
                            }

                            break;
                    }
                }
                while (targetCandidate && previousBubblingTarget !== targetCandidate);

                return eventPath;
            }
        },

        /**
         * @private
         */
        _elementEventHandlerByElement: {
            enumerable: false,
            value: null
        },

        /**
         @function
         @param {Event} anElementEventHandler
         @param {Element} anElement
         */
        registerEventHandlerForElement: {
            enumerable: false,
            value: function (anElementEventHandler, anElement) {
                // console.log("registerEventHandlerForElement",anElementEventHandler,anElementEventHandler,anElement)
                var oldEventHandler = this.eventHandlerForElement(anElement);
                // unreference unused event handlers
                if (oldEventHandler) {
                    this.unregisterEventHandlerForElement(anElement);
                }
                this._elementEventHandlerByElement.set(anElement,anElementEventHandler);
            }
        },

        /**
         @function
         @param {Element} anElement
         */
        unregisterEventHandlerForElement: {
            enumerable: false,
            value: function (anElement) {
                this._elementEventHandlerByElement.delete(anElement);
            }
        },
        /**
         @function
         @param {Element} anElement
         @returns this._elementEventHandlerByElement.get(anElement)
         */
        eventHandlerForElement: {
            enumerable: false,
            value: function (anElement) {
                return this._elementEventHandlerByElement.get(anElement);
            }
        },

        /**
         * @private
         */
        _activeTarget: {
            value: null
        },

        /**
         * The logical component that has focus within the application
         *
         * This can be used as the proximal target for dispatching in
         * situations where it logically makes sense that and event, while
         * created by some other component, should appear to originate from
         * where the user is currently focused.
         *
         * This is particularly useful for things such as keyboard shortcuts or
         * menuAction events.
         *
         * Prior to setting the activeTarget manually the desired target should
         * be checked to see if it `acceptsActiveTarget`. In the course of then
         * setting that target as the activeTarget, the current activeTarget
         * will be instructed to `surrendersActiveTarget`. If the activeTarget
         * refuses to surrender, the change is rejected.
         */
        activeTarget: {
            get: function () {
                return this._activeTarget || this.application;
            },
            set: function (value) {

                if (!value) {
                    value = this.application;
                }

                if (value === this._activeTarget || (this.activeTarget && !this.activeTarget.surrendersActiveTarget(value))) {
                    return;
                }

                if (value) {
                    value.willBecomeActiveTarget(this.activeTarget);
                    this._activeTarget = value;
                    value.didBecomeActiveTarget();
                }
            }
        }

    });

    Object.defineProperty(exports, "defaultEventManager", {
        get: function () {
            if (typeof defaultEventManager === "undefined") {
                defaultEventManager = new EventManager();
                if (typeof window !== "undefined") {
                    defaultEventManager.initWithWindow(window);
                }
            }
            return defaultEventManager;
        }
    });