More event system cleanup and scaffolding (#18179)
Dominic Gannaway committed
Mar 2, 2020 at 10:59 UTC
62861bbcc752c63728592fdc45078854f9aae161
10 files changed
+396
-134
packages/legacy-events/PluginModuleType.js
+1
-1
@@ -29,7 +29,7 @@ export type PluginModule<NativeEvent> = {
29
nativeTarget: NativeEvent,
30
nativeEventTarget: null | EventTarget,
31
eventSystemFlags: EventSystemFlags,
32
- container?: Document | Element | Node,
32
+ container?: Document | Element,
33
) => ?ReactSyntheticEvent,
34
tapMoveThreshold?: number,
35
};
packages/react-dom/src/client/ReactDOMComponent.js
+103
-37
@@ -10,6 +10,7 @@
10
import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
11
import {canUseDOM} from 'shared/ExecutionEnvironment';
12
import endsWith from 'shared/endsWith';
13
+import invariant from 'shared/invariant';
14
import {setListenToResponderEventTypes} from '../events/DeprecatedDOMEventResponderSystem';
15
16
import {
@@ -59,7 +60,6 @@ import {getListenerMapForElement} from '../events/DOMEventListenerMap';
60
import {
61
addResponderEventSystemEvent,
62
removeActiveResponderEventSystemEvent,
62
- trapBubbledEvent,
63
} from '../events/ReactDOMEventListener.js';
64
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
65
import {
@@ -74,7 +74,12 @@ import {
74
shouldRemoveAttribute,
75
} from '../shared/DOMProperty';
76
import assertValidProps from '../shared/assertValidProps';
77
-import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType';
77
+import {
78
+ DOCUMENT_NODE,
79
+ DOCUMENT_FRAGMENT_NODE,
80
+ ELEMENT_NODE,
81
+ COMMENT_NODE,
82
+} from '../shared/HTMLNodeType';
83
import isCustomComponent from '../shared/isCustomComponent';
84
import possibleStandardNames from '../shared/possibleStandardNames';
85
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
@@ -84,8 +89,13 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
89
import {
90
enableDeprecatedFlareAPI,
91
enableTrustedTypesIntegration,
92
+ enableModernEventSystem,
93
} from 'shared/ReactFeatureFlags';
88
-import {legacyListenToEvent} from '../events/DOMLegacyEventPluginSystem';
94
+import {
95
+ legacyListenToEvent,
96
+ legacyTrapBubbledEvent,
97
+} from '../events/DOMLegacyEventPluginSystem';
98
+import {listenToEvent} from '../events/DOMModernPluginEventSystem';
99
100
let didWarnInvalidHydration = false;
101
let didWarnScriptTags = false;
@@ -260,16 +270,36 @@ if (__DEV__) {
270
}
271
272
function ensureListeningTo(
263
- rootContainerElement: Element | Node,
273
+ rootContainerInstance: Element | Node,
274
registrationName: string,
275
): void {
266
- const isDocumentOrFragment =
267
- rootContainerElement.nodeType === DOCUMENT_NODE ||
268
- rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
269
- const doc = isDocumentOrFragment
270
- ? rootContainerElement
271
- : rootContainerElement.ownerDocument;
272
- legacyListenToEvent(registrationName, doc);
276
+ if (enableModernEventSystem) {
277
+ // If we have a comment node, then use the parent node,
278
+ // which should be an element.
279
+ const rootContainerElement =
280
+ rootContainerInstance.nodeType === COMMENT_NODE
281
+ ? rootContainerInstance.parentNode
282
+ : rootContainerInstance;
283
+ // Containers can only ever be element nodes. We do not
284
+ // want to register events to document fragments or documents
285
+ // with the modern plugin event system.
286
+ invariant(
287
+ rootContainerElement != null &&
288
+ rootContainerElement.nodeType === ELEMENT_NODE,
289
+ 'ensureListeningTo(): received a container that was not an element node. ' +
290
+ 'This is likely a bug in React.',
291
+ );
292
+ listenToEvent(registrationName, ((rootContainerElement: any): Element));
293
+ } else {
294
+ // Legacy plugin event system path
295
+ const isDocumentOrFragment =
296
+ rootContainerInstance.nodeType === DOCUMENT_NODE ||
297
+ rootContainerInstance.nodeType === DOCUMENT_FRAGMENT_NODE;
298
+ const doc = isDocumentOrFragment
299
+ ? rootContainerInstance
300
+ : rootContainerInstance.ownerDocument;
301
+ legacyListenToEvent(registrationName, ((doc: any): Document));
302
+ }
303
}
304
305
function getOwnerDocumentFromRootContainer(
@@ -514,41 +544,55 @@ export function setInitialProperties(
544
case 'iframe':
545
case 'object':
546
case 'embed':
517
- trapBubbledEvent(TOP_LOAD, domElement);
547
+ if (!enableModernEventSystem) {
548
+ legacyTrapBubbledEvent(TOP_LOAD, domElement);
549
+ }
550
props = rawProps;
551
break;
552
case 'video':
553
case 'audio':
522
- // Create listener for each media event
523
- for (let i = 0; i < mediaEventTypes.length; i++) {
524
- trapBubbledEvent(mediaEventTypes[i], domElement);
554
+ if (!enableModernEventSystem) {
555
+ // Create listener for each media event
556
+ for (let i = 0; i < mediaEventTypes.length; i++) {
557
+ legacyTrapBubbledEvent(mediaEventTypes[i], domElement);
558
+ }
559
}
560
props = rawProps;
561
break;
562
case 'source':
529
- trapBubbledEvent(TOP_ERROR, domElement);
563
+ if (!enableModernEventSystem) {
564
+ legacyTrapBubbledEvent(TOP_ERROR, domElement);
565
+ }
566
props = rawProps;
567
break;
568
case 'img':
569
case 'image':
570
case 'link':
535
- trapBubbledEvent(TOP_ERROR, domElement);
536
- trapBubbledEvent(TOP_LOAD, domElement);
571
+ if (!enableModernEventSystem) {
572
+ legacyTrapBubbledEvent(TOP_ERROR, domElement);
573
+ legacyTrapBubbledEvent(TOP_LOAD, domElement);
574
+ }
575
props = rawProps;
576
break;
577
case 'form':
540
- trapBubbledEvent(TOP_RESET, domElement);
541
- trapBubbledEvent(TOP_SUBMIT, domElement);
578
+ if (!enableModernEventSystem) {
579
+ legacyTrapBubbledEvent(TOP_RESET, domElement);
580
+ legacyTrapBubbledEvent(TOP_SUBMIT, domElement);
581
+ }
582
props = rawProps;
583
break;
584
case 'details':
545
- trapBubbledEvent(TOP_TOGGLE, domElement);
585
+ if (!enableModernEventSystem) {
586
+ legacyTrapBubbledEvent(TOP_TOGGLE, domElement);
587
+ }
588
props = rawProps;
589
break;
590
case 'input':
591
ReactDOMInputInitWrapperState(domElement, rawProps);
592
props = ReactDOMInputGetHostProps(domElement, rawProps);
551
- trapBubbledEvent(TOP_INVALID, domElement);
593
+ if (!enableModernEventSystem) {
594
+ legacyTrapBubbledEvent(TOP_INVALID, domElement);
595
+ }
596
// For controlled components we always need to ensure we're listening
597
// to onChange. Even if there is no listener.
598
ensureListeningTo(rootContainerElement, 'onChange');
@@ -560,7 +604,9 @@ export function setInitialProperties(
604
case 'select':
605
ReactDOMSelectInitWrapperState(domElement, rawProps);
606
props = ReactDOMSelectGetHostProps(domElement, rawProps);
563
- trapBubbledEvent(TOP_INVALID, domElement);
607
+ if (!enableModernEventSystem) {
608
+ legacyTrapBubbledEvent(TOP_INVALID, domElement);
609
+ }
610
// For controlled components we always need to ensure we're listening
611
// to onChange. Even if there is no listener.
612
ensureListeningTo(rootContainerElement, 'onChange');
@@ -568,7 +614,9 @@ export function setInitialProperties(
614
case 'textarea':
615
ReactDOMTextareaInitWrapperState(domElement, rawProps);
616
props = ReactDOMTextareaGetHostProps(domElement, rawProps);
571
- trapBubbledEvent(TOP_INVALID, domElement);
617
+ if (!enableModernEventSystem) {
618
+ legacyTrapBubbledEvent(TOP_INVALID, domElement);
619
+ }
620
// For controlled components we always need to ensure we're listening
621
// to onChange. Even if there is no listener.
622
ensureListeningTo(rootContainerElement, 'onChange');
@@ -898,34 +946,48 @@ export function diffHydratedProperties(
946
case 'iframe':
947
case 'object':
948
case 'embed':
901
- trapBubbledEvent(TOP_LOAD, domElement);
949
+ if (!enableModernEventSystem) {
950
+ legacyTrapBubbledEvent(TOP_LOAD, domElement);
951
+ }
952
break;
953
case 'video':
954
case 'audio':
905
- // Create listener for each media event
906
- for (let i = 0; i < mediaEventTypes.length; i++) {
907
- trapBubbledEvent(mediaEventTypes[i], domElement);
955
+ if (!enableModernEventSystem) {
956
+ // Create listener for each media event
957
+ for (let i = 0; i < mediaEventTypes.length; i++) {
958
+ legacyTrapBubbledEvent(mediaEventTypes[i], domElement);
959
+ }
960
}
961
break;
962
case 'source':
911
- trapBubbledEvent(TOP_ERROR, domElement);
963
+ if (!enableModernEventSystem) {
964
+ legacyTrapBubbledEvent(TOP_ERROR, domElement);
965
+ }
966
break;
967
case 'img':
968
case 'image':
969
case 'link':
916
- trapBubbledEvent(TOP_ERROR, domElement);
917
- trapBubbledEvent(TOP_LOAD, domElement);
970
+ if (!enableModernEventSystem) {
971
+ legacyTrapBubbledEvent(TOP_ERROR, domElement);
972
+ legacyTrapBubbledEvent(TOP_LOAD, domElement);
973
+ }
974
break;
975
case 'form':
920
- trapBubbledEvent(TOP_RESET, domElement);
921
- trapBubbledEvent(TOP_SUBMIT, domElement);
976
+ if (!enableModernEventSystem) {
977
+ legacyTrapBubbledEvent(TOP_RESET, domElement);
978
+ legacyTrapBubbledEvent(TOP_SUBMIT, domElement);
979
+ }
980
break;
981
case 'details':
924
- trapBubbledEvent(TOP_TOGGLE, domElement);
982
+ if (!enableModernEventSystem) {
983
+ legacyTrapBubbledEvent(TOP_TOGGLE, domElement);
984
+ }
985
break;
986
case 'input':
987
ReactDOMInputInitWrapperState(domElement, rawProps);
928
- trapBubbledEvent(TOP_INVALID, domElement);
988
+ if (!enableModernEventSystem) {
989
+ legacyTrapBubbledEvent(TOP_INVALID, domElement);
990
+ }
991
// For controlled components we always need to ensure we're listening
992
// to onChange. Even if there is no listener.
993
ensureListeningTo(rootContainerElement, 'onChange');
@@ -935,14 +997,18 @@ export function diffHydratedProperties(
997
break;
998
case 'select':
999
ReactDOMSelectInitWrapperState(domElement, rawProps);
938
- trapBubbledEvent(TOP_INVALID, domElement);
1000
+ if (!enableModernEventSystem) {
1001
+ legacyTrapBubbledEvent(TOP_INVALID, domElement);
1002
+ }
1003
// For controlled components we always need to ensure we're listening
1004
// to onChange. Even if there is no listener.
1005
ensureListeningTo(rootContainerElement, 'onChange');
1006
break;
1007
case 'textarea':
1008
ReactDOMTextareaInitWrapperState(domElement, rawProps);
945
- trapBubbledEvent(TOP_INVALID, domElement);
1009
+ if (!enableModernEventSystem) {
1010
+ legacyTrapBubbledEvent(TOP_INVALID, domElement);
1011
+ }
1012
// For controlled components we always need to ensure we're listening
1013
// to onChange. Even if there is no listener.
1014
ensureListeningTo(rootContainerElement, 'onChange');
packages/react-dom/src/events/DOMEventListenerMap.js
+18
@@ -9,6 +9,8 @@
9
10
import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
11
12
+import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
13
+
14
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
15
// prettier-ignore
16
const elementListenerMap:
@@ -29,3 +31,19 @@ export function getListenerMapForElement(
31
}
32
return listenerMap;
33
}
34
+
35
+export function isListeningToAllDependencies(
36
+ registrationName: string,
37
+ mountAt: Document | Element,
38
+): boolean {
39
+ const listenerMap = getListenerMapForElement(mountAt);
40
+ const dependencies = registrationNameDependencies[registrationName];
41
+
42
+ for (let i = 0; i < dependencies.length; i++) {
43
+ const dependency = dependencies[i];
44
+ if (!listenerMap.has(dependency)) {
45
+ return false;
46
+ }
47
+ }
48
+ return true;
49
+}
packages/react-dom/src/events/DOMLegacyEventPluginSystem.js
+19
-21
@@ -25,7 +25,6 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
25
26
import getEventTarget from './getEventTarget';
27
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
28
-import {trapCapturedEvent, trapBubbledEvent} from './ReactDOMEventListener';
28
import {getListenerMapForElement} from './DOMEventListenerMap';
29
import isEventSupported from './isEventSupported';
30
import {
@@ -40,6 +39,7 @@ import {
39
getRawEventName,
40
mediaEventTypes,
41
} from './DOMTopLevelEventTypes';
42
+import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
43
44
/**
45
* Summary of `DOMEventPluginSystem` event handling:
@@ -309,7 +309,7 @@ export function dispatchEventForLegacyPluginEventSystem(
309
*/
310
export function legacyListenToEvent(
311
registrationName: string,
312
- mountAt: Document | Element | Node,
312
+ mountAt: Document | Element,
313
): void {
314
const listenerMap = getListenerMapForElement(mountAt);
315
const dependencies = registrationNameDependencies[registrationName];
@@ -322,18 +322,18 @@ export function legacyListenToEvent(
322
323
export function legacyListenToTopLevelEvent(
324
topLevelType: DOMTopLevelEventType,
325
- mountAt: Document | Element | Node,
325
+ mountAt: Document | Element,
326
listenerMap: Map<DOMTopLevelEventType | string, null | (any => void)>,
327
): void {
328
if (!listenerMap.has(topLevelType)) {
329
switch (topLevelType) {
330
case TOP_SCROLL:
331
- trapCapturedEvent(TOP_SCROLL, mountAt);
331
+ legacyTrapCapturedEvent(TOP_SCROLL, mountAt);
332
break;
333
case TOP_FOCUS:
334
case TOP_BLUR:
335
- trapCapturedEvent(TOP_FOCUS, mountAt);
336
- trapCapturedEvent(TOP_BLUR, mountAt);
335
+ legacyTrapCapturedEvent(TOP_FOCUS, mountAt);
336
+ legacyTrapCapturedEvent(TOP_BLUR, mountAt);
337
// We set the flag for a single dependency later in this function,
338
// but this ensures we mark both as attached rather than just one.
339
listenerMap.set(TOP_BLUR, null);
@@ -342,7 +342,7 @@ export function legacyListenToTopLevelEvent(
342
case TOP_CANCEL:
343
case TOP_CLOSE:
344
if (isEventSupported(getRawEventName(topLevelType))) {
345
- trapCapturedEvent(topLevelType, mountAt);
345
+ legacyTrapCapturedEvent(topLevelType, mountAt);
346
}
347
break;
348
case TOP_INVALID:
@@ -356,7 +356,7 @@ export function legacyListenToTopLevelEvent(
356
// Media events don't bubble so adding the listener wouldn't do anything.
357
const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1;
358
if (!isMediaEvent) {
359
- trapBubbledEvent(topLevelType, mountAt);
359
+ legacyTrapBubbledEvent(topLevelType, mountAt);
360
}
361
break;
362
}
@@ -364,18 +364,16 @@ export function legacyListenToTopLevelEvent(
364
}
365
}
366
367
-export function isListeningToAllDependencies(
368
- registrationName: string,
369
- mountAt: Document | Element,
370
-): boolean {
371
- const listenerMap = getListenerMapForElement(mountAt);
372
- const dependencies = registrationNameDependencies[registrationName];
367
+export function legacyTrapBubbledEvent(
368
+ topLevelType: DOMTopLevelEventType,
369
+ element: Document | Element,
370
+): void {
371
+ trapEventForPluginEventSystem(element, topLevelType, false);
372
+}
373
374
- for (let i = 0; i < dependencies.length; i++) {
375
- const dependency = dependencies[i];
376
- if (!listenerMap.has(dependency)) {
377
- return false;
378
- }
379
- }
380
- return true;
374
+export function legacyTrapCapturedEvent(
375
+ topLevelType: DOMTopLevelEventType,
376
+ element: Document | Element,
377
+): void {
378
+ trapEventForPluginEventSystem(element, topLevelType, true);
379
}
packages/react-dom/src/events/DOMModernPluginEventSystem.js
new
+127
@@ -0,0 +1,127 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its 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 {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11
+import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
12
+import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
13
+import type {Fiber} from 'react-reconciler/src/ReactFiber';
14
+
15
+import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
16
+
17
+import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
18
+import {getListenerMapForElement} from './DOMEventListenerMap';
19
+import {
20
+ TOP_FOCUS,
21
+ TOP_LOAD,
22
+ TOP_ABORT,
23
+ TOP_CANCEL,
24
+ TOP_INVALID,
25
+ TOP_BLUR,
26
+ TOP_SCROLL,
27
+ TOP_CLOSE,
28
+ TOP_RESET,
29
+ TOP_SUBMIT,
30
+ TOP_CAN_PLAY,
31
+ TOP_CAN_PLAY_THROUGH,
32
+ TOP_DURATION_CHANGE,
33
+ TOP_EMPTIED,
34
+ TOP_ENCRYPTED,
35
+ TOP_ENDED,
36
+ TOP_ERROR,
37
+ TOP_WAITING,
38
+ TOP_VOLUME_CHANGE,
39
+ TOP_TIME_UPDATE,
40
+ TOP_SUSPEND,
41
+ TOP_STALLED,
42
+ TOP_SEEKING,
43
+ TOP_SEEKED,
44
+ TOP_PLAY,
45
+ TOP_PAUSE,
46
+ TOP_LOAD_START,
47
+ TOP_LOADED_DATA,
48
+ TOP_LOADED_METADATA,
49
+ TOP_RATE_CHANGE,
50
+ TOP_PROGRESS,
51
+ TOP_PLAYING,
52
+} from './DOMTopLevelEventTypes';
53
+
54
+const capturePhaseEvents = new Set([
55
+ TOP_FOCUS,
56
+ TOP_BLUR,
57
+ TOP_SCROLL,
58
+ TOP_LOAD,
59
+ TOP_ABORT,
60
+ TOP_CANCEL,
61
+ TOP_CLOSE,
62
+ TOP_INVALID,
63
+ TOP_RESET,
64
+ TOP_SUBMIT,
65
+ TOP_ABORT,
66
+ TOP_CAN_PLAY,
67
+ TOP_CAN_PLAY_THROUGH,
68
+ TOP_DURATION_CHANGE,
69
+ TOP_EMPTIED,
70
+ TOP_ENCRYPTED,
71
+ TOP_ENDED,
72
+ TOP_ERROR,
73
+ TOP_LOADED_DATA,
74
+ TOP_LOADED_METADATA,
75
+ TOP_LOAD_START,
76
+ TOP_PAUSE,
77
+ TOP_PLAY,
78
+ TOP_PLAYING,
79
+ TOP_PROGRESS,
80
+ TOP_RATE_CHANGE,
81
+ TOP_SEEKED,
82
+ TOP_SEEKING,
83
+ TOP_STALLED,
84
+ TOP_SUSPEND,
85
+ TOP_TIME_UPDATE,
86
+ TOP_VOLUME_CHANGE,
87
+ TOP_WAITING,
88
+]);
89
+
90
+export function listenToTopLevelEvent(
91
+ topLevelType: DOMTopLevelEventType,
92
+ rootContainerElement: Element,
93
+ listenerMap: Map<DOMTopLevelEventType | string, null | (any => void)>,
94
+): void {
95
+ if (!listenerMap.has(topLevelType)) {
96
+ const isCapturePhase = capturePhaseEvents.has(topLevelType);
97
+ trapEventForPluginEventSystem(
98
+ rootContainerElement,
99
+ topLevelType,
100
+ isCapturePhase,
101
+ );
102
+ listenerMap.set(topLevelType, null);
103
+ }
104
+}
105
+
106
+export function listenToEvent(
107
+ registrationName: string,
108
+ rootContainerElement: Element,
109
+): void {
110
+ const listenerMap = getListenerMapForElement(rootContainerElement);
111
+ const dependencies = registrationNameDependencies[registrationName];
112
+
113
+ for (let i = 0; i < dependencies.length; i++) {
114
+ const dependency = dependencies[i];
115
+ listenToTopLevelEvent(dependency, rootContainerElement, listenerMap);
116
+ }
117
+}
118
+
119
+export function dispatchEventForPluginEventSystem(
120
+ topLevelType: DOMTopLevelEventType,
121
+ eventSystemFlags: EventSystemFlags,
122
+ nativeEvent: AnyNativeEvent,
123
+ targetInst: null | Fiber,
124
+ rootContainer: Document | Element,
125
+): void {
126
+ // TODO
127
+}
packages/react-dom/src/events/ReactDOMEventListener.js
+83
-61
@@ -53,7 +53,10 @@ import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
53
import {getRawEventName} from './DOMTopLevelEventTypes';
54
import {passiveBrowserEventsSupported} from './checkPassiveEvents';
55
56
-import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
56
+import {
57
+ enableDeprecatedFlareAPI,
58
+ enableModernEventSystem,
59
+} from 'shared/ReactFeatureFlags';
60
import {
61
UserBlockingEvent,
62
ContinuousEvent,
@@ -61,6 +64,7 @@ import {
64
} from 'shared/ReactTypes';
65
import {getEventPriorityForPluginSystem} from './DOMEventProperties';
66
import {dispatchEventForLegacyPluginEventSystem} from './DOMLegacyEventPluginSystem';
67
+import {dispatchEventForPluginEventSystem} from './DOMModernPluginEventSystem';
68
69
const {
70
unstable_UserBlockingPriority: UserBlockingPriority,
@@ -78,20 +82,6 @@ export function isEnabled() {
82
return _enabled;
83
}
84
81
-export function trapBubbledEvent(
82
- topLevelType: DOMTopLevelEventType,
83
- element: Document | Element | Node,
84
-): void {
85
- trapEventForPluginEventSystem(element, topLevelType, false);
86
-}
87
-
88
-export function trapCapturedEvent(
89
- topLevelType: DOMTopLevelEventType,
90
- element: Document | Element | Node,
91
-): void {
92
- trapEventForPluginEventSystem(element, topLevelType, true);
93
-}
94
-
85
export function addResponderEventSystemEvent(
86
document: Document,
87
topLevelType: string,
@@ -149,39 +139,31 @@ export function removeActiveResponderEventSystemEvent(
139
}
140
}
141
152
-function trapEventForPluginEventSystem(
153
- container: Document | Element | Node,
142
+export function trapEventForPluginEventSystem(
143
+ container: Document | Element,
144
topLevelType: DOMTopLevelEventType,
145
capture: boolean,
146
): void {
147
let listener;
148
+ let listenerWrapper;
149
switch (getEventPriorityForPluginSystem(topLevelType)) {
150
case DiscreteEvent:
160
- listener = dispatchDiscreteEvent.bind(
161
- null,
162
- topLevelType,
163
- PLUGIN_EVENT_SYSTEM,
164
- container,
165
- );
151
+ listenerWrapper = dispatchDiscreteEvent;
152
break;
153
case UserBlockingEvent:
168
- listener = dispatchUserBlockingUpdate.bind(
169
- null,
170
- topLevelType,
171
- PLUGIN_EVENT_SYSTEM,
172
- container,
173
- );
154
+ listenerWrapper = dispatchUserBlockingUpdate;
155
break;
156
case ContinuousEvent:
157
default:
177
- listener = dispatchEvent.bind(
178
- null,
179
- topLevelType,
180
- PLUGIN_EVENT_SYSTEM,
181
- container,
182
- );
158
+ listenerWrapper = dispatchEvent;
159
break;
160
}
161
+ listener = listenerWrapper.bind(
162
+ null,
163
+ topLevelType,
164
+ PLUGIN_EVENT_SYSTEM,
165
+ container,
166
+ );
167
168
const rawEventName = getRawEventName(topLevelType);
169
if (capture) {
@@ -228,7 +210,7 @@ function dispatchUserBlockingUpdate(
210
export function dispatchEvent(
211
topLevelType: DOMTopLevelEventType,
212
eventSystemFlags: EventSystemFlags,
231
- container: Document | Element | Node,
213
+ container: Document | Element,
214
nativeEvent: AnyNativeEvent,
215
): void {
216
if (!_enabled) {
@@ -293,12 +275,22 @@ export function dispatchEvent(
275
// in case the event system needs to trace it.
276
if (enableDeprecatedFlareAPI) {
277
if (eventSystemFlags & PLUGIN_EVENT_SYSTEM) {
296
- dispatchEventForLegacyPluginEventSystem(
297
- topLevelType,
298
- eventSystemFlags,
299
- nativeEvent,
300
- null,
301
- );
278
+ if (enableModernEventSystem) {
279
+ dispatchEventForPluginEventSystem(
280
+ topLevelType,
281
+ eventSystemFlags,
282
+ nativeEvent,
283
+ null,
284
+ container,
285
+ );
286
+ } else {
287
+ dispatchEventForLegacyPluginEventSystem(
288
+ topLevelType,
289
+ eventSystemFlags,
290
+ nativeEvent,
291
+ null,
292
+ );
293
+ }
294
}
295
if (eventSystemFlags & RESPONDER_EVENT_SYSTEM) {
296
// React Flare event system
@@ -311,12 +303,22 @@ export function dispatchEvent(
303
);
304
}
305
} else {
314
- dispatchEventForLegacyPluginEventSystem(
315
- topLevelType,
316
- eventSystemFlags,
317
- nativeEvent,
318
- null,
319
- );
306
+ if (enableModernEventSystem) {
307
+ dispatchEventForPluginEventSystem(
308
+ topLevelType,
309
+ eventSystemFlags,
310
+ nativeEvent,
311
+ null,
312
+ container,
313
+ );
314
+ } else {
315
+ dispatchEventForLegacyPluginEventSystem(
316
+ topLevelType,
317
+ eventSystemFlags,
318
+ nativeEvent,
319
+ null,
320
+ );
321
+ }
322
}
323
}
324
@@ -324,7 +326,7 @@ export function dispatchEvent(
326
export function attemptToDispatchEvent(
327
topLevelType: DOMTopLevelEventType,
328
eventSystemFlags: EventSystemFlags,
327
- container: Document | Element | Node,
329
+ container: Document | Element,
330
nativeEvent: AnyNativeEvent,
331
): null | Container | SuspenseInstance {
332
// TODO: Warn if _enabled is false.
@@ -372,12 +374,22 @@ export function attemptToDispatchEvent(
374
375
if (enableDeprecatedFlareAPI) {
376
if (eventSystemFlags & PLUGIN_EVENT_SYSTEM) {
375
- dispatchEventForLegacyPluginEventSystem(
376
- topLevelType,
377
- eventSystemFlags,
378
- nativeEvent,
379
- targetInst,
380
- );
377
+ if (enableModernEventSystem) {
378
+ dispatchEventForPluginEventSystem(
379
+ topLevelType,
380
+ eventSystemFlags,
381
+ nativeEvent,
382
+ targetInst,
383
+ container,
384
+ );
385
+ } else {
386
+ dispatchEventForLegacyPluginEventSystem(
387
+ topLevelType,
388
+ eventSystemFlags,
389
+ nativeEvent,
390
+ targetInst,
391
+ );
392
+ }
393
}
394
if (eventSystemFlags & RESPONDER_EVENT_SYSTEM) {
395
// React Flare event system
@@ -390,12 +402,22 @@ export function attemptToDispatchEvent(
402
);
403
}
404
} else {
393
- dispatchEventForLegacyPluginEventSystem(
394
- topLevelType,
395
- eventSystemFlags,
396
- nativeEvent,
397
- targetInst,
398
- );
405
+ if (enableModernEventSystem) {
406
+ dispatchEventForPluginEventSystem(
407
+ topLevelType,
408
+ eventSystemFlags,
409
+ nativeEvent,
410
+ targetInst,
411
+ container,
412
+ );
413
+ } else {
414
+ dispatchEventForLegacyPluginEventSystem(
415
+ topLevelType,
416
+ eventSystemFlags,
417
+ nativeEvent,
418
+ targetInst,
419
+ );
420
+ }
421
}
422
// We're not blocked on anything.
423
return null;
packages/react-dom/src/events/ReactDOMEventReplaying.js
+36
-6
@@ -16,6 +16,7 @@ import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
16
import {
17
enableDeprecatedFlareAPI,
18
enableSelectiveHydration,
19
+ enableModernEventSystem,
20
} from 'shared/ReactFeatureFlags';
21
import {
22
unstable_runWithPriority as runWithPriority,
@@ -118,13 +119,14 @@ import {
119
} from './DOMTopLevelEventTypes';
120
import {IS_REPLAYED} from 'legacy-events/EventSystemFlags';
121
import {legacyListenToTopLevelEvent} from './DOMLegacyEventPluginSystem';
122
+import {listenToTopLevelEvent} from './DOMModernPluginEventSystem';
123
124
type QueuedReplayableEvent = {|
125
blockedOn: null | Container | SuspenseInstance,
126
topLevelType: DOMTopLevelEventType,
127
eventSystemFlags: EventSystemFlags,
128
nativeEvent: AnyNativeEvent,
127
- container: Document | Element | Node,
129
+ container: Document | Element,
130
|};
131
132
let hasScheduledReplayAttempt = false;
@@ -211,12 +213,22 @@ export function isReplayableDiscreteEvent(
213
return discreteReplayableEvents.indexOf(eventType) > -1;
214
}
215
216
+function trapReplayableEventForContainer(
217
+ topLevelType: DOMTopLevelEventType,
218
+ container: Container,
219
+ listenerMap: Map<DOMTopLevelEventType | string, null | (any => void)>,
220
+) {
221
+ listenToTopLevelEvent(topLevelType, ((container: any): Element), listenerMap);
222
+}
223
+
224
function trapReplayableEventForDocument(
225
topLevelType: DOMTopLevelEventType,
226
document: Document,
227
listenerMap: Map<DOMTopLevelEventType | string, null | (any => void)>,
228
) {
219
- legacyListenToTopLevelEvent(topLevelType, document, listenerMap);
229
+ if (!enableModernEventSystem) {
230
+ legacyListenToTopLevelEvent(topLevelType, document, listenerMap);
231
+ }
232
if (enableDeprecatedFlareAPI) {
233
// Trap events for the responder system.
234
const topLevelTypeString = unsafeCastDOMTopLevelTypeToString(topLevelType);
@@ -241,12 +253,30 @@ export function eagerlyTrapReplayableEvents(
253
document: Document,
254
) {
255
const listenerMapForDoc = getListenerMapForElement(document);
256
+ let listenerMapForContainer;
257
+ if (enableModernEventSystem) {
258
+ listenerMapForContainer = getListenerMapForElement(container);
259
+ }
260
// Discrete
261
discreteReplayableEvents.forEach(topLevelType => {
262
+ if (enableModernEventSystem) {
263
+ trapReplayableEventForContainer(
264
+ topLevelType,
265
+ container,
266
+ listenerMapForContainer,
267
+ );
268
+ }
269
trapReplayableEventForDocument(topLevelType, document, listenerMapForDoc);
270
});
271
// Continuous
272
continuousReplayableEvents.forEach(topLevelType => {
273
+ if (enableModernEventSystem) {
274
+ trapReplayableEventForContainer(
275
+ topLevelType,
276
+ container,
277
+ listenerMapForContainer,
278
+ );
279
+ }
280
trapReplayableEventForDocument(topLevelType, document, listenerMapForDoc);
281
});
282
}
@@ -255,7 +285,7 @@ function createQueuedReplayableEvent(
285
blockedOn: null | Container | SuspenseInstance,
286
topLevelType: DOMTopLevelEventType,
287
eventSystemFlags: EventSystemFlags,
258
- container: Document | Element | Node,
288
+ container: Document | Element,
289
nativeEvent: AnyNativeEvent,
290
): QueuedReplayableEvent {
291
return {
@@ -271,7 +301,7 @@ export function queueDiscreteEvent(
301
blockedOn: null | Container | SuspenseInstance,
302
topLevelType: DOMTopLevelEventType,
303
eventSystemFlags: EventSystemFlags,
274
- container: Document | Element | Node,
304
+ container: Document | Element,
305
nativeEvent: AnyNativeEvent,
306
): void {
307
const queuedEvent = createQueuedReplayableEvent(
@@ -346,7 +376,7 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
376
blockedOn: null | Container | SuspenseInstance,
377
topLevelType: DOMTopLevelEventType,
378
eventSystemFlags: EventSystemFlags,
349
- container: Document | Element | Node,
379
+ container: Document | Element,
380
nativeEvent: AnyNativeEvent,
381
): QueuedReplayableEvent {
382
if (
@@ -381,7 +411,7 @@ export function queueIfContinuousEvent(
411
blockedOn: null | Container | SuspenseInstance,
412
topLevelType: DOMTopLevelEventType,
413
eventSystemFlags: EventSystemFlags,
384
- container: Document | Element | Node,
414
+ container: Document | Element,
415
nativeEvent: AnyNativeEvent,
416
): boolean {
417
// These set relatedTarget to null because the replayed event will be treated as if we
packages/react-dom/src/events/SelectEventPlugin.js
+1
-1
@@ -26,7 +26,7 @@ import getActiveElement from '../client/getActiveElement';
26
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
27
import {hasSelectionCapabilities} from '../client/ReactInputSelection';
28
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
29
-import {isListeningToAllDependencies} from './DOMLegacyEventPluginSystem';
29
+import {isListeningToAllDependencies} from './DOMEventListenerMap';
30
31
const skipSelectionChangeEvent =
32
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
packages/react-dom/src/events/forks/EventListener-www.js
+6
-6
@@ -16,16 +16,16 @@ export function addEventBubbleListener(
16
element: Element,
17
eventType: string,
18
listener: Function,
19
-): void {
20
- EventListenerWWW.listen(element, eventType, listener);
19
+) {
20
+ return EventListenerWWW.listen(element, eventType, listener);
21
}
22
23
export function addEventCaptureListener(
24
element: Element,
25
eventType: string,
26
listener: Function,
27
-): void {
28
- EventListenerWWW.capture(element, eventType, listener);
27
+) {
28
+ return EventListenerWWW.capture(element, eventType, listener);
29
}
30
31
export function addEventCaptureListenerWithPassiveFlag(
@@ -33,8 +33,8 @@ export function addEventCaptureListenerWithPassiveFlag(
33
eventType: string,
34
listener: Function,
35
passive: boolean,
36
-): void {
37
- EventListenerWWW.captureWithPassiveFlag(
36
+) {
37
+ return EventListenerWWW.captureWithPassiveFlag(
38
element,
39
eventType,
40
listener,
scripts/error-codes/codes.json
+2
-1
@@ -345,5 +345,6 @@
345
"344": "Expected prepareToHydrateHostSuspenseInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
346
"345": "Root did not complete. This is a bug in React.",
347
"346": "An event responder context was used outside of an event cycle.",
348
- "347": "Maps are not valid as a React child (found: %s). Consider converting children to an array of keyed ReactElements instead."
348
+ "347": "Maps are not valid as a React child (found: %s). Consider converting children to an array of keyed ReactElements instead.",
349
+ "348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React."
350
}