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 {
    
  11.   AnyNativeEvent,
    
  12.   EventTypes,
    
  13. } from './legacy-events/PluginModuleType';
    
  14. import type {TopLevelType} from './legacy-events/TopLevelEventTypes';
    
  15. import SyntheticEvent from './legacy-events/SyntheticEvent';
    
  16. 
    
  17. // Module provided by RN:
    
  18. import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
    
  19. import accumulateInto from './legacy-events/accumulateInto';
    
  20. import getListener from './ReactNativeGetListener';
    
  21. import forEachAccumulated from './legacy-events/forEachAccumulated';
    
  22. import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
    
  23. 
    
  24. const {customBubblingEventTypes, customDirectEventTypes} =
    
  25.   ReactNativeViewConfigRegistry;
    
  26. 
    
  27. // Start of inline: the below functions were inlined from
    
  28. // EventPropagator.js, as they deviated from ReactDOM's newer
    
  29. // implementations.
    
  30. // $FlowFixMe[missing-local-annot]
    
  31. function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
    
  32.   const registrationName =
    
  33.     event.dispatchConfig.phasedRegistrationNames[propagationPhase];
    
  34.   return getListener(inst, registrationName);
    
  35. }
    
  36. 
    
  37. // $FlowFixMe[missing-local-annot]
    
  38. function accumulateDirectionalDispatches(inst, phase, event) {
    
  39.   if (__DEV__) {
    
  40.     if (!inst) {
    
  41.       console.error('Dispatching inst must not be null');
    
  42.     }
    
  43.   }
    
  44.   const listener = listenerAtPhase(inst, event, phase);
    
  45.   if (listener) {
    
  46.     event._dispatchListeners = accumulateInto(
    
  47.       event._dispatchListeners,
    
  48.       listener,
    
  49.     );
    
  50.     event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
    
  51.   }
    
  52. }
    
  53. 
    
  54. // $FlowFixMe[missing-local-annot]
    
  55. function getParent(inst) {
    
  56.   do {
    
  57.     inst = inst.return;
    
  58.     // TODO: If this is a HostRoot we might want to bail out.
    
  59.     // That is depending on if we want nested subtrees (layers) to bubble
    
  60.     // events to their parent. We could also go through parentNode on the
    
  61.     // host node but that wouldn't work for React Native and doesn't let us
    
  62.     // do the portal feature.
    
  63.   } while (inst && inst.tag !== HostComponent);
    
  64.   if (inst) {
    
  65.     return inst;
    
  66.   }
    
  67.   return null;
    
  68. }
    
  69. 
    
  70. /**
    
  71.  * Simulates the traversal of a two-phase, capture/bubble event dispatch.
    
  72.  */
    
  73. export function traverseTwoPhase(
    
  74.   inst: Object,
    
  75.   fn: Function,
    
  76.   arg: Function,
    
  77.   skipBubbling: boolean,
    
  78. ) {
    
  79.   const path = [];
    
  80.   while (inst) {
    
  81.     path.push(inst);
    
  82.     inst = getParent(inst);
    
  83.   }
    
  84.   let i;
    
  85.   for (i = path.length; i-- > 0; ) {
    
  86.     fn(path[i], 'captured', arg);
    
  87.   }
    
  88.   if (skipBubbling) {
    
  89.     // Dispatch on target only
    
  90.     fn(path[0], 'bubbled', arg);
    
  91.   } else {
    
  92.     for (i = 0; i < path.length; i++) {
    
  93.       fn(path[i], 'bubbled', arg);
    
  94.     }
    
  95.   }
    
  96. }
    
  97. 
    
  98. // $FlowFixMe[missing-local-annot]
    
  99. function accumulateTwoPhaseDispatchesSingle(event) {
    
  100.   if (event && event.dispatchConfig.phasedRegistrationNames) {
    
  101.     traverseTwoPhase(
    
  102.       event._targetInst,
    
  103.       accumulateDirectionalDispatches,
    
  104.       event,
    
  105.       false,
    
  106.     );
    
  107.   }
    
  108. }
    
  109. 
    
  110. // $FlowFixMe[missing-local-annot]
    
  111. function accumulateTwoPhaseDispatches(events) {
    
  112.   forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
    
  113. }
    
  114. 
    
  115. // $FlowFixMe[missing-local-annot]
    
  116. function accumulateCapturePhaseDispatches(event) {
    
  117.   if (event && event.dispatchConfig.phasedRegistrationNames) {
    
  118.     traverseTwoPhase(
    
  119.       event._targetInst,
    
  120.       accumulateDirectionalDispatches,
    
  121.       event,
    
  122.       true,
    
  123.     );
    
  124.   }
    
  125. }
    
  126. 
    
  127. /**
    
  128.  * Accumulates without regard to direction, does not look for phased
    
  129.  * registration names. Same as `accumulateDirectDispatchesSingle` but without
    
  130.  * requiring that the `dispatchMarker` be the same as the dispatched ID.
    
  131.  */
    
  132. function accumulateDispatches(
    
  133.   inst: Object,
    
  134.   ignoredDirection: ?boolean,
    
  135.   event: Object,
    
  136. ): void {
    
  137.   if (inst && event && event.dispatchConfig.registrationName) {
    
  138.     const registrationName = event.dispatchConfig.registrationName;
    
  139.     const listener = getListener(inst, registrationName);
    
  140.     if (listener) {
    
  141.       event._dispatchListeners = accumulateInto(
    
  142.         event._dispatchListeners,
    
  143.         listener,
    
  144.       );
    
  145.       event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
    
  146.     }
    
  147.   }
    
  148. }
    
  149. 
    
  150. /**
    
  151.  * Accumulates dispatches on an `SyntheticEvent`, but only for the
    
  152.  * `dispatchMarker`.
    
  153.  * @param {SyntheticEvent} event
    
  154.  */
    
  155. function accumulateDirectDispatchesSingle(event: Object) {
    
  156.   if (event && event.dispatchConfig.registrationName) {
    
  157.     accumulateDispatches(event._targetInst, null, event);
    
  158.   }
    
  159. }
    
  160. 
    
  161. function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
    
  162.   forEachAccumulated(events, accumulateDirectDispatchesSingle);
    
  163. }
    
  164. 
    
  165. // End of inline
    
  166. type PropagationPhases = 'bubbled' | 'captured';
    
  167. 
    
  168. const ReactNativeBridgeEventPlugin = {
    
  169.   eventTypes: ({}: EventTypes),
    
  170. 
    
  171.   extractEvents: function (
    
  172.     topLevelType: TopLevelType,
    
  173.     targetInst: null | Object,
    
  174.     nativeEvent: AnyNativeEvent,
    
  175.     nativeEventTarget: null | Object,
    
  176.   ): ?Object {
    
  177.     if (targetInst == null) {
    
  178.       // Probably a node belonging to another renderer's tree.
    
  179.       return null;
    
  180.     }
    
  181.     const bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
    
  182.     const directDispatchConfig = customDirectEventTypes[topLevelType];
    
  183. 
    
  184.     if (!bubbleDispatchConfig && !directDispatchConfig) {
    
  185.       throw new Error(
    
  186.         // $FlowFixMe[incompatible-type] - Flow doesn't like this string coercion because DOMTopLevelEventType is opaque
    
  187.         `Unsupported top level event type "${topLevelType}" dispatched`,
    
  188.       );
    
  189.     }
    
  190. 
    
  191.     const event = SyntheticEvent.getPooled(
    
  192.       bubbleDispatchConfig || directDispatchConfig,
    
  193.       targetInst,
    
  194.       nativeEvent,
    
  195.       nativeEventTarget,
    
  196.     );
    
  197.     if (bubbleDispatchConfig) {
    
  198.       const skipBubbling =
    
  199.         event != null &&
    
  200.         event.dispatchConfig.phasedRegistrationNames != null &&
    
  201.         event.dispatchConfig.phasedRegistrationNames.skipBubbling;
    
  202.       if (skipBubbling) {
    
  203.         accumulateCapturePhaseDispatches(event);
    
  204.       } else {
    
  205.         accumulateTwoPhaseDispatches(event);
    
  206.       }
    
  207.     } else if (directDispatchConfig) {
    
  208.       accumulateDirectDispatches(event);
    
  209.     } else {
    
  210.       return null;
    
  211.     }
    
  212.     return event;
    
  213.   },
    
  214. };
    
  215. 
    
  216. export default ReactNativeBridgeEventPlugin;