Modern Event System: refine flags and handling of enableLegacyFBSupport (#18466)
Dominic Gannaway committed
Apr 2, 2020 at 18:26 UTC
d5e4b3ae1d5f7d76bb3894e8d814fd4ba19dcfd5
9 files changed
+112
-39
packages/legacy-events/EventSystemFlags.js
+6
-6
@@ -11,10 +11,10 @@ export type EventSystemFlags = number;
11
12
export const PLUGIN_EVENT_SYSTEM = 1;
13
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
14
-export const IS_PASSIVE = 1 << 2;
15
-export const IS_ACTIVE = 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 IS_TARGET_EVENT_ONLY = 1 << 7;
14
+export const USE_EVENT_SYSTEM = 1 << 2;
15
+export const IS_TARGET_PHASE_ONLY = 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/__tests__/ReactDOM-test.js
+25
-4
@@ -13,6 +13,7 @@ let React;
13
let ReactDOM;
14
let ReactDOMServer;
15
let ReactTestUtils;
16
+const ReactFeatureFlags = require('shared/ReactFeatureFlags');
17
18
describe('ReactDOM', () => {
19
beforeEach(() => {
@@ -354,11 +355,31 @@ describe('ReactDOM', () => {
355
document.body.appendChild(container);
356
try {
357
ReactDOM.render(<Wrapper />, container);
358
+ let expected;
359
+
360
+ if (
361
+ ReactFeatureFlags.enableModernEventSystem &
362
+ ReactFeatureFlags.enableLegacyFBSupport
363
+ ) {
364
+ // We expect to duplicate the 2nd handler because this test is
365
+ // not really designed around how the legacy FB support system works.
366
+ // This is because the above test sync fires a click() event
367
+ // during that of another click event, which causes the FB support system
368
+ // to duplicate adding an event listener. In practice this would never
369
+ // happen, as we only apply the legacy FB logic for "click" events,
370
+ // which would never stack this way in product code.
371
+ expected = [
372
+ '1st node clicked',
373
+ "2nd node clicked imperatively from 1st's handler",
374
+ "2nd node clicked imperatively from 1st's handler",
375
+ ];
376
+ } else {
377
+ expected = [
378
+ '1st node clicked',
379
+ "2nd node clicked imperatively from 1st's handler",
380
+ ];
381
+ }
382
358
- const expected = [
359
- '1st node clicked',
360
- "2nd node clicked imperatively from 1st's handler",
361
- ];
383
expect(actual).toEqual(expected);
384
} finally {
385
document.body.removeChild(container);
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+25
-8
@@ -14,6 +14,7 @@ describe('ReactDOMComponent', () => {
14
let ReactTestUtils;
15
let ReactDOM;
16
let ReactDOMServer;
17
+ const ReactFeatureFlags = require('shared/ReactFeatureFlags');
18
19
function normalizeCodeLocInfo(str) {
20
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
@@ -2595,14 +2596,30 @@ describe('ReactDOMComponent', () => {
2596
// might depend on this.
2597
//
2598
// @see https://github.com/facebook/react/pull/12919#issuecomment-395224674
2598
- expect(eventOrder).toEqual([
2599
- 'document capture',
2600
- 'inner capture',
2601
- 'inner bubble',
2602
- 'outer capture',
2603
- 'outer bubble',
2604
- 'document bubble',
2605
- ]);
2599
+ if (
2600
+ ReactFeatureFlags.enableModernEventSystem &
2601
+ ReactFeatureFlags.enableLegacyFBSupport
2602
+ ) {
2603
+ // The order will change here, as the legacy FB support adds
2604
+ // the event listener onto the document after the one above has.
2605
+ expect(eventOrder).toEqual([
2606
+ 'document capture',
2607
+ 'document bubble',
2608
+ 'inner capture',
2609
+ 'inner bubble',
2610
+ 'outer capture',
2611
+ 'outer bubble',
2612
+ ]);
2613
+ } else {
2614
+ expect(eventOrder).toEqual([
2615
+ 'document capture',
2616
+ 'inner capture',
2617
+ 'inner bubble',
2618
+ 'outer capture',
2619
+ 'outer bubble',
2620
+ 'document bubble',
2621
+ ]);
2622
+ }
2623
} finally {
2624
document.body.removeChild(container);
2625
}
packages/react-dom/src/client/ReactDOMHostConfig.js
+3
@@ -76,6 +76,8 @@ import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
76
import {
77
RESPONDER_EVENT_SYSTEM,
78
IS_PASSIVE,
79
+ PLUGIN_EVENT_SYSTEM,
80
+ USE_EVENT_SYSTEM,
81
} from 'legacy-events/EventSystemFlags';
82
import {
83
isManagedDOMElement,
@@ -1156,6 +1158,7 @@ export function registerEvent(
1158
type,
1159
rootContainerInstance,
1160
listenerMap,
1161
+ PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM,
1162
passive,
1163
priority,
1164
);
packages/react-dom/src/events/DOMLegacyEventPluginSystem.js
+16
-3
@@ -21,7 +21,10 @@ import {
21
HostComponent,
22
HostText,
23
} from 'react-reconciler/src/ReactWorkTags';
24
-import {IS_FIRST_ANCESTOR} from 'legacy-events/EventSystemFlags';
24
+import {
25
+ IS_FIRST_ANCESTOR,
26
+ PLUGIN_EVENT_SYSTEM,
27
+} from 'legacy-events/EventSystemFlags';
28
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
29
import {runEventsInBatch} from 'legacy-events/EventBatching';
30
import {plugins} from 'legacy-events/EventPluginRegistry';
@@ -372,7 +375,12 @@ export function legacyTrapBubbledEvent(
375
element: Document | Element,
376
listenerMap?: ElementListenerMap,
377
): void {
375
- const listener = addTrappedEventListener(element, topLevelType, false);
378
+ const listener = addTrappedEventListener(
379
+ element,
380
+ topLevelType,
381
+ PLUGIN_EVENT_SYSTEM,
382
+ false,
383
+ );
384
if (listenerMap) {
385
listenerMap.set(topLevelType, {passive: undefined, listener});
386
}
@@ -383,6 +391,11 @@ export function legacyTrapCapturedEvent(
391
element: Document | Element,
392
listenerMap: ElementListenerMap,
393
): void {
386
- const listener = addTrappedEventListener(element, topLevelType, true);
394
+ const listener = addTrappedEventListener(
395
+ element,
396
+ topLevelType,
397
+ PLUGIN_EVENT_SYSTEM,
398
+ true,
399
+ );
400
listenerMap.set(topLevelType, {passive: undefined, listener});
401
}
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+17
-4
@@ -28,9 +28,11 @@ import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
28
import {executeDispatchesInOrder} from 'legacy-events/EventPluginUtils';
29
import {plugins} from 'legacy-events/EventPluginRegistry';
30
import {
31
+ PLUGIN_EVENT_SYSTEM,
32
LEGACY_FB_SUPPORT,
33
IS_REPLAYED,
33
- IS_TARGET_EVENT_ONLY,
34
+ IS_TARGET_PHASE_ONLY,
35
+ USE_EVENT_SYSTEM,
36
} from 'legacy-events/EventSystemFlags';
37
38
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
@@ -229,6 +231,7 @@ export function listenToTopLevelEvent(
231
topLevelType: DOMTopLevelEventType,
232
targetContainer: EventTarget,
233
listenerMap: ElementListenerMap,
234
+ eventSystemFlags: EventSystemFlags,
235
passive?: boolean,
236
priority?: EventPriority,
237
capture?: boolean,
@@ -265,6 +268,7 @@ export function listenToTopLevelEvent(
268
const listener = addTrappedEventListener(
269
targetContainer,
270
topLevelType,
271
+ eventSystemFlags,
272
isCapturePhase,
273
false,
274
passive,
@@ -283,7 +287,12 @@ export function listenToEvent(
287
288
for (let i = 0; i < dependencies.length; i++) {
289
const dependency = dependencies[i];
286
- listenToTopLevelEvent(dependency, rootContainerElement, listenerMap);
290
+ listenToTopLevelEvent(
291
+ dependency,
292
+ rootContainerElement,
293
+ listenerMap,
294
+ PLUGIN_EVENT_SYSTEM,
295
+ );
296
}
297
}
298
@@ -301,6 +310,7 @@ function willDeferLaterForLegacyFBSupport(
310
addTrappedEventListener(
311
targetContainer,
312
topLevelType,
313
+ PLUGIN_EVENT_SYSTEM | LEGACY_FB_SUPPORT,
314
false,
315
isDeferredListenerForLegacyFBSupport,
316
);
@@ -343,10 +353,9 @@ export function dispatchEventForPluginEventSystem(
353
): void {
354
let ancestorInst = targetInst;
355
if (targetContainer !== null) {
346
- if (eventTargetEventListenerStore.has(targetContainer)) {
356
+ if (eventSystemFlags & IS_TARGET_PHASE_ONLY) {
357
// For TargetEvent nodes (i.e. document, window)
358
ancestorInst = null;
349
- eventSystemFlags |= IS_TARGET_EVENT_ONLY;
359
} else {
360
const targetContainerNode = ((targetContainer: any): Node);
361
@@ -362,6 +371,8 @@ export function dispatchEventForPluginEventSystem(
371
(eventSystemFlags & LEGACY_FB_SUPPORT) === 0 &&
372
// We also don't want to defer during event replaying.
373
(eventSystemFlags & IS_REPLAYED) === 0 &&
374
+ // We don't want to apply the legacy FB support for the useEvent API.
375
+ (eventSystemFlags & USE_EVENT_SYSTEM) === 0 &&
376
willDeferLaterForLegacyFBSupport(topLevelType, targetContainer)
377
) {
378
return;
@@ -483,6 +494,7 @@ export function attachListenerToManagedDOMElement(
494
type,
495
containerEventTarget,
496
listenerMap,
497
+ PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM,
498
passive,
499
priority,
500
);
@@ -522,6 +534,7 @@ export function attachTargetEventListener(listener: ReactDOMListener): void {
534
type,
535
eventTarget,
536
listenerMap,
537
+ PLUGIN_EVENT_SYSTEM | USE_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
538
passive,
539
priority,
540
capture,
packages/react-dom/src/events/ReactDOMEventListener.js
+10
-10
@@ -37,12 +37,11 @@ import {
37
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
38
import {
39
type EventSystemFlags,
40
+ LEGACY_FB_SUPPORT,
41
PLUGIN_EVENT_SYSTEM,
42
RESPONDER_EVENT_SYSTEM,
43
IS_PASSIVE,
43
- IS_ACTIVE,
44
PASSIVE_NOT_SUPPORTED,
45
- LEGACY_FB_SUPPORT,
45
} from 'legacy-events/EventSystemFlags';
46
47
import {
@@ -103,12 +102,9 @@ export function addResponderEventSystemEvent(
102
if (passiveBrowserEventsSupported) {
103
eventFlags |= IS_PASSIVE;
104
} else {
106
- eventFlags |= IS_ACTIVE;
105
eventFlags |= PASSIVE_NOT_SUPPORTED;
106
passive = false;
107
}
110
- } else {
111
- eventFlags |= IS_ACTIVE;
108
}
109
// Check if interactive and wrap in discreteUpdates
110
const listener = dispatchEvent.bind(
@@ -132,6 +128,7 @@ export function addResponderEventSystemEvent(
128
export function addTrappedEventListener(
129
targetContainer: EventTarget,
130
topLevelType: DOMTopLevelEventType,
131
+ eventSystemFlags: EventSystemFlags,
132
capture: boolean,
133
isDeferredListenerForLegacyFBSupport?: boolean,
134
passive?: boolean,
@@ -160,10 +157,6 @@ export function addTrappedEventListener(
157
if (passive === true && !passiveBrowserEventsSupported) {
158
passive = false;
159
}
163
- const eventSystemFlags =
164
- enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
165
- ? PLUGIN_EVENT_SYSTEM | LEGACY_FB_SUPPORT
166
- : PLUGIN_EVENT_SYSTEM;
160
161
listener = listenerWrapper.bind(
162
null,
@@ -268,7 +261,14 @@ function dispatchDiscreteEvent(
261
container,
262
nativeEvent,
263
) {
271
- flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
264
+ if (
265
+ !enableLegacyFBSupport ||
266
+ // If we have Legacy FB support, it means we've already
267
+ // flushed for this event and we don't need to do it again.
268
+ (eventSystemFlags & LEGACY_FB_SUPPORT) === 0
269
+ ) {
270
+ flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
271
+ }
272
discreteUpdates(
273
dispatchEvent,
274
topLevelType,
packages/react-dom/src/events/ReactDOMEventReplaying.js
+7
-2
@@ -118,7 +118,7 @@ import {
118
TOP_FOCUS,
119
TOP_BLUR,
120
} from './DOMTopLevelEventTypes';
121
-import {IS_REPLAYED} from 'legacy-events/EventSystemFlags';
121
+import {IS_REPLAYED, PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
122
import {legacyListenToTopLevelEvent} from './DOMLegacyEventPluginSystem';
123
import {listenToTopLevelEvent} from './DOMModernPluginEventSystem';
124
@@ -219,7 +219,12 @@ function trapReplayableEventForContainer(
219
container: Container,
220
listenerMap: ElementListenerMap,
221
) {
222
- listenToTopLevelEvent(topLevelType, ((container: any): Element), listenerMap);
222
+ listenToTopLevelEvent(
223
+ topLevelType,
224
+ ((container: any): Element),
225
+ listenerMap,
226
+ PLUGIN_EVENT_SYSTEM,
227
+ );
228
}
229
230
function trapReplayableEventForDocument(
packages/react-dom/src/events/SimpleEventPlugin.js
+3
-2
@@ -17,6 +17,7 @@ import type {PluginModule} from 'legacy-events/PluginModuleType';
17
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
18
19
import SyntheticEvent from 'legacy-events/SyntheticEvent';
20
+import {IS_TARGET_PHASE_ONLY} from 'legacy-events/EventSystemFlags';
21
22
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
23
import {
@@ -38,7 +39,7 @@ import SyntheticWheelEvent from './SyntheticWheelEvent';
39
import getEventCharCode from './getEventCharCode';
40
import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
41
import accumulateEventTargetListeners from './accumulateEventTargetListeners';
41
-import {IS_TARGET_EVENT_ONLY} from 'legacy-events/EventSystemFlags';
42
+
43
import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
44
45
// Only used in DEV for exhaustiveness validation.
@@ -210,7 +211,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
211
if (
212
enableUseEventAPI &&
213
eventSystemFlags !== undefined &&
213
- eventSystemFlags & IS_TARGET_EVENT_ONLY &&
214
+ eventSystemFlags & IS_TARGET_PHASE_ONLY &&
215
targetContainer != null
216
) {
217
accumulateEventTargetListeners(event, targetContainer);