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 CompositionEventPlugin
   * @typechecks static-only
   */
  
  "use strict";
  
  var EventConstants = require('EventConstants');
  var EventPropagators = require('EventPropagators');
  var ExecutionEnvironment = require('ExecutionEnvironment');
  var ReactInputSelection = require('ReactInputSelection');
  var SyntheticCompositionEvent = require('SyntheticCompositionEvent');
  
  var getTextContentAccessor = require('getTextContentAccessor');
  var keyOf = require('keyOf');
  
  var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
  var START_KEYCODE = 229;
  
  var useCompositionEvent = ExecutionEnvironment.canUseDOM &&
    'CompositionEvent' in window;
  var topLevelTypes = EventConstants.topLevelTypes;
  var currentComposition = null;
  
  // Events and their corresponding property names.
  var eventTypes = {
    compositionEnd: {
      phasedRegistrationNames: {
        bubbled: keyOf({onCompositionEnd: null}),
        captured: keyOf({onCompositionEndCapture: null})
      }
    },
    compositionStart: {
      phasedRegistrationNames: {
        bubbled: keyOf({onCompositionStart: null}),
        captured: keyOf({onCompositionStartCapture: null})
      }
    },
    compositionUpdate: {
      phasedRegistrationNames: {
        bubbled: keyOf({onCompositionUpdate: null}),
        captured: keyOf({onCompositionUpdateCapture: null})
      }
    }
  };
  
  /**
   * Translate native top level events into event types.
   *
   * @param {string} topLevelType
   * @return {object}
   */
  function getCompositionEventType(topLevelType) {
    switch (topLevelType) {
      case topLevelTypes.topCompositionStart:
        return eventTypes.compositionStart;
      case topLevelTypes.topCompositionEnd:
        return eventTypes.compositionEnd;
      case topLevelTypes.topCompositionUpdate:
        return eventTypes.compositionUpdate;
    }
  }
  
  /**
   * Does our fallback best-guess model think this event signifies that
   * composition has begun?
   *
   * @param {string} topLevelType
   * @param {object} nativeEvent
   * @return {boolean}
   */
  function isFallbackStart(topLevelType, nativeEvent) {
    return (
      topLevelType === topLevelTypes.topKeyDown &&
      nativeEvent.keyCode === START_KEYCODE
    );
  }
  
  /**
   * Does our fallback mode think that this event is the end of composition?
   *
   * @param {string} topLevelType
   * @param {object} nativeEvent
   * @return {boolean}
   */
  function isFallbackEnd(topLevelType, nativeEvent) {
    switch (topLevelType) {
      case topLevelTypes.topKeyUp:
        // Command keys insert or clear IME input.
        return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1);
      case topLevelTypes.topKeyDown:
        // Expect IME keyCode on each keydown. If we get any other
        // code we must have exited earlier.
        return (nativeEvent.keyCode !== START_KEYCODE);
      case topLevelTypes.topKeyPress:
      case topLevelTypes.topMouseDown:
      case topLevelTypes.topBlur:
        // Events are not possible without cancelling IME.
        return true;
      default:
        return false;
    }
  }
  
  /**
   * Helper class stores information about selection and document state
   * so we can figure out what changed at a later date.
   *
   * @param {DOMEventTarget} root
   */
  function FallbackCompositionState(root) {
    this.root = root;
    this.startSelection = ReactInputSelection.getSelection(root);
    this.startValue = this.getText();
  }
  
  /**
   * Get current text of input.
   *
   * @return {string}
   */
  FallbackCompositionState.prototype.getText = function() {
    return this.root.value || this.root[getTextContentAccessor()];
  };
  
  /**
   * Text that has changed since the start of composition.
   *
   * @return {string}
   */
  FallbackCompositionState.prototype.getData = function() {
    var endValue = this.getText();
    var prefixLength = this.startSelection.start;
    var suffixLength = this.startValue.length - this.startSelection.end;
  
    return endValue.substr(
      prefixLength,
      endValue.length - suffixLength - prefixLength
    );
  };
  
  /**
   * This plugin creates `onCompositionStart`, `onCompositionUpdate` and
   * `onCompositionEnd` events on inputs, textareas and contentEditable
   * nodes.
   */
  var CompositionEventPlugin = {
  
    eventTypes: eventTypes,
  
    /**
     * @param {string} topLevelType Record from `EventConstants`.
     * @param {DOMEventTarget} topLevelTarget The listening component root node.
     * @param {string} topLevelTargetID ID of `topLevelTarget`.
     * @param {object} nativeEvent Native browser event.
     * @return {*} An accumulation of synthetic events.
     * @see {EventPluginHub.extractEvents}
     */
    extractEvents: function(
        topLevelType,
        topLevelTarget,
        topLevelTargetID,
        nativeEvent) {
  
      var eventType;
      var data;
  
      if (useCompositionEvent) {
        eventType = getCompositionEventType(topLevelType);
      } else if (!currentComposition) {
        if (isFallbackStart(topLevelType, nativeEvent)) {
          eventType = eventTypes.start;
          currentComposition = new FallbackCompositionState(topLevelTarget);
        }
      } else if (isFallbackEnd(topLevelType, nativeEvent)) {
        eventType = eventTypes.compositionEnd;
        data = currentComposition.getData();
        currentComposition = null;
      }
  
      if (eventType) {
        var event = SyntheticCompositionEvent.getPooled(
          eventType,
          topLevelTargetID,
          nativeEvent
        );
        if (data) {
          // Inject data generated from fallback path into the synthetic event.
          // This matches the property of native CompositionEventInterface.
          event.data = data;
        }
        EventPropagators.accumulateTwoPhaseDispatches(event);
        return event;
      }
    }
  };
  
  module.exports = CompositionEventPlugin;
  