text: /**
   * Copyright 2013 Facebook, Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * @providesModule ReactEventEmitter
   * @typechecks static-only
   */
  
  "use strict";
  
  var EventConstants = require('EventConstants');
  var EventListener = require('EventListener');
  var EventPluginHub = require('EventPluginHub');
  var EventPluginRegistry = require('EventPluginRegistry');
  var ExecutionEnvironment = require('ExecutionEnvironment');
  var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
  var ViewportMetrics = require('ViewportMetrics');
  
  var invariant = require('invariant');
  var isEventSupported = require('isEventSupported');
  var merge = require('merge');
  
  /**
   * Summary of `ReactEventEmitter` event handling:
   *
   *  - Top-level delegation is used to trap native browser events. We normalize
   *    and de-duplicate events to account for browser quirks.
   *
   *  - Forward these native events (with the associated top-level type used to
   *    trap it) to `EventPluginHub`, which in turn will ask plugins if they want
   *    to extract any synthetic events.
   *
   *  - The `EventPluginHub` will then process each event by annotating them with
   *    "dispatches", a sequence of listeners and IDs that care about that event.
   *
   *  - The `EventPluginHub` then dispatches the events.
   *
   * Overview of React and the event system:
   *
   *                   .
   * +------------+    .
   * |    DOM     |    .
   * +------------+    .                         +-----------+
   *       +           .               +--------+|SimpleEvent|
   *       |           .               |         |Plugin     |
   * +-----|------+    .               v         +-----------+
   * |     |      |    .    +--------------+                    +------------+
   * |     +-----------.--->|EventPluginHub|                    |    Event   |
   * |            |    .    |              |     +-----------+  | Propagators|
   * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
   * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
   * |            |    .    |              |     +-----------+  |  utilities |
   * |     +-----------.--->|              |                    +------------+
   * |     |      |    .    +--------------+
   * +-----|------+    .                ^        +-----------+
   *       |           .                |        |Enter/Leave|
   *       +           .                +-------+|Plugin     |
   * +-------------+   .                         +-----------+
   * | application |   .
   * |-------------|   .
   * |             |   .
   * |             |   .
   * +-------------+   .
   *                   .
   *    React Core     .  General Purpose Event Plugin System
   */
  
  var alreadyListeningTo = {};
  var isMonitoringScrollValue = false;
  var reactTopListenersCounter = 0;
  
  // For events like 'submit' which don't consistently bubble (which we trap at a
  // lower node than `document`), binding at `document` would cause duplicate
  // events so we don't include them here
  var topEventMapping = {
    topBlur: 'blur',
    topChange: 'change',
    topClick: 'click',
    topCompositionEnd: 'compositionend',
    topCompositionStart: 'compositionstart',
    topCompositionUpdate: 'compositionupdate',
    topContextMenu: 'contextmenu',
    topCopy: 'copy',
    topCut: 'cut',
    topDoubleClick: 'dblclick',
    topDrag: 'drag',
    topDragEnd: 'dragend',
    topDragEnter: 'dragenter',
    topDragExit: 'dragexit',
    topDragLeave: 'dragleave',
    topDragOver: 'dragover',
    topDragStart: 'dragstart',
    topDrop: 'drop',
    topFocus: 'focus',
    topInput: 'input',
    topKeyDown: 'keydown',
    topKeyPress: 'keypress',
    topKeyUp: 'keyup',
    topMouseDown: 'mousedown',
    topMouseMove: 'mousemove',
    topMouseOut: 'mouseout',
    topMouseOver: 'mouseover',
    topMouseUp: 'mouseup',
    topPaste: 'paste',
    topScroll: 'scroll',
    topSelectionChange: 'selectionchange',
    topTouchCancel: 'touchcancel',
    topTouchEnd: 'touchend',
    topTouchMove: 'touchmove',
    topTouchStart: 'touchstart',
    topWheel: 'wheel'
  };
  
  /**
   * To ensure no conflicts with other potential React instances on the page
   */
  var topListenersIDKey = "_reactListenersID" + String(Math.random()).slice(2);
  
  function getListeningForDocument(mountAt) {
    if (mountAt[topListenersIDKey] == null) {
      mountAt[topListenersIDKey] = reactTopListenersCounter++;
      alreadyListeningTo[mountAt[topListenersIDKey]] = {};
    }
    return alreadyListeningTo[mountAt[topListenersIDKey]];
  }
  
  /**
   * Traps top-level events by using event bubbling.
   *
   * @param {string} topLevelType Record from `EventConstants`.
   * @param {string} handlerBaseName Event name (e.g. "click").
   * @param {DOMEventTarget} element Element on which to attach listener.
   * @internal
   */
  function trapBubbledEvent(topLevelType, handlerBaseName, element) {
    EventListener.listen(
      element,
      handlerBaseName,
      ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
        topLevelType
      )
    );
  }
  
  /**
   * Traps a top-level event by using event capturing.
   *
   * @param {string} topLevelType Record from `EventConstants`.
   * @param {string} handlerBaseName Event name (e.g. "click").
   * @param {DOMEventTarget} element Element on which to attach listener.
   * @internal
   */
  function trapCapturedEvent(topLevelType, handlerBaseName, element) {
    EventListener.capture(
      element,
      handlerBaseName,
      ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
        topLevelType
      )
    );
  }
  
  /**
   * `ReactEventEmitter` is used to attach top-level event listeners. For example:
   *
   *   ReactEventEmitter.putListener('myID', 'onClick', myFunction);
   *
   * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
   *
   * @internal
   */
  var ReactEventEmitter = merge(ReactEventEmitterMixin, {
  
    /**
     * React references `ReactEventTopLevelCallback` using this property in order
     * to allow dependency injection.
     */
    TopLevelCallbackCreator: null,
  
    /**
     * Sets whether or not any created callbacks should be enabled.
     *
     * @param {boolean} enabled True if callbacks should be enabled.
     */
    setEnabled: function(enabled) {
      invariant(
        ExecutionEnvironment.canUseDOM,
        'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
        'This is likely a bug in the framework. Please report immediately.'
      );
      if (ReactEventEmitter.TopLevelCallbackCreator) {
        ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
      }
    },
  
    /**
     * @return {boolean} True if callbacks are enabled.
     */
    isEnabled: function() {
      return !!(
        ReactEventEmitter.TopLevelCallbackCreator &&
        ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
      );
    },
  
    /**
     * We listen for bubbled touch events on the document object.
     *
     * Firefox v8.01 (and possibly others) exhibited strange behavior when
     * mounting `onmousemove` events at some node that was not the document
     * element. The symptoms were that if your mouse is not moving over something
     * contained within that mount point (for example on the background) the
     * top-level listeners for `onmousemove` won't be called. However, if you
     * register the `mousemove` on the document object, then it will of course
     * catch all `mousemove`s. This along with iOS quirks, justifies restricting
     * top-level listeners to the document object only, at least for these
     * movement types of events and possibly all events.
     *
     * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
     *
     * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
     * they bubble to document.
     *
     * @param {string} registrationName Name of listener (e.g. `onClick`).
     * @param {DOMDocument} contentDocument Document which owns the container
     */
    listenTo: function(registrationName, contentDocument) {
      var mountAt = contentDocument;
      var isListening = getListeningForDocument(mountAt);
      var dependencies = EventPluginRegistry.
        registrationNameDependencies[registrationName];
  
      var topLevelTypes = EventConstants.topLevelTypes;
      for (var i = 0, l = dependencies.length; i < l; i++) {
        var dependency = dependencies[i];
        if (!isListening[dependency]) {
          var topLevelType = topLevelTypes[dependency];
  
          if (topLevelType === topLevelTypes.topWheel) {
            if (isEventSupported('wheel')) {
              trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
            } else if (isEventSupported('mousewheel')) {
              trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
            } else {
              // Firefox needs to capture a different mouse scroll event.
              // @see http://www.quirksmode.org/dom/events/tests/scroll.html
              trapBubbledEvent(
                topLevelTypes.topWheel,
                'DOMMouseScroll',
                mountAt);
            }
          } else if (topLevelType === topLevelTypes.topScroll) {
  
            if (isEventSupported('scroll', true)) {
              trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
            } else {
              trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
            }
          } else if (topLevelType === topLevelTypes.topFocus ||
              topLevelType === topLevelTypes.topBlur) {
  
            if (isEventSupported('focus', true)) {
              trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
              trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
            } else if (isEventSupported('focusin')) {
              // IE has `focusin` and `focusout` events which bubble.
              // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
              trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
              trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
            }
  
            // to make sure blur and focus event listeners are only attached once
            isListening[topLevelTypes.topBlur] = true;
            isListening[topLevelTypes.topFocus] = true;
          } else if (topEventMapping[dependency]) {
            trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt);
          }
  
          isListening[dependency] = true;
        }
      }
    },
  
    /**
     * Listens to window scroll and resize events. We cache scroll values so that
     * application code can access them without triggering reflows.
     *
     * NOTE: Scroll events do not bubble.
     *
     * @see http://www.quirksmode.org/dom/events/scroll.html
     */
    ensureScrollValueMonitoring: function(){
      if (!isMonitoringScrollValue) {
        var refresh = ViewportMetrics.refreshScrollValues;
        EventListener.listen(window, 'scroll', refresh);
        EventListener.listen(window, 'resize', refresh);
        isMonitoringScrollValue = true;
      }
    },
  
    registrationNameModules: EventPluginHub.registrationNameModules,
  
    putListener: EventPluginHub.putListener,
  
    getListener: EventPluginHub.getListener,
  
    deleteListener: EventPluginHub.deleteListener,
  
    deleteAllListeners: EventPluginHub.deleteAllListeners,
  
    trapBubbledEvent: trapBubbledEvent,
  
    trapCapturedEvent: trapCapturedEvent
  
  });
  
  module.exports = ReactEventEmitter;
  