@samitouri / QOS-React-2 / commits / 4e3545fd6f

ReactDOMEventListener: clean up module (#18713)

Dominic Gannaway committed Apr 23, 2020 at 20:19 UTC 4e3545fd6fc62842d7dbf772923214474f59871a
8 files changed +193 -204
packages/react-dom/src/client/ReactDOMComponent.js
+5 -5
@@ -10,7 +10,11 @@
10 import {registrationNameModules} from 'legacy-events/EventPluginRegistry';
11 import {canUseDOM} from 'shared/ExecutionEnvironment';
12 import invariant from 'shared/invariant';
13 -import {setListenToResponderEventTypes} from '../events/DeprecatedDOMEventResponderSystem';
13 +import {
14 + setListenToResponderEventTypes,
15 + addResponderEventSystemEvent,
16 + removeTrappedEventListener,
17 +} from '../events/DeprecatedDOMEventResponderSystem';
18
19 import {
20 getValueForAttribute,
@@ -56,10 +60,6 @@ import {
60 TOP_TOGGLE,
61 } from '../events/DOMTopLevelEventTypes';
62 import {getListenerMapForElement} from '../events/DOMEventListenerMap';
59 -import {
60 - addResponderEventSystemEvent,
61 - removeTrappedEventListener,
62 -} from '../events/ReactDOMEventListener.js';
63 import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
64 import {
65 createDangerousStringForStyles,
packages/react-dom/src/events/DOMLegacyEventPluginSystem.js
+20 -1
@@ -44,9 +44,10 @@ import {
44 getRawEventName,
45 mediaEventTypes,
46 } from './DOMTopLevelEventTypes';
47 -import {addTrappedEventListener} from './ReactDOMEventListener';
47 +import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener';
48 import {batchedEventUpdates} from './ReactDOMUpdateBatching';
49 import getListener from './getListener';
50 +import {addEventCaptureListener, addEventBubbleListener} from './EventListener';
51
52 /**
53 * Summary of `DOMEventPluginSystem` event handling:
@@ -399,6 +400,24 @@ export function legacyTrapCapturedEvent(
400 listenerMap.set(topLevelType, {passive: undefined, listener});
401 }
402
403 +function addTrappedEventListener(
404 + targetContainer: EventTarget,
405 + topLevelType: DOMTopLevelEventType,
406 + eventSystemFlags: EventSystemFlags,
407 + capture: boolean,
408 +): any => void {
409 + const rawEventName = getRawEventName(topLevelType);
410 + const listener = createEventListenerWrapperWithPriority(
411 + targetContainer,
412 + topLevelType,
413 + eventSystemFlags,
414 + );
415 + const unsubscribeListener = capture
416 + ? addEventCaptureListener(targetContainer, rawEventName, listener)
417 + : addEventBubbleListener(targetContainer, rawEventName, listener);
418 + return unsubscribeListener;
419 +}
420 +
421 function getParent(inst: Object | null): Object | null {
422 if (!inst) {
423 return null;
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+86 -54
@@ -35,10 +35,6 @@ import {
35 HostComponent,
36 } from 'react-reconciler/src/ReactWorkTags';
37
38 -import {
39 - addTrappedEventListener,
40 - removeTrappedEventListener,
41 -} from './ReactDOMEventListener';
38 import getEventTarget from './getEventTarget';
39 import {getListenerMapForElement} from './DOMEventListenerMap';
40 import {
@@ -76,17 +72,25 @@ import {
72 TOP_PLAYING,
73 TOP_CLICK,
74 TOP_SELECTION_CHANGE,
75 + getRawEventName,
76 } from './DOMTopLevelEventTypes';
77 import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
78 import {COMMENT_NODE} from '../shared/HTMLNodeType';
79 import {batchedEventUpdates} from './ReactDOMUpdateBatching';
80 import getListener from './getListener';
81 +import {passiveBrowserEventsSupported} from './checkPassiveEvents';
82
83 import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
84 import {
85 invokeGuardedCallbackAndCatchFirstError,
86 rethrowCaughtError,
87 } from 'shared/ReactErrorUtils';
88 +import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener';
89 +import {
90 + removeEventListener,
91 + addEventCaptureListener,
92 + addEventBubbleListener,
93 +} from './EventListener';
94
95 const capturePhaseEvents = new Set([
96 TOP_FOCUS,
@@ -219,25 +223,6 @@ function dispatchEventsForPlugins(
223 dispatchEventsInBatch(syntheticEvents);
224 }
225
222 -function shouldUpgradeListener(
223 - listenerEntry: void | ElementListenerMapEntry,
224 - passive: void | boolean,
225 -): boolean {
226 - if (listenerEntry === undefined) {
227 - return false;
228 - }
229 - // Upgrade from passive to active.
230 - if (passive !== true && listenerEntry.passive) {
231 - return true;
232 - }
233 - // Upgrade from default-active (browser default) to active.
234 - if (passive === false && listenerEntry.passive === undefined) {
235 - return true;
236 - }
237 - // Otherwise, do not upgrade
238 - return false;
239 -}
240 -
226 export function listenToTopLevelEvent(
227 topLevelType: DOMTopLevelEventType,
228 targetContainer: EventTarget,
@@ -247,21 +232,6 @@ export function listenToTopLevelEvent(
232 priority?: EventPriority,
233 capture?: boolean,
234 ): void {
250 - // If we explicitly define capture, then these are for EventTarget objects,
251 - // rather than React managed DOM elements. So we need to ensure we separate
252 - // capture and non-capture events. For React managed DOM nodes we only use
253 - // one or the other, never both. Which one we use is determined by the the
254 - // capturePhaseEvents Set (in this module) that defines if the event listener
255 - // should use the capture phase – otherwise we always use the bubble phase.
256 - // Finally, when we get to dispatching and accumulating event listeners, we
257 - // check if the user wanted capture/bubble and emulate the behavior at that
258 - // point (we call this accumulating two phase listeners).
259 - const typeStr = ((topLevelType: any): string);
260 - const listenerMapKey =
261 - capture === undefined
262 - ? topLevelType
263 - : `${typeStr}_${capture ? 'capture' : 'bubble'}`;
264 -
235 // TOP_SELECTION_CHANGE needs to be attached to the document
236 // otherwise it won't capture incoming events that are only
237 // triggered on the document directly.
@@ -269,21 +239,12 @@ export function listenToTopLevelEvent(
239 targetContainer = (targetContainer: any).ownerDocument || targetContainer;
240 listenerMap = getListenerMapForElement(targetContainer);
241 }
272 - const listenerEntry = listenerMap.get(listenerMapKey);
273 - const shouldUpgrade = shouldUpgradeListener(listenerEntry, passive);
274 - if (listenerEntry === undefined || shouldUpgrade) {
275 - const isCapturePhase =
276 - capture === undefined ? capturePhaseEvents.has(topLevelType) : capture;
277 - // If we should upgrade, then we need to remove the existing trapped
278 - // event listener for the target container.
279 - if (shouldUpgrade) {
280 - removeTrappedEventListener(
281 - targetContainer,
282 - topLevelType,
283 - isCapturePhase,
284 - ((listenerEntry: any): ElementListenerMapEntry).listener,
285 - );
286 - }
242 + const listenerEntry: ElementListenerMapEntry | void = listenerMap.get(
243 + topLevelType,
244 + );
245 + const isCapturePhase =
246 + capture === undefined ? capturePhaseEvents.has(topLevelType) : capture;
247 + if (listenerEntry === undefined) {
248 const listener = addTrappedEventListener(
249 targetContainer,
250 topLevelType,
@@ -293,7 +254,7 @@ export function listenToTopLevelEvent(
254 passive,
255 priority,
256 );
296 - listenerMap.set(listenerMapKey, {passive, listener});
257 + listenerMap.set(topLevelType, {passive, listener});
258 }
259 }
260
@@ -315,6 +276,77 @@ export function listenToEvent(
276 }
277 }
278
279 +function addTrappedEventListener(
280 + targetContainer: EventTarget,
281 + topLevelType: DOMTopLevelEventType,
282 + eventSystemFlags: EventSystemFlags,
283 + capture: boolean,
284 + isDeferredListenerForLegacyFBSupport?: boolean,
285 + passive?: boolean,
286 + priority?: EventPriority,
287 +): any => void {
288 + let listener = createEventListenerWrapperWithPriority(
289 + targetContainer,
290 + topLevelType,
291 + eventSystemFlags,
292 + priority,
293 + );
294 + // If passive option is not supported, then the event will be
295 + // active and not passive.
296 + if (passive === true && !passiveBrowserEventsSupported) {
297 + passive = false;
298 + }
299 +
300 + targetContainer =
301 + enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
302 + ? (targetContainer: any).ownerDocument
303 + : targetContainer;
304 +
305 + const rawEventName = getRawEventName(topLevelType);
306 +
307 + let unsubscribeListener;
308 + // When legacyFBSupport is enabled, it's for when we
309 + // want to add a one time event listener to a container.
310 + // This should only be used with enableLegacyFBSupport
311 + // due to requirement to provide compatibility with
312 + // internal FB www event tooling. This works by removing
313 + // the event listener as soon as it is invoked. We could
314 + // also attempt to use the {once: true} param on
315 + // addEventListener, but that requires support and some
316 + // browsers do not support this today, and given this is
317 + // to support legacy code patterns, it's likely they'll
318 + // need support for such browsers.
319 + if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
320 + const originalListener = listener;
321 + listener = function(...p) {
322 + try {
323 + return originalListener.apply(this, p);
324 + } finally {
325 + removeEventListener(
326 + targetContainer,
327 + rawEventName,
328 + unsubscribeListener,
329 + capture,
330 + );
331 + }
332 + };
333 + }
334 + if (capture) {
335 + unsubscribeListener = addEventCaptureListener(
336 + targetContainer,
337 + rawEventName,
338 + listener,
339 + );
340 + } else {
341 + unsubscribeListener = addEventBubbleListener(
342 + targetContainer,
343 + rawEventName,
344 + listener,
345 + );
346 + }
347 + return unsubscribeListener;
348 +}
349 +
350 function willDeferLaterForLegacyFBSupport(
351 topLevelType: DOMTopLevelEventType,
352 targetContainer: EventTarget,
packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js
+57
@@ -10,6 +10,7 @@ import {
10 type EventSystemFlags,
11 IS_PASSIVE,
12 PASSIVE_NOT_SUPPORTED,
13 + RESPONDER_EVENT_SYSTEM,
14 } from './EventSystemFlags';
15 import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
16 import {
@@ -37,6 +38,15 @@ import invariant from 'shared/invariant';
38
39 import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
40 import {enqueueStateRestore} from './ReactDOMControlledComponent';
41 +import {createEventListenerWrapper} from './ReactDOMEventListener';
42 +import {passiveBrowserEventsSupported} from './checkPassiveEvents';
43 +import {getRawEventName} from './DOMTopLevelEventTypes';
44 +import {
45 + addEventCaptureListener,
46 + addEventCaptureListenerWithPassiveFlag,
47 + removeEventListener,
48 +} from './EventListener';
49 +
50 import {
51 ContinuousEvent,
52 UserBlockingEvent,
@@ -571,3 +581,50 @@ function DEPRECATED_registerRootEventType(
581 rootEventTypesSet.add(rootEventType);
582 rootEventResponderInstances.add(eventResponderInstance);
583 }
584 +
585 +export function addResponderEventSystemEvent(
586 + document: Document,
587 + topLevelType: string,
588 + passive: boolean,
589 +): any => void {
590 + let eventFlags = RESPONDER_EVENT_SYSTEM;
591 +
592 + // If passive option is not supported, then the event will be
593 + // active and not passive, but we flag it as using not being
594 + // supported too. This way the responder event plugins know,
595 + // and can provide polyfills if needed.
596 + if (passive) {
597 + if (passiveBrowserEventsSupported) {
598 + eventFlags |= IS_PASSIVE;
599 + } else {
600 + eventFlags |= PASSIVE_NOT_SUPPORTED;
601 + passive = false;
602 + }
603 + }
604 + // Check if interactive and wrap in discreteUpdates
605 + const listener = createEventListenerWrapper(
606 + document,
607 + ((topLevelType: any): DOMTopLevelEventType),
608 + eventFlags,
609 + );
610 + if (passiveBrowserEventsSupported) {
611 + return addEventCaptureListenerWithPassiveFlag(
612 + document,
613 + topLevelType,
614 + listener,
615 + passive,
616 + );
617 + } else {
618 + return addEventCaptureListener(document, topLevelType, listener);
619 + }
620 +}
621 +
622 +export function removeTrappedEventListener(
623 + targetContainer: EventTarget,
624 + topLevelType: DOMTopLevelEventType,
625 + capture: boolean,
626 + listener: any => void,
627 +): void {
628 + const rawEventName = getRawEventName(topLevelType);
629 + removeEventListener(targetContainer, rawEventName, listener, capture);
630 +}
packages/react-dom/src/events/ReactDOMEventListener.js
+12 -116
@@ -36,20 +36,10 @@ import {
36 LEGACY_FB_SUPPORT,
37 PLUGIN_EVENT_SYSTEM,
38 RESPONDER_EVENT_SYSTEM,
39 - IS_PASSIVE,
40 - PASSIVE_NOT_SUPPORTED,
39 } from './EventSystemFlags';
40
43 -import {
44 - addEventBubbleListener,
45 - addEventCaptureListener,
46 - addEventCaptureListenerWithPassiveFlag,
47 - removeEventListener,
48 -} from './EventListener';
41 import getEventTarget from './getEventTarget';
42 import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
51 -import {getRawEventName} from './DOMTopLevelEventTypes';
52 -import {passiveBrowserEventsSupported} from './checkPassiveEvents';
43
44 import {
45 enableDeprecatedFlareAPI,
@@ -85,58 +75,29 @@ export function isEnabled() {
75 return _enabled;
76 }
77
88 -export function addResponderEventSystemEvent(
89 - document: Document,
90 - topLevelType: string,
91 - passive: boolean,
92 -): any => void {
93 - let eventFlags = RESPONDER_EVENT_SYSTEM;
94 -
95 - // If passive option is not supported, then the event will be
96 - // active and not passive, but we flag it as using not being
97 - // supported too. This way the responder event plugins know,
98 - // and can provide polyfills if needed.
99 - if (passive) {
100 - if (passiveBrowserEventsSupported) {
101 - eventFlags |= IS_PASSIVE;
102 - } else {
103 - eventFlags |= PASSIVE_NOT_SUPPORTED;
104 - passive = false;
105 - }
106 - }
107 - // Check if interactive and wrap in discreteUpdates
108 - const listener = dispatchEvent.bind(
78 +export function createEventListenerWrapper(
79 + targetContainer: EventTarget,
80 + topLevelType: DOMTopLevelEventType,
81 + eventSystemFlags: EventSystemFlags,
82 +): Function {
83 + return dispatchEvent.bind(
84 null,
110 - ((topLevelType: any): DOMTopLevelEventType),
111 - eventFlags,
112 - document,
85 + topLevelType,
86 + eventSystemFlags,
87 + targetContainer,
88 );
114 - if (passiveBrowserEventsSupported) {
115 - return addEventCaptureListenerWithPassiveFlag(
116 - document,
117 - topLevelType,
118 - listener,
119 - passive,
120 - );
121 - } else {
122 - return addEventCaptureListener(document, topLevelType, listener);
123 - }
89 }
90
126 -export function addTrappedEventListener(
91 +export function createEventListenerWrapperWithPriority(
92 targetContainer: EventTarget,
93 topLevelType: DOMTopLevelEventType,
94 eventSystemFlags: EventSystemFlags,
130 - capture: boolean,
131 - isDeferredListenerForLegacyFBSupport?: boolean,
132 - passive?: boolean,
95 priority?: EventPriority,
134 -): any => void {
96 +): Function {
97 const eventPriority =
98 priority === undefined
99 ? getEventPriorityForPluginSystem(topLevelType)
100 : priority;
139 - let listener;
101 let listenerWrapper;
102 switch (eventPriority) {
103 case DiscreteEvent:
@@ -150,77 +111,12 @@ export function addTrappedEventListener(
111 listenerWrapper = dispatchEvent;
112 break;
113 }
153 - // If passive option is not supported, then the event will be
154 - // active and not passive.
155 - if (passive === true && !passiveBrowserEventsSupported) {
156 - passive = false;
157 - }
158 -
159 - listener = listenerWrapper.bind(
114 + return listenerWrapper.bind(
115 null,
116 topLevelType,
117 eventSystemFlags,
118 targetContainer,
119 );
165 -
166 - targetContainer =
167 - enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
168 - ? (targetContainer: any).ownerDocument
169 - : targetContainer;
170 -
171 - const rawEventName = getRawEventName(topLevelType);
172 -
173 - let unsubscribeListener;
174 - // When legacyFBSupport is enabled, it's for when we
175 - // want to add a one time event listener to a container.
176 - // This should only be used with enableLegacyFBSupport
177 - // due to requirement to provide compatibility with
178 - // internal FB www event tooling. This works by removing
179 - // the event listener as soon as it is invoked. We could
180 - // also attempt to use the {once: true} param on
181 - // addEventListener, but that requires support and some
182 - // browsers do not support this today, and given this is
183 - // to support legacy code patterns, it's likely they'll
184 - // need support for such browsers.
185 - if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
186 - const originalListener = listener;
187 - listener = function(...p) {
188 - try {
189 - return originalListener.apply(this, p);
190 - } finally {
191 - removeEventListener(
192 - targetContainer,
193 - rawEventName,
194 - unsubscribeListener,
195 - capture,
196 - );
197 - }
198 - };
199 - }
200 - if (capture) {
201 - unsubscribeListener = addEventCaptureListener(
202 - targetContainer,
203 - rawEventName,
204 - listener,
205 - );
206 - } else {
207 - unsubscribeListener = addEventBubbleListener(
208 - targetContainer,
209 - rawEventName,
210 - listener,
211 - );
212 - }
213 - return unsubscribeListener;
214 -}
215 -
216 -export function removeTrappedEventListener(
217 - targetContainer: EventTarget,
218 - topLevelType: DOMTopLevelEventType,
219 - capture: boolean,
220 - listener: any => void,
221 -): void {
222 - const rawEventName = getRawEventName(topLevelType);
223 - removeEventListener(targetContainer, rawEventName, listener, capture);
120 }
121
122 function dispatchDiscreteEvent(
packages/react-dom/src/events/ReactDOMEventReplaying.js
+2 -4
@@ -30,10 +30,7 @@ import {
30 getContainerFromFiber,
31 getSuspenseInstanceFromFiber,
32 } from 'react-reconciler/src/ReactFiberTreeReflection';
33 -import {
34 - attemptToDispatchEvent,
35 - addResponderEventSystemEvent,
36 -} from './ReactDOMEventListener';
33 +import {attemptToDispatchEvent} from './ReactDOMEventListener';
34 import {getListenerMapForElement} from './DOMEventListenerMap';
35 import {
36 getInstanceFromNode,
@@ -121,6 +118,7 @@ import {
118 import {IS_REPLAYED, PLUGIN_EVENT_SYSTEM} from './EventSystemFlags';
119 import {legacyListenToTopLevelEvent} from './DOMLegacyEventPluginSystem';
120 import {listenToTopLevelEvent} from './DOMModernPluginEventSystem';
121 +import {addResponderEventSystemEvent} from './DeprecatedDOMEventResponderSystem';
122
123 type QueuedReplayableEvent = {|
124 blockedOn: null | Container | SuspenseInstance,
packages/react-dom/src/events/plugins/__tests__/ModernChangeEventPlugin-test.internal.js
+9 -23
@@ -9,9 +9,9 @@
9
10 'use strict';
11
12 -let React = require('react');
13 -let ReactDOM = require('react-dom');
14 -let TestUtils = require('react-dom/test-utils');
12 +let React;
13 +let ReactDOM;
14 +let TestUtils;
15 let ReactFeatureFlags;
16 let Scheduler;
17
@@ -34,6 +34,7 @@ describe('ChangeEventPlugin', () => {
34 let container;
35
36 beforeEach(() => {
37 + jest.resetModules();
38 ReactFeatureFlags = require('shared/ReactFeatureFlags');
39 // TODO pull this into helper method, reduce repetition.
40 // mock the browser APIs which are used in schedule:
@@ -53,7 +54,11 @@ describe('ChangeEventPlugin', () => {
54 postMessageCallback(postMessageEvent);
55 }
56 };
56 - jest.resetModules();
57 + ReactFeatureFlags.enableModernEventSystem = true;
58 + React = require('react');
59 + ReactDOM = require('react-dom');
60 + TestUtils = require('react-dom/test-utils');
61 + Scheduler = require('scheduler');
62 container = document.createElement('div');
63 document.body.appendChild(container);
64 });
@@ -467,17 +472,6 @@ describe('ChangeEventPlugin', () => {
472 });
473
474 describe('concurrent mode', () => {
470 - beforeEach(() => {
471 - jest.resetModules();
472 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
473 - ReactFeatureFlags.enableModernEventSystem = true;
474 -
475 - React = require('react');
476 - ReactDOM = require('react-dom');
477 - TestUtils = require('react-dom/test-utils');
478 - Scheduler = require('scheduler');
479 - });
480 -
475 // @gate experimental
476 it('text input', () => {
477 const root = ReactDOM.createRoot(container);
@@ -719,14 +713,6 @@ describe('ChangeEventPlugin', () => {
713
714 // @gate experimental
715 it('mouse enter/leave should be user-blocking but not discrete', async () => {
722 - // This is currently behind a feature flag
723 - jest.resetModules();
724 - ReactFeatureFlags.enableModernEventSystem = true;
725 - React = require('react');
726 - ReactDOM = require('react-dom');
727 - TestUtils = require('react-dom/test-utils');
728 - Scheduler = require('scheduler');
729 -
716 const {act} = TestUtils;
717 const {useState} = React;
718
packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.internal.js
+2 -1
@@ -234,7 +234,8 @@ describe('SimpleEventPlugin', function() {
234 describe('interactive events, in concurrent mode', () => {
235 beforeEach(() => {
236 jest.resetModules();
237 -
237 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
238 + ReactFeatureFlags.enableModernEventSystem = true;
239 ReactDOM = require('react-dom');
240 Scheduler = require('scheduler');
241 });