text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
  import {registrationNameModules} from 'events/EventPluginRegistry';
  import warning from 'shared/warning';
  import {canUseDOM} from 'shared/ExecutionEnvironment';
  import warningWithoutStack from 'shared/warningWithoutStack';
  import type {ReactEventResponderEventType} from 'shared/ReactTypes';
  import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
  import {
    setListenToResponderEventTypes,
    generateListeningKey,
  } from '../events/DOMEventResponderSystem';
  
  import {
    getValueForAttribute,
    getValueForProperty,
    setValueForProperty,
  } from './DOMPropertyOperations';
  import {
    initWrapperState as ReactDOMInputInitWrapperState,
    getHostProps as ReactDOMInputGetHostProps,
    postMountWrapper as ReactDOMInputPostMountWrapper,
    updateChecked as ReactDOMInputUpdateChecked,
    updateWrapper as ReactDOMInputUpdateWrapper,
    restoreControlledState as ReactDOMInputRestoreControlledState,
  } from './ReactDOMInput';
  import {
    getHostProps as ReactDOMOptionGetHostProps,
    postMountWrapper as ReactDOMOptionPostMountWrapper,
    validateProps as ReactDOMOptionValidateProps,
  } from './ReactDOMOption';
  import {
    initWrapperState as ReactDOMSelectInitWrapperState,
    getHostProps as ReactDOMSelectGetHostProps,
    postMountWrapper as ReactDOMSelectPostMountWrapper,
    restoreControlledState as ReactDOMSelectRestoreControlledState,
    postUpdateWrapper as ReactDOMSelectPostUpdateWrapper,
  } from './ReactDOMSelect';
  import {
    initWrapperState as ReactDOMTextareaInitWrapperState,
    getHostProps as ReactDOMTextareaGetHostProps,
    postMountWrapper as ReactDOMTextareaPostMountWrapper,
    updateWrapper as ReactDOMTextareaUpdateWrapper,
    restoreControlledState as ReactDOMTextareaRestoreControlledState,
  } from './ReactDOMTextarea';
  import {track} from './inputValueTracking';
  import setInnerHTML from './setInnerHTML';
  import setTextContent from './setTextContent';
  import {
    TOP_ERROR,
    TOP_INVALID,
    TOP_LOAD,
    TOP_RESET,
    TOP_SUBMIT,
    TOP_TOGGLE,
  } from '../events/DOMTopLevelEventTypes';
  import {
    listenTo,
    trapBubbledEvent,
    getListeningSetForElement,
  } from '../events/ReactBrowserEventEmitter';
  import {trapEventForResponderEventSystem} from '../events/ReactDOMEventListener.js';
  import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
  import {
    createDangerousStringForStyles,
    setValueForStyles,
    validateShorthandPropertyCollisionInDev,
  } from '../shared/CSSPropertyOperations';
  import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces';
  import {
    getPropertyInfo,
    shouldIgnoreAttribute,
    shouldRemoveAttribute,
  } from '../shared/DOMProperty';
  import assertValidProps from '../shared/assertValidProps';
  import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType';
  import isCustomComponent from '../shared/isCustomComponent';
  import possibleStandardNames from '../shared/possibleStandardNames';
  import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
  import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
  import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
  
  import {enableEventAPI} from 'shared/ReactFeatureFlags';
  
  let didWarnInvalidHydration = false;
  let didWarnShadyDOM = false;
  
  const DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
  const SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
  const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
  const HYDRATE_TOUCH_HIT_TARGET = 'hydrateTouchHitTarget';
  const AUTOFOCUS = 'autoFocus';
  const CHILDREN = 'children';
  const STYLE = 'style';
  const HTML = '__html';
  
  const {html: HTML_NAMESPACE} = Namespaces;
  
  let warnedUnknownTags;
  let suppressHydrationWarning;
  
  let validatePropertiesInDevelopment;
  let warnForTextDifference;
  let warnForPropDifference;
  let warnForExtraAttributes;
  let warnForInvalidEventListener;
  let canDiffStyleForHydrationWarning;
  
  let normalizeMarkupForTextOrAttribute;
  let normalizeHTML;
  
  if (__DEV__) {
    warnedUnknownTags = {
      // Chrome is the only major browser not shipping <time>. But as of July
      // 2017 it intends to ship it due to widespread usage. We intentionally
      // *don't* warn for <time> even if it's unrecognized by Chrome because
      // it soon will be, and many apps have been using it anyway.
      time: true,
      // There are working polyfills for <dialog>. Let people use it.
      dialog: true,
      // Electron ships a custom <webview> tag to display external web content in
      // an isolated frame and process.
      // This tag is not present in non Electron environments such as JSDom which
      // is often used for testing purposes.
      // @see https://electronjs.org/docs/api/webview-tag
      webview: true,
    };
  
    validatePropertiesInDevelopment = function(type, props) {
      validateARIAProperties(type, props);
      validateInputProperties(type, props);
      validateUnknownProperties(type, props, /* canUseEventSystem */ true);
    };
  
    // IE 11 parses & normalizes the style attribute as opposed to other
    // browsers. It adds spaces and sorts the properties in some
    // non-alphabetical order. Handling that would require sorting CSS
    // properties in the client & server versions or applying
    // `expectedStyle` to a temporary DOM node to read its `style` attribute
    // normalized. Since it only affects IE, we're skipping style warnings
    // in that browser completely in favor of doing all that work.
    // See https://github.com/facebook/react/issues/11807
    canDiffStyleForHydrationWarning = canUseDOM && !document.documentMode;
  
    // HTML parsing normalizes CR and CRLF to LF.
    // It also can turn \u0000 into \uFFFD inside attributes.
    // https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream
    // If we have a mismatch, it might be caused by that.
    // We will still patch up in this case but not fire the warning.
    const NORMALIZE_NEWLINES_REGEX = /\r\n?/g;
    const NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g;
  
    normalizeMarkupForTextOrAttribute = function(markup: mixed): string {
      const markupString =
        typeof markup === 'string' ? markup : '' + (markup: any);
      return markupString
        .replace(NORMALIZE_NEWLINES_REGEX, '\n')
        .replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, '');
    };
  
    warnForTextDifference = function(
      serverText: string,
      clientText: string | number,
    ) {
      if (didWarnInvalidHydration) {
        return;
      }
      const normalizedClientText = normalizeMarkupForTextOrAttribute(clientText);
      const normalizedServerText = normalizeMarkupForTextOrAttribute(serverText);
      if (normalizedServerText === normalizedClientText) {
        return;
      }
      didWarnInvalidHydration = true;
      warningWithoutStack(
        false,
        'Text content did not match. Server: "%s" Client: "%s"',
        normalizedServerText,
        normalizedClientText,
      );
    };
  
    warnForPropDifference = function(
      propName: string,
      serverValue: mixed,
      clientValue: mixed,
    ) {
      if (didWarnInvalidHydration) {
        return;
      }
      const normalizedClientValue = normalizeMarkupForTextOrAttribute(
        clientValue,
      );
      const normalizedServerValue = normalizeMarkupForTextOrAttribute(
        serverValue,
      );
      if (normalizedServerValue === normalizedClientValue) {
        return;
      }
      didWarnInvalidHydration = true;
      warningWithoutStack(
        false,
        'Prop `%s` did not match. Server: %s Client: %s',
        propName,
        JSON.stringify(normalizedServerValue),
        JSON.stringify(normalizedClientValue),
      );
    };
  
    warnForExtraAttributes = function(attributeNames: Set<string>) {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      const names = [];
      attributeNames.forEach(function(name) {
        names.push(name);
      });
      warningWithoutStack(false, 'Extra attributes from the server: %s', names);
    };
  
    warnForInvalidEventListener = function(registrationName, listener) {
      if (listener === false) {
        warning(
          false,
          'Expected `%s` listener to be a function, instead got `false`.\n\n' +
            'If you used to conditionally omit it with %s={condition && value}, ' +
            'pass %s={condition ? value : undefined} instead.',
          registrationName,
          registrationName,
          registrationName,
        );
      } else {
        warning(
          false,
          'Expected `%s` listener to be a function, instead got a value of `%s` type.',
          registrationName,
          typeof listener,
        );
      }
    };
  
    // Parse the HTML and read it back to normalize the HTML string so that it
    // can be used for comparison.
    normalizeHTML = function(parent: Element, html: string) {
      // We could have created a separate document here to avoid
      // re-initializing custom elements if they exist. But this breaks
      // how <noscript> is being handled. So we use the same document.
      // See the discussion in https://github.com/facebook/react/pull/11157.
      const testElement =
        parent.namespaceURI === HTML_NAMESPACE
          ? parent.ownerDocument.createElement(parent.tagName)
          : parent.ownerDocument.createElementNS(
              (parent.namespaceURI: any),
              parent.tagName,
            );
      testElement.innerHTML = html;
      return testElement.innerHTML;
    };
  }
  
  function ensureListeningTo(
    rootContainerElement: Element | Node,
    registrationName: string,
  ): void {
    const isDocumentOrFragment =
      rootContainerElement.nodeType === DOCUMENT_NODE ||
      rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
    const doc = isDocumentOrFragment
      ? rootContainerElement
      : rootContainerElement.ownerDocument;
    listenTo(registrationName, doc);
  }
  
  function getOwnerDocumentFromRootContainer(
    rootContainerElement: Element | Document,
  ): Document {
    return rootContainerElement.nodeType === DOCUMENT_NODE
      ? (rootContainerElement: any)
      : rootContainerElement.ownerDocument;
  }
  
  function noop() {}
  
  export function trapClickOnNonInteractiveElement(node: HTMLElement) {
    // Mobile Safari does not fire properly bubble click events on
    // non-interactive elements, which means delegated click listeners do not
    // fire. The workaround for this bug involves attaching an empty click
    // listener on the target node.
    // http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
    // Just set it using the onclick property so that we don't have to manage any
    // bookkeeping for it. Not sure if we need to clear it when the listener is
    // removed.
    // TODO: Only do this for the relevant Safaris maybe?
    node.onclick = noop;
  }
  
  function setInitialDOMProperties(
    tag: string,
    domElement: Element,
    rootContainerElement: Element | Document,
    nextProps: Object,
    isCustomComponentTag: boolean,
  ): void {
    for (const propKey in nextProps) {
      if (!nextProps.hasOwnProperty(propKey)) {
        continue;
      }
      const nextProp = nextProps[propKey];
      if (propKey === STYLE) {
        if (__DEV__) {
          if (nextProp) {
            // Freeze the next style object so that we can assume it won't be
            // mutated. We have already warned for this in the past.
            Object.freeze(nextProp);
          }
        }
        // Relies on `updateStylesByID` not mutating `styleUpdates`.
        setValueForStyles(domElement, nextProp);
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        const nextHtml = nextProp ? nextProp[HTML] : undefined;
        if (nextHtml != null) {
          setInnerHTML(domElement, nextHtml);
        }
      } else if (propKey === CHILDREN) {
        if (typeof nextProp === 'string') {
          // Avoid setting initial textContent when the text is empty. In IE11 setting
          // textContent on a <textarea> will cause the placeholder to not
          // show within the <textarea> until it has been focused and blurred again.
          // https://github.com/facebook/react/issues/6731#issuecomment-254874553
          const canSetTextContent = tag !== 'textarea' || nextProp !== '';
          if (canSetTextContent) {
            setTextContent(domElement, nextProp);
          }
        } else if (typeof nextProp === 'number') {
          setTextContent(domElement, '' + nextProp);
        }
      } else if (
        propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
        propKey === SUPPRESS_HYDRATION_WARNING
      ) {
        // Noop
      } else if (propKey === AUTOFOCUS) {
        // We polyfill it separately on the client during commit.
        // We could have excluded it in the property list instead of
        // adding a special case here, but then it wouldn't be emitted
        // on server rendering (but we *do* want to emit it in SSR).
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        if (nextProp != null) {
          if (__DEV__ && typeof nextProp !== 'function') {
            warnForInvalidEventListener(propKey, nextProp);
          }
          ensureListeningTo(rootContainerElement, propKey);
        }
      } else if (nextProp != null) {
        setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
      }
    }
  }
  
  function updateDOMProperties(
    domElement: Element,
    updatePayload: Array<any>,
    wasCustomComponentTag: boolean,
    isCustomComponentTag: boolean,
  ): void {
    // TODO: Handle wasCustomComponentTag
    for (let i = 0; i < updatePayload.length; i += 2) {
      const propKey = updatePayload[i];
      const propValue = updatePayload[i + 1];
      if (propKey === STYLE) {
        setValueForStyles(domElement, propValue);
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        setInnerHTML(domElement, propValue);
      } else if (propKey === CHILDREN) {
        setTextContent(domElement, propValue);
      } else {
        setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
      }
    }
  }
  
  export function createElement(
    type: string,
    props: Object,
    rootContainerElement: Element | Document,
    parentNamespace: string,
  ): Element {
    let isCustomComponentTag;
  
    // We create tags in the namespace of their parent container, except HTML
    // tags get no namespace.
    const ownerDocument: Document = getOwnerDocumentFromRootContainer(
      rootContainerElement,
    );
    let domElement: Element;
    let namespaceURI = parentNamespace;
    if (namespaceURI === HTML_NAMESPACE) {
      namespaceURI = getIntrinsicNamespace(type);
    }
    if (namespaceURI === HTML_NAMESPACE) {
      if (__DEV__) {
        isCustomComponentTag = isCustomComponent(type, props);
        // Should this check be gated by parent namespace? Not sure we want to
        // allow <SVG> or <mATH>.
        warning(
          isCustomComponentTag || type === type.toLowerCase(),
          '<%s /> is using incorrect casing. ' +
            'Use PascalCase for React components, ' +
            'or lowercase for HTML elements.',
          type,
        );
      }
  
      if (type === 'script') {
        // Create the script via .innerHTML so its "parser-inserted" flag is
        // set to true and it does not execute
        const div = ownerDocument.createElement('div');
        div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
        // This is guaranteed to yield a script element.
        const firstChild = ((div.firstChild: any): HTMLScriptElement);
        domElement = div.removeChild(firstChild);
      } else if (typeof props.is === 'string') {
        // $FlowIssue `createElement` should be updated for Web Components
        domElement = ownerDocument.createElement(type, {is: props.is});
      } else {
        // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
        // See discussion in https://github.com/facebook/react/pull/6896
        // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
        domElement = ownerDocument.createElement(type);
        // Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size`
        // attributes on `select`s needs to be added before `option`s are inserted.
        // This prevents:
        // - a bug where the `select` does not scroll to the correct option because singular
        //  `select` elements automatically pick the first item #13222
        // - a bug where the `select` set the first item as selected despite the `size` attribute #14239
        // See https://github.com/facebook/react/issues/13222
        // and https://github.com/facebook/react/issues/14239
        if (type === 'select') {
          const node = ((domElement: any): HTMLSelectElement);
          if (props.multiple) {
            node.multiple = true;
          } else if (props.size) {
            // Setting a size greater than 1 causes a select to behave like `multiple=true`, where
            // it is possible that no option is selected.
            //
            // This is only necessary when a select in "single selection mode".
            node.size = props.size;
          }
        }
      }
    } else {
      domElement = ownerDocument.createElementNS(namespaceURI, type);
    }
  
    if (__DEV__) {
      if (namespaceURI === HTML_NAMESPACE) {
        if (
          !isCustomComponentTag &&
          Object.prototype.toString.call(domElement) ===
            '[object HTMLUnknownElement]' &&
          !Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)
        ) {
          warnedUnknownTags[type] = true;
          warning(
            false,
            'The tag <%s> is unrecognized in this browser. ' +
              'If you meant to render a React component, start its name with ' +
              'an uppercase letter.',
            type,
          );
        }
      }
    }
  
    return domElement;
  }
  
  export function createTextNode(
    text: string,
    rootContainerElement: Element | Document,
  ): Text {
    return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
      text,
    );
  }
  
  export function setInitialProperties(
    domElement: Element,
    tag: string,
    rawProps: Object,
    rootContainerElement: Element | Document,
  ): void {
    const isCustomComponentTag = isCustomComponent(tag, rawProps);
    if (__DEV__) {
      validatePropertiesInDevelopment(tag, rawProps);
      if (
        isCustomComponentTag &&
        !didWarnShadyDOM &&
        (domElement: any).shadyRoot
      ) {
        warning(
          false,
          '%s is using shady DOM. Using shady DOM with React can ' +
            'cause things to break subtly.',
          getCurrentFiberOwnerNameInDevOrNull() || 'A component',
        );
        didWarnShadyDOM = true;
      }
    }
  
    // TODO: Make sure that we check isMounted before firing any of these events.
    let props: Object;
    switch (tag) {
      case 'iframe':
      case 'object':
        trapBubbledEvent(TOP_LOAD, domElement);
        props = rawProps;
        break;
      case 'video':
      case 'audio':
        // Create listener for each media event
        for (let i = 0; i < mediaEventTypes.length; i++) {
          trapBubbledEvent(mediaEventTypes[i], domElement);
        }
        props = rawProps;
        break;
      case 'source':
        trapBubbledEvent(TOP_ERROR, domElement);
        props = rawProps;
        break;
      case 'img':
      case 'image':
      case 'link':
        trapBubbledEvent(TOP_ERROR, domElement);
        trapBubbledEvent(TOP_LOAD, domElement);
        props = rawProps;
        break;
      case 'form':
        trapBubbledEvent(TOP_RESET, domElement);
        trapBubbledEvent(TOP_SUBMIT, domElement);
        props = rawProps;
        break;
      case 'details':
        trapBubbledEvent(TOP_TOGGLE, domElement);
        props = rawProps;
        break;
      case 'input':
        ReactDOMInputInitWrapperState(domElement, rawProps);
        props = ReactDOMInputGetHostProps(domElement, rawProps);
        trapBubbledEvent(TOP_INVALID, domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'option':
        ReactDOMOptionValidateProps(domElement, rawProps);
        props = ReactDOMOptionGetHostProps(domElement, rawProps);
        break;
      case 'select':
        ReactDOMSelectInitWrapperState(domElement, rawProps);
        props = ReactDOMSelectGetHostProps(domElement, rawProps);
        trapBubbledEvent(TOP_INVALID, domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'textarea':
        ReactDOMTextareaInitWrapperState(domElement, rawProps);
        props = ReactDOMTextareaGetHostProps(domElement, rawProps);
        trapBubbledEvent(TOP_INVALID, domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      default:
        props = rawProps;
    }
  
    assertValidProps(tag, props);
  
    setInitialDOMProperties(
      tag,
      domElement,
      rootContainerElement,
      props,
      isCustomComponentTag,
    );
  
    switch (tag) {
      case 'input':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track((domElement: any));
        ReactDOMInputPostMountWrapper(domElement, rawProps, false);
        break;
      case 'textarea':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track((domElement: any));
        ReactDOMTextareaPostMountWrapper(domElement, rawProps);
        break;
      case 'option':
        ReactDOMOptionPostMountWrapper(domElement, rawProps);
        break;
      case 'select':
        ReactDOMSelectPostMountWrapper(domElement, rawProps);
        break;
      default:
        if (typeof props.onClick === 'function') {
          // TODO: This cast may not be sound for SVG, MathML or custom elements.
          trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
        }
        break;
    }
  }
  
  // Calculate the diff between the two objects.
  export function diffProperties(
    domElement: Element,
    tag: string,
    lastRawProps: Object,
    nextRawProps: Object,
    rootContainerElement: Element | Document,
  ): null | Array<mixed> {
    if (__DEV__) {
      validatePropertiesInDevelopment(tag, nextRawProps);
    }
  
    let updatePayload: null | Array<any> = null;
  
    let lastProps: Object;
    let nextProps: Object;
    switch (tag) {
      case 'input':
        lastProps = ReactDOMInputGetHostProps(domElement, lastRawProps);
        nextProps = ReactDOMInputGetHostProps(domElement, nextRawProps);
        updatePayload = [];
        break;
      case 'option':
        lastProps = ReactDOMOptionGetHostProps(domElement, lastRawProps);
        nextProps = ReactDOMOptionGetHostProps(domElement, nextRawProps);
        updatePayload = [];
        break;
      case 'select':
        lastProps = ReactDOMSelectGetHostProps(domElement, lastRawProps);
        nextProps = ReactDOMSelectGetHostProps(domElement, nextRawProps);
        updatePayload = [];
        break;
      case 'textarea':
        lastProps = ReactDOMTextareaGetHostProps(domElement, lastRawProps);
        nextProps = ReactDOMTextareaGetHostProps(domElement, nextRawProps);
        updatePayload = [];
        break;
      default:
        lastProps = lastRawProps;
        nextProps = nextRawProps;
        if (
          typeof lastProps.onClick !== 'function' &&
          typeof nextProps.onClick === 'function'
        ) {
          // TODO: This cast may not be sound for SVG, MathML or custom elements.
          trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
        }
        break;
    }
  
    assertValidProps(tag, nextProps);
  
    let propKey;
    let styleName;
    let styleUpdates = null;
    for (propKey in lastProps) {
      if (
        nextProps.hasOwnProperty(propKey) ||
        !lastProps.hasOwnProperty(propKey) ||
        lastProps[propKey] == null
      ) {
        continue;
      }
      if (propKey === STYLE) {
        const lastStyle = lastProps[propKey];
        for (styleName in lastStyle) {
          if (lastStyle.hasOwnProperty(styleName)) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = '';
          }
        }
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
        // Noop. This is handled by the clear text mechanism.
      } else if (
        propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
        propKey === SUPPRESS_HYDRATION_WARNING
      ) {
        // Noop
      } else if (propKey === AUTOFOCUS) {
        // Noop. It doesn't work on updates anyway.
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        // This is a special case. If any listener updates we need to ensure
        // that the "current" fiber pointer gets updated so we need a commit
        // to update this element.
        if (!updatePayload) {
          updatePayload = [];
        }
      } else {
        // For all other deleted properties we add it to the queue. We use
        // the whitelist in the commit phase instead.
        (updatePayload = updatePayload || []).push(propKey, null);
      }
    }
    for (propKey in nextProps) {
      const nextProp = nextProps[propKey];
      const lastProp = lastProps != null ? lastProps[propKey] : undefined;
      if (
        !nextProps.hasOwnProperty(propKey) ||
        nextProp === lastProp ||
        (nextProp == null && lastProp == null)
      ) {
        continue;
      }
      if (propKey === STYLE) {
        if (__DEV__) {
          if (nextProp) {
            // Freeze the next style object so that we can assume it won't be
            // mutated. We have already warned for this in the past.
            Object.freeze(nextProp);
          }
        }
        if (lastProp) {
          // Unset styles on `lastProp` but not on `nextProp`.
          for (styleName in lastProp) {
            if (
              lastProp.hasOwnProperty(styleName) &&
              (!nextProp || !nextProp.hasOwnProperty(styleName))
            ) {
              if (!styleUpdates) {
                styleUpdates = {};
              }
              styleUpdates[styleName] = '';
            }
          }
          // Update styles that changed since `lastProp`.
          for (styleName in nextProp) {
            if (
              nextProp.hasOwnProperty(styleName) &&
              lastProp[styleName] !== nextProp[styleName]
            ) {
              if (!styleUpdates) {
                styleUpdates = {};
              }
              styleUpdates[styleName] = nextProp[styleName];
            }
          }
        } else {
          // Relies on `updateStylesByID` not mutating `styleUpdates`.
          if (!styleUpdates) {
            if (!updatePayload) {
              updatePayload = [];
            }
            updatePayload.push(propKey, styleUpdates);
          }
          styleUpdates = nextProp;
        }
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        const nextHtml = nextProp ? nextProp[HTML] : undefined;
        const lastHtml = lastProp ? lastProp[HTML] : undefined;
        if (nextHtml != null) {
          if (lastHtml !== nextHtml) {
            (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
          }
        } else {
          // TODO: It might be too late to clear this if we have children
          // inserted already.
        }
      } else if (propKey === CHILDREN) {
        if (
          lastProp !== nextProp &&
          (typeof nextProp === 'string' || typeof nextProp === 'number')
        ) {
          (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
        }
      } else if (
        propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
        propKey === SUPPRESS_HYDRATION_WARNING
      ) {
        // Noop
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        if (nextProp != null) {
          // We eagerly listen to this even though we haven't committed yet.
          if (__DEV__ && typeof nextProp !== 'function') {
            warnForInvalidEventListener(propKey, nextProp);
          }
          ensureListeningTo(rootContainerElement, propKey);
        }
        if (!updatePayload && lastProp !== nextProp) {
          // This is a special case. If any listener updates we need to ensure
          // that the "current" props pointer gets updated so we need a commit
          // to update this element.
          updatePayload = [];
        }
      } else {
        // For any other property we always add it to the queue and then we
        // filter it out using the whitelist during the commit.
        (updatePayload = updatePayload || []).push(propKey, nextProp);
      }
    }
    if (styleUpdates) {
      if (__DEV__) {
        validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE]);
      }
      (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
    }
    return updatePayload;
  }
  
  // Apply the diff.
  export function updateProperties(
    domElement: Element,
    updatePayload: Array<any>,
    tag: string,
    lastRawProps: Object,
    nextRawProps: Object,
  ): void {
    // Update checked *before* name.
    // In the middle of an update, it is possible to have multiple checked.
    // When a checked radio tries to change name, browser makes another radio's checked false.
    if (
      tag === 'input' &&
      nextRawProps.type === 'radio' &&
      nextRawProps.name != null
    ) {
      ReactDOMInputUpdateChecked(domElement, nextRawProps);
    }
  
    const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
    const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
    // Apply the diff.
    updateDOMProperties(
      domElement,
      updatePayload,
      wasCustomComponentTag,
      isCustomComponentTag,
    );
  
    // TODO: Ensure that an update gets scheduled if any of the special props
    // changed.
    switch (tag) {
      case 'input':
        // Update the wrapper around inputs *after* updating props. This has to
        // happen after `updateDOMProperties`. Otherwise HTML5 input validations
        // raise warnings and prevent the new value from being assigned.
        ReactDOMInputUpdateWrapper(domElement, nextRawProps);
        break;
      case 'textarea':
        ReactDOMTextareaUpdateWrapper(domElement, nextRawProps);
        break;
      case 'select':
        // <select> value update needs to occur after <option> children
        // reconciliation
        ReactDOMSelectPostUpdateWrapper(domElement, nextRawProps);
        break;
    }
  }
  
  function getPossibleStandardName(propName: string): string | null {
    if (__DEV__) {
      const lowerCasedName = propName.toLowerCase();
      if (!possibleStandardNames.hasOwnProperty(lowerCasedName)) {
        return null;
      }
      return possibleStandardNames[lowerCasedName] || null;
    }
    return null;
  }
  
  export function diffHydratedProperties(
    domElement: Element,
    tag: string,
    rawProps: Object,
    parentNamespace: string,
    rootContainerElement: Element | Document,
  ): null | Array<mixed> {
    let isCustomComponentTag;
    let extraAttributeNames: Set<string>;
  
    if (__DEV__) {
      suppressHydrationWarning = rawProps[SUPPRESS_HYDRATION_WARNING] === true;
      isCustomComponentTag = isCustomComponent(tag, rawProps);
      validatePropertiesInDevelopment(tag, rawProps);
      if (
        isCustomComponentTag &&
        !didWarnShadyDOM &&
        (domElement: any).shadyRoot
      ) {
        warning(
          false,
          '%s is using shady DOM. Using shady DOM with React can ' +
            'cause things to break subtly.',
          getCurrentFiberOwnerNameInDevOrNull() || 'A component',
        );
        didWarnShadyDOM = true;
      }
    }
  
    // TODO: Make sure that we check isMounted before firing any of these events.
    switch (tag) {
      case 'iframe':
      case 'object':
        trapBubbledEvent(TOP_LOAD, domElement);
        break;
      case 'video':
      case 'audio':
        // Create listener for each media event
        for (let i = 0; i < mediaEventTypes.length; i++) {
          trapBubbledEvent(mediaEventTypes[i], domElement);
        }
        break;
      case 'source':
        trapBubbledEvent(TOP_ERROR, domElement);
        break;
      case 'img':
      case 'image':
      case 'link':
        trapBubbledEvent(TOP_ERROR, domElement);
        trapBubbledEvent(TOP_LOAD, domElement);
        break;
      case 'form':
        trapBubbledEvent(TOP_RESET, domElement);
        trapBubbledEvent(TOP_SUBMIT, domElement);
        break;
      case 'details':
        trapBubbledEvent(TOP_TOGGLE, domElement);
        break;
      case 'input':
        ReactDOMInputInitWrapperState(domElement, rawProps);
        trapBubbledEvent(TOP_INVALID, domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'option':
        ReactDOMOptionValidateProps(domElement, rawProps);
        break;
      case 'select':
        ReactDOMSelectInitWrapperState(domElement, rawProps);
        trapBubbledEvent(TOP_INVALID, domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'textarea':
        ReactDOMTextareaInitWrapperState(domElement, rawProps);
        trapBubbledEvent(TOP_INVALID, domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
    }
  
    assertValidProps(tag, rawProps);
  
    if (__DEV__) {
      extraAttributeNames = new Set();
      const attributes = domElement.attributes;
      for (let i = 0; i < attributes.length; i++) {
        const name = attributes[i].name.toLowerCase();
        switch (name) {
          // Built-in SSR attribute is whitelisted
          case 'data-reactroot':
            break;
          // Controlled attributes are not validated
          // TODO: Only ignore them on controlled tags.
          case 'value':
            break;
          case 'checked':
            break;
          case 'selected':
            break;
          default:
            // Intentionally use the original name.
            // See discussion in https://github.com/facebook/react/pull/10676.
            extraAttributeNames.add(attributes[i].name);
        }
      }
    }
  
    let updatePayload = null;
    for (const propKey in rawProps) {
      if (!rawProps.hasOwnProperty(propKey)) {
        continue;
      }
      const nextProp = rawProps[propKey];
      if (propKey === CHILDREN) {
        // For text content children we compare against textContent. This
        // might match additional HTML that is hidden when we read it using
        // textContent. E.g. "foo" will match "f<span>oo</span>" but that still
        // satisfies our requirement. Our requirement is not to produce perfect
        // HTML and attributes. Ideally we should preserve structure but it's
        // ok not to if the visible content is still enough to indicate what
        // even listeners these nodes might be wired up to.
        // TODO: Warn if there is more than a single textNode as a child.
        // TODO: Should we use domElement.firstChild.nodeValue to compare?
        if (typeof nextProp === 'string') {
          if (domElement.textContent !== nextProp) {
            if (__DEV__ && !suppressHydrationWarning) {
              warnForTextDifference(domElement.textContent, nextProp);
            }
            updatePayload = [CHILDREN, nextProp];
          }
        } else if (typeof nextProp === 'number') {
          if (domElement.textContent !== '' + nextProp) {
            if (__DEV__ && !suppressHydrationWarning) {
              warnForTextDifference(domElement.textContent, nextProp);
            }
            updatePayload = [CHILDREN, '' + nextProp];
          }
        }
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        if (nextProp != null) {
          if (__DEV__ && typeof nextProp !== 'function') {
            warnForInvalidEventListener(propKey, nextProp);
          }
          ensureListeningTo(rootContainerElement, propKey);
        }
      } else if (enableEventAPI && propKey === HYDRATE_TOUCH_HIT_TARGET) {
        updatePayload = [STYLE, rawProps.style];
      } else if (
        __DEV__ &&
        // Convince Flow we've calculated it (it's DEV-only in this method.)
        typeof isCustomComponentTag === 'boolean'
      ) {
        // Validate that the properties correspond to their expected values.
        let serverValue;
        const propertyInfo = getPropertyInfo(propKey);
        if (suppressHydrationWarning) {
          // Don't bother comparing. We're ignoring all these warnings.
        } else if (
          propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
          propKey === SUPPRESS_HYDRATION_WARNING ||
          // Controlled attributes are not validated
          // TODO: Only ignore them on controlled tags.
          propKey === 'value' ||
          propKey === 'checked' ||
          propKey === 'selected'
        ) {
          // Noop
        } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
          const serverHTML = domElement.innerHTML;
          const nextHtml = nextProp ? nextProp[HTML] : undefined;
          const expectedHTML = normalizeHTML(
            domElement,
            nextHtml != null ? nextHtml : '',
          );
          if (expectedHTML !== serverHTML) {
            warnForPropDifference(propKey, serverHTML, expectedHTML);
          }
        } else if (propKey === STYLE) {
          // $FlowFixMe - Should be inferred as not undefined.
          extraAttributeNames.delete(propKey);
  
          if (canDiffStyleForHydrationWarning) {
            const expectedStyle = createDangerousStringForStyles(nextProp);
            serverValue = domElement.getAttribute('style');
            if (expectedStyle !== serverValue) {
              warnForPropDifference(propKey, serverValue, expectedStyle);
            }
          }
        } else if (isCustomComponentTag) {
          // $FlowFixMe - Should be inferred as not undefined.
          extraAttributeNames.delete(propKey.toLowerCase());
          serverValue = getValueForAttribute(domElement, propKey, nextProp);
  
          if (nextProp !== serverValue) {
            warnForPropDifference(propKey, serverValue, nextProp);
          }
        } else if (
          !shouldIgnoreAttribute(propKey, propertyInfo, isCustomComponentTag) &&
          !shouldRemoveAttribute(
            propKey,
            nextProp,
            propertyInfo,
            isCustomComponentTag,
          )
        ) {
          let isMismatchDueToBadCasing = false;
          if (propertyInfo !== null) {
            // $FlowFixMe - Should be inferred as not undefined.
            extraAttributeNames.delete(propertyInfo.attributeName);
            serverValue = getValueForProperty(
              domElement,
              propKey,
              nextProp,
              propertyInfo,
            );
          } else {
            let ownNamespace = parentNamespace;
            if (ownNamespace === HTML_NAMESPACE) {
              ownNamespace = getIntrinsicNamespace(tag);
            }
            if (ownNamespace === HTML_NAMESPACE) {
              // $FlowFixMe - Should be inferred as not undefined.
              extraAttributeNames.delete(propKey.toLowerCase());
            } else {
              const standardName = getPossibleStandardName(propKey);
              if (standardName !== null && standardName !== propKey) {
                // If an SVG prop is supplied with bad casing, it will
                // be successfully parsed from HTML, but will produce a mismatch
                // (and would be incorrectly rendered on the client).
                // However, we already warn about bad casing elsewhere.
                // So we'll skip the misleading extra mismatch warning in this case.
                isMismatchDueToBadCasing = true;
                // $FlowFixMe - Should be inferred as not undefined.
                extraAttributeNames.delete(standardName);
              }
              // $FlowFixMe - Should be inferred as not undefined.
              extraAttributeNames.delete(propKey);
            }
            serverValue = getValueForAttribute(domElement, propKey, nextProp);
          }
  
          if (nextProp !== serverValue && !isMismatchDueToBadCasing) {
            warnForPropDifference(propKey, serverValue, nextProp);
          }
        }
      }
    }
  
    if (__DEV__) {
      // $FlowFixMe - Should be inferred as not undefined.
      if (extraAttributeNames.size > 0 && !suppressHydrationWarning) {
        // $FlowFixMe - Should be inferred as not undefined.
        warnForExtraAttributes(extraAttributeNames);
      }
    }
  
    switch (tag) {
      case 'input':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track((domElement: any));
        ReactDOMInputPostMountWrapper(domElement, rawProps, true);
        break;
      case 'textarea':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track((domElement: any));
        ReactDOMTextareaPostMountWrapper(domElement, rawProps);
        break;
      case 'select':
      case 'option':
        // For input and textarea we current always set the value property at
        // post mount to force it to diverge from attributes. However, for
        // option and select we don't quite do the same thing and select
        // is not resilient to the DOM state changing so we don't do that here.
        // TODO: Consider not doing this for input and textarea.
        break;
      default:
        if (typeof rawProps.onClick === 'function') {
          // TODO: This cast may not be sound for SVG, MathML or custom elements.
          trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
        }
        break;
    }
  
    return updatePayload;
  }
  
  export function diffHydratedText(textNode: Text, text: string): boolean {
    const isDifferent = textNode.nodeValue !== text;
    return isDifferent;
  }
  
  export function warnForUnmatchedText(textNode: Text, text: string) {
    if (__DEV__) {
      warnForTextDifference(textNode.nodeValue, text);
    }
  }
  
  export function warnForDeletedHydratableElement(
    parentNode: Element | Document,
    child: Element,
  ) {
    if (__DEV__) {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warningWithoutStack(
        false,
        'Did not expect server HTML to contain a <%s> in <%s>.',
        child.nodeName.toLowerCase(),
        parentNode.nodeName.toLowerCase(),
      );
    }
  }
  
  export function warnForDeletedHydratableText(
    parentNode: Element | Document,
    child: Text,
  ) {
    if (__DEV__) {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warningWithoutStack(
        false,
        'Did not expect server HTML to contain the text node "%s" in <%s>.',
        child.nodeValue,
        parentNode.nodeName.toLowerCase(),
      );
    }
  }
  
  export function warnForInsertedHydratedElement(
    parentNode: Element | Document,
    tag: string,
    props: Object,
  ) {
    if (__DEV__) {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warningWithoutStack(
        false,
        'Expected server HTML to contain a matching <%s> in <%s>.',
        tag,
        parentNode.nodeName.toLowerCase(),
      );
    }
  }
  
  export function warnForInsertedHydratedText(
    parentNode: Element | Document,
    text: string,
  ) {
    if (__DEV__) {
      if (text === '') {
        // We expect to insert empty text nodes since they're not represented in
        // the HTML.
        // TODO: Remove this special case if we can just avoid inserting empty
        // text nodes.
        return;
      }
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warningWithoutStack(
        false,
        'Expected server HTML to contain a matching text node for "%s" in <%s>.',
        text,
        parentNode.nodeName.toLowerCase(),
      );
    }
  }
  
  export function restoreControlledState(
    domElement: Element,
    tag: string,
    props: Object,
  ): void {
    switch (tag) {
      case 'input':
        ReactDOMInputRestoreControlledState(domElement, props);
        return;
      case 'textarea':
        ReactDOMTextareaRestoreControlledState(domElement, props);
        return;
      case 'select':
        ReactDOMSelectRestoreControlledState(domElement, props);
        return;
    }
  }
  
  export function listenToEventResponderEventTypes(
    eventTypes: Array<ReactEventResponderEventType>,
    element: Element | Document,
  ): void {
    if (enableEventAPI) {
      // Get the listening Set for this element. We use this to track
      // what events we're listening to.
      const listeningSet = getListeningSetForElement(element);
  
      // Go through each target event type of the event responder
      for (let i = 0, length = eventTypes.length; i < length; ++i) {
        const targetEventType = eventTypes[i];
        let topLevelType;
        let passive = true;
  
        // If no event config object is provided (i.e. - only a string),
        // we default to enabling passive and not capture.
        if (typeof targetEventType === 'string') {
          topLevelType = targetEventType;
        } else {
          if (__DEV__) {
            warning(
              typeof targetEventType === 'object' && targetEventType !== null,
              'Event Responder: invalid entry in event types array. ' +
                'Entry must be string or an object. Instead, got %s.',
              targetEventType,
            );
          }
          const targetEventConfigObject = ((targetEventType: any): {
            name: string,
            passive?: boolean,
          });
          topLevelType = targetEventConfigObject.name;
          if (targetEventConfigObject.passive !== undefined) {
            passive = targetEventConfigObject.passive;
          }
        }
        const listeningName = generateListeningKey(topLevelType, passive);
        if (!listeningSet.has(listeningName)) {
          trapEventForResponderEventSystem(
            element,
            ((topLevelType: any): DOMTopLevelEventType),
            passive,
          );
          listeningSet.add(listeningName);
        }
      }
    }
  }
  
  // We can remove this once the event API is stable and out of a flag
  if (enableEventAPI) {
    setListenToResponderEventTypes(listenToEventResponderEventTypes);
  }
  