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.   LegacyPluginModule,
    
  13. } from './legacy-events/PluginModuleType';
    
  14. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  15. import type {ReactSyntheticEvent} from './legacy-events/ReactSyntheticEventType';
    
  16. import type {TopLevelType} from './legacy-events/TopLevelEventTypes';
    
  17. 
    
  18. import {
    
  19.   registrationNameModules,
    
  20.   plugins,
    
  21. } from './legacy-events/EventPluginRegistry';
    
  22. import {batchedUpdates} from './legacy-events/ReactGenericBatching';
    
  23. import {runEventsInBatch} from './legacy-events/EventBatching';
    
  24. import getListener from './ReactNativeGetListener';
    
  25. import accumulateInto from './legacy-events/accumulateInto';
    
  26. 
    
  27. import {getInstanceFromNode} from './ReactNativeComponentTree';
    
  28. 
    
  29. export {getListener, registrationNameModules as registrationNames};
    
  30. 
    
  31. /**
    
  32.  * Version of `ReactBrowserEventEmitter` that works on the receiving side of a
    
  33.  * serialized worker boundary.
    
  34.  */
    
  35. 
    
  36. // Shared default empty native event - conserve memory.
    
  37. const EMPTY_NATIVE_EVENT = (({}: any): AnyNativeEvent);
    
  38. 
    
  39. /**
    
  40.  * Selects a subsequence of `Touch`es, without destroying `touches`.
    
  41.  *
    
  42.  * @param {Array<Touch>} touches Deserialized touch objects.
    
  43.  * @param {Array<number>} indices Indices by which to pull subsequence.
    
  44.  * @return {Array<Touch>} Subsequence of touch objects.
    
  45.  */
    
  46. // $FlowFixMe[missing-local-annot]
    
  47. function touchSubsequence(touches, indices) {
    
  48.   const ret = [];
    
  49.   for (let i = 0; i < indices.length; i++) {
    
  50.     ret.push(touches[indices[i]]);
    
  51.   }
    
  52.   return ret;
    
  53. }
    
  54. 
    
  55. /**
    
  56.  * TODO: Pool all of this.
    
  57.  *
    
  58.  * Destroys `touches` by removing touch objects at indices `indices`. This is
    
  59.  * to maintain compatibility with W3C touch "end" events, where the active
    
  60.  * touches don't include the set that has just been "ended".
    
  61.  *
    
  62.  * @param {Array<Touch>} touches Deserialized touch objects.
    
  63.  * @param {Array<number>} indices Indices to remove from `touches`.
    
  64.  * @return {Array<Touch>} Subsequence of removed touch objects.
    
  65.  */
    
  66. function removeTouchesAtIndices(
    
  67.   touches: Array<Object>,
    
  68.   indices: Array<number>,
    
  69. ): Array<Object> {
    
  70.   const rippedOut = [];
    
  71.   // use an unsafe downcast to alias to nullable elements,
    
  72.   // so we can delete and then compact.
    
  73.   const temp: Array<?Object> = (touches: Array<any>);
    
  74.   for (let i = 0; i < indices.length; i++) {
    
  75.     const index = indices[i];
    
  76.     rippedOut.push(touches[index]);
    
  77.     temp[index] = null;
    
  78.   }
    
  79.   let fillAt = 0;
    
  80.   for (let j = 0; j < temp.length; j++) {
    
  81.     const cur = temp[j];
    
  82.     if (cur !== null) {
    
  83.       temp[fillAt++] = cur;
    
  84.     }
    
  85.   }
    
  86.   temp.length = fillAt;
    
  87.   return rippedOut;
    
  88. }
    
  89. 
    
  90. /**
    
  91.  * Internal version of `receiveEvent` in terms of normalized (non-tag)
    
  92.  * `rootNodeID`.
    
  93.  *
    
  94.  * @see receiveEvent.
    
  95.  *
    
  96.  * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
    
  97.  * @param {TopLevelType} topLevelType Top level type of event.
    
  98.  * @param {?object} nativeEventParam Object passed from native.
    
  99.  */
    
  100. function _receiveRootNodeIDEvent(
    
  101.   rootNodeID: number,
    
  102.   topLevelType: TopLevelType,
    
  103.   nativeEventParam: ?AnyNativeEvent,
    
  104. ) {
    
  105.   const nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
    
  106.   const inst = getInstanceFromNode(rootNodeID);
    
  107. 
    
  108.   let target = null;
    
  109.   if (inst != null) {
    
  110.     target = inst.stateNode;
    
  111.   }
    
  112. 
    
  113.   batchedUpdates(function () {
    
  114.     runExtractedPluginEventsInBatch(topLevelType, inst, nativeEvent, target);
    
  115.   });
    
  116.   // React Native doesn't use ReactControlledComponent but if it did, here's
    
  117.   // where it would do it.
    
  118. }
    
  119. 
    
  120. /**
    
  121.  * Allows registered plugins an opportunity to extract events from top-level
    
  122.  * native browser events.
    
  123.  *
    
  124.  * @return {*} An accumulation of synthetic events.
    
  125.  * @internal
    
  126.  */
    
  127. function extractPluginEvents(
    
  128.   topLevelType: TopLevelType,
    
  129.   targetInst: null | Fiber,
    
  130.   nativeEvent: AnyNativeEvent,
    
  131.   nativeEventTarget: null | EventTarget,
    
  132. ): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
    
  133.   let events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null = null;
    
  134.   const legacyPlugins = ((plugins: any): Array<LegacyPluginModule<Event>>);
    
  135.   for (let i = 0; i < legacyPlugins.length; i++) {
    
  136.     // Not every plugin in the ordering may be loaded at runtime.
    
  137.     const possiblePlugin: LegacyPluginModule<AnyNativeEvent> = legacyPlugins[i];
    
  138.     if (possiblePlugin) {
    
  139.       const extractedEvents = possiblePlugin.extractEvents(
    
  140.         topLevelType,
    
  141.         targetInst,
    
  142.         nativeEvent,
    
  143.         nativeEventTarget,
    
  144.       );
    
  145.       if (extractedEvents) {
    
  146.         events = accumulateInto(events, extractedEvents);
    
  147.       }
    
  148.     }
    
  149.   }
    
  150.   return events;
    
  151. }
    
  152. 
    
  153. function runExtractedPluginEventsInBatch(
    
  154.   topLevelType: TopLevelType,
    
  155.   targetInst: null | Fiber,
    
  156.   nativeEvent: AnyNativeEvent,
    
  157.   nativeEventTarget: null | EventTarget,
    
  158. ) {
    
  159.   const events = extractPluginEvents(
    
  160.     topLevelType,
    
  161.     targetInst,
    
  162.     nativeEvent,
    
  163.     nativeEventTarget,
    
  164.   );
    
  165.   runEventsInBatch(events);
    
  166. }
    
  167. 
    
  168. /**
    
  169.  * Publicly exposed method on module for native objc to invoke when a top
    
  170.  * level event is extracted.
    
  171.  * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
    
  172.  * @param {TopLevelType} topLevelType Top level type of event.
    
  173.  * @param {object} nativeEventParam Object passed from native.
    
  174.  */
    
  175. export function receiveEvent(
    
  176.   rootNodeID: number,
    
  177.   topLevelType: TopLevelType,
    
  178.   nativeEventParam: AnyNativeEvent,
    
  179. ) {
    
  180.   _receiveRootNodeIDEvent(rootNodeID, topLevelType, nativeEventParam);
    
  181. }
    
  182. 
    
  183. /**
    
  184.  * Simple multi-wrapper around `receiveEvent` that is intended to receive an
    
  185.  * efficient representation of `Touch` objects, and other information that
    
  186.  * can be used to construct W3C compliant `Event` and `Touch` lists.
    
  187.  *
    
  188.  * This may create dispatch behavior that differs than web touch handling. We
    
  189.  * loop through each of the changed touches and receive it as a single event.
    
  190.  * So two `touchStart`/`touchMove`s that occur simultaneously are received as
    
  191.  * two separate touch event dispatches - when they arguably should be one.
    
  192.  *
    
  193.  * This implementation reuses the `Touch` objects themselves as the `Event`s
    
  194.  * since we dispatch an event for each touch (though that might not be spec
    
  195.  * compliant). The main purpose of reusing them is to save allocations.
    
  196.  *
    
  197.  * TODO: Dispatch multiple changed touches in one event. The bubble path
    
  198.  * could be the first common ancestor of all the `changedTouches`.
    
  199.  *
    
  200.  * One difference between this behavior and W3C spec: cancelled touches will
    
  201.  * not appear in `.touches`, or in any future `.touches`, though they may
    
  202.  * still be "actively touching the surface".
    
  203.  *
    
  204.  * Web desktop polyfills only need to construct a fake touch event with
    
  205.  * identifier 0, also abandoning traditional click handlers.
    
  206.  */
    
  207. export function receiveTouches(
    
  208.   eventTopLevelType: TopLevelType,
    
  209.   touches: Array<Object>,
    
  210.   changedIndices: Array<number>,
    
  211. ) {
    
  212.   const changedTouches =
    
  213.     eventTopLevelType === 'topTouchEnd' ||
    
  214.     eventTopLevelType === 'topTouchCancel'
    
  215.       ? removeTouchesAtIndices(touches, changedIndices)
    
  216.       : touchSubsequence(touches, changedIndices);
    
  217. 
    
  218.   for (let jj = 0; jj < changedTouches.length; jj++) {
    
  219.     const touch = changedTouches[jj];
    
  220.     // Touch objects can fulfill the role of `DOM` `Event` objects if we set
    
  221.     // the `changedTouches`/`touches`. This saves allocations.
    
  222.     touch.changedTouches = changedTouches;
    
  223.     touch.touches = touches;
    
  224.     const nativeEvent = touch;
    
  225.     let rootNodeID = null;
    
  226.     const target = nativeEvent.target;
    
  227.     if (target !== null && target !== undefined) {
    
  228.       if (target < 1) {
    
  229.         if (__DEV__) {
    
  230.           console.error(
    
  231.             'A view is reporting that a touch occurred on tag zero.',
    
  232.           );
    
  233.         }
    
  234.       } else {
    
  235.         rootNodeID = target;
    
  236.       }
    
  237.     }
    
  238.     // $FlowFixMe[incompatible-call] Shouldn't we *not* call it if rootNodeID is null?
    
  239.     _receiveRootNodeIDEvent(rootNodeID, eventTopLevelType, nativeEvent);
    
  240.   }
    
  241. }