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