@samitouri / QOS-React-2 / commits / 26071abbe1

Refine event registration + event signatures (#19244)

* Refine event registration + event signatures * Address feedback

Dominic Gannaway committed Jul 6, 2020 at 17:42 UTC 26071abbe1eda6fb88dfaef9e7bce43f3a774818
10 files changed +69 -69
packages/react-dom/src/client/ReactDOMComponent.js
+11 -4
@@ -7,6 +7,8 @@
7 * @flow
8 */
9
10 +import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
11 +
12 import {
13 registrationNameDependencies,
14 possibleRegistrationNames,
@@ -82,7 +84,7 @@ import {
84 enableDeprecatedFlareAPI,
85 enableTrustedTypesIntegration,
86 } from 'shared/ReactFeatureFlags';
85 -import {listenToEvent} from '../events/DOMModernPluginEventSystem';
87 +import {listenToReactPropEvent} from '../events/DOMModernPluginEventSystem';
88 import {getEventListenerMap} from './ReactDOMComponentTree';
89
90 let didWarnInvalidHydration = false;
@@ -262,7 +264,7 @@ if (__DEV__) {
264
265 export function ensureListeningTo(
266 rootContainerInstance: Element | Node,
265 - registrationName: string,
267 + reactPropEvent: string,
268 ): void {
269 // If we have a comment node, then use the parent node,
270 // which should be an element.
@@ -279,7 +281,10 @@ export function ensureListeningTo(
281 'ensureListeningTo(): received a container that was not an element node. ' +
282 'This is likely a bug in React.',
283 );
282 - listenToEvent(registrationName, ((rootContainerElement: any): Element));
284 + listenToReactPropEvent(
285 + reactPropEvent,
286 + ((rootContainerElement: any): Element),
287 + );
288 }
289
290 function getOwnerDocumentFromRootContainer(
@@ -1267,7 +1272,9 @@ export function listenToEventResponderEventTypes(
1272 // existing passive event listener before we add the
1273 // active event listener.
1274 const passiveKey = targetEventType + '_passive';
1270 - const passiveItem = listenerMap.get(passiveKey);
1275 + const passiveItem = ((listenerMap.get(
1276 + passiveKey,
1277 + ): any): ElementListenerMapEntry | void);
1278 if (passiveItem !== undefined) {
1279 removeTrappedEventListener(
1280 document,
packages/react-dom/src/client/ReactDOMComponentTree.js
+1 -1
@@ -42,7 +42,7 @@ const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
42
43 export type ElementListenerMap = Map<
44 DOMTopLevelEventType | string,
45 - ElementListenerMapEntry,
45 + ElementListenerMapEntry | null,
46 >;
47
48 export type ElementListenerMapEntry = {
packages/react-dom/src/client/ReactDOMEventHandle.js
+4 -1
@@ -26,6 +26,7 @@ import {ELEMENT_NODE} from '../shared/HTMLNodeType';
26 import {
27 listenToTopLevelEvent,
28 addEventTypeToDispatchConfig,
29 + capturePhaseEvents,
30 } from '../events/DOMModernPluginEventSystem';
31
32 import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
@@ -98,11 +99,13 @@ function registerEventOnNearestTargetContainer(
99 );
100 }
101 const listenerMap = getEventListenerMap(targetContainer);
102 + const capture = capturePhaseEvents.has(topLevelType);
103 listenToTopLevelEvent(
104 topLevelType,
105 targetContainer,
106 listenerMap,
107 PLUGIN_EVENT_SYSTEM,
108 + capture,
109 passive,
110 priority,
111 );
@@ -201,9 +204,9 @@ export function createEventHandle(
204 eventTarget,
205 listenerMap,
206 PLUGIN_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
207 + capture,
208 passive,
209 priority,
206 - capture,
210 );
211 } else {
212 invariant(
packages/react-dom/src/client/ReactDOMHostConfig.js
+2 -2
@@ -81,7 +81,7 @@ import {
81 import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
82 import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes';
83 import {
84 - listenToEvent,
84 + listenToReactPropEvent,
85 clearEventHandleListenersForTarget,
86 } from '../events/DOMModernPluginEventSystem';
87
@@ -1122,7 +1122,7 @@ export function makeOpaqueHydratingObject(
1122 }
1123
1124 export function preparePortalMount(portalInstance: Instance): void {
1125 - listenToEvent('onMouseEnter', portalInstance);
1125 + listenToReactPropEvent('onMouseEnter', portalInstance);
1126 }
1127
1128 export function prepareScopeUpdate(
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+25 -12
@@ -30,6 +30,7 @@ import {
30 LEGACY_FB_SUPPORT,
31 IS_REPLAYED,
32 IS_TARGET_PHASE_ONLY,
33 + IS_CAPTURE_PHASE,
34 } from './EventSystemFlags';
35
36 import {
@@ -301,9 +302,9 @@ export function listenToTopLevelEvent(
302 target: EventTarget,
303 listenerMap: ElementListenerMap,
304 eventSystemFlags: EventSystemFlags,
305 + capture: boolean,
306 passive?: boolean,
307 priority?: EventPriority,
306 - capture?: boolean,
308 ): void {
309 // TOP_SELECTION_CHANGE needs to be attached to the document
310 // otherwise it won't capture incoming events that are only
@@ -312,12 +313,10 @@ export function listenToTopLevelEvent(
313 target = (target: any).ownerDocument || target;
314 listenerMap = getEventListenerMap(target);
315 }
315 - capture =
316 - capture === undefined ? capturePhaseEvents.has(topLevelType) : capture;
316 const listenerMapKey = getListenerMapKey(topLevelType, capture);
318 - const listenerEntry: ElementListenerMapEntry | void = listenerMap.get(
317 + const listenerEntry = ((listenerMap.get(
318 listenerMapKey,
320 - );
319 + ): any): ElementListenerMapEntry | void);
320 const shouldUpgrade = shouldUpgradeListener(listenerEntry, passive);
321
322 // If the listener entry is empty or we should upgrade, then
@@ -333,6 +332,9 @@ export function listenToTopLevelEvent(
332 ((listenerEntry: any): ElementListenerMapEntry).listener,
333 );
334 }
335 + if (capture) {
336 + eventSystemFlags |= IS_CAPTURE_PHASE;
337 + }
338 const listener = addTrappedEventListener(
339 target,
340 topLevelType,
@@ -346,20 +348,31 @@ export function listenToTopLevelEvent(
348 }
349 }
350
349 -export function listenToEvent(
350 - registrationName: string,
351 +export function listenToReactPropEvent(
352 + reactPropEvent: string,
353 rootContainerElement: Element,
354 ): void {
355 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];
365
366 for (let i = 0; i < dependencies.length; i++) {
367 const dependency = dependencies[i];
368 + const capture = capturePhaseEvents.has(dependency);
369 +
370 listenToTopLevelEvent(
371 dependency,
372 rootContainerElement,
373 listenerMap,
374 PLUGIN_EVENT_SYSTEM,
375 + capture,
376 );
377 }
378 }
@@ -892,10 +905,11 @@ export function accumulateEnterLeaveListeners(
905 }
906 }
907
895 -export function accumulateEventTargetListeners(
908 +export function accumulateEventHandleTargetListeners(
909 dispatchQueue: DispatchQueue,
910 event: ReactSyntheticEvent,
911 currentTarget: EventTarget,
912 + inCapturePhase: boolean,
913 ): void {
914 const capturePhase: DispatchQueueItemPhase = [];
915 const bubblePhase: DispatchQueueItemPhase = [];
@@ -904,17 +918,16 @@ export function accumulateEventTargetListeners(
918 if (eventListeners !== null) {
919 const listenersArr = Array.from(eventListeners);
920 const targetType = ((event.type: any): DOMTopLevelEventType);
907 - const isCapturePhase = (event: any).eventPhase === 1;
921
922 for (let i = 0; i < listenersArr.length; i++) {
923 const listener = listenersArr[i];
924 const {callback, capture, type} = listener;
925 if (type === targetType) {
913 - if (isCapturePhase && capture) {
926 + if (inCapturePhase && capture) {
927 capturePhase.push(
928 createDispatchQueueItemPhaseEntry(null, callback, currentTarget),
929 );
917 - } else if (!isCapturePhase && !capture) {
930 + } else if (!inCapturePhase && !capture) {
931 bubblePhase.push(
932 createDispatchQueueItemPhaseEntry(null, callback, currentTarget),
933 );
packages/react-dom/src/events/EventSystemFlags.js
+6 -5
@@ -12,8 +12,9 @@ export type EventSystemFlags = number;
12 export const PLUGIN_EVENT_SYSTEM = 1;
13 export const RESPONDER_EVENT_SYSTEM = 1 << 1;
14 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
+1
@@ -220,6 +220,7 @@ function trapReplayableEventForContainer(
220 ((container: any): Element),
221 listenerMap,
222 PLUGIN_EVENT_SYSTEM,
223 + false,
224 );
225 }
226
packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js
+5 -37
@@ -29,11 +29,7 @@ import {
29 } from '../../client/ReactDOMComponentTree';
30 import {hasSelectionCapabilities} from '../../client/ReactInputSelection';
31 import {DOCUMENT_NODE} from '../../shared/HTMLNodeType';
32 -import {
33 - accumulateTwoPhaseListeners,
34 - getListenerMapKey,
35 - capturePhaseEvents,
36 -} from '../DOMModernPluginEventSystem';
32 +import {accumulateTwoPhaseListeners} from '../DOMModernPluginEventSystem';
33
34 const skipSelectionChangeEvent =
35 canUseDOM && 'documentMode' in document && document.documentMode <= 11;
@@ -148,32 +144,6 @@ function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
144 }
145 }
146
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 -
147 /**
148 * This plugin creates an `onSelect` event that normalizes select events
149 * across form elements.
@@ -197,19 +167,17 @@ function extractEvents(
167 eventSystemFlags,
168 targetContainer,
169 ) {
200 - const doc = getEventTargetDocument(nativeEventTarget);
170 + const eventListenerMap = getEventListenerMap(targetContainer);
171 // Track whether all listeners exists for this plugin. If none exist, we do
172 // not extract events. See #3639.
173 if (
204 - // We only listen to TOP_SELECTION_CHANGE on the document, never the
205 - // root.
206 - !isListeningToEvent(TOP_SELECTION_CHANGE, doc) ||
174 // If we are handling TOP_SELECTION_CHANGE, then we don't need to
175 // check for the other dependencies, as TOP_SELECTION_CHANGE is only
176 // event attached from the onChange plugin and we don't expose an
177 // 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')
181 ) {
182 return;
183 }
packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js
+9 -3
@@ -24,10 +24,9 @@ import {
24 } from '../DOMEventProperties';
25 import {
26 accumulateTwoPhaseListeners,
27 - accumulateEventTargetListeners,
27 + accumulateEventHandleTargetListeners,
28 } from '../DOMModernPluginEventSystem';
29 import {IS_TARGET_PHASE_ONLY} from '../EventSystemFlags';
30 -
30 import SyntheticAnimationEvent from '../SyntheticAnimationEvent';
31 import SyntheticClipboardEvent from '../SyntheticClipboardEvent';
32 import SyntheticFocusEvent from '../SyntheticFocusEvent';
@@ -40,6 +39,7 @@ import SyntheticTransitionEvent from '../SyntheticTransitionEvent';
39 import SyntheticUIEvent from '../SyntheticUIEvent';
40 import SyntheticWheelEvent from '../SyntheticWheelEvent';
41 import getEventCharCode from '../getEventCharCode';
42 +import {IS_CAPTURE_PHASE} from '../EventSystemFlags';
43
44 import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
45
@@ -158,7 +158,13 @@ function extractEvents(
158 eventSystemFlags & IS_TARGET_PHASE_ONLY &&
159 targetContainer != null
160 ) {
161 - accumulateEventTargetListeners(dispatchQueue, event, targetContainer);
161 + const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
162 + accumulateEventHandleTargetListeners(
163 + dispatchQueue,
164 + event,
165 + targetContainer,
166 + inCapturePhase,
167 + );
168 } else {
169 accumulateTwoPhaseListeners(targetInst, dispatchQueue, event, true);
170 }
packages/react-interactions/events/src/dom/create-event-handle/Focus.js
+5 -4
@@ -126,6 +126,7 @@ function handleGlobalFocusVisibleEvent(
126 }
127
128 const passiveObject = {passive: true};
129 +const passiveCaptureObject = {capture: true, passive: false};
130
131 function handleFocusVisibleTargetEvent(
132 type: string,
@@ -242,8 +243,8 @@ export function useFocus(
243 ): void {
244 // Setup controlled state for this useFocus hook
245 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);
248 const focusVisibleHandles = useFocusVisibleInputHandles();
249
250 useEffect(() => {
@@ -329,8 +330,8 @@ export function useFocusWithin(
330 ) {
331 // Setup controlled state for this useFocus hook
332 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);
335 const afterBlurHandle = useEvent('afterblur', passiveObject);
336 const beforeBlurHandle = useEvent('beforeblur', passiveObject);
337 const focusVisibleHandles = useFocusVisibleInputHandles();