ModernEventSystem: refactor accumulateTwoPhaseListeners (#18274)
Dominic Gannaway committed
Mar 11, 2020 at 18:55 UTC
30a998debf79f8c8efb3fa9b6ff5458fb319d7a1
7 files changed
+60
-10
packages/legacy-events/ReactSyntheticEventType.js
+3
@@ -31,4 +31,7 @@ export type ReactSyntheticEvent = {|
31
nativeEventTarget: EventTarget,
32
) => ReactSyntheticEvent,
33
isPersistent: () => boolean,
34
+ _dispatchInstances: null | Array<Fiber>,
35
+ _dispatchListeners: null | Array<Function>,
36
+ _targetInst: null | Fiber,
37
|};
packages/legacy-events/SyntheticEvent.js
+2
@@ -76,6 +76,8 @@ function SyntheticEvent(
76
this.dispatchConfig = dispatchConfig;
77
this._targetInst = targetInst;
78
this.nativeEvent = nativeEvent;
79
+ this._dispatchListeners = null;
80
+ this._dispatchInstances = null;
81
82
const Interface = this.constructor.Interface;
83
for (const propName in Interface) {
packages/react-dom/src/events/BeforeInputEventPlugin.js
+3
-3
@@ -7,7 +7,6 @@
7
8
import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
9
10
-import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators';
10
import {canUseDOM} from 'shared/ExecutionEnvironment';
11
12
import {
@@ -29,6 +28,7 @@ import {
28
} from './FallbackCompositionState';
29
import SyntheticCompositionEvent from './SyntheticCompositionEvent';
30
import SyntheticInputEvent from './SyntheticInputEvent';
31
+import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
32
33
const END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
34
const START_KEYCODE = 229;
@@ -276,7 +276,7 @@ function extractCompositionEvent(
276
}
277
}
278
279
- accumulateTwoPhaseDispatchesSingle(event);
279
+ accumulateTwoPhaseListeners(event);
280
return event;
281
}
282
@@ -437,7 +437,7 @@ function extractBeforeInputEvent(
437
);
438
439
event.data = chars;
440
- accumulateTwoPhaseDispatchesSingle(event);
440
+ accumulateTwoPhaseListeners(event);
441
return event;
442
}
443
packages/react-dom/src/events/ChangeEventPlugin.js
+3
-2
@@ -6,7 +6,6 @@
6
*/
7
8
import {runEventsInBatch} from 'legacy-events/EventBatching';
9
-import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators';
9
import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent';
10
import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
11
import SyntheticEvent from 'legacy-events/SyntheticEvent';
@@ -28,6 +27,8 @@ import isEventSupported from './isEventSupported';
27
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
28
import {updateValueIfChanged} from '../client/inputValueTracking';
29
import {setDefaultValue} from '../client/ReactDOMInput';
30
+import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
31
+
32
import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags';
33
34
const eventTypes = {
@@ -59,7 +60,7 @@ function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
60
event.type = 'change';
61
// Flag this event loop as needing state restore.
62
enqueueStateRestore(target);
62
- accumulateTwoPhaseDispatchesSingle(event);
63
+ accumulateTwoPhaseListeners(event);
64
return event;
65
}
66
/**
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+45
-1
@@ -19,8 +19,9 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
19
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
20
import {executeDispatchesInOrder} from 'legacy-events/EventPluginUtils';
21
import {plugins} from 'legacy-events/EventPluginRegistry';
22
+import getListener from 'legacy-events/getListener';
23
23
-import {HostRoot, HostPortal} from 'shared/ReactWorkTags';
24
+import {HostRoot, HostPortal, HostComponent} from 'shared/ReactWorkTags';
25
26
import {addTrappedEventListener} from './ReactDOMEventListener';
27
import getEventTarget from './getEventTarget';
@@ -305,3 +306,46 @@ export function attachElementListener(listener: ReactDOMListener): void {
306
export function detachElementListener(listener: ReactDOMListener): void {
307
// TODO
308
}
309
+
310
+export function accumulateTwoPhaseListeners(event: ReactSyntheticEvent): void {
311
+ const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
312
+ if (phasedRegistrationNames == null) {
313
+ return;
314
+ }
315
+ const {bubbled, captured} = phasedRegistrationNames;
316
+ const dispatchListeners = [];
317
+ const dispatchInstances = [];
318
+ let node = event._targetInst;
319
+ let hasListeners = false;
320
+
321
+ // Accumulate all instances and listeners via the target -> root path.
322
+ while (node !== null) {
323
+ // We only care for listeners that are on HostComponents (i.e. <div>)
324
+ if (node.tag === HostComponent) {
325
+ // Standard React on* listeners, i.e. onClick prop
326
+ const captureListener = getListener(node, captured);
327
+ if (captureListener != null) {
328
+ hasListeners = true;
329
+ // Capture listeners/instances should go at the start, so we
330
+ // unshift them to the start of the array.
331
+ dispatchListeners.unshift(captureListener);
332
+ dispatchInstances.unshift(node);
333
+ }
334
+ const bubbleListener = getListener(node, bubbled);
335
+ if (bubbleListener != null) {
336
+ hasListeners = true;
337
+ // Bubble listeners/instances should go at the end, so we
338
+ // push them to the end of the array.
339
+ dispatchListeners.push(bubbleListener);
340
+ dispatchInstances.push(node);
341
+ }
342
+ }
343
+ node = node.return;
344
+ }
345
+ // To prevent allocation to the event unless we actually
346
+ // have listeners we use the flag we would have set above.
347
+ if (hasListeners) {
348
+ event._dispatchListeners = dispatchListeners;
349
+ event._dispatchInstances = dispatchInstances;
350
+ }
351
+}
packages/react-dom/src/events/SelectEventPlugin.js
+2
-2
@@ -5,7 +5,6 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators';
8
import {canUseDOM} from 'shared/ExecutionEnvironment';
9
import SyntheticEvent from 'legacy-events/SyntheticEvent';
10
import isTextInputElement from 'shared/isTextInputElement';
@@ -27,6 +26,7 @@ import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
26
import {hasSelectionCapabilities} from '../client/ReactInputSelection';
27
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
28
import {isListeningToAllDependencies} from './DOMEventListenerMap';
29
+import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
30
31
const skipSelectionChangeEvent =
32
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
@@ -135,7 +135,7 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) {
135
syntheticEvent.type = 'select';
136
syntheticEvent.target = activeElement;
137
138
- accumulateTwoPhaseDispatchesSingle(syntheticEvent);
138
+ accumulateTwoPhaseListeners(syntheticEvent);
139
140
return syntheticEvent;
141
}
packages/react-dom/src/events/SimpleEventPlugin.js
+2
-2
@@ -16,7 +16,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
16
import type {PluginModule} from 'legacy-events/PluginModuleType';
17
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
18
19
-import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators';
19
import SyntheticEvent from 'legacy-events/SyntheticEvent';
20
21
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
@@ -37,6 +36,7 @@ import SyntheticTransitionEvent from './SyntheticTransitionEvent';
36
import SyntheticUIEvent from './SyntheticUIEvent';
37
import SyntheticWheelEvent from './SyntheticWheelEvent';
38
import getEventCharCode from './getEventCharCode';
39
+import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem';
40
41
// Only used in DEV for exhaustiveness validation.
42
const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
@@ -191,7 +191,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
191
nativeEvent,
192
nativeEventTarget,
193
);
194
- accumulateTwoPhaseDispatchesSingle(event);
194
+ accumulateTwoPhaseListeners(event);
195
return event;
196
},
197
};