Skip to content

Commit 62861bb

Browse files
authored
More event system cleanup and scaffolding (#18179)
1 parent 8ccfce4 commit 62861bb

10 files changed

+396
-134
lines changed

packages/legacy-events/PluginModuleType.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type PluginModule<NativeEvent> = {
2929
nativeTarget: NativeEvent,
3030
nativeEventTarget: null | EventTarget,
3131
eventSystemFlags: EventSystemFlags,
32-
container?: Document | Element | Node,
32+
container?: Document | Element,
3333
) => ?ReactSyntheticEvent,
3434
tapMoveThreshold?: number,
3535
};

packages/react-dom/src/client/ReactDOMComponent.js

+103-37
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
1111
import {canUseDOM} from 'shared/ExecutionEnvironment';
1212
import endsWith from 'shared/endsWith';
13+
import invariant from 'shared/invariant';
1314
import {setListenToResponderEventTypes} from '../events/DeprecatedDOMEventResponderSystem';
1415

1516
import {
@@ -59,7 +60,6 @@ import {getListenerMapForElement} from '../events/DOMEventListenerMap';
5960
import {
6061
addResponderEventSystemEvent,
6162
removeActiveResponderEventSystemEvent,
62-
trapBubbledEvent,
6363
} from '../events/ReactDOMEventListener.js';
6464
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
6565
import {
@@ -74,7 +74,12 @@ import {
7474
shouldRemoveAttribute,
7575
} from '../shared/DOMProperty';
7676
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';
7883
import isCustomComponent from '../shared/isCustomComponent';
7984
import possibleStandardNames from '../shared/possibleStandardNames';
8085
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
@@ -84,8 +89,13 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
8489
import {
8590
enableDeprecatedFlareAPI,
8691
enableTrustedTypesIntegration,
92+
enableModernEventSystem,
8793
} 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';
8999

90100
let didWarnInvalidHydration = false;
91101
let didWarnScriptTags = false;
@@ -260,16 +270,36 @@ if (__DEV__) {
260270
}
261271

262272
function ensureListeningTo(
263-
rootContainerElement: Element | Node,
273+
rootContainerInstance: Element | Node,
264274
registrationName: string,
265275
): 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+
}
273303
}
274304

275305
function getOwnerDocumentFromRootContainer(
@@ -514,41 +544,55 @@ export function setInitialProperties(
514544
case 'iframe':
515545
case 'object':
516546
case 'embed':
517-
trapBubbledEvent(TOP_LOAD, domElement);
547+
if (!enableModernEventSystem) {
548+
legacyTrapBubbledEvent(TOP_LOAD, domElement);
549+
}
518550
props = rawProps;
519551
break;
520552
case 'video':
521553
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+
}
525559
}
526560
props = rawProps;
527561
break;
528562
case 'source':
529-
trapBubbledEvent(TOP_ERROR, domElement);
563+
if (!enableModernEventSystem) {
564+
legacyTrapBubbledEvent(TOP_ERROR, domElement);
565+
}
530566
props = rawProps;
531567
break;
532568
case 'img':
533569
case 'image':
534570
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+
}
537575
props = rawProps;
538576
break;
539577
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+
}
542582
props = rawProps;
543583
break;
544584
case 'details':
545-
trapBubbledEvent(TOP_TOGGLE, domElement);
585+
if (!enableModernEventSystem) {
586+
legacyTrapBubbledEvent(TOP_TOGGLE, domElement);
587+
}
546588
props = rawProps;
547589
break;
548590
case 'input':
549591
ReactDOMInputInitWrapperState(domElement, rawProps);
550592
props = ReactDOMInputGetHostProps(domElement, rawProps);
551-
trapBubbledEvent(TOP_INVALID, domElement);
593+
if (!enableModernEventSystem) {
594+
legacyTrapBubbledEvent(TOP_INVALID, domElement);
595+
}
552596
// For controlled components we always need to ensure we're listening
553597
// to onChange. Even if there is no listener.
554598
ensureListeningTo(rootContainerElement, 'onChange');
@@ -560,15 +604,19 @@ export function setInitialProperties(
560604
case 'select':
561605
ReactDOMSelectInitWrapperState(domElement, rawProps);
562606
props = ReactDOMSelectGetHostProps(domElement, rawProps);
563-
trapBubbledEvent(TOP_INVALID, domElement);
607+
if (!enableModernEventSystem) {
608+
legacyTrapBubbledEvent(TOP_INVALID, domElement);
609+
}
564610
// For controlled components we always need to ensure we're listening
565611
// to onChange. Even if there is no listener.
566612
ensureListeningTo(rootContainerElement, 'onChange');
567613
break;
568614
case 'textarea':
569615
ReactDOMTextareaInitWrapperState(domElement, rawProps);
570616
props = ReactDOMTextareaGetHostProps(domElement, rawProps);
571-
trapBubbledEvent(TOP_INVALID, domElement);
617+
if (!enableModernEventSystem) {
618+
legacyTrapBubbledEvent(TOP_INVALID, domElement);
619+
}
572620
// For controlled components we always need to ensure we're listening
573621
// to onChange. Even if there is no listener.
574622
ensureListeningTo(rootContainerElement, 'onChange');
@@ -898,34 +946,48 @@ export function diffHydratedProperties(
898946
case 'iframe':
899947
case 'object':
900948
case 'embed':
901-
trapBubbledEvent(TOP_LOAD, domElement);
949+
if (!enableModernEventSystem) {
950+
legacyTrapBubbledEvent(TOP_LOAD, domElement);
951+
}
902952
break;
903953
case 'video':
904954
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+
}
908960
}
909961
break;
910962
case 'source':
911-
trapBubbledEvent(TOP_ERROR, domElement);
963+
if (!enableModernEventSystem) {
964+
legacyTrapBubbledEvent(TOP_ERROR, domElement);
965+
}
912966
break;
913967
case 'img':
914968
case 'image':
915969
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+
}
918974
break;
919975
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+
}
922980
break;
923981
case 'details':
924-
trapBubbledEvent(TOP_TOGGLE, domElement);
982+
if (!enableModernEventSystem) {
983+
legacyTrapBubbledEvent(TOP_TOGGLE, domElement);
984+
}
925985
break;
926986
case 'input':
927987
ReactDOMInputInitWrapperState(domElement, rawProps);
928-
trapBubbledEvent(TOP_INVALID, domElement);
988+
if (!enableModernEventSystem) {
989+
legacyTrapBubbledEvent(TOP_INVALID, domElement);
990+
}
929991
// For controlled components we always need to ensure we're listening
930992
// to onChange. Even if there is no listener.
931993
ensureListeningTo(rootContainerElement, 'onChange');
@@ -935,14 +997,18 @@ export function diffHydratedProperties(
935997
break;
936998
case 'select':
937999
ReactDOMSelectInitWrapperState(domElement, rawProps);
938-
trapBubbledEvent(TOP_INVALID, domElement);
1000+
if (!enableModernEventSystem) {
1001+
legacyTrapBubbledEvent(TOP_INVALID, domElement);
1002+
}
9391003
// For controlled components we always need to ensure we're listening
9401004
// to onChange. Even if there is no listener.
9411005
ensureListeningTo(rootContainerElement, 'onChange');
9421006
break;
9431007
case 'textarea':
9441008
ReactDOMTextareaInitWrapperState(domElement, rawProps);
945-
trapBubbledEvent(TOP_INVALID, domElement);
1009+
if (!enableModernEventSystem) {
1010+
legacyTrapBubbledEvent(TOP_INVALID, domElement);
1011+
}
9461012
// For controlled components we always need to ensure we're listening
9471013
// to onChange. Even if there is no listener.
9481014
ensureListeningTo(rootContainerElement, 'onChange');

packages/react-dom/src/events/DOMEventListenerMap.js

+18
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
1111

12+
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
13+
1214
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
1315
// prettier-ignore
1416
const elementListenerMap:
@@ -29,3 +31,19 @@ export function getListenerMapForElement(
2931
}
3032
return listenerMap;
3133
}
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
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
2525

