1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {DOMEventName} from './DOMEventNames';
    
  11. import type {EventSystemFlags} from './EventSystemFlags';
    
  12. import type {AnyNativeEvent} from './PluginModuleType';
    
  13. import type {
    
  14.   KnownReactSyntheticEvent,
    
  15.   ReactSyntheticEvent,
    
  16. } from './ReactSyntheticEventType';
    
  17. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  18. 
    
  19. import {allNativeEvents} from './EventRegistry';
    
  20. import {
    
  21.   SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE,
    
  22.   IS_LEGACY_FB_SUPPORT_MODE,
    
  23.   SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS,
    
  24.   IS_CAPTURE_PHASE,
    
  25.   IS_EVENT_HANDLE_NON_MANAGED_NODE,
    
  26.   IS_NON_DELEGATED,
    
  27. } from './EventSystemFlags';
    
  28. import {isReplayingEvent} from './CurrentReplayingEvent';
    
  29. 
    
  30. import {
    
  31.   HostRoot,
    
  32.   HostPortal,
    
  33.   HostComponent,
    
  34.   HostHoistable,
    
  35.   HostSingleton,
    
  36.   HostText,
    
  37.   ScopeComponent,
    
  38. } from 'react-reconciler/src/ReactWorkTags';
    
  39. 
    
  40. import getEventTarget from './getEventTarget';
    
  41. import {
    
  42.   getClosestInstanceFromNode,
    
  43.   getEventListenerSet,
    
  44.   getEventHandlerListeners,
    
  45. } from '../client/ReactDOMComponentTree';
    
  46. import {COMMENT_NODE, DOCUMENT_NODE} from '../client/HTMLNodeType';
    
  47. import {batchedUpdates} from './ReactDOMUpdateBatching';
    
  48. import getListener from './getListener';
    
  49. import {passiveBrowserEventsSupported} from './checkPassiveEvents';
    
  50. 
    
  51. import {
    
  52.   enableLegacyFBSupport,
    
  53.   enableCreateEventHandleAPI,
    
  54.   enableScopeAPI,
    
  55.   enableFloat,
    
  56.   enableHostSingletons,
    
  57.   enableFormActions,
    
  58. } from 'shared/ReactFeatureFlags';
    
  59. import {
    
  60.   invokeGuardedCallbackAndCatchFirstError,
    
  61.   rethrowCaughtError,
    
  62. } from 'shared/ReactErrorUtils';
    
  63. import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener';
    
  64. import {
    
  65.   removeEventListener,
    
  66.   addEventCaptureListener,
    
  67.   addEventBubbleListener,
    
  68.   addEventBubbleListenerWithPassiveFlag,
    
  69.   addEventCaptureListenerWithPassiveFlag,
    
  70. } from './EventListener';
    
  71. import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';
    
  72. import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
    
  73. import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';
    
  74. import * as SelectEventPlugin from './plugins/SelectEventPlugin';
    
  75. import * as SimpleEventPlugin from './plugins/SimpleEventPlugin';
    
  76. import * as FormActionEventPlugin from './plugins/FormActionEventPlugin';
    
  77. 
    
  78. type DispatchListener = {
    
  79.   instance: null | Fiber,
    
  80.   listener: Function,
    
  81.   currentTarget: EventTarget,
    
  82. };
    
  83. 
    
  84. type DispatchEntry = {
    
  85.   event: ReactSyntheticEvent,
    
  86.   listeners: Array<DispatchListener>,
    
  87. };
    
  88. 
    
  89. export type DispatchQueue = Array<DispatchEntry>;
    
  90. 
    
  91. // TODO: remove top-level side effect.
    
  92. SimpleEventPlugin.registerEvents();
    
  93. EnterLeaveEventPlugin.registerEvents();
    
  94. ChangeEventPlugin.registerEvents();
    
  95. SelectEventPlugin.registerEvents();
    
  96. BeforeInputEventPlugin.registerEvents();
    
  97. 
    
  98. function extractEvents(
    
  99.   dispatchQueue: DispatchQueue,
    
  100.   domEventName: DOMEventName,
    
  101.   targetInst: null | Fiber,
    
  102.   nativeEvent: AnyNativeEvent,
    
  103.   nativeEventTarget: null | EventTarget,
    
  104.   eventSystemFlags: EventSystemFlags,
    
  105.   targetContainer: EventTarget,
    
  106. ) {
    
  107.   // TODO: we should remove the concept of a "SimpleEventPlugin".
    
  108.   // This is the basic functionality of the event system. All
    
  109.   // the other plugins are essentially polyfills. So the plugin
    
  110.   // should probably be inlined somewhere and have its logic
    
  111.   // be core the to event system. This would potentially allow
    
  112.   // us to ship builds of React without the polyfilled plugins below.
    
  113.   SimpleEventPlugin.extractEvents(
    
  114.     dispatchQueue,
    
  115.     domEventName,
    
  116.     targetInst,
    
  117.     nativeEvent,
    
  118.     nativeEventTarget,
    
  119.     eventSystemFlags,
    
  120.     targetContainer,
    
  121.   );
    
  122.   const shouldProcessPolyfillPlugins =
    
  123.     (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
    
  124.   // We don't process these events unless we are in the
    
  125.   // event's native "bubble" phase, which means that we're
    
  126.   // not in the capture phase. That's because we emulate
    
  127.   // the capture phase here still. This is a trade-off,
    
  128.   // because in an ideal world we would not emulate and use
    
  129.   // the phases properly, like we do with the SimpleEvent
    
  130.   // plugin. However, the plugins below either expect
    
  131.   // emulation (EnterLeave) or use state localized to that
    
  132.   // plugin (BeforeInput, Change, Select). The state in
    
  133.   // these modules complicates things, as you'll essentially
    
  134.   // get the case where the capture phase event might change
    
  135.   // state, only for the following bubble event to come in
    
  136.   // later and not trigger anything as the state now
    
  137.   // invalidates the heuristics of the event plugin. We
    
  138.   // could alter all these plugins to work in such ways, but
    
  139.   // that might cause other unknown side-effects that we
    
  140.   // can't foresee right now.
    
  141.   if (shouldProcessPolyfillPlugins) {
    
  142.     EnterLeaveEventPlugin.extractEvents(
    
  143.       dispatchQueue,
    
  144.       domEventName,
    
  145.       targetInst,
    
  146.       nativeEvent,
    
  147.       nativeEventTarget,
    
  148.       eventSystemFlags,
    
  149.       targetContainer,
    
  150.     );
    
  151.     ChangeEventPlugin.extractEvents(
    
  152.       dispatchQueue,
    
  153.       domEventName,
    
  154.       targetInst,
    
  155.       nativeEvent,
    
  156.       nativeEventTarget,
    
  157.       eventSystemFlags,
    
  158.       targetContainer,
    
  159.     );
    
  160.     SelectEventPlugin.extractEvents(
    
  161.       dispatchQueue,
    
  162.       domEventName,
    
  163.       targetInst,
    
  164.       nativeEvent,
    
  165.       nativeEventTarget,
    
  166.       eventSystemFlags,
    
  167.       targetContainer,
    
  168.     );
    
  169.     BeforeInputEventPlugin.extractEvents(
    
  170.       dispatchQueue,
    
  171.       domEventName,
    
  172.       targetInst,
    
  173.       nativeEvent,
    
  174.       nativeEventTarget,
    
  175.       eventSystemFlags,
    
  176.       targetContainer,
    
  177.     );
    
  178.     if (enableFormActions) {
    
  179.       FormActionEventPlugin.extractEvents(
    
  180.         dispatchQueue,
    
  181.         domEventName,
    
  182.         targetInst,
    
  183.         nativeEvent,
    
  184.         nativeEventTarget,
    
  185.         eventSystemFlags,
    
  186.         targetContainer,
    
  187.       );
    
  188.     }
    
  189.   }
    
  190. }
    
  191. 
    
  192. // List of events that need to be individually attached to media elements.
    
  193. export const mediaEventTypes: Array<DOMEventName> = [
    
  194.   'abort',
    
  195.   'canplay',
    
  196.   'canplaythrough',
    
  197.   'durationchange',
    
  198.   'emptied',
    
  199.   'encrypted',
    
  200.   'ended',
    
  201.   'error',
    
  202.   'loadeddata',
    
  203.   'loadedmetadata',
    
  204.   'loadstart',
    
  205.   'pause',
    
  206.   'play',
    
  207.   'playing',
    
  208.   'progress',
    
  209.   'ratechange',
    
  210.   'resize',
    
  211.   'seeked',
    
  212.   'seeking',
    
  213.   'stalled',
    
  214.   'suspend',
    
  215.   'timeupdate',
    
  216.   'volumechange',
    
  217.   'waiting',
    
  218. ];
    
  219. 
    
  220. // We should not delegate these events to the container, but rather
    
  221. // set them on the actual target element itself. This is primarily
    
  222. // because these events do not consistently bubble in the DOM.
    
  223. export const nonDelegatedEvents: Set<DOMEventName> = new Set([
    
  224.   'cancel',
    
  225.   'close',
    
  226.   'invalid',
    
  227.   'load',
    
  228.   'scroll',
    
  229.   'scrollend',
    
  230.   'toggle',
    
  231.   // In order to reduce bytes, we insert the above array of media events
    
  232.   // into this Set. Note: the "error" event isn't an exclusive media event,
    
  233.   // and can occur on other elements too. Rather than duplicate that event,
    
  234.   // we just take it from the media events array.
    
  235.   ...mediaEventTypes,
    
  236. ]);
    
  237. 
    
  238. function executeDispatch(
    
  239.   event: ReactSyntheticEvent,
    
  240.   listener: Function,
    
  241.   currentTarget: EventTarget,
    
  242. ): void {
    
  243.   const type = event.type || 'unknown-event';
    
  244.   event.currentTarget = currentTarget;
    
  245.   invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
    
  246.   event.currentTarget = null;
    
  247. }
    
  248. 
    
  249. function processDispatchQueueItemsInOrder(
    
  250.   event: ReactSyntheticEvent,
    
  251.   dispatchListeners: Array<DispatchListener>,
    
  252.   inCapturePhase: boolean,
    
  253. ): void {
    
  254.   let previousInstance;
    
  255.   if (inCapturePhase) {
    
  256.     for (let i = dispatchListeners.length - 1; i >= 0; i--) {
    
  257.       const {instance, currentTarget, listener} = dispatchListeners[i];
    
  258.       if (instance !== previousInstance && event.isPropagationStopped()) {
    
  259.         return;
    
  260.       }
    
  261.       executeDispatch(event, listener, currentTarget);
    
  262.       previousInstance = instance;
    
  263.     }
    
  264.   } else {
    
  265.     for (let i = 0; i < dispatchListeners.length; i++) {
    
  266.       const {instance, currentTarget, listener} = dispatchListeners[i];
    
  267.       if (instance !== previousInstance && event.isPropagationStopped()) {
    
  268.         return;
    
  269.       }
    
  270.       executeDispatch(event, listener, currentTarget);
    
  271.       previousInstance = instance;
    
  272.     }
    
  273.   }
    
  274. }
    
  275. 
    
  276. export function processDispatchQueue(
    
  277.   dispatchQueue: DispatchQueue,
    
  278.   eventSystemFlags: EventSystemFlags,
    
  279. ): void {
    
  280.   const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
    
  281.   for (let i = 0; i < dispatchQueue.length; i++) {
    
  282.     const {event, listeners} = dispatchQueue[i];
    
  283.     processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
    
  284.     //  event system doesn't use pooling.
    
  285.   }
    
  286.   // This would be a good time to rethrow if any of the event handlers threw.
    
  287.   rethrowCaughtError();
    
  288. }
    
  289. 
    
  290. function dispatchEventsForPlugins(
    
  291.   domEventName: DOMEventName,
    
  292.   eventSystemFlags: EventSystemFlags,
    
  293.   nativeEvent: AnyNativeEvent,
    
  294.   targetInst: null | Fiber,
    
  295.   targetContainer: EventTarget,
    
  296. ): void {
    
  297.   const nativeEventTarget = getEventTarget(nativeEvent);
    
  298.   const dispatchQueue: DispatchQueue = [];
    
  299.   extractEvents(
    
  300.     dispatchQueue,
    
  301.     domEventName,
    
  302.     targetInst,
    
  303.     nativeEvent,
    
  304.     nativeEventTarget,
    
  305.     eventSystemFlags,
    
  306.     targetContainer,
    
  307.   );
    
  308.   processDispatchQueue(dispatchQueue, eventSystemFlags);
    
  309. }
    
  310. 
    
  311. export function listenToNonDelegatedEvent(
    
  312.   domEventName: DOMEventName,
    
  313.   targetElement: Element,
    
  314. ): void {
    
  315.   if (__DEV__) {
    
  316.     if (!nonDelegatedEvents.has(domEventName)) {
    
  317.       console.error(
    
  318.         'Did not expect a listenToNonDelegatedEvent() call for "%s". ' +
    
  319.           'This is a bug in React. Please file an issue.',
    
  320.         domEventName,
    
  321.       );
    
  322.     }
    
  323.   }
    
  324.   const isCapturePhaseListener = false;
    
  325.   const listenerSet = getEventListenerSet(targetElement);
    
  326.   const listenerSetKey = getListenerSetKey(
    
  327.     domEventName,
    
  328.     isCapturePhaseListener,
    
  329.   );
    
  330.   if (!listenerSet.has(listenerSetKey)) {
    
  331.     addTrappedEventListener(
    
  332.       targetElement,
    
  333.       domEventName,
    
  334.       IS_NON_DELEGATED,
    
  335.       isCapturePhaseListener,
    
  336.     );
    
  337.     listenerSet.add(listenerSetKey);
    
  338.   }
    
  339. }
    
  340. 
    
  341. export function listenToNativeEvent(
    
  342.   domEventName: DOMEventName,
    
  343.   isCapturePhaseListener: boolean,
    
  344.   target: EventTarget,
    
  345. ): void {
    
  346.   if (__DEV__) {
    
  347.     if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
    
  348.       console.error(
    
  349.         'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +
    
  350.           'This is a bug in React. Please file an issue.',
    
  351.         domEventName,
    
  352.       );
    
  353.     }
    
  354.   }
    
  355. 
    
  356.   let eventSystemFlags = 0;
    
  357.   if (isCapturePhaseListener) {
    
  358.     eventSystemFlags |= IS_CAPTURE_PHASE;
    
  359.   }
    
  360.   addTrappedEventListener(
    
  361.     target,
    
  362.     domEventName,
    
  363.     eventSystemFlags,
    
  364.     isCapturePhaseListener,
    
  365.   );
    
  366. }
    
  367. 
    
  368. // This is only used by createEventHandle when the
    
  369. // target is not a DOM element. E.g. window.
    
  370. export function listenToNativeEventForNonManagedEventTarget(
    
  371.   domEventName: DOMEventName,
    
  372.   isCapturePhaseListener: boolean,
    
  373.   target: EventTarget,
    
  374. ): void {
    
  375.   let eventSystemFlags = IS_EVENT_HANDLE_NON_MANAGED_NODE;
    
  376.   const listenerSet = getEventListenerSet(target);
    
  377.   const listenerSetKey = getListenerSetKey(
    
  378.     domEventName,
    
  379.     isCapturePhaseListener,
    
  380.   );
    
  381.   if (!listenerSet.has(listenerSetKey)) {
    
  382.     if (isCapturePhaseListener) {
    
  383.       eventSystemFlags |= IS_CAPTURE_PHASE;
    
  384.     }
    
  385.     addTrappedEventListener(
    
  386.       target,
    
  387.       domEventName,
    
  388.       eventSystemFlags,
    
  389.       isCapturePhaseListener,
    
  390.     );
    
  391.     listenerSet.add(listenerSetKey);
    
  392.   }
    
  393. }
    
  394. 
    
  395. const listeningMarker = '_reactListening' + Math.random().toString(36).slice(2);
    
  396. 
    
  397. export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
    
  398.   if (!(rootContainerElement: any)[listeningMarker]) {
    
  399.     (rootContainerElement: any)[listeningMarker] = true;
    
  400.     allNativeEvents.forEach(domEventName => {
    
  401.       // We handle selectionchange separately because it
    
  402.       // doesn't bubble and needs to be on the document.
    
  403.       if (domEventName !== 'selectionchange') {
    
  404.         if (!nonDelegatedEvents.has(domEventName)) {
    
  405.           listenToNativeEvent(domEventName, false, rootContainerElement);
    
  406.         }
    
  407.         listenToNativeEvent(domEventName, true, rootContainerElement);
    
  408.       }
    
  409.     });
    
  410.     const ownerDocument =
    
  411.       (rootContainerElement: any).nodeType === DOCUMENT_NODE
    
  412.         ? rootContainerElement
    
  413.         : (rootContainerElement: any).ownerDocument;
    
  414.     if (ownerDocument !== null) {
    
  415.       // The selectionchange event also needs deduplication
    
  416.       // but it is attached to the document.
    
  417.       if (!(ownerDocument: any)[listeningMarker]) {
    
  418.         (ownerDocument: any)[listeningMarker] = true;
    
  419.         listenToNativeEvent('selectionchange', false, ownerDocument);
    
  420.       }
    
  421.     }
    
  422.   }
    
  423. }
    
  424. 
    
  425. function addTrappedEventListener(
    
  426.   targetContainer: EventTarget,
    
  427.   domEventName: DOMEventName,
    
  428.   eventSystemFlags: EventSystemFlags,
    
  429.   isCapturePhaseListener: boolean,
    
  430.   isDeferredListenerForLegacyFBSupport?: boolean,
    
  431. ) {
    
  432.   let listener = createEventListenerWrapperWithPriority(
    
  433.     targetContainer,
    
  434.     domEventName,
    
  435.     eventSystemFlags,
    
  436.   );
    
  437.   // If passive option is not supported, then the event will be
    
  438.   // active and not passive.
    
  439.   let isPassiveListener: void | boolean = undefined;
    
  440.   if (passiveBrowserEventsSupported) {
    
  441.     // Browsers introduced an intervention, making these events
    
  442.     // passive by default on document. React doesn't bind them
    
  443.     // to document anymore, but changing this now would undo
    
  444.     // the performance wins from the change. So we emulate
    
  445.     // the existing behavior manually on the roots now.
    
  446.     // https://github.com/facebook/react/issues/19651
    
  447.     if (
    
  448.       domEventName === 'touchstart' ||
    
  449.       domEventName === 'touchmove' ||
    
  450.       domEventName === 'wheel'
    
  451.     ) {
    
  452.       isPassiveListener = true;
    
  453.     }
    
  454.   }
    
  455. 
    
  456.   targetContainer =
    
  457.     enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
    
  458.       ? (targetContainer: any).ownerDocument
    
  459.       : targetContainer;
    
  460. 
    
  461.   let unsubscribeListener;
    
  462.   // When legacyFBSupport is enabled, it's for when we
    
  463.   // want to add a one time event listener to a container.
    
  464.   // This should only be used with enableLegacyFBSupport
    
  465.   // due to requirement to provide compatibility with
    
  466.   // internal FB www event tooling. This works by removing
    
  467.   // the event listener as soon as it is invoked. We could
    
  468.   // also attempt to use the {once: true} param on
    
  469.   // addEventListener, but that requires support and some
    
  470.   // browsers do not support this today, and given this is
    
  471.   // to support legacy code patterns, it's likely they'll
    
  472.   // need support for such browsers.
    
  473.   if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
    
  474.     const originalListener = listener;
    
  475.     // $FlowFixMe[missing-this-annot]
    
  476.     listener = function (...p) {
    
  477.       removeEventListener(
    
  478.         targetContainer,
    
  479.         domEventName,
    
  480.         unsubscribeListener,
    
  481.         isCapturePhaseListener,
    
  482.       );
    
  483.       return originalListener.apply(this, p);
    
  484.     };
    
  485.   }
    
  486.   // TODO: There are too many combinations here. Consolidate them.
    
  487.   if (isCapturePhaseListener) {
    
  488.     if (isPassiveListener !== undefined) {
    
  489.       unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
    
  490.         targetContainer,
    
  491.         domEventName,
    
  492.         listener,
    
  493.         isPassiveListener,
    
  494.       );
    
  495.     } else {
    
  496.       unsubscribeListener = addEventCaptureListener(
    
  497.         targetContainer,
    
  498.         domEventName,
    
  499.         listener,
    
  500.       );
    
  501.     }
    
  502.   } else {
    
  503.     if (isPassiveListener !== undefined) {
    
  504.       unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
    
  505.         targetContainer,
    
  506.         domEventName,
    
  507.         listener,
    
  508.         isPassiveListener,
    
  509.       );
    
  510.     } else {
    
  511.       unsubscribeListener = addEventBubbleListener(
    
  512.         targetContainer,
    
  513.         domEventName,
    
  514.         listener,
    
  515.       );
    
  516.     }
    
  517.   }
    
  518. }
    
  519. 
    
  520. function deferClickToDocumentForLegacyFBSupport(
    
  521.   domEventName: DOMEventName,
    
  522.   targetContainer: EventTarget,
    
  523. ): void {
    
  524.   // We defer all click events with legacy FB support mode on.
    
  525.   // This means we add a one time event listener to trigger
    
  526.   // after the FB delegated listeners fire.
    
  527.   const isDeferredListenerForLegacyFBSupport = true;
    
  528.   addTrappedEventListener(
    
  529.     targetContainer,
    
  530.     domEventName,
    
  531.     IS_LEGACY_FB_SUPPORT_MODE,
    
  532.     false,
    
  533.     isDeferredListenerForLegacyFBSupport,
    
  534.   );
    
  535. }
    
  536. 
    
  537. function isMatchingRootContainer(
    
  538.   grandContainer: Element,
    
  539.   targetContainer: EventTarget,
    
  540. ): boolean {
    
  541.   return (
    
  542.     grandContainer === targetContainer ||
    
  543.     (grandContainer.nodeType === COMMENT_NODE &&
    
  544.       grandContainer.parentNode === targetContainer)
    
  545.   );
    
  546. }
    
  547. 
    
  548. export function dispatchEventForPluginEventSystem(
    
  549.   domEventName: DOMEventName,
    
  550.   eventSystemFlags: EventSystemFlags,
    
  551.   nativeEvent: AnyNativeEvent,
    
  552.   targetInst: null | Fiber,
    
  553.   targetContainer: EventTarget,
    
  554. ): void {
    
  555.   let ancestorInst = targetInst;
    
  556.   if (
    
  557.     (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
    
  558.     (eventSystemFlags & IS_NON_DELEGATED) === 0
    
  559.   ) {
    
  560.     const targetContainerNode = ((targetContainer: any): Node);
    
  561. 
    
  562.     // If we are using the legacy FB support flag, we
    
  563.     // defer the event to the null with a one
    
  564.     // time event listener so we can defer the event.
    
  565.     if (
    
  566.       enableLegacyFBSupport &&
    
  567.       // If our event flags match the required flags for entering
    
  568.       // FB legacy mode and we are processing the "click" event,
    
  569.       // then we can defer the event to the "document", to allow
    
  570.       // for legacy FB support, where the expected behavior was to
    
  571.       // match React < 16 behavior of delegated clicks to the doc.
    
  572.       domEventName === 'click' &&
    
  573.       (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 &&
    
  574.       !isReplayingEvent(nativeEvent)
    
  575.     ) {
    
  576.       deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer);
    
  577.       return;
    
  578.     }
    
  579.     if (targetInst !== null) {
    
  580.       // The below logic attempts to work out if we need to change
    
  581.       // the target fiber to a different ancestor. We had similar logic
    
  582.       // in the legacy event system, except the big difference between
    
  583.       // systems is that the modern event system now has an event listener
    
  584.       // attached to each React Root and React Portal Root. Together,
    
  585.       // the DOM nodes representing these roots are the "rootContainer".
    
  586.       // To figure out which ancestor instance we should use, we traverse
    
  587.       // up the fiber tree from the target instance and attempt to find
    
  588.       // root boundaries that match that of our current "rootContainer".
    
  589.       // If we find that "rootContainer", we find the parent fiber
    
  590.       // sub-tree for that root and make that our ancestor instance.
    
  591.       let node: null | Fiber = targetInst;
    
  592. 
    
  593.       mainLoop: while (true) {
    
  594.         if (node === null) {
    
  595.           return;
    
  596.         }
    
  597.         const nodeTag = node.tag;
    
  598.         if (nodeTag === HostRoot || nodeTag === HostPortal) {
    
  599.           let container = node.stateNode.containerInfo;
    
  600.           if (isMatchingRootContainer(container, targetContainerNode)) {
    
  601.             break;
    
  602.           }
    
  603.           if (nodeTag === HostPortal) {
    
  604.             // The target is a portal, but it's not the rootContainer we're looking for.
    
  605.             // Normally portals handle their own events all the way down to the root.
    
  606.             // So we should be able to stop now. However, we don't know if this portal
    
  607.             // was part of *our* root.
    
  608.             let grandNode = node.return;
    
  609.             while (grandNode !== null) {
    
  610.               const grandTag = grandNode.tag;
    
  611.               if (grandTag === HostRoot || grandTag === HostPortal) {
    
  612.                 const grandContainer = grandNode.stateNode.containerInfo;
    
  613.                 if (
    
  614.                   isMatchingRootContainer(grandContainer, targetContainerNode)
    
  615.                 ) {
    
  616.                   // This is the rootContainer we're looking for and we found it as
    
  617.                   // a parent of the Portal. That means we can ignore it because the
    
  618.                   // Portal will bubble through to us.
    
  619.                   return;
    
  620.                 }
    
  621.               }
    
  622.               grandNode = grandNode.return;
    
  623.             }
    
  624.           }
    
  625.           // Now we need to find it's corresponding host fiber in the other
    
  626.           // tree. To do this we can use getClosestInstanceFromNode, but we
    
  627.           // need to validate that the fiber is a host instance, otherwise
    
  628.           // we need to traverse up through the DOM till we find the correct
    
  629.           // node that is from the other tree.
    
  630.           while (container !== null) {
    
  631.             const parentNode = getClosestInstanceFromNode(container);
    
  632.             if (parentNode === null) {
    
  633.               return;
    
  634.             }
    
  635.             const parentTag = parentNode.tag;
    
  636.             if (
    
  637.               parentTag === HostComponent ||
    
  638.               parentTag === HostText ||
    
  639.               (enableFloat ? parentTag === HostHoistable : false) ||
    
  640.               (enableHostSingletons ? parentTag === HostSingleton : false)
    
  641.             ) {
    
  642.               node = ancestorInst = parentNode;
    
  643.               continue mainLoop;
    
  644.             }
    
  645.             container = container.parentNode;
    
  646.           }
    
  647.         }
    
  648.         node = node.return;
    
  649.       }
    
  650.     }
    
  651.   }
    
  652. 
    
  653.   batchedUpdates(() =>
    
  654.     dispatchEventsForPlugins(
    
  655.       domEventName,
    
  656.       eventSystemFlags,
    
  657.       nativeEvent,
    
  658.       ancestorInst,
    
  659.       targetContainer,
    
  660.     ),
    
  661.   );
    
  662. }
    
  663. 
    
  664. function createDispatchListener(
    
  665.   instance: null | Fiber,
    
  666.   listener: Function,
    
  667.   currentTarget: EventTarget,
    
  668. ): DispatchListener {
    
  669.   return {
    
  670.     instance,
    
  671.     listener,
    
  672.     currentTarget,
    
  673.   };
    
  674. }
    
  675. 
    
  676. export function accumulateSinglePhaseListeners(
    
  677.   targetFiber: Fiber | null,
    
  678.   reactName: string | null,
    
  679.   nativeEventType: string,
    
  680.   inCapturePhase: boolean,
    
  681.   accumulateTargetOnly: boolean,
    
  682.   nativeEvent: AnyNativeEvent,
    
  683. ): Array<DispatchListener> {
    
  684.   const captureName = reactName !== null ? reactName + 'Capture' : null;
    
  685.   const reactEventName = inCapturePhase ? captureName : reactName;
    
  686.   let listeners: Array<DispatchListener> = [];
    
  687. 
    
  688.   let instance = targetFiber;
    
  689.   let lastHostComponent = null;
    
  690. 
    
  691.   // Accumulate all instances and listeners via the target -> root path.
    
  692.   while (instance !== null) {
    
  693.     const {stateNode, tag} = instance;
    
  694.     // Handle listeners that are on HostComponents (i.e. <div>)
    
  695.     if (
    
  696.       (tag === HostComponent ||
    
  697.         (enableFloat ? tag === HostHoistable : false) ||
    
  698.         (enableHostSingletons ? tag === HostSingleton : false)) &&
    
  699.       stateNode !== null
    
  700.     ) {
    
  701.       lastHostComponent = stateNode;
    
  702. 
    
  703.       // createEventHandle listeners
    
  704.       if (enableCreateEventHandleAPI) {
    
  705.         const eventHandlerListeners =
    
  706.           getEventHandlerListeners(lastHostComponent);
    
  707.         if (eventHandlerListeners !== null) {
    
  708.           eventHandlerListeners.forEach(entry => {
    
  709.             if (
    
  710.               entry.type === nativeEventType &&
    
  711.               entry.capture === inCapturePhase
    
  712.             ) {
    
  713.               listeners.push(
    
  714.                 createDispatchListener(
    
  715.                   instance,
    
  716.                   entry.callback,
    
  717.                   (lastHostComponent: any),
    
  718.                 ),
    
  719.               );
    
  720.             }
    
  721.           });
    
  722.         }
    
  723.       }
    
  724. 
    
  725.       // Standard React on* listeners, i.e. onClick or onClickCapture
    
  726.       if (reactEventName !== null) {
    
  727.         const listener = getListener(instance, reactEventName);
    
  728.         if (listener != null) {
    
  729.           listeners.push(
    
  730.             createDispatchListener(instance, listener, lastHostComponent),
    
  731.           );
    
  732.         }
    
  733.       }
    
  734.     } else if (
    
  735.       enableCreateEventHandleAPI &&
    
  736.       enableScopeAPI &&
    
  737.       tag === ScopeComponent &&
    
  738.       lastHostComponent !== null &&
    
  739.       stateNode !== null
    
  740.     ) {
    
  741.       // Scopes
    
  742.       const reactScopeInstance = stateNode;
    
  743.       const eventHandlerListeners =
    
  744.         getEventHandlerListeners(reactScopeInstance);
    
  745.       if (eventHandlerListeners !== null) {
    
  746.         eventHandlerListeners.forEach(entry => {
    
  747.           if (
    
  748.             entry.type === nativeEventType &&
    
  749.             entry.capture === inCapturePhase
    
  750.           ) {
    
  751.             listeners.push(
    
  752.               createDispatchListener(
    
  753.                 instance,
    
  754.                 entry.callback,
    
  755.                 (lastHostComponent: any),
    
  756.               ),
    
  757.             );
    
  758.           }
    
  759.         });
    
  760.       }
    
  761.     }
    
  762.     // If we are only accumulating events for the target, then we don't
    
  763.     // continue to propagate through the React fiber tree to find other
    
  764.     // listeners.
    
  765.     if (accumulateTargetOnly) {
    
  766.       break;
    
  767.     }
    
  768.     // If we are processing the onBeforeBlur event, then we need to take
    
  769.     // into consideration that part of the React tree might have been hidden
    
  770.     // or deleted (as we're invoking this event during commit). We can find
    
  771.     // this out by checking if intercept fiber set on the event matches the
    
  772.     // current instance fiber. In which case, we should clear all existing
    
  773.     // listeners.
    
  774.     if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {
    
  775.       // $FlowFixMe[prop-missing] internal field
    
  776.       const detachedInterceptFiber = nativeEvent._detachedInterceptFiber;
    
  777.       if (
    
  778.         detachedInterceptFiber !== null &&
    
  779.         (detachedInterceptFiber === instance ||
    
  780.           detachedInterceptFiber === instance.alternate)
    
  781.       ) {
    
  782.         listeners = [];
    
  783.       }
    
  784.     }
    
  785.     instance = instance.return;
    
  786.   }
    
  787.   return listeners;
    
  788. }
    
  789. 
    
  790. // We should only use this function for:
    
  791. // - BeforeInputEventPlugin
    
  792. // - ChangeEventPlugin
    
  793. // - SelectEventPlugin
    
  794. // This is because we only process these plugins
    
  795. // in the bubble phase, so we need to accumulate two
    
  796. // phase event listeners (via emulation).
    
  797. export function accumulateTwoPhaseListeners(
    
  798.   targetFiber: Fiber | null,
    
  799.   reactName: string,
    
  800. ): Array<DispatchListener> {
    
  801.   const captureName = reactName + 'Capture';
    
  802.   const listeners: Array<DispatchListener> = [];
    
  803.   let instance = targetFiber;
    
  804. 
    
  805.   // Accumulate all instances and listeners via the target -> root path.
    
  806.   while (instance !== null) {
    
  807.     const {stateNode, tag} = instance;
    
  808.     // Handle listeners that are on HostComponents (i.e. <div>)
    
  809.     if (
    
  810.       (tag === HostComponent ||
    
  811.         (enableFloat ? tag === HostHoistable : false) ||
    
  812.         (enableHostSingletons ? tag === HostSingleton : false)) &&
    
  813.       stateNode !== null
    
  814.     ) {
    
  815.       const currentTarget = stateNode;
    
  816.       const captureListener = getListener(instance, captureName);
    
  817.       if (captureListener != null) {
    
  818.         listeners.unshift(
    
  819.           createDispatchListener(instance, captureListener, currentTarget),
    
  820.         );
    
  821.       }
    
  822.       const bubbleListener = getListener(instance, reactName);
    
  823.       if (bubbleListener != null) {
    
  824.         listeners.push(
    
  825.           createDispatchListener(instance, bubbleListener, currentTarget),
    
  826.         );
    
  827.       }
    
  828.     }
    
  829.     instance = instance.return;
    
  830.   }
    
  831.   return listeners;
    
  832. }
    
  833. 
    
  834. function getParent(inst: Fiber | null): Fiber | null {
    
  835.   if (inst === null) {
    
  836.     return null;
    
  837.   }
    
  838.   do {
    
  839.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  840.     inst = inst.return;
    
  841.     // TODO: If this is a HostRoot we might want to bail out.
    
  842.     // That is depending on if we want nested subtrees (layers) to bubble
    
  843.     // events to their parent. We could also go through parentNode on the
    
  844.     // host node but that wouldn't work for React Native and doesn't let us
    
  845.     // do the portal feature.
    
  846.   } while (
    
  847.     inst &&
    
  848.     inst.tag !== HostComponent &&
    
  849.     (!enableHostSingletons ? true : inst.tag !== HostSingleton)
    
  850.   );
    
  851.   if (inst) {
    
  852.     return inst;
    
  853.   }
    
  854.   return null;
    
  855. }
    
  856. 
    
  857. /**
    
  858.  * Return the lowest common ancestor of A and B, or null if they are in
    
  859.  * different trees.
    
  860.  */
    
  861. function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
    
  862.   let nodeA: null | Fiber = instA;
    
  863.   let nodeB: null | Fiber = instB;
    
  864.   let depthA = 0;
    
  865.   for (let tempA: null | Fiber = nodeA; tempA; tempA = getParent(tempA)) {
    
  866.     depthA++;
    
  867.   }
    
  868.   let depthB = 0;
    
  869.   for (let tempB: null | Fiber = nodeB; tempB; tempB = getParent(tempB)) {
    
  870.     depthB++;
    
  871.   }
    
  872. 
    
  873.   // If A is deeper, crawl up.
    
  874.   while (depthA - depthB > 0) {
    
  875.     nodeA = getParent(nodeA);
    
  876.     depthA--;
    
  877.   }
    
  878. 
    
  879.   // If B is deeper, crawl up.
    
  880.   while (depthB - depthA > 0) {
    
  881.     nodeB = getParent(nodeB);
    
  882.     depthB--;
    
  883.   }
    
  884. 
    
  885.   // Walk in lockstep until we find a match.
    
  886.   let depth = depthA;
    
  887.   while (depth--) {
    
  888.     if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
    
  889.       return nodeA;
    
  890.     }
    
  891.     nodeA = getParent(nodeA);
    
  892.     nodeB = getParent(nodeB);
    
  893.   }
    
  894.   return null;
    
  895. }
    
  896. 
    
  897. function accumulateEnterLeaveListenersForEvent(
    
  898.   dispatchQueue: DispatchQueue,
    
  899.   event: KnownReactSyntheticEvent,
    
  900.   target: Fiber,
    
  901.   common: Fiber | null,
    
  902.   inCapturePhase: boolean,
    
  903. ): void {
    
  904.   const registrationName = event._reactName;
    
  905.   const listeners: Array<DispatchListener> = [];
    
  906. 
    
  907.   let instance: null | Fiber = target;
    
  908.   while (instance !== null) {
    
  909.     if (instance === common) {
    
  910.       break;
    
  911.     }
    
  912.     const {alternate, stateNode, tag} = instance;
    
  913.     if (alternate !== null && alternate === common) {
    
  914.       break;
    
  915.     }
    
  916.     if (
    
  917.       (tag === HostComponent ||
    
  918.         (enableFloat ? tag === HostHoistable : false) ||
    
  919.         (enableHostSingletons ? tag === HostSingleton : false)) &&
    
  920.       stateNode !== null
    
  921.     ) {
    
  922.       const currentTarget = stateNode;
    
  923.       if (inCapturePhase) {
    
  924.         const captureListener = getListener(instance, registrationName);
    
  925.         if (captureListener != null) {
    
  926.           listeners.unshift(
    
  927.             createDispatchListener(instance, captureListener, currentTarget),
    
  928.           );
    
  929.         }
    
  930.       } else if (!inCapturePhase) {
    
  931.         const bubbleListener = getListener(instance, registrationName);
    
  932.         if (bubbleListener != null) {
    
  933.           listeners.push(
    
  934.             createDispatchListener(instance, bubbleListener, currentTarget),
    
  935.           );
    
  936.         }
    
  937.       }
    
  938.     }
    
  939.     instance = instance.return;
    
  940.   }
    
  941.   if (listeners.length !== 0) {
    
  942.     dispatchQueue.push({event, listeners});
    
  943.   }
    
  944. }
    
  945. 
    
  946. // We should only use this function for:
    
  947. // - EnterLeaveEventPlugin
    
  948. // This is because we only process this plugin
    
  949. // in the bubble phase, so we need to accumulate two
    
  950. // phase event listeners.
    
  951. export function accumulateEnterLeaveTwoPhaseListeners(
    
  952.   dispatchQueue: DispatchQueue,
    
  953.   leaveEvent: KnownReactSyntheticEvent,
    
  954.   enterEvent: null | KnownReactSyntheticEvent,
    
  955.   from: Fiber | null,
    
  956.   to: Fiber | null,
    
  957. ): void {
    
  958.   const common = from && to ? getLowestCommonAncestor(from, to) : null;
    
  959. 
    
  960.   if (from !== null) {
    
  961.     accumulateEnterLeaveListenersForEvent(
    
  962.       dispatchQueue,
    
  963.       leaveEvent,
    
  964.       from,
    
  965.       common,
    
  966.       false,
    
  967.     );
    
  968.   }
    
  969.   if (to !== null && enterEvent !== null) {
    
  970.     accumulateEnterLeaveListenersForEvent(
    
  971.       dispatchQueue,
    
  972.       enterEvent,
    
  973.       to,
    
  974.       common,
    
  975.       true,
    
  976.     );
    
  977.   }
    
  978. }
    
  979. 
    
  980. export function accumulateEventHandleNonManagedNodeListeners(
    
  981.   reactEventType: DOMEventName,
    
  982.   currentTarget: EventTarget,
    
  983.   inCapturePhase: boolean,
    
  984. ): Array<DispatchListener> {
    
  985.   const listeners: Array<DispatchListener> = [];
    
  986. 
    
  987.   const eventListeners = getEventHandlerListeners(currentTarget);
    
  988.   if (eventListeners !== null) {
    
  989.     eventListeners.forEach(entry => {
    
  990.       if (entry.type === reactEventType && entry.capture === inCapturePhase) {
    
  991.         listeners.push(
    
  992.           createDispatchListener(null, entry.callback, currentTarget),
    
  993.         );
    
  994.       }
    
  995.     });
    
  996.   }
    
  997.   return listeners;
    
  998. }
    
  999. 
    
  1000. export function getListenerSetKey(
    
  1001.   domEventName: DOMEventName,
    
  1002.   capture: boolean,
    
  1003. ): string {
    
  1004.   return `${domEventName}__${capture ? 'capture' : 'bubble'}`;
    
  1005. }