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;
    }
});