2626
import getEventTarget from './getEventTarget';
2727
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
28-
import {trapCapturedEvent, trapBubbledEvent} from './ReactDOMEventListener';
2928
import {getListenerMapForElement} from './DOMEventListenerMap';
3029
import isEventSupported from './isEventSupported';
3130
import {
@@ -40,6 +39,7 @@ import {
4039
getRawEventName,
4140
mediaEventTypes,
4241
} from './DOMTopLevelEventTypes';
42+
import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
4343

4444
/**
4545
* Summary of `DOMEventPluginSystem` event handling:
@@ -309,7 +309,7 @@ export function dispatchEventForLegacyPluginEventSystem(
309309
*/
310310
export function legacyListenToEvent(
311311
registrationName: string,
312-
mountAt: Document | Element | Node,
312+
mountAt: Document | Element,
313313
): void {
314314
const listenerMap = getListenerMapForElement(mountAt);
315315
const dependencies = registrationNameDependencies[registrationName];
@@ -322,18 +322,18 @@ export function legacyListenToEvent(
322322

323323
export function legacyListenToTopLevelEvent(
324324
topLevelType: DOMTopLevelEventType,
325-
mountAt: Document | Element | Node,
325+
mountAt: Document | Element,
326326
listenerMap: Map<DOMTopLevelEventType | string, null | (any => void)>,
327327
): void {
328328
if (!listenerMap.has(topLevelType)) {
329329
switch (topLevelType) {
330330
case TOP_SCROLL:
331-
trapCapturedEvent(TOP_SCROLL, mountAt);
331+
legacyTrapCapturedEvent(TOP_SCROLL, mountAt);
332332
break;
333333
case TOP_FOCUS:
334334
case TOP_BLUR:
335-
trapCapturedEvent(TOP_FOCUS, mountAt);
336-
trapCapturedEvent(TOP_BLUR, mountAt);
335+
legacyTrapCapturedEvent(TOP_FOCUS, mountAt);
336+
legacyTrapCapturedEvent(TOP_BLUR, mountAt);
337337
// We set the flag for a single dependency later in this function,
338338
// but this ensures we mark both as attached rather than just one.
339339
listenerMap.set(TOP_BLUR, null);
@@ -342,7 +342,7 @@ export function legacyListenToTopLevelEvent(
342342
case TOP_CANCEL:
343343
case TOP_CLOSE:
344344
if (isEventSupported(getRawEventName(topLevelType))) {
345-
trapCapturedEvent(topLevelType, mountAt);
345+
legacyTrapCapturedEvent(topLevelType, mountAt);
346346
}
347347
break;
348348
case TOP_INVALID:
@@ -356,26 +356,24 @@ export function legacyListenToTopLevelEvent(
356356
// Media events don't bubble so adding the listener wouldn't do anything.
357357
const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1;
358358
if (!isMediaEvent) {
359-
trapBubbledEvent(topLevelType, mountAt);
359+
legacyTrapBubbledEvent(topLevelType, mountAt);
360360
}
361361
break;
362362
}
363363
listenerMap.set(topLevelType, null);
364364
}
365365
}
366366

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+
}
373373

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);
381379
}

0 commit comments

Comments
 (0)