text: /*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.
              if (aWindow.EventTarget) {
                  aWindow.EventTarget.prototype.nativeAddEventListener = aWindow.EventTarget.prototype.addEventListener;
              }
              
              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;
              }
  
              if (aWindow.EventTarget) {
                  aWindow.EventTarget.prototype.nativeRemoveEventListener = aWindow.EventTarget.prototype.removeEventListener;
              }
              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.EventTarget) {
                  aWindow.EventTarget.prototype.addEventListener = aWindow.addEventListener;
              }
  
              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.EventTarget) {
                  aWindow.EventTarget.prototype.removeEventListener = aWindow.removeEventListener;
              }
  
              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
              if (aWindow.EventTarget) {
                  aWindow.EventTarget.prototype.addEventListener = aWindow.EventTarget.prototype.nativeAddEventListener;
              }
              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;
              }
  
              if (aWindow.EventTarget) {
                  aWindow.EventTarget.prototype.removeEventListener = aWindow.EventTarget.prototype.nativeRemoveEventListener;
              }
              
              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
              if (aWindow.EventTarget) {
                  delete aWindow.EventTarget.prototype.nativeAddEventListener;
              }
              
              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;
              }
  
              if (aWindow.EventTarget) {
                  delete aWindow.EventTarget.prototype.nativeRemoveEventListener;
              }
              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
  
      _passiveEvents: {
          enumerable: false,
          value: [
              /*
              // Disabled for flow
              'wheel',
              'mousewheel',
              'touchstart',
              'touchmove',
              */
              'scroll'
          ]
      },
  
      isPassiveEventType: {
          enumerable: false,
          value: function (eventType) {
              return this._passiveEvents.indexOf(eventType) !== -1;
          }
      },
  
      /**
       * @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 isPassiveEventType = this.isPassiveEventType(eventType),
                      eventOpts = isPassiveEventType ? {
                          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;
      }
  });
  