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 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     |
   * +-------------+   .         v               +-----------+
   * | application |   .    +----------+
   * |-------------|   .    | callback |
   * |             |   .    | registry |
   * |             |   .    +----------+
   * +-------------+   .
   *                   .
   *    React Core     .  General Purpose Event Plugin System
   */
  
  /**
   * 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
      )
    );
  }
  
  /**
   * 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.
   *
   * @private
   * @see http://www.quirksmode.org/dom/events/scroll.html
   */
  function registerScrollValueMonitoring() {
    var refresh = ViewportMetrics.refreshScrollValues;
    EventListener.listen(window, 'scroll', refresh);
    EventListener.listen(window, 'resize', refresh);
  }
  
  /**
   * `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,
  
    /**
     * Ensures that top-level event delegation listeners are installed.
     *
     * There are issues with listening to both touch events and mouse events on
     * the top-level, so we make the caller choose which one to listen to. (If
     * there's a touch top-level listeners, anchors don't receive clicks for some
     * reason, and only in some cases).
     *
     * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
     * @param {DOMDocument} contentDocument DOM document to listen on
     */
    ensureListening: function(touchNotMouse, contentDocument) {
      ("production" !== process.env.NODE_ENV ? invariant(
        ExecutionEnvironment.canUseDOM,
        'ensureListening(...): Cannot toggle event listening in a Worker ' +
        'thread. This is likely a bug in the framework. Please report ' +
        'immediately.'
      ) : invariant(ExecutionEnvironment.canUseDOM));
      ("production" !== process.env.NODE_ENV ? invariant(
        ReactEventEmitter.TopLevelCallbackCreator,
        'ensureListening(...): Cannot be called without a top level callback ' +
        'creator being injected.'
      ) : invariant(ReactEventEmitter.TopLevelCallbackCreator));
      // Call out to base implementation.
      ReactEventEmitterMixin.ensureListening.call(
        ReactEventEmitter,
        {
          touchNotMouse: touchNotMouse,
          contentDocument: contentDocument
        }
      );
    },
  
    /**
     * Sets whether or not any created callbacks should be enabled.
     *
     * @param {boolean} enabled True if callbacks should be enabled.
     */
    setEnabled: function(enabled) {
      ("production" !== process.env.NODE_ENV ? invariant(
        ExecutionEnvironment.canUseDOM,
        'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
        'This is likely a bug in the framework. Please report immediately.'
      ) : invariant(ExecutionEnvironment.canUseDOM));
      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 {boolean} touchNotMouse Listen to touch events instead of mouse.
     * @param {DOMDocument} contentDocument Document which owns the container
     * @private
     * @see http://www.quirksmode.org/dom/events/keys.html.
     */
    listenAtTopLevel: function(touchNotMouse, contentDocument) {
      ("production" !== process.env.NODE_ENV ? invariant(
        !contentDocument._isListening,
        'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
      ) : invariant(!contentDocument._isListening));
      var topLevelTypes = EventConstants.topLevelTypes;
      var mountAt = contentDocument;
  
      registerScrollValueMonitoring();
      trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
      trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
      trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
      trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
      trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
      trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
      trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
      trapBubbledEvent(topLevelTypes.topContextMenu, 'contextmenu', mountAt);
      if (touchNotMouse) {
        trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
        trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
        trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
        trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
      }
      trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
      trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
      trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
      trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
      trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
      trapBubbledEvent(
        topLevelTypes.topSelectionChange,
        'selectionchange',
        mountAt
      );
  
      trapBubbledEvent(
        topLevelTypes.topCompositionEnd,
        'compositionend',
        mountAt
      );
      trapBubbledEvent(
        topLevelTypes.topCompositionStart,
        'compositionstart',
        mountAt
      );
      trapBubbledEvent(
        topLevelTypes.topCompositionUpdate,
        'compositionupdate',
        mountAt
      );
  
      if (isEventSupported('drag')) {
        trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
        trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
        trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
        trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
        trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
        trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
        trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
        trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
      }
  
      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);
      }
  
      // IE<9 does not support capturing so just trap the bubbled event there.
      if (isEventSupported('scroll', true)) {
        trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
      } else {
        trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
      }
  
      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);
      }
  
      if (isEventSupported('copy')) {
        trapBubbledEvent(topLevelTypes.topCopy, 'copy', mountAt);
        trapBubbledEvent(topLevelTypes.topCut, 'cut', mountAt);
        trapBubbledEvent(topLevelTypes.topPaste, 'paste', mountAt);
      }
    },
  
    registrationNames: EventPluginHub.registrationNames,
  
    putListener: EventPluginHub.putListener,
  
    getListener: EventPluginHub.getListener,
  
    deleteListener: EventPluginHub.deleteListener,
  
    deleteAllListeners: EventPluginHub.deleteAllListeners,
  
    trapBubbledEvent: trapBubbledEvent,
  
    trapCapturedEvent: trapCapturedEvent
  
  });
  
  
  module.exports = ReactEventEmitter;
  