-
Notifications
You must be signed in to change notification settings - Fork 45.5k
/
accumulateTwoPhaseListeners.js
140 lines (130 loc) · 5.3 KB
/
accumulateTwoPhaseListeners.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
import getListener from 'legacy-events/getListener';
import {getListenersFromTarget} from '../client/ReactDOMComponentTree';
import {IS_TARGET_EVENT_ONLY} from 'legacy-events/EventSystemFlags';
import {eventTargetEventListenerStore} from './DOMModernPluginEventSystem';
export default function accumulateTwoPhaseListeners(
event: ReactSyntheticEvent,
accumulateUseEventListeners?: boolean,
eventSystemFlags?: EventSystemFlags,
targetContainer?: null | EventTarget,
): void {
const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
const dispatchListeners = [];
const dispatchInstances = [];
// For TargetEvent only accumulation, we do not traverse through
// the React tree looking for managed React DOM elements that have
// events. Instead we only check the EventTarget Store Map to see
// if the container has listeners for the particular phase we're
// interested in. This is because we attach the native event listener
// only in the given phase.
if (
enableUseEventAPI &&
accumulateUseEventListeners &&
eventSystemFlags !== undefined &&
eventSystemFlags & IS_TARGET_EVENT_ONLY &&
targetContainer != null
) {
const eventTypeMap = eventTargetEventListenerStore.get(targetContainer);
if (eventTypeMap !== undefined) {
const type = ((event.type: any): DOMTopLevelEventType);
const listeners = eventTypeMap.get(type);
if (listeners !== undefined) {
const isCapturePhase = (event: any).eventPhase === 1;
if (isCapturePhase) {
const captureListeners = Array.from(listeners.captured);
for (let i = captureListeners.length - 1; i >= 0; i--) {
const listener = captureListeners[i];
const {callback} = listener;
dispatchListeners.push(callback);
dispatchInstances.push(targetContainer);
}
} else {
const bubbleListeners = Array.from(listeners.bubbled);
for (let i = 0; i < bubbleListeners.length; i++) {
const listener = bubbleListeners[i];
const {callback} = listener;
dispatchListeners.push(callback);
dispatchInstances.push(targetContainer);
}
}
}
}
} else {
const {bubbled, captured} = phasedRegistrationNames;
// If we are not handling EventTarget only phase, then we're doing the
// usual two phase accumulation using the React fiber tree to pick up
// all relevant useEvent and on* prop events.
let node = event._targetInst;
// Accumulate all instances and listeners via the target -> root path.
while (node !== null) {
// We only care for listeners that are on HostComponents (i.e. <div>)
if (node.tag === HostComponent) {
// For useEvent listenrs
if (enableUseEventAPI && accumulateUseEventListeners) {
// useEvent event listeners
const instance = node.stateNode;
const targetType = event.type;
const listeners = getListenersFromTarget(instance);
if (listeners !== null) {
const listenersArr = Array.from(listeners);
for (let i = 0; i < listenersArr.length; i++) {
const listener = listenersArr[i];
const {
callback,
event: {capture, type},
} = listener;
if (type === targetType) {
if (capture === true) {
dispatchListeners.unshift(callback);
dispatchInstances.unshift(node);
} else {
dispatchListeners.push(callback);
dispatchInstances.push(node);
}
}
}
}
}
// Standard React on* listeners, i.e. onClick prop
if (captured !== null) {
const captureListener = getListener(node, captured);
if (captureListener != null) {
// Capture listeners/instances should go at the start, so we
// unshift them to the start of the array.
dispatchListeners.unshift(captureListener);
dispatchInstances.unshift(node);
}
}
if (bubbled !== null) {
const bubbleListener = getListener(node, bubbled);
if (bubbleListener != null) {
// Bubble listeners/instances should go at the end, so we
// push them to the end of the array.
dispatchListeners.push(bubbleListener);
dispatchInstances.push(node);
}
}
}
node = node.return;
}
}
// To prevent allocation to the event unless we actually
// have listeners we check the length of one of the arrays.
if (dispatchListeners.length > 0) {
event._dispatchListeners = dispatchListeners;
event._dispatchInstances = dispatchInstances;
}
}