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