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.  * @noflow
    
  8.  */
    
  9. 
    
  10. import {
    
  11.   executeDirectDispatch,
    
  12.   hasDispatches,
    
  13.   executeDispatchesInOrderStopAtTrue,
    
  14.   getInstanceFromNode,
    
  15.   getFiberCurrentPropsFromNode,
    
  16. } from './EventPluginUtils';
    
  17. import ResponderSyntheticEvent from './ResponderSyntheticEvent';
    
  18. import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
    
  19. import accumulate from './accumulate';
    
  20. import {
    
  21.   TOP_SCROLL,
    
  22.   TOP_SELECTION_CHANGE,
    
  23.   TOP_TOUCH_CANCEL,
    
  24.   isStartish,
    
  25.   isMoveish,
    
  26.   isEndish,
    
  27.   startDependencies,
    
  28.   moveDependencies,
    
  29.   endDependencies,
    
  30. } from './ResponderTopLevelEventTypes';
    
  31. import accumulateInto from './accumulateInto';
    
  32. import forEachAccumulated from './forEachAccumulated';
    
  33. import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
    
  34. 
    
  35. /**
    
  36.  * Instance of element that should respond to touch/move types of interactions,
    
  37.  * as indicated explicitly by relevant callbacks.
    
  38.  */
    
  39. let responderInst = null;
    
  40. 
    
  41. /**
    
  42.  * Count of current touches. A textInput should become responder iff the
    
  43.  * selection changes while there is a touch on the screen.
    
  44.  */
    
  45. let trackedTouchCount = 0;
    
  46. 
    
  47. function changeResponder(nextResponderInst, blockHostResponder) {
    
  48.   const oldResponderInst = responderInst;
    
  49.   responderInst = nextResponderInst;
    
  50.   if (ResponderEventPlugin.GlobalResponderHandler !== null) {
    
  51.     ResponderEventPlugin.GlobalResponderHandler.onChange(
    
  52.       oldResponderInst,
    
  53.       nextResponderInst,
    
  54.       blockHostResponder,
    
  55.     );
    
  56.   }
    
  57. }
    
  58. 
    
  59. const eventTypes = {
    
  60.   /**
    
  61.    * On a `touchStart`/`mouseDown`, is it desired that this element become the
    
  62.    * responder?
    
  63.    */
    
  64.   startShouldSetResponder: {
    
  65.     phasedRegistrationNames: {
    
  66.       bubbled: 'onStartShouldSetResponder',
    
  67.       captured: 'onStartShouldSetResponderCapture',
    
  68.     },
    
  69.     dependencies: startDependencies,
    
  70.   },
    
  71. 
    
  72.   /**
    
  73.    * On a `scroll`, is it desired that this element become the responder? This
    
  74.    * is usually not needed, but should be used to retroactively infer that a
    
  75.    * `touchStart` had occurred during momentum scroll. During a momentum scroll,
    
  76.    * a touch start will be immediately followed by a scroll event if the view is
    
  77.    * currently scrolling.
    
  78.    *
    
  79.    * TODO: This shouldn't bubble.
    
  80.    */
    
  81.   scrollShouldSetResponder: {
    
  82.     phasedRegistrationNames: {
    
  83.       bubbled: 'onScrollShouldSetResponder',
    
  84.       captured: 'onScrollShouldSetResponderCapture',
    
  85.     },
    
  86.     dependencies: [TOP_SCROLL],
    
  87.   },
    
  88. 
    
  89.   /**
    
  90.    * On text selection change, should this element become the responder? This
    
  91.    * is needed for text inputs or other views with native selection, so the
    
  92.    * JS view can claim the responder.
    
  93.    *
    
  94.    * TODO: This shouldn't bubble.
    
  95.    */
    
  96.   selectionChangeShouldSetResponder: {
    
  97.     phasedRegistrationNames: {
    
  98.       bubbled: 'onSelectionChangeShouldSetResponder',
    
  99.       captured: 'onSelectionChangeShouldSetResponderCapture',
    
  100.     },
    
  101.     dependencies: [TOP_SELECTION_CHANGE],
    
  102.   },
    
  103. 
    
  104.   /**
    
  105.    * On a `touchMove`/`mouseMove`, is it desired that this element become the
    
  106.    * responder?
    
  107.    */
    
  108.   moveShouldSetResponder: {
    
  109.     phasedRegistrationNames: {
    
  110.       bubbled: 'onMoveShouldSetResponder',
    
  111.       captured: 'onMoveShouldSetResponderCapture',
    
  112.     },
    
  113.     dependencies: moveDependencies,
    
  114.   },
    
  115. 
    
  116.   /**
    
  117.    * Direct responder events dispatched directly to responder. Do not bubble.
    
  118.    */
    
  119.   responderStart: {
    
  120.     registrationName: 'onResponderStart',
    
  121.     dependencies: startDependencies,
    
  122.   },
    
  123.   responderMove: {
    
  124.     registrationName: 'onResponderMove',
    
  125.     dependencies: moveDependencies,
    
  126.   },
    
  127.   responderEnd: {
    
  128.     registrationName: 'onResponderEnd',
    
  129.     dependencies: endDependencies,
    
  130.   },
    
  131.   responderRelease: {
    
  132.     registrationName: 'onResponderRelease',
    
  133.     dependencies: endDependencies,
    
  134.   },
    
  135.   responderTerminationRequest: {
    
  136.     registrationName: 'onResponderTerminationRequest',
    
  137.     dependencies: [],
    
  138.   },
    
  139.   responderGrant: {
    
  140.     registrationName: 'onResponderGrant',
    
  141.     dependencies: [],
    
  142.   },
    
  143.   responderReject: {
    
  144.     registrationName: 'onResponderReject',
    
  145.     dependencies: [],
    
  146.   },
    
  147.   responderTerminate: {
    
  148.     registrationName: 'onResponderTerminate',
    
  149.     dependencies: [],
    
  150.   },
    
  151. };
    
  152. 
    
  153. // Start of inline: the below functions were inlined from
    
  154. // EventPropagator.js, as they deviated from ReactDOM's newer
    
  155. // implementations.
    
  156. 
    
  157. function getParent(inst) {
    
  158.   do {
    
  159.     inst = inst.return;
    
  160.     // TODO: If this is a HostRoot we might want to bail out.
    
  161.     // That is depending on if we want nested subtrees (layers) to bubble
    
  162.     // events to their parent. We could also go through parentNode on the
    
  163.     // host node but that wouldn't work for React Native and doesn't let us
    
  164.     // do the portal feature.
    
  165.   } while (inst && inst.tag !== HostComponent);
    
  166.   if (inst) {
    
  167.     return inst;
    
  168.   }
    
  169.   return null;
    
  170. }
    
  171. 
    
  172. /**
    
  173.  * Return the lowest common ancestor of A and B, or null if they are in
    
  174.  * different trees.
    
  175.  */
    
  176. export function getLowestCommonAncestor(instA, instB) {
    
  177.   let depthA = 0;
    
  178.   for (let tempA = instA; tempA; tempA = getParent(tempA)) {
    
  179.     depthA++;
    
  180.   }
    
  181.   let depthB = 0;
    
  182.   for (let tempB = instB; tempB; tempB = getParent(tempB)) {
    
  183.     depthB++;
    
  184.   }
    
  185. 
    
  186.   // If A is deeper, crawl up.
    
  187.   while (depthA - depthB > 0) {
    
  188.     instA = getParent(instA);
    
  189.     depthA--;
    
  190.   }
    
  191. 
    
  192.   // If B is deeper, crawl up.
    
  193.   while (depthB - depthA > 0) {
    
  194.     instB = getParent(instB);
    
  195.     depthB--;
    
  196.   }
    
  197. 
    
  198.   // Walk in lockstep until we find a match.
    
  199.   let depth = depthA;
    
  200.   while (depth--) {
    
  201.     if (instA === instB || instA === instB.alternate) {
    
  202.       return instA;
    
  203.     }
    
  204.     instA = getParent(instA);
    
  205.     instB = getParent(instB);
    
  206.   }
    
  207.   return null;
    
  208. }
    
  209. 
    
  210. /**
    
  211.  * Return if A is an ancestor of B.
    
  212.  */
    
  213. function isAncestor(instA, instB) {
    
  214.   while (instB) {
    
  215.     if (instA === instB || instA === instB.alternate) {
    
  216.       return true;
    
  217.     }
    
  218.     instB = getParent(instB);
    
  219.   }
    
  220.   return false;
    
  221. }
    
  222. 
    
  223. /**
    
  224.  * Simulates the traversal of a two-phase, capture/bubble event dispatch.
    
  225.  */
    
  226. function traverseTwoPhase(inst, fn, arg) {
    
  227.   const path = [];
    
  228.   while (inst) {
    
  229.     path.push(inst);
    
  230.     inst = getParent(inst);
    
  231.   }
    
  232.   let i;
    
  233.   for (i = path.length; i-- > 0; ) {
    
  234.     fn(path[i], 'captured', arg);
    
  235.   }
    
  236.   for (i = 0; i < path.length; i++) {
    
  237.     fn(path[i], 'bubbled', arg);
    
  238.   }
    
  239. }
    
  240. 
    
  241. function getListener(inst, registrationName) {
    
  242.   const stateNode = inst.stateNode;
    
  243.   if (stateNode === null) {
    
  244.     // Work in progress (ex: onload events in incremental mode).
    
  245.     return null;
    
  246.   }
    
  247.   const props = getFiberCurrentPropsFromNode(stateNode);
    
  248.   if (props === null) {
    
  249.     // Work in progress.
    
  250.     return null;
    
  251.   }
    
  252.   const listener = props[registrationName];
    
  253. 
    
  254.   if (listener && typeof listener !== 'function') {
    
  255.     throw new Error(
    
  256.       `Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,
    
  257.     );
    
  258.   }
    
  259. 
    
  260.   return listener;
    
  261. }
    
  262. 
    
  263. function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
    
  264.   const registrationName =
    
  265.     event.dispatchConfig.phasedRegistrationNames[propagationPhase];
    
  266.   return getListener(inst, registrationName);
    
  267. }
    
  268. 
    
  269. function accumulateDirectionalDispatches(inst, phase, event) {
    
  270.   if (__DEV__) {
    
  271.     if (!inst) {
    
  272.       console.error('Dispatching inst must not be null');
    
  273.     }
    
  274.   }
    
  275.   const listener = listenerAtPhase(inst, event, phase);
    
  276.   if (listener) {
    
  277.     event._dispatchListeners = accumulateInto(
    
  278.       event._dispatchListeners,
    
  279.       listener,
    
  280.     );
    
  281.     event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
    
  282.   }
    
  283. }
    
  284. 
    
  285. /**
    
  286.  * Accumulates without regard to direction, does not look for phased
    
  287.  * registration names. Same as `accumulateDirectDispatchesSingle` but without
    
  288.  * requiring that the `dispatchMarker` be the same as the dispatched ID.
    
  289.  */
    
  290. function accumulateDispatches(
    
  291.   inst: Object,
    
  292.   ignoredDirection: ?boolean,
    
  293.   event: Object,
    
  294. ): void {
    
  295.   if (inst && event && event.dispatchConfig.registrationName) {
    
  296.     const registrationName = event.dispatchConfig.registrationName;
    
  297.     const listener = getListener(inst, registrationName);
    
  298.     if (listener) {
    
  299.       event._dispatchListeners = accumulateInto(
    
  300.         event._dispatchListeners,
    
  301.         listener,
    
  302.       );
    
  303.       event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
    
  304.     }
    
  305.   }
    
  306. }
    
  307. 
    
  308. /**
    
  309.  * Accumulates dispatches on an `SyntheticEvent`, but only for the
    
  310.  * `dispatchMarker`.
    
  311.  * @param {SyntheticEvent} event
    
  312.  */
    
  313. function accumulateDirectDispatchesSingle(event: Object) {
    
  314.   if (event && event.dispatchConfig.registrationName) {
    
  315.     accumulateDispatches(event._targetInst, null, event);
    
  316.   }
    
  317. }
    
  318. 
    
  319. function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
    
  320.   forEachAccumulated(events, accumulateDirectDispatchesSingle);
    
  321. }
    
  322. 
    
  323. function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
    
  324.   if (event && event.dispatchConfig.phasedRegistrationNames) {
    
  325.     const targetInst = event._targetInst;
    
  326.     const parentInst = targetInst ? getParent(targetInst) : null;
    
  327.     traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
    
  328.   }
    
  329. }
    
  330. 
    
  331. function accumulateTwoPhaseDispatchesSkipTarget(events) {
    
  332.   forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
    
  333. }
    
  334. 
    
  335. function accumulateTwoPhaseDispatchesSingle(event) {
    
  336.   if (event && event.dispatchConfig.phasedRegistrationNames) {
    
  337.     traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
    
  338.   }
    
  339. }
    
  340. 
    
  341. function accumulateTwoPhaseDispatches(events) {
    
  342.   forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
    
  343. }
    
  344. // End of inline
    
  345. 
    
  346. /**
    
  347.  *
    
  348.  * Responder System:
    
  349.  * ----------------
    
  350.  *
    
  351.  * - A global, solitary "interaction lock" on a view.
    
  352.  * - If a node becomes the responder, it should convey visual feedback
    
  353.  *   immediately to indicate so, either by highlighting or moving accordingly.
    
  354.  * - To be the responder means, that touches are exclusively important to that
    
  355.  *   responder view, and no other view.
    
  356.  * - While touches are still occurring, the responder lock can be transferred to
    
  357.  *   a new view, but only to increasingly "higher" views (meaning ancestors of
    
  358.  *   the current responder).
    
  359.  *
    
  360.  * Responder being granted:
    
  361.  * ------------------------
    
  362.  *
    
  363.  * - Touch starts, moves, and scrolls can cause an ID to become the responder.
    
  364.  * - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to
    
  365.  *   the "appropriate place".
    
  366.  * - If nothing is currently the responder, the "appropriate place" is the
    
  367.  *   initiating event's `targetID`.
    
  368.  * - If something *is* already the responder, the "appropriate place" is the
    
  369.  *   first common ancestor of the event target and the current `responderInst`.
    
  370.  * - Some negotiation happens: See the timing diagram below.
    
  371.  * - Scrolled views automatically become responder. The reasoning is that a
    
  372.  *   platform scroll view that isn't built on top of the responder system has
    
  373.  *   began scrolling, and the active responder must now be notified that the
    
  374.  *   interaction is no longer locked to it - the system has taken over.
    
  375.  *
    
  376.  * - Responder being released:
    
  377.  *   As soon as no more touches that *started* inside of descendants of the
    
  378.  *   *current* responderInst, an `onResponderRelease` event is dispatched to the
    
  379.  *   current responder, and the responder lock is released.
    
  380.  *
    
  381.  * TODO:
    
  382.  * - on "end", a callback hook for `onResponderEndShouldRemainResponder` that
    
  383.  *   determines if the responder lock should remain.
    
  384.  * - If a view shouldn't "remain" the responder, any active touches should by
    
  385.  *   default be considered "dead" and do not influence future negotiations or
    
  386.  *   bubble paths. It should be as if those touches do not exist.
    
  387.  * -- For multitouch: Usually a translate-z will choose to "remain" responder
    
  388.  *  after one out of many touches ended. For translate-y, usually the view
    
  389.  *  doesn't wish to "remain" responder after one of many touches end.
    
  390.  * - Consider building this on top of a `stopPropagation` model similar to
    
  391.  *   `W3C` events.
    
  392.  * - Ensure that `onResponderTerminate` is called on touch cancels, whether or
    
  393.  *   not `onResponderTerminationRequest` returns `true` or `false`.
    
  394.  *
    
  395.  */
    
  396. 
    
  397. /*                                             Negotiation Performed
    
  398.                                              +-----------------------+
    
  399.                                             /                         \
    
  400. Process low level events to    +     Current Responder      +   wantsResponderID
    
  401. determine who to perform negot-|   (if any exists at all)   |
    
  402. iation/transition              | Otherwise just pass through|
    
  403. -------------------------------+----------------------------+------------------+
    
  404. Bubble to find first ID        |                            |
    
  405. to return true:wantsResponderID|                            |
    
  406.                                |                            |
    
  407.      +-------------+           |                            |
    
  408.      | onTouchStart|           |                            |
    
  409.      +------+------+     none  |                            |
    
  410.             |            return|                            |
    
  411. +-----------v-------------+true| +------------------------+ |
    
  412. |onStartShouldSetResponder|----->|onResponderStart (cur)  |<-----------+
    
  413. +-----------+-------------+    | +------------------------+ |          |
    
  414.             |                  |                            | +--------+-------+
    
  415.             | returned true for|       false:REJECT +-------->|onResponderReject
    
  416.             | wantsResponderID |                    |       | +----------------+
    
  417.             | (now attempt     | +------------------+-----+ |
    
  418.             |  handoff)        | |   onResponder          | |
    
  419.             +------------------->|      TerminationRequest| |
    
  420.                                | +------------------+-----+ |
    
  421.                                |                    |       | +----------------+
    
  422.                                |         true:GRANT +-------->|onResponderGrant|
    
  423.                                |                            | +--------+-------+
    
  424.                                | +------------------------+ |          |
    
  425.                                | |   onResponderTerminate |<-----------+
    
  426.                                | +------------------+-----+ |
    
  427.                                |                    |       | +----------------+
    
  428.                                |                    +-------->|onResponderStart|
    
  429.                                |                            | +----------------+
    
  430. Bubble to find first ID        |                            |
    
  431. to return true:wantsResponderID|                            |
    
  432.                                |                            |
    
  433.      +-------------+           |                            |
    
  434.      | onTouchMove |           |                            |
    
  435.      +------+------+     none  |                            |
    
  436.             |            return|                            |
    
  437. +-----------v-------------+true| +------------------------+ |
    
  438. |onMoveShouldSetResponder |----->|onResponderMove (cur)   |<-----------+
    
  439. +-----------+-------------+    | +------------------------+ |          |
    
  440.             |                  |                            | +--------+-------+
    
  441.             | returned true for|       false:REJECT +-------->|onResponderRejec|
    
  442.             | wantsResponderID |                    |       | +----------------+
    
  443.             | (now attempt     | +------------------+-----+ |
    
  444.             |  handoff)        | |   onResponder          | |
    
  445.             +------------------->|      TerminationRequest| |
    
  446.                                | +------------------+-----+ |
    
  447.                                |                    |       | +----------------+
    
  448.                                |         true:GRANT +-------->|onResponderGrant|
    
  449.                                |                            | +--------+-------+
    
  450.                                | +------------------------+ |          |
    
  451.                                | |   onResponderTerminate |<-----------+
    
  452.                                | +------------------+-----+ |
    
  453.                                |                    |       | +----------------+
    
  454.                                |                    +-------->|onResponderMove |
    
  455.                                |                            | +----------------+
    
  456.                                |                            |
    
  457.                                |                            |
    
  458.       Some active touch started|                            |
    
  459.       inside current responder | +------------------------+ |
    
  460.       +------------------------->|      onResponderEnd    | |
    
  461.       |                        | +------------------------+ |
    
  462.   +---+---------+              |                            |
    
  463.   | onTouchEnd  |              |                            |
    
  464.   +---+---------+              |                            |
    
  465.       |                        | +------------------------+ |
    
  466.       +------------------------->|     onResponderEnd     | |
    
  467.       No active touches started| +-----------+------------+ |
    
  468.       inside current responder |             |              |
    
  469.                                |             v              |
    
  470.                                | +------------------------+ |
    
  471.                                | |    onResponderRelease  | |
    
  472.                                | +------------------------+ |
    
  473.                                |                            |
    
  474.                                +                            + */
    
  475. 
    
  476. /**
    
  477.  * A note about event ordering in the `EventPluginRegistry`.
    
  478.  *
    
  479.  * Suppose plugins are injected in the following order:
    
  480.  *
    
  481.  * `[R, S, C]`
    
  482.  *
    
  483.  * To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
    
  484.  * `onClick` etc) and `R` is `ResponderEventPlugin`.
    
  485.  *
    
  486.  * "Deferred-Dispatched Events":
    
  487.  *
    
  488.  * - The current event plugin system will traverse the list of injected plugins,
    
  489.  *   in order, and extract events by collecting the plugin's return value of
    
  490.  *   `extractEvents()`.
    
  491.  * - These events that are returned from `extractEvents` are "deferred
    
  492.  *   dispatched events".
    
  493.  * - When returned from `extractEvents`, deferred-dispatched events contain an
    
  494.  *   "accumulation" of deferred dispatches.
    
  495.  * - These deferred dispatches are accumulated/collected before they are
    
  496.  *   returned, but processed at a later time by the `EventPluginRegistry` (hence the
    
  497.  *   name deferred).
    
  498.  *
    
  499.  * In the process of returning their deferred-dispatched events, event plugins
    
  500.  * themselves can dispatch events on-demand without returning them from
    
  501.  * `extractEvents`. Plugins might want to do this, so that they can use event
    
  502.  * dispatching as a tool that helps them decide which events should be extracted
    
  503.  * in the first place.
    
  504.  *
    
  505.  * "On-Demand-Dispatched Events":
    
  506.  *
    
  507.  * - On-demand-dispatched events are not returned from `extractEvents`.
    
  508.  * - On-demand-dispatched events are dispatched during the process of returning
    
  509.  *   the deferred-dispatched events.
    
  510.  * - They should not have side effects.
    
  511.  * - They should be avoided, and/or eventually be replaced with another
    
  512.  *   abstraction that allows event plugins to perform multiple "rounds" of event
    
  513.  *   extraction.
    
  514.  *
    
  515.  * Therefore, the sequence of event dispatches becomes:
    
  516.  *
    
  517.  * - `R`s on-demand events (if any)   (dispatched by `R` on-demand)
    
  518.  * - `S`s on-demand events (if any)   (dispatched by `S` on-demand)
    
  519.  * - `C`s on-demand events (if any)   (dispatched by `C` on-demand)
    
  520.  * - `R`s extracted events (if any)   (dispatched by `EventPluginRegistry`)
    
  521.  * - `S`s extracted events (if any)   (dispatched by `EventPluginRegistry`)
    
  522.  * - `C`s extracted events (if any)   (dispatched by `EventPluginRegistry`)
    
  523.  *
    
  524.  * In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
    
  525.  * on-demand dispatch returns `true` (and some other details are satisfied) the
    
  526.  * `onResponderGrant` deferred dispatched event is returned from
    
  527.  * `extractEvents`. The sequence of dispatch executions in this case
    
  528.  * will appear as follows:
    
  529.  *
    
  530.  * - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
    
  531.  * - `touchStartCapture`       (`EventPluginRegistry` dispatches as usual)
    
  532.  * - `touchStart`              (`EventPluginRegistry` dispatches as usual)
    
  533.  * - `responderGrant/Reject`   (`EventPluginRegistry` dispatches as usual)
    
  534.  */
    
  535. 
    
  536. function setResponderAndExtractTransfer(
    
  537.   topLevelType,
    
  538.   targetInst,
    
  539.   nativeEvent,
    
  540.   nativeEventTarget,
    
  541. ) {
    
  542.   const shouldSetEventType = isStartish(topLevelType)
    
  543.     ? eventTypes.startShouldSetResponder
    
  544.     : isMoveish(topLevelType)
    
  545.     ? eventTypes.moveShouldSetResponder
    
  546.     : topLevelType === TOP_SELECTION_CHANGE
    
  547.     ? eventTypes.selectionChangeShouldSetResponder
    
  548.     : eventTypes.scrollShouldSetResponder;
    
  549. 
    
  550.   // TODO: stop one short of the current responder.
    
  551.   const bubbleShouldSetFrom = !responderInst
    
  552.     ? targetInst
    
  553.     : getLowestCommonAncestor(responderInst, targetInst);
    
  554. 
    
  555.   // When capturing/bubbling the "shouldSet" event, we want to skip the target
    
  556.   // (deepest ID) if it happens to be the current responder. The reasoning:
    
  557.   // It's strange to get an `onMoveShouldSetResponder` when you're *already*
    
  558.   // the responder.
    
  559.   const skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst;
    
  560.   const shouldSetEvent = ResponderSyntheticEvent.getPooled(
    
  561.     shouldSetEventType,
    
  562.     bubbleShouldSetFrom,
    
  563.     nativeEvent,
    
  564.     nativeEventTarget,
    
  565.   );
    
  566.   shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
    
  567.   if (skipOverBubbleShouldSetFrom) {
    
  568.     accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent);
    
  569.   } else {
    
  570.     accumulateTwoPhaseDispatches(shouldSetEvent);
    
  571.   }
    
  572.   const wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
    
  573.   if (!shouldSetEvent.isPersistent()) {
    
  574.     shouldSetEvent.constructor.release(shouldSetEvent);
    
  575.   }
    
  576. 
    
  577.   if (!wantsResponderInst || wantsResponderInst === responderInst) {
    
  578.     return null;
    
  579.   }
    
  580.   let extracted;
    
  581.   const grantEvent = ResponderSyntheticEvent.getPooled(
    
  582.     eventTypes.responderGrant,
    
  583.     wantsResponderInst,
    
  584.     nativeEvent,
    
  585.     nativeEventTarget,
    
  586.   );
    
  587.   grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
    
  588. 
    
  589.   accumulateDirectDispatches(grantEvent);
    
  590.   const blockHostResponder = executeDirectDispatch(grantEvent) === true;
    
  591.   if (responderInst) {
    
  592.     const terminationRequestEvent = ResponderSyntheticEvent.getPooled(
    
  593.       eventTypes.responderTerminationRequest,
    
  594.       responderInst,
    
  595.       nativeEvent,
    
  596.       nativeEventTarget,
    
  597.     );
    
  598.     terminationRequestEvent.touchHistory =
    
  599.       ResponderTouchHistoryStore.touchHistory;
    
  600.     accumulateDirectDispatches(terminationRequestEvent);
    
  601.     const shouldSwitch =
    
  602.       !hasDispatches(terminationRequestEvent) ||
    
  603.       executeDirectDispatch(terminationRequestEvent);
    
  604.     if (!terminationRequestEvent.isPersistent()) {
    
  605.       terminationRequestEvent.constructor.release(terminationRequestEvent);
    
  606.     }
    
  607. 
    
  608.     if (shouldSwitch) {
    
  609.       const terminateEvent = ResponderSyntheticEvent.getPooled(
    
  610.         eventTypes.responderTerminate,
    
  611.         responderInst,
    
  612.         nativeEvent,
    
  613.         nativeEventTarget,
    
  614.       );
    
  615.       terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
    
  616.       accumulateDirectDispatches(terminateEvent);
    
  617.       extracted = accumulate(extracted, [grantEvent, terminateEvent]);
    
  618.       changeResponder(wantsResponderInst, blockHostResponder);
    
  619.     } else {
    
  620.       const rejectEvent = ResponderSyntheticEvent.getPooled(
    
  621.         eventTypes.responderReject,
    
  622.         wantsResponderInst,
    
  623.         nativeEvent,
    
  624.         nativeEventTarget,
    
  625.       );
    
  626.       rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
    
  627.       accumulateDirectDispatches(rejectEvent);
    
  628.       extracted = accumulate(extracted, rejectEvent);
    
  629.     }
    
  630.   } else {
    
  631.     extracted = accumulate(extracted, grantEvent);
    
  632.     changeResponder(wantsResponderInst, blockHostResponder);
    
  633.   }
    
  634.   return extracted;
    
  635. }
    
  636. 
    
  637. /**
    
  638.  * A transfer is a negotiation between a currently set responder and the next
    
  639.  * element to claim responder status. Any start event could trigger a transfer
    
  640.  * of responderInst. Any move event could trigger a transfer.
    
  641.  *
    
  642.  * @param {string} topLevelType Record from `BrowserEventConstants`.
    
  643.  * @return {boolean} True if a transfer of responder could possibly occur.
    
  644.  */
    
  645. function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
    
  646.   return (
    
  647.     topLevelInst &&
    
  648.     // responderIgnoreScroll: We are trying to migrate away from specifically
    
  649.     // tracking native scroll events here and responderIgnoreScroll indicates we
    
  650.     // will send topTouchCancel to handle canceling touch events instead
    
  651.     ((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||
    
  652.       (trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||
    
  653.       isStartish(topLevelType) ||
    
  654.       isMoveish(topLevelType))
    
  655.   );
    
  656. }
    
  657. 
    
  658. /**
    
  659.  * Returns whether or not this touch end event makes it such that there are no
    
  660.  * longer any touches that started inside of the current `responderInst`.
    
  661.  *
    
  662.  * @param {NativeEvent} nativeEvent Native touch end event.
    
  663.  * @return {boolean} Whether or not this touch end event ends the responder.
    
  664.  */
    
  665. function noResponderTouches(nativeEvent) {
    
  666.   const touches = nativeEvent.touches;
    
  667.   if (!touches || touches.length === 0) {
    
  668.     return true;
    
  669.   }
    
  670.   for (let i = 0; i < touches.length; i++) {
    
  671.     const activeTouch = touches[i];
    
  672.     const target = activeTouch.target;
    
  673.     if (target !== null && target !== undefined && target !== 0) {
    
  674.       // Is the original touch location inside of the current responder?
    
  675.       const targetInst = getInstanceFromNode(target);
    
  676.       if (isAncestor(responderInst, targetInst)) {
    
  677.         return false;
    
  678.       }
    
  679.     }
    
  680.   }
    
  681.   return true;
    
  682. }
    
  683. 
    
  684. const ResponderEventPlugin = {
    
  685.   /* For unit testing only */
    
  686.   _getResponder: function () {
    
  687.     return responderInst;
    
  688.   },
    
  689. 
    
  690.   eventTypes: eventTypes,
    
  691. 
    
  692.   /**
    
  693.    * We must be resilient to `targetInst` being `null` on `touchMove` or
    
  694.    * `touchEnd`. On certain platforms, this means that a native scroll has
    
  695.    * assumed control and the original touch targets are destroyed.
    
  696.    */
    
  697.   extractEvents: function (
    
  698.     topLevelType,
    
  699.     targetInst,
    
  700.     nativeEvent,
    
  701.     nativeEventTarget,
    
  702.     eventSystemFlags,
    
  703.   ) {
    
  704.     if (isStartish(topLevelType)) {
    
  705.       trackedTouchCount += 1;
    
  706.     } else if (isEndish(topLevelType)) {
    
  707.       if (trackedTouchCount >= 0) {
    
  708.         trackedTouchCount -= 1;
    
  709.       } else {
    
  710.         if (__DEV__) {
    
  711.           console.warn(
    
  712.             'Ended a touch event which was not counted in `trackedTouchCount`.',
    
  713.           );
    
  714.         }
    
  715.         return null;
    
  716.       }
    
  717.     }
    
  718. 
    
  719.     ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);
    
  720. 
    
  721.     let extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent)
    
  722.       ? setResponderAndExtractTransfer(
    
  723.           topLevelType,
    
  724.           targetInst,
    
  725.           nativeEvent,
    
  726.           nativeEventTarget,
    
  727.         )
    
  728.       : null;
    
  729.     // Responder may or may not have transferred on a new touch start/move.
    
  730.     // Regardless, whoever is the responder after any potential transfer, we
    
  731.     // direct all touch start/move/ends to them in the form of
    
  732.     // `onResponderMove/Start/End`. These will be called for *every* additional
    
  733.     // finger that move/start/end, dispatched directly to whoever is the
    
  734.     // current responder at that moment, until the responder is "released".
    
  735.     //
    
  736.     // These multiple individual change touch events are are always bookended
    
  737.     // by `onResponderGrant`, and one of
    
  738.     // (`onResponderRelease/onResponderTerminate`).
    
  739.     const isResponderTouchStart = responderInst && isStartish(topLevelType);
    
  740.     const isResponderTouchMove = responderInst && isMoveish(topLevelType);
    
  741.     const isResponderTouchEnd = responderInst && isEndish(topLevelType);
    
  742.     const incrementalTouch = isResponderTouchStart
    
  743.       ? eventTypes.responderStart
    
  744.       : isResponderTouchMove
    
  745.       ? eventTypes.responderMove
    
  746.       : isResponderTouchEnd
    
  747.       ? eventTypes.responderEnd
    
  748.       : null;
    
  749. 
    
  750.     if (incrementalTouch) {
    
  751.       const gesture = ResponderSyntheticEvent.getPooled(
    
  752.         incrementalTouch,
    
  753.         responderInst,
    
  754.         nativeEvent,
    
  755.         nativeEventTarget,
    
  756.       );
    
  757.       gesture.touchHistory = ResponderTouchHistoryStore.touchHistory;
    
  758.       accumulateDirectDispatches(gesture);
    
  759.       extracted = accumulate(extracted, gesture);
    
  760.     }
    
  761. 
    
  762.     const isResponderTerminate =
    
  763.       responderInst && topLevelType === TOP_TOUCH_CANCEL;
    
  764.     const isResponderRelease =
    
  765.       responderInst &&
    
  766.       !isResponderTerminate &&
    
  767.       isEndish(topLevelType) &&
    
  768.       noResponderTouches(nativeEvent);
    
  769.     const finalTouch = isResponderTerminate
    
  770.       ? eventTypes.responderTerminate
    
  771.       : isResponderRelease
    
  772.       ? eventTypes.responderRelease
    
  773.       : null;
    
  774.     if (finalTouch) {
    
  775.       const finalEvent = ResponderSyntheticEvent.getPooled(
    
  776.         finalTouch,
    
  777.         responderInst,
    
  778.         nativeEvent,
    
  779.         nativeEventTarget,
    
  780.       );
    
  781.       finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
    
  782.       accumulateDirectDispatches(finalEvent);
    
  783.       extracted = accumulate(extracted, finalEvent);
    
  784.       changeResponder(null);
    
  785.     }
    
  786. 
    
  787.     return extracted;
    
  788.   },
    
  789. 
    
  790.   GlobalResponderHandler: null,
    
  791. 
    
  792.   injection: {
    
  793.     /**
    
  794.      * @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler
    
  795.      * Object that handles any change in responder. Use this to inject
    
  796.      * integration with an existing touch handling system etc.
    
  797.      */
    
  798.     injectGlobalResponderHandler(GlobalResponderHandler) {
    
  799.       ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
    
  800.     },
    
  801.   },
    
  802. };
    
  803. 
    
  804. export default ResponderEventPlugin;