Skip to content

Commit 26071ab

Browse files
authored
Refine event registration + event signatures (facebook#19244)
* Refine event registration + event signatures * Address feedback
1 parent 1cbaf48 commit 26071ab

File tree

10 files changed

+69
-69
lines changed

10 files changed

+69
-69
lines changed

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* @flow
88
*/
99

10+
import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
11+
1012
import {
1113
registrationNameDependencies,
1214
possibleRegistrationNames,
@@ -82,7 +84,7 @@ import {
8284
enableDeprecatedFlareAPI,
8385
enableTrustedTypesIntegration,
8486
} from 'shared/ReactFeatureFlags';
85-
import {listenToEvent} from '../events/DOMModernPluginEventSystem';
87+
import {listenToReactPropEvent} from '../events/DOMModernPluginEventSystem';
8688
import {getEventListenerMap} from './ReactDOMComponentTree';
8789

8890
let didWarnInvalidHydration = false;
@@ -262,7 +264,7 @@ if (__DEV__) {
262264

263265
export function ensureListeningTo(
264266
rootContainerInstance: Element | Node,
265-
registrationName: string,
267+
reactPropEvent: string,
266268
): void {
267269
// If we have a comment node, then use the parent node,
268270
// which should be an element.
@@ -279,7 +281,10 @@ export function ensureListeningTo(
279281
'ensureListeningTo(): received a container that was not an element node. ' +
280282
'This is likely a bug in React.',
281283
);
282-
listenToEvent(registrationName, ((rootContainerElement: any): Element));
284+
listenToReactPropEvent(
285+
reactPropEvent,
286+
((rootContainerElement: any): Element),
287+
);
283288
}
284289

285290
function getOwnerDocumentFromRootContainer(
@@ -1267,7 +1272,9 @@ export function listenToEventResponderEventTypes(
12671272
// existing passive event listener before we add the
12681273
// active event listener.
12691274
const passiveKey = targetEventType + '_passive';
1270-
const passiveItem = listenerMap.get(passiveKey);
1275+
const passiveItem = ((listenerMap.get(
1276+
passiveKey,
1277+
): any): ElementListenerMapEntry | void);
12711278
if (passiveItem !== undefined) {
12721279
removeTrappedEventListener(
12731280
document,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
4242

4343
export type ElementListenerMap = Map<
4444
DOMTopLevelEventType | string,
45-
ElementListenerMapEntry,
45+
ElementListenerMapEntry | null,
4646
>;
4747

4848
export type ElementListenerMapEntry = {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {ELEMENT_NODE} from '../shared/HTMLNodeType';
2626
import {
2727
listenToTopLevelEvent,
2828
addEventTypeToDispatchConfig,
29+
capturePhaseEvents,
2930
} from '../events/DOMModernPluginEventSystem';
3031

3132
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
@@ -98,11 +99,13 @@ function registerEventOnNearestTargetContainer(
9899
);
99100
}
100101
const listenerMap = getEventListenerMap(targetContainer);
102+
const capture = capturePhaseEvents.has(topLevelType);
101103
listenToTopLevelEvent(
102104
topLevelType,
103105
targetContainer,
104106
listenerMap,
105107
PLUGIN_EVENT_SYSTEM,
108+
capture,
106109
passive,
107110
priority,
108111
);
@@ -201,9 +204,9 @@ export function createEventHandle(
201204
eventTarget,
202205
listenerMap,
203206
PLUGIN_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
207+
capture,
204208
passive,
205209
priority,
206-
capture,
207210
);
208211
} else {
209212
invariant(

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import {
8181
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
8282
import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes';
8383
import {
84-
listenToEvent,
84+
listenToReactPropEvent,
8585
clearEventHandleListenersForTarget,
8686
} from '../events/DOMModernPluginEventSystem';
8787

@@ -1122,7 +1122,7 @@ export function makeOpaqueHydratingObject(
11221122
}
11231123

11241124
export function preparePortalMount(portalInstance: Instance): void {
1125-
listenToEvent('onMouseEnter', portalInstance);
1125+
listenToReactPropEvent('onMouseEnter', portalInstance);
11261126
}
11271127

11281128
export function prepareScopeUpdate(

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
LEGACY_FB_SUPPORT,
3131
IS_REPLAYED,
3232
IS_TARGET_PHASE_ONLY,
33+
IS_CAPTURE_PHASE,
3334
} from './EventSystemFlags';
3435

3536
import {
@@ -301,9 +302,9 @@ export function listenToTopLevelEvent(
301302
target: EventTarget,
302303
listenerMap: ElementListenerMap,
303304
eventSystemFlags: EventSystemFlags,
305+
capture: boolean,
304306
passive?: boolean,
305307
priority?: EventPriority,
306-
capture?: boolean,
307308
): void {
308309
// TOP_SELECTION_CHANGE needs to be attached to the document
309310
// otherwise it won't capture incoming events that are only
@@ -312,12 +313,10 @@ export function listenToTopLevelEvent(
312313
target = (target: any).ownerDocument || target;
313314
listenerMap = getEventListenerMap(target);
314315
}
315-
capture =
316-
capture === undefined ? capturePhaseEvents.has(topLevelType) : capture;
317316
const listenerMapKey = getListenerMapKey(topLevelType, capture);
318-
const listenerEntry: ElementListenerMapEntry | void = listenerMap.get(
317+
const listenerEntry = ((listenerMap.get(
319318
listenerMapKey,
320-
);
319+
): any): ElementListenerMapEntry | void);
321320
const shouldUpgrade = shouldUpgradeListener(listenerEntry, passive);
322321

323322
// If the listener entry is empty or we should upgrade, then
@@ -333,6 +332,9 @@ export function listenToTopLevelEvent(
333332
((listenerEntry: any): ElementListenerMapEntry).listener,
334333
);
335334
}
335+
if (capture) {
336+
eventSystemFlags |= IS_CAPTURE_PHASE;
337+
}
336338
const listener = addTrappedEventListener(
337339
target,
338340
topLevelType,
@@ -346,20 +348,31 @@ export function listenToTopLevelEvent(
346348
}
347349
}
348350

349-
export function listenToEvent(
350-
registrationName: string,
351+
export function listenToReactPropEvent(
352+
reactPropEvent: string,
351353
rootContainerElement: Element,
352354
): void {
353355
const listenerMap = getEventListenerMap(rootContainerElement);
354-
const dependencies = registrationNameDependencies[registrationName];
356+
// For optimization, let's check if we have the registration name
357+
// on the rootContainerElement.
358+
if (listenerMap.has(reactPropEvent)) {
359+
return;
360+
}
361+
// Add the registration name to the map, so we can avoid processing
362+
// this React prop event again.
363+
listenerMap.set(reactPropEvent, null);
364+
const dependencies = registrationNameDependencies[reactPropEvent];
355365

356366
for (let i = 0; i < dependencies.length; i++) {
357367
const dependency = dependencies[i];
368+
const capture = capturePhaseEvents.has(dependency);
369+
358370
listenToTopLevelEvent(
359371
dependency,
360372
rootContainerElement,
361373
listenerMap,
362374
PLUGIN_EVENT_SYSTEM,
375+
capture,
363376
);
364377
}
365378
}
@@ -892,10 +905,11 @@ export function accumulateEnterLeaveListeners(
892905
}
893906
}
894907

895-
export function accumulateEventTargetListeners(
908+
export function accumulateEventHandleTargetListeners(
896909
dispatchQueue: DispatchQueue,
897910
event: ReactSyntheticEvent,
898911
currentTarget: EventTarget,
912+
inCapturePhase: boolean,
899913
): void {
900914
const capturePhase: DispatchQueueItemPhase = [];
901915
const bubblePhase: DispatchQueueItemPhase = [];
@@ -904,17 +918,16 @@ export function accumulateEventTargetListeners(
904918
if (eventListeners !== null) {
905919
const listenersArr = Array.from(eventListeners);
906920
const targetType = ((event.type: any): DOMTopLevelEventType);
907-
const isCapturePhase = (event: any).eventPhase === 1;
908921

909922
for (let i = 0; i < listenersArr.length; i++) {
910923
const listener = listenersArr[i];
911924
const {callback, capture, type} = listener;
912925
if (type === targetType) {
913-
if (isCapturePhase && capture) {
926+
if (inCapturePhase && capture) {
914927
capturePhase.push(
915928
createDispatchQueueItemPhaseEntry(null, callback, currentTarget),
916929
);
917-
} else if (!isCapturePhase && !capture) {
930+
} else if (!inCapturePhase && !capture) {
918931
bubblePhase.push(
919932
createDispatchQueueItemPhaseEntry(null, callback, currentTarget),
920933
);

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ export type EventSystemFlags = number;
1212
export const PLUGIN_EVENT_SYSTEM = 1;
1313
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
1414
export const IS_TARGET_PHASE_ONLY = 1 << 2;
15-
export const IS_PASSIVE = 1 << 3;
16-
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
17-
export const IS_REPLAYED = 1 << 5;
18-
export const IS_FIRST_ANCESTOR = 1 << 6;
19-
export const LEGACY_FB_SUPPORT = 1 << 7;
15+
export const IS_CAPTURE_PHASE = 1 << 3;
16+
export const IS_PASSIVE = 1 << 4;
17+
export const PASSIVE_NOT_SUPPORTED = 1 << 5;
18+
export const IS_REPLAYED = 1 << 6;
19+
export const IS_FIRST_ANCESTOR = 1 << 7;
20+
export const LEGACY_FB_SUPPORT = 1 << 8;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ function trapReplayableEventForContainer(
220220
((container: any): Element),
221221
listenerMap,
222222
PLUGIN_EVENT_SYSTEM,
223+
false,
223224
);
224225
}
225226

packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@ import {
2929
} from '../../client/ReactDOMComponentTree';
3030
import {hasSelectionCapabilities} from '../../client/ReactInputSelection';
3131
import {DOCUMENT_NODE} from '../../shared/HTMLNodeType';
32-
import {
33-
accumulateTwoPhaseListeners,
34-
getListenerMapKey,
35-
capturePhaseEvents,
36-
} from '../DOMModernPluginEventSystem';
32+
import {accumulateTwoPhaseListeners} from '../DOMModernPluginEventSystem';
3733

3834
const skipSelectionChangeEvent =
3935
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
@@ -148,32 +144,6 @@ function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
148144
}
149145
}
150146

151-
function isListeningToEvents(
152-
events: Array<string>,
153-
mountAt: Document | Element,
154-
): boolean {
155-
const listenerMap = getEventListenerMap(mountAt);
156-
for (let i = 0; i < events.length; i++) {
157-
const event = events[i];
158-
const capture = capturePhaseEvents.has(event);
159-
const listenerMapKey = getListenerMapKey(event, capture);
160-
if (!listenerMap.has(listenerMapKey)) {
161-
return false;
162-
}
163-
}
164-
return true;
165-
}
166-
167-
function isListeningToEvent(
168-
registrationName: string,
169-
mountAt: Document | Element,
170-
): boolean {
171-
const listenerMap = getEventListenerMap(mountAt);
172-
const capture = capturePhaseEvents.has(registrationName);
173-
const listenerMapKey = getListenerMapKey(registrationName, capture);
174-
return listenerMap.has(listenerMapKey);
175-
}
176-
177147
/**
178148
* This plugin creates an `onSelect` event that normalizes select events
179149
* across form elements.
@@ -197,19 +167,17 @@ function extractEvents(
197167
eventSystemFlags,
198168
targetContainer,
199169
) {
200-
const doc = getEventTargetDocument(nativeEventTarget);
170+
const eventListenerMap = getEventListenerMap(targetContainer);
201171
// Track whether all listeners exists for this plugin. If none exist, we do
202172
// not extract events. See #3639.
203173
if (
204-
// We only listen to TOP_SELECTION_CHANGE on the document, never the
205-
// root.
206-
!isListeningToEvent(TOP_SELECTION_CHANGE, doc) ||
207174
// If we are handling TOP_SELECTION_CHANGE, then we don't need to
208175
// check for the other dependencies, as TOP_SELECTION_CHANGE is only
209176
// event attached from the onChange plugin and we don't expose an
210177
// onSelectionChange event from React.
211-
(topLevelType !== TOP_SELECTION_CHANGE &&
212-
!isListeningToEvents(rootTargetDependencies, targetContainer))
178+
topLevelType !== TOP_SELECTION_CHANGE &&
179+
!eventListenerMap.has('onSelect') &&
180+
!eventListenerMap.has('onSelectCapture')
213181
) {
214182
return;
215183
}

packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ import {
2424
} from '../DOMEventProperties';
2525
import {
2626
accumulateTwoPhaseListeners,
27-
accumulateEventTargetListeners,
27+
accumulateEventHandleTargetListeners,
2828
} from '../DOMModernPluginEventSystem';
2929
import {IS_TARGET_PHASE_ONLY} from '../EventSystemFlags';
30-
3130
import SyntheticAnimationEvent from '../SyntheticAnimationEvent';
3231
import SyntheticClipboardEvent from '../SyntheticClipboardEvent';
3332
import SyntheticFocusEvent from '../SyntheticFocusEvent';
@@ -40,6 +39,7 @@ import SyntheticTransitionEvent from '../SyntheticTransitionEvent';
4039
import SyntheticUIEvent from '../SyntheticUIEvent';
4140
import SyntheticWheelEvent from '../SyntheticWheelEvent';
4241
import getEventCharCode from '../getEventCharCode';
42+
import {IS_CAPTURE_PHASE} from '../EventSystemFlags';
4343

4444
import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
4545

@@ -158,7 +158,13 @@ function extractEvents(
158158
eventSystemFlags & IS_TARGET_PHASE_ONLY &&
159159
targetContainer != null
160160
) {
161-
accumulateEventTargetListeners(dispatchQueue, event, targetContainer);
161+
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
162+
accumulateEventHandleTargetListeners(
163+
dispatchQueue,
164+
event,
165+
targetContainer,
166+
inCapturePhase,
167+
);
162168
} else {
163169
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event, true);
164170
}

packages/react-interactions/events/src/dom/create-event-handle/Focus.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ function handleGlobalFocusVisibleEvent(
126126
}
127127

128128
const passiveObject = {passive: true};
129+
const passiveCaptureObject = {capture: true, passive: false};
129130

130131
function handleFocusVisibleTargetEvent(
131132
type: string,
@@ -242,8 +243,8 @@ export function useFocus(
242243
): void {
243244
// Setup controlled state for this useFocus hook
244245
const stateRef = useRef({isFocused: false, isFocusVisible: false});
245-
const focusHandle = useEvent('focus', passiveObject);
246-
const blurHandle = useEvent('blur', passiveObject);
246+
const focusHandle = useEvent('focus', passiveCaptureObject);
247+
const blurHandle = useEvent('blur', passiveCaptureObject);
247248
const focusVisibleHandles = useFocusVisibleInputHandles();
248249

249250
useEffect(() => {
@@ -329,8 +330,8 @@ export function useFocusWithin(
329330
) {
330331
// Setup controlled state for this useFocus hook
331332
const stateRef = useRef({isFocused: false, isFocusVisible: false});
332-
const focusHandle = useEvent('focus', passiveObject);
333-
const blurHandle = useEvent('blur', passiveObject);
333+
const focusHandle = useEvent('focus', passiveCaptureObject);
334+
const blurHandle = useEvent('blur', passiveCaptureObject);
334335
const afterBlurHandle = useEvent('afterblur', passiveObject);
335336
const beforeBlurHandle = useEvent('beforeblur', passiveObject);
336337
const focusVisibleHandles = useFocusVisibleInputHandles();

0 commit comments

Comments
 (0)