text: /**
   * Copyright 2013-2014, Facebook, Inc.
   * All rights reserved.
   *
   * This source code is licensed under the BSD-style license found in the
   * LICENSE file in the root directory of this source tree. An additional grant
   * of patent rights can be found in the PATENTS file in the same directory.
   *
   * @providesModule ReactDOMComponent
   * @typechecks static-only
   */
  
  /* global hasOwnProperty:true */
  
  'use strict';
  
  var CSSPropertyOperations = require('CSSPropertyOperations');
  var DOMProperty = require('DOMProperty');
  var DOMPropertyOperations = require('DOMPropertyOperations');
  var ReactComponent = require('ReactComponent');
  var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
  var ReactMount = require('ReactMount');
  var ReactMultiChild = require('ReactMultiChild');
  var ReactPerf = require('ReactPerf');
  
  var assign = require('Object.assign');
  var escapeTextForBrowser = require('escapeTextForBrowser');
  var invariant = require('invariant');
  var isEventSupported = require('isEventSupported');
  var keyOf = require('keyOf');
  var monitorCodeUse = require('monitorCodeUse');
  var warning = require('warning');
  
  var deleteListener = ReactBrowserEventEmitter.deleteListener;
  var listenTo = ReactBrowserEventEmitter.listenTo;
  var registrationNameModules = ReactBrowserEventEmitter.registrationNameModules;
  
  // For quickly matching children type, to test if can be treated as content.
  var CONTENT_TYPES = {'string': true, 'number': true};
  
  var STYLE = keyOf({style: null});
  
  var ELEMENT_NODE_TYPE = 1;
  
  /**
   * Optionally injectable operations for mutating the DOM
   */
  var BackendIDOperations = null;
  
  /**
   * @param {?object} props
   */
  function assertValidProps(props) {
    if (!props) {
      return;
    }
    // Note the use of `==` which checks for null or undefined.
    if (props.dangerouslySetInnerHTML != null) {
      invariant(
        props.children == null,
        'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
      );
      invariant(
        props.dangerouslySetInnerHTML.__html != null,
        '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
        'For more information, lookup documentation on `dangerouslySetInnerHTML`.'
      );
    }
    if (__DEV__) {
      warning(
        props.innerHTML == null,
        'Directly setting property `innerHTML` is not permitted. ' +
        'For more information, lookup documentation on `dangerouslySetInnerHTML`.'
      );
      if (props.contentEditable && props.children != null) {
        console.warn(
          'A component is `contentEditable` and contains `children` managed by ' +
          'React. It is now your responsibility to guarantee that none of ' +
          'those nodes are unexpectedly modified or duplicated. This is ' +
          'probably not intentional.'
        );
      }
    }
    invariant(
      props.style == null || typeof props.style === 'object',
      'The `style` prop expects a mapping from style properties to values, ' +
      'not a string. For example, style={{marginRight: spacing + \'em\'}} when ' +
      'using JSX.'
    );
  }
  
  function putListener(id, registrationName, listener, transaction) {
    if (__DEV__) {
      // IE8 has no API for event capturing and the `onScroll` event doesn't
      // bubble.
      if (registrationName === 'onScroll' &&
          !isEventSupported('scroll', true)) {
        monitorCodeUse('react_no_scroll_event');
        console.warn('This browser doesn\'t support the `onScroll` event');
      }
    }
    var container = ReactMount.findReactContainerForID(id);
    if (container) {
      var doc = container.nodeType === ELEMENT_NODE_TYPE ?
        container.ownerDocument :
        container;
      listenTo(registrationName, doc);
    }
    transaction.getPutListenerQueue().enqueuePutListener(
      id,
      registrationName,
      listener
    );
  }
  
  // For HTML, certain tags should omit their close tag. We keep a whitelist for
  // those special cased tags.
  
  var omittedCloseTags = {
    'area': true,
    'base': true,
    'br': true,
    'col': true,
    'embed': true,
    'hr': true,
    'img': true,
    'input': true,
    'keygen': true,
    'link': true,
    'meta': true,
    'param': true,
    'source': true,
    'track': true,
    'wbr': true
    // NOTE: menuitem's close tag should be omitted, but that causes problems.
  };
  
  // We accept any tag to be rendered but since this gets injected into abitrary
  // HTML, we want to make sure that it's a safe tag.
  // http://www.w3.org/TR/REC-xml/#NT-Name
  
  var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset
  var validatedTagCache = {};
  var hasOwnProperty = {}.hasOwnProperty;
  
  function validateDangerousTag(tag) {
    if (!hasOwnProperty.call(validatedTagCache, tag)) {
      invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag);
      validatedTagCache[tag] = true;
    }
  }
  
  /**
   * Creates a new React class that is idempotent and capable of containing other
   * React components. It accepts event listeners and DOM properties that are
   * valid according to `DOMProperty`.
   *
   *  - Event listeners: `onClick`, `onMouseDown`, etc.
   *  - DOM properties: `className`, `name`, `title`, etc.
   *
   * The `style` property functions differently from the DOM API. It accepts an
   * object mapping of style properties to values.
   *
   * @constructor ReactDOMComponent
   * @extends ReactComponent
   * @extends ReactMultiChild
   */
  function ReactDOMComponent(tag) {
    validateDangerousTag(tag);
    this._tag = tag;
    this._renderedChildren = null;
    this._previousStyleCopy = null;
    this._rootNodeID = null;
  }
  
  ReactDOMComponent.displayName = 'ReactDOMComponent';
  
  ReactDOMComponent.Mixin = {
  
    /**
     * Generates root tag markup then recurses. This method has side effects and
     * is not idempotent.
     *
     * @internal
     * @param {string} rootID The root DOM ID for this node.
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @return {string} The computed markup.
     */
    mountComponent: function(rootID, transaction, context) {
      ReactComponent.Mixin.mountComponent.call(
        this,
        rootID,
        transaction,
        context
      );
      this._rootNodeID = rootID;
      assertValidProps(this._currentElement.props);
      var closeTag = omittedCloseTags[this._tag] ? '' : '</' + this._tag + '>';
      return (
        this._createOpenTagMarkupAndPutListeners(transaction) +
        this._createContentMarkup(transaction, context) +
        closeTag
      );
    },
  
    /**
     * Creates markup for the open tag and all attributes.
     *
     * This method has side effects because events get registered.
     *
     * Iterating over object properties is faster than iterating over arrays.
     * @see http://jsperf.com/obj-vs-arr-iteration
     *
     * @private
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @return {string} Markup of opening tag.
     */
    _createOpenTagMarkupAndPutListeners: function(transaction) {
      var props = this._currentElement.props;
      var ret = '<' + this._tag;
  
      for (var propKey in props) {
        if (!props.hasOwnProperty(propKey)) {
          continue;
        }
        var propValue = props[propKey];
        if (propValue == null) {
          continue;
        }
        if (registrationNameModules.hasOwnProperty(propKey)) {
          putListener(this._rootNodeID, propKey, propValue, transaction);
        } else {
          if (propKey === STYLE) {
            if (propValue) {
              propValue = this._previousStyleCopy = assign({}, props.style);
            }
            propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
          }
          var markup =
            DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
          if (markup) {
            ret += ' ' + markup;
          }
        }
      }
  
      // For static pages, no need to put React ID and checksum. Saves lots of
      // bytes.
      if (transaction.renderToStaticMarkup) {
        return ret + '>';
      }
  
      var markupForID = DOMPropertyOperations.createMarkupForID(this._rootNodeID);
      return ret + ' ' + markupForID + '>';
    },
  
    /**
     * Creates markup for the content between the tags.
     *
     * @private
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @param {object} context
     * @return {string} Content markup.
     */
    _createContentMarkup: function(transaction, context) {
      var prefix = '';
      if (this._tag === 'listing' ||
          this._tag === 'pre' ||
          this._tag === 'textarea') {
        // Add an initial newline because browsers ignore the first newline in
        // a <listing>, <pre>, or <textarea> as an "authoring convenience" -- see
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody.
        prefix = '\n';
      }
  
      var props = this._currentElement.props;
  
      // Intentional use of != to avoid catching zero/false.
      var innerHTML = props.dangerouslySetInnerHTML;
      if (innerHTML != null) {
        if (innerHTML.__html != null) {
          return prefix + innerHTML.__html;
        }
      } else {
        var contentToUse =
          CONTENT_TYPES[typeof props.children] ? props.children : null;
        var childrenToUse = contentToUse != null ? null : props.children;
        if (contentToUse != null) {
          return prefix + escapeTextForBrowser(contentToUse);
        } else if (childrenToUse != null) {
          var mountImages = this.mountChildren(
            childrenToUse,
            transaction,
            context
          );
          return prefix + mountImages.join('');
        }
      }
      return prefix;
    },
  
    receiveComponent: function(nextElement, transaction, context) {
      if (nextElement === this._currentElement &&
          nextElement._owner != null) {
        // Since elements are immutable after the owner is rendered,
        // we can do a cheap identity compare here to determine if this is a
        // superfluous reconcile. It's possible for state to be mutable but such
        // change should trigger an update of the owner which would recreate
        // the element. We explicitly check for the existence of an owner since
        // it's possible for an element created outside a composite to be
        // deeply mutated and reused.
        return;
      }
  
      var prevElement = this._currentElement;
      this._currentElement = nextElement;
      this.updateComponent(transaction, prevElement, nextElement, context);
    },
  
    /**
     * Updates a native DOM component after it has already been allocated and
     * attached to the DOM. Reconciles the root DOM node, then recurses.
     *
     * @param {ReactReconcileTransaction} transaction
     * @param {ReactElement} prevElement
     * @param {ReactElement} nextElement
     * @internal
     * @overridable
     */
    updateComponent: function(transaction, prevElement, nextElement, context) {
      assertValidProps(this._currentElement.props);
      ReactComponent.Mixin.updateComponent.call(
        this,
        transaction,
        prevElement,
        nextElement,
        context
      );
      this._updateDOMProperties(prevElement.props, transaction);
      this._updateDOMChildren(prevElement.props, transaction, context);
    },
  
    /**
     * Reconciles the properties by detecting differences in property values and
     * updating the DOM as necessary. This function is probably the single most
     * critical path for performance optimization.
     *
     * TODO: Benchmark whether checking for changed values in memory actually
     *       improves performance (especially statically positioned elements).
     * TODO: Benchmark the effects of putting this at the top since 99% of props
     *       do not change for a given reconciliation.
     * TODO: Benchmark areas that can be improved with caching.
     *
     * @private
     * @param {object} lastProps
     * @param {ReactReconcileTransaction} transaction
     */
    _updateDOMProperties: function(lastProps, transaction) {
      var nextProps = this._currentElement.props;
      var propKey;
      var styleName;
      var styleUpdates;
      for (propKey in lastProps) {
        if (nextProps.hasOwnProperty(propKey) ||
           !lastProps.hasOwnProperty(propKey)) {
          continue;
        }
        if (propKey === STYLE) {
          var lastStyle = this._previousStyleCopy;
          for (styleName in lastStyle) {
            if (lastStyle.hasOwnProperty(styleName)) {
              styleUpdates = styleUpdates || {};
              styleUpdates[styleName] = '';
            }
          }
        } else if (registrationNameModules.hasOwnProperty(propKey)) {
          deleteListener(this._rootNodeID, propKey);
        } else if (
            DOMProperty.isStandardName[propKey] ||
            DOMProperty.isCustomAttribute(propKey)) {
          BackendIDOperations.deletePropertyByID(
            this._rootNodeID,
            propKey
          );
        }
      }
      for (propKey in nextProps) {
        var nextProp = nextProps[propKey];
        var lastProp = propKey === STYLE ?
          this._previousStyleCopy :
          lastProps[propKey];
        if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
          continue;
        }
        if (propKey === STYLE) {
          if (nextProp) {
            nextProp = this._previousStyleCopy = assign({}, nextProp);
          }
          if (lastProp) {
            // Unset styles on `lastProp` but not on `nextProp`.
            for (styleName in lastProp) {
              if (lastProp.hasOwnProperty(styleName) &&
                  (!nextProp || !nextProp.hasOwnProperty(styleName))) {
                styleUpdates = styleUpdates || {};
                styleUpdates[styleName] = '';
              }
            }
            // Update styles that changed since `lastProp`.
            for (styleName in nextProp) {
              if (nextProp.hasOwnProperty(styleName) &&
                  lastProp[styleName] !== nextProp[styleName]) {
                styleUpdates = styleUpdates || {};
                styleUpdates[styleName] = nextProp[styleName];
              }
            }
          } else {
            // Relies on `updateStylesByID` not mutating `styleUpdates`.
            styleUpdates = nextProp;
          }
        } else if (registrationNameModules.hasOwnProperty(propKey)) {
          putListener(this._rootNodeID, propKey, nextProp, transaction);
        } else if (
            DOMProperty.isStandardName[propKey] ||
            DOMProperty.isCustomAttribute(propKey)) {
          BackendIDOperations.updatePropertyByID(
            this._rootNodeID,
            propKey,
            nextProp
          );
        }
      }
      if (styleUpdates) {
        BackendIDOperations.updateStylesByID(
          this._rootNodeID,
          styleUpdates
        );
      }
    },
  
    /**
     * Reconciles the children with the various properties that affect the
     * children content.
     *
     * @param {object} lastProps
     * @param {ReactReconcileTransaction} transaction
     */
    _updateDOMChildren: function(lastProps, transaction, context) {
      var nextProps = this._currentElement.props;
  
      var lastContent =
        CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
      var nextContent =
        CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
  
      var lastHtml =
        lastProps.dangerouslySetInnerHTML &&
        lastProps.dangerouslySetInnerHTML.__html;
      var nextHtml =
        nextProps.dangerouslySetInnerHTML &&
        nextProps.dangerouslySetInnerHTML.__html;
  
      // Note the use of `!=` which checks for null or undefined.
      var lastChildren = lastContent != null ? null : lastProps.children;
      var nextChildren = nextContent != null ? null : nextProps.children;
  
      // If we're switching from children to content/html or vice versa, remove
      // the old content
      var lastHasContentOrHtml = lastContent != null || lastHtml != null;
      var nextHasContentOrHtml = nextContent != null || nextHtml != null;
      if (lastChildren != null && nextChildren == null) {
        this.updateChildren(null, transaction, context);
      } else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
        this.updateTextContent('');
      }
  
      if (nextContent != null) {
        if (lastContent !== nextContent) {
          this.updateTextContent('' + nextContent);
        }
      } else if (nextHtml != null) {
        if (lastHtml !== nextHtml) {
          BackendIDOperations.updateInnerHTMLByID(
            this._rootNodeID,
            nextHtml
          );
        }
      } else if (nextChildren != null) {
        this.updateChildren(nextChildren, transaction, context);
      }
    },
  
    /**
     * Destroys all event registrations for this instance. Does not remove from
     * the DOM. That must be done by the parent.
     *
     * @internal
     */
    unmountComponent: function() {
      this.unmountChildren();
      ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID);
      ReactComponent.Mixin.unmountComponent.call(this);
      ReactMount.purgeID(this._rootNodeID);
      this._rootNodeID = null;
    }
  
  };
  
  ReactPerf.measureMethods(ReactDOMComponent, 'ReactDOMComponent', {
    mountComponent: 'mountComponent',
    updateComponent: 'updateComponent'
  });
  
  assign(
    ReactDOMComponent.prototype,
    ReactComponent.Mixin,
    ReactDOMComponent.Mixin,
    ReactMultiChild.Mixin
  );
  
  ReactDOMComponent.injection = {
    injectIDOperations: function(IDOperations) {
      ReactDOMComponent.BackendIDOperations = BackendIDOperations = IDOperations;
    }
  };
  
  module.exports = ReactDOMComponent;
  