[Events] Make passiveness and priority non-configurable (#19807)
Dan Abramov committed
Sep 14, 2020 at 13:54 UTC
11ee82df4572fff2bbe82fef5fc98281d977b072
10 files changed
+161
-480
packages/react-dom/src/client/ReactDOMComponentTree.js
+5
-16
@@ -20,7 +20,6 @@ import type {
20
SuspenseInstance,
21
Props,
22
} from './ReactDOMHostConfig';
23
-import type {DOMEventName} from '../events/DOMEventNames';
23
24
import {
25
HostComponent,
@@ -44,16 +43,6 @@ const internalEventHandlersKey = '__reactEvents$' + randomKey;
43
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
44
const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
45
47
-export type ElementListenerMap = Map<
48
- DOMEventName | string,
49
- ElementListenerMapEntry | null,
50
->;
51
-
52
-export type ElementListenerMapEntry = {
53
- passive: void | boolean,
54
- listener: any => void,
55
-};
56
-
46
export function precacheFiberNode(
47
hostInst: Fiber,
48
node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
@@ -207,12 +196,12 @@ export function updateFiberProps(
196
(node: any)[internalPropsKey] = props;
197
}
198
210
-export function getEventListenerMap(node: EventTarget): ElementListenerMap {
211
- let elementListenerMap = (node: any)[internalEventHandlersKey];
212
- if (elementListenerMap === undefined) {
213
- elementListenerMap = (node: any)[internalEventHandlersKey] = new Map();
199
+export function getEventListenerSet(node: EventTarget): Set<string> {
200
+ let elementListenerSet = (node: any)[internalEventHandlersKey];
201
+ if (elementListenerSet === undefined) {
202
+ elementListenerSet = (node: any)[internalEventHandlersKey] = new Set();
203
}
215
- return elementListenerMap;
204
+ return elementListenerSet;
205
}
206
207
export function getFiberFromScopeInstance(
packages/react-dom/src/client/ReactDOMEventHandle.js
+69
-104
@@ -8,13 +8,12 @@
8
*/
9
10
import type {DOMEventName} from '../events/DOMEventNames';
11
-import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes';
11
+import type {ReactScopeInstance} from 'shared/ReactTypes';
12
import type {
13
ReactDOMEventHandle,
14
ReactDOMEventHandleListener,
15
} from '../shared/ReactDOMTypes';
16
17
-import {getEventPriorityForListenerSystem} from '../events/DOMEventProperties';
17
import {allNativeEvents} from '../events/EventRegistry';
18
import {
19
getClosestInstanceFromNode,
@@ -25,10 +24,7 @@ import {
24
addEventHandleToTarget,
25
} from './ReactDOMComponentTree';
26
import {ELEMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';
28
-import {
29
- listenToNativeEvent,
30
- addEventTypeToDispatchConfig,
31
-} from '../events/DOMPluginEventSystem';
27
+import {listenToNativeEvent} from '../events/DOMPluginEventSystem';
28
29
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
30
import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../events/EventSystemFlags';
@@ -42,8 +38,6 @@ import invariant from 'shared/invariant';
38
39
type EventHandleOptions = {|
40
capture?: boolean,
45
- passive?: boolean,
46
- priority?: EventPriority,
41
|};
42
43
function getNearestRootOrPortalContainer(node: Fiber): null | Element {
@@ -82,76 +76,76 @@ function createEventHandleListener(
76
function registerEventOnNearestTargetContainer(
77
targetFiber: Fiber,
78
domEventName: DOMEventName,
85
- isPassiveListener: boolean | void,
86
- listenerPriority: EventPriority | void,
79
isCapturePhaseListener: boolean,
80
targetElement: Element | null,
81
): void {
90
- // If it is, find the nearest root or portal and make it
91
- // our event handle target container.
92
- let targetContainer = getNearestRootOrPortalContainer(targetFiber);
93
- if (targetContainer === null) {
94
- invariant(
95
- false,
96
- 'ReactDOM.createEventHandle: setListener called on an target ' +
97
- 'that did not have a corresponding root. This is likely a bug in React.',
82
+ if (!enableEagerRootListeners) {
83
+ // If it is, find the nearest root or portal and make it
84
+ // our event handle target container.
85
+ let targetContainer = getNearestRootOrPortalContainer(targetFiber);
86
+ if (targetContainer === null) {
87
+ if (__DEV__) {
88
+ console.error(
89
+ 'ReactDOM.createEventHandle: setListener called on an target ' +
90
+ 'that did not have a corresponding root. This is likely a bug in React.',
91
+ );
92
+ }
93
+ return;
94
+ }
95
+ if (targetContainer.nodeType === COMMENT_NODE) {
96
+ targetContainer = ((targetContainer.parentNode: any): Element);
97
+ }
98
+ listenToNativeEvent(
99
+ domEventName,
100
+ isCapturePhaseListener,
101
+ targetContainer,
102
+ targetElement,
103
);
104
}
100
- if (targetContainer.nodeType === COMMENT_NODE) {
101
- targetContainer = ((targetContainer.parentNode: any): Element);
102
- }
103
- listenToNativeEvent(
104
- domEventName,
105
- isCapturePhaseListener,
106
- targetContainer,
107
- targetElement,
108
- isPassiveListener,
109
- listenerPriority,
110
- );
105
}
106
107
function registerReactDOMEvent(
108
target: EventTarget | ReactScopeInstance,
109
domEventName: DOMEventName,
116
- isPassiveListener: boolean | void,
110
isCapturePhaseListener: boolean,
118
- listenerPriority: EventPriority | void,
111
): void {
112
// Check if the target is a DOM element.
113
if ((target: any).nodeType === ELEMENT_NODE) {
122
- const targetElement = ((target: any): Element);
123
- // Check if the DOM element is managed by React.
124
- const targetFiber = getClosestInstanceFromNode(targetElement);
125
- if (targetFiber === null) {
126
- invariant(
127
- false,
128
- 'ReactDOM.createEventHandle: setListener called on an element ' +
129
- 'target that is not managed by React. Ensure React rendered the DOM element.',
114
+ if (!enableEagerRootListeners) {
115
+ const targetElement = ((target: any): Element);
116
+ // Check if the DOM element is managed by React.
117
+ const targetFiber = getClosestInstanceFromNode(targetElement);
118
+ if (targetFiber === null) {
119
+ if (__DEV__) {
120
+ console.error(
121
+ 'ReactDOM.createEventHandle: setListener called on an element ' +
122
+ 'target that is not managed by React. Ensure React rendered the DOM element.',
123
+ );
124
+ }
125
+ return;
126
+ }
127
+ registerEventOnNearestTargetContainer(
128
+ targetFiber,
129
+ domEventName,
130
+ isCapturePhaseListener,
131
+ targetElement,
132
);
133
}
132
- registerEventOnNearestTargetContainer(
133
- targetFiber,
134
- domEventName,
135
- isPassiveListener,
136
- listenerPriority,
137
- isCapturePhaseListener,
138
- targetElement,
139
- );
134
} else if (enableScopeAPI && isReactScope(target)) {
141
- const scopeTarget = ((target: any): ReactScopeInstance);
142
- const targetFiber = getFiberFromScopeInstance(scopeTarget);
143
- if (targetFiber === null) {
144
- // Scope is unmounted, do not proceed.
145
- return;
135
+ if (!enableEagerRootListeners) {
136
+ const scopeTarget = ((target: any): ReactScopeInstance);
137
+ const targetFiber = getFiberFromScopeInstance(scopeTarget);
138
+ if (targetFiber === null) {
139
+ // Scope is unmounted, do not proceed.
140
+ return;
141
+ }
142
+ registerEventOnNearestTargetContainer(
143
+ targetFiber,
144
+ domEventName,
145
+ isCapturePhaseListener,
146
+ null,
147
+ );
148
}
147
- registerEventOnNearestTargetContainer(
148
- targetFiber,
149
- domEventName,
150
- isPassiveListener,
151
- listenerPriority,
152
- isCapturePhaseListener,
153
- null,
154
- );
149
} else if (isValidEventTarget(target)) {
150
const eventTarget = ((target: any): EventTarget);
151
// These are valid event targets, but they are also
@@ -161,8 +155,6 @@ function registerReactDOMEvent(
155
isCapturePhaseListener,
156
eventTarget,
157
null,
164
- isPassiveListener,
165
- listenerPriority,
158
IS_EVENT_HANDLE_NON_MANAGED_NODE,
159
);
160
} else {
@@ -181,46 +173,27 @@ export function createEventHandle(
173
if (enableCreateEventHandleAPI) {
174
const domEventName = ((type: any): DOMEventName);
175
184
- if (enableEagerRootListeners) {
185
- // We cannot support arbitrary native events with eager root listeners
186
- // because the eager strategy relies on knowing the whole list ahead of time.
187
- // If we wanted to support this, we'd have to add code to keep track
188
- // (or search) for all portal and root containers, and lazily add listeners
189
- // to them whenever we see a previously unknown event. This seems like a lot
190
- // of complexity for something we don't even have a particular use case for.
191
- // Unfortunately, the downside of this invariant is that *removing* a native
192
- // event from the list of known events has now become a breaking change for
193
- // any code relying on the createEventHandle API.
194
- invariant(
195
- allNativeEvents.has(domEventName) ||
196
- domEventName === 'beforeblur' ||
197
- domEventName === 'afterblur',
198
- 'Cannot call unstable_createEventHandle with "%s", as it is not an event known to React.',
199
- domEventName,
200
- );
201
- }
176
+ // We cannot support arbitrary native events with eager root listeners
177
+ // because the eager strategy relies on knowing the whole list ahead of time.
178
+ // If we wanted to support this, we'd have to add code to keep track
179
+ // (or search) for all portal and root containers, and lazily add listeners
180
+ // to them whenever we see a previously unknown event. This seems like a lot
181
+ // of complexity for something we don't even have a particular use case for.
182
+ // Unfortunately, the downside of this invariant is that *removing* a native
183
+ // event from the list of known events has now become a breaking change for
184
+ // any code relying on the createEventHandle API.
185
+ invariant(
186
+ allNativeEvents.has(domEventName),
187
+ 'Cannot call unstable_createEventHandle with "%s", as it is not an event known to React.',
188
+ domEventName,
189
+ );
190
191
let isCapturePhaseListener = false;
204
- let isPassiveListener = undefined; // Undefined means to use the browser default
205
- let listenerPriority;
206
-
192
if (options != null) {
193
const optionsCapture = options.capture;
209
- const optionsPassive = options.passive;
210
- const optionsPriority = options.priority;
211
-
194
if (typeof optionsCapture === 'boolean') {
195
isCapturePhaseListener = optionsCapture;
196
}
215
- if (typeof optionsPassive === 'boolean') {
216
- isPassiveListener = optionsPassive;
217
- }
218
- if (typeof optionsPriority === 'number') {
219
- listenerPriority = optionsPriority;
220
- }
221
- }
222
- if (listenerPriority === undefined) {
223
- listenerPriority = getEventPriorityForListenerSystem(domEventName);
197
}
198
199
const eventHandle = (
@@ -234,15 +207,7 @@ export function createEventHandle(
207
);
208
if (!doesTargetHaveEventHandle(target, eventHandle)) {
209
addEventHandleToTarget(target, eventHandle);
237
- registerReactDOMEvent(
238
- target,
239
- domEventName,
240
- isPassiveListener,
241
- isCapturePhaseListener,
242
- listenerPriority,
243
- );
244
- // Add the event to our known event types list.
245
- addEventTypeToDispatchConfig(domEventName);
210
+ registerReactDOMEvent(target, domEventName, isCapturePhaseListener);
211
}
212
const listener = createEventHandleListener(
213
domEventName,
packages/react-dom/src/events/DOMEventProperties.js
+5
-1
@@ -89,6 +89,10 @@ const otherDiscreteEvents: Array<DOMEventName> = [
89
];
90
91
if (enableCreateEventHandleAPI) {
92
+ // Special case: these two events don't have on* React handler
93
+ // and are only accessible via the createEventHandle API.
94
+ topLevelEventsToReactNames.set('beforeblur', null);
95
+ topLevelEventsToReactNames.set('afterblur', null);
96
otherDiscreteEvents.push('beforeblur', 'afterblur');
97
}
98
@@ -202,7 +206,7 @@ export function getEventPriorityForListenerSystem(
206
if (__DEV__) {
207
console.warn(
208
'The event "%s" provided to createEventHandle() does not have a known priority type.' +
205
- ' It is recommended to provide a "priority" option to specify a priority.',
209
+ ' This is likely a bug in React.',
210
type,
211
);
212
}
packages/react-dom/src/events/DOMPluginEventSystem.js
+36
-86
@@ -19,8 +19,6 @@ import type {
19
KnownReactSyntheticEvent,
20
ReactSyntheticEvent,
21
} from './ReactSyntheticEventType';
22
-import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
23
-import type {EventPriority} from 'shared/ReactTypes';
22
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
23
24
import {registrationNameDependencies, allNativeEvents} from './EventRegistry';
@@ -41,7 +39,7 @@ import {
39
import getEventTarget from './getEventTarget';
40
import {
41
getClosestInstanceFromNode,
44
- getEventListenerMap,
42
+ getEventListenerSet,
43
getEventHandlerListeners,
44
} from '../client/ReactDOMComponentTree';
45
import {COMMENT_NODE} from '../shared/HTMLNodeType';
@@ -69,7 +67,6 @@ import {
67
addEventBubbleListenerWithPassiveFlag,
68
addEventCaptureListenerWithPassiveFlag,
69
} from './EventListener';
72
-import {topLevelEventsToReactNames} from './DOMEventProperties';
70
import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';
71
import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
72
import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';
@@ -296,36 +293,24 @@ function dispatchEventsForPlugins(
293
processDispatchQueue(dispatchQueue, eventSystemFlags);
294
}
295
299
-function shouldUpgradeListener(
300
- listenerEntry: void | ElementListenerMapEntry,
301
- passive: void | boolean,
302
-): boolean {
303
- return (
304
- listenerEntry !== undefined && listenerEntry.passive === true && !passive
305
- );
306
-}
307
-
296
export function listenToNonDelegatedEvent(
297
domEventName: DOMEventName,
298
targetElement: Element,
299
): void {
300
const isCapturePhaseListener = false;
313
- const listenerMap = getEventListenerMap(targetElement);
314
- const listenerMapKey = getListenerMapKey(
301
+ const listenerSet = getEventListenerSet(targetElement);
302
+ const listenerSetKey = getListenerSetKey(
303
domEventName,
304
isCapturePhaseListener,
305
);
318
- const listenerEntry = ((listenerMap.get(
319
- listenerMapKey,
320
- ): any): ElementListenerMapEntry | void);
321
- if (listenerEntry === undefined) {
322
- const listener = addTrappedEventListener(
306
+ if (!listenerSet.has(listenerSetKey)) {
307
+ addTrappedEventListener(
308
targetElement,
309
domEventName,
310
IS_NON_DELEGATED,
311
isCapturePhaseListener,
312
);
328
- listenerMap.set(listenerMapKey, {passive: false, listener});
313
+ listenerSet.add(listenerSetKey);
314
}
315
}
316
@@ -369,8 +354,6 @@ export function listenToNativeEvent(
354
isCapturePhaseListener: boolean,
355
rootContainerElement: EventTarget,
356
targetElement: Element | null,
372
- isPassiveListener?: boolean,
373
- listenerPriority?: EventPriority,
357
eventSystemFlags?: EventSystemFlags = 0,
358
): void {
359
let target = rootContainerElement;
@@ -384,21 +367,6 @@ export function listenToNativeEvent(
367
) {
368
target = (rootContainerElement: any).ownerDocument;
369
}
387
- if (enablePassiveEventIntervention && isPassiveListener === undefined) {
388
- // Browsers introduced an intervention, making these events
389
- // passive by default on document. React doesn't bind them
390
- // to document anymore, but changing this now would undo
391
- // the performance wins from the change. So we emulate
392
- // the existing behavior manually on the roots now.
393
- // https://github.com/facebook/react/issues/19651
394
- if (
395
- domEventName === 'touchstart' ||
396
- domEventName === 'touchmove' ||
397
- domEventName === 'wheel'
398
- ) {
399
- isPassiveListener = true;
400
- }
401
- }
370
// If the event can be delegated (or is capture phase), we can
371
// register it to the root container. Otherwise, we should
372
// register the event to the target element and mark it as
@@ -423,42 +391,24 @@ export function listenToNativeEvent(
391
eventSystemFlags |= IS_NON_DELEGATED;
392
target = targetElement;
393
}
426
- const listenerMap = getEventListenerMap(target);
427
- const listenerMapKey = getListenerMapKey(
394
+ const listenerSet = getEventListenerSet(target);
395
+ const listenerSetKey = getListenerSetKey(
396
domEventName,
397
isCapturePhaseListener,
398
);
431
- const listenerEntry = ((listenerMap.get(
432
- listenerMapKey,
433
- ): any): ElementListenerMapEntry | void);
434
- const shouldUpgrade = shouldUpgradeListener(listenerEntry, isPassiveListener);
435
-
399
// If the listener entry is empty or we should upgrade, then
400
// we need to trap an event listener onto the target.
438
- if (listenerEntry === undefined || shouldUpgrade) {
439
- // If we should upgrade, then we need to remove the existing trapped
440
- // event listener for the target container.
441
- if (shouldUpgrade) {
442
- removeEventListener(
443
- target,
444
- domEventName,
445
- ((listenerEntry: any): ElementListenerMapEntry).listener,
446
- isCapturePhaseListener,
447
- );
448
- }
401
+ if (!listenerSet.has(listenerSetKey)) {
402
if (isCapturePhaseListener) {
403
eventSystemFlags |= IS_CAPTURE_PHASE;
404
}
452
- const listener = addTrappedEventListener(
405
+ addTrappedEventListener(
406
target,
407
domEventName,
408
eventSystemFlags,
409
isCapturePhaseListener,
457
- false,
458
- isPassiveListener,
459
- listenerPriority,
410
);
461
- listenerMap.set(listenerMapKey, {passive: isPassiveListener, listener});
411
+ listenerSet.add(listenerSetKey);
412
}
413
}
414
@@ -480,11 +430,13 @@ export function listenToReactEvent(
430
const isPolyfillEventPlugin = dependenciesLength !== 1;
431
432
if (isPolyfillEventPlugin) {
483
- const listenerMap = getEventListenerMap(rootContainerElement);
484
- // For optimization, we register plugins on the listener map, so we
485
- // don't need to check each of their dependencies each time.
486
- if (!listenerMap.has(reactEvent)) {
487
- listenerMap.set(reactEvent, null);
433
+ const listenerSet = getEventListenerSet(rootContainerElement);
434
+ // When eager listeners are off, this Set has a dual purpose: it both
435
+ // captures which native listeners we registered (e.g. "click__bubble")
436
+ // and *React* lazy listeners (e.g. "onClick") so we don't do extra checks.
437
+ // This second usage does not exist in the eager mode.
438
+ if (!listenerSet.has(reactEvent)) {
439
+ listenerSet.add(reactEvent);
440
for (let i = 0; i < dependenciesLength; i++) {
441
listenToNativeEvent(
442
dependencies[i],
@@ -520,19 +472,29 @@ function addTrappedEventListener(
472
eventSystemFlags: EventSystemFlags,
473
isCapturePhaseListener: boolean,
474
isDeferredListenerForLegacyFBSupport?: boolean,
523
- isPassiveListener?: boolean,
524
- listenerPriority?: EventPriority,
525
-): any => void {
475
+) {
476
let listener = createEventListenerWrapperWithPriority(
477
targetContainer,
478
domEventName,
479
eventSystemFlags,
530
- listenerPriority,
480
);
481
// If passive option is not supported, then the event will be
482
// active and not passive.
534
- if (isPassiveListener === true && !passiveBrowserEventsSupported) {
535
- isPassiveListener = false;
483
+ let isPassiveListener = undefined;
484
+ if (enablePassiveEventIntervention && passiveBrowserEventsSupported) {
485
+ // Browsers introduced an intervention, making these events
486
+ // passive by default on document. React doesn't bind them
487
+ // to document anymore, but changing this now would undo
488
+ // the performance wins from the change. So we emulate
489
+ // the existing behavior manually on the roots now.
490
+ // https://github.com/facebook/react/issues/19651
491
+ if (
492
+ domEventName === 'touchstart' ||
493
+ domEventName === 'touchmove' ||
494
+ domEventName === 'wheel'
495
+ ) {
496
+ isPassiveListener = true;
497
+ }
498
}
499
500
targetContainer =
@@ -564,6 +526,7 @@ function addTrappedEventListener(
526
return originalListener.apply(this, p);
527
};
528
}
529
+ // TODO: There are too many combinations here. Consolidate them.
530
if (isCapturePhaseListener) {
531
if (isPassiveListener !== undefined) {
532
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
@@ -595,7 +558,6 @@ function addTrappedEventListener(
558
);
559
}
560
}
598
- return unsubscribeListener;
561
}
562
563
function deferClickToDocumentForLegacyFBSupport(
@@ -1085,19 +1047,7 @@ export function accumulateEventHandleNonManagedNodeListeners(
1047
}
1048
}
1049
1088
-export function addEventTypeToDispatchConfig(type: DOMEventName): void {
1089
- const reactName = topLevelEventsToReactNames.get(type);
1090
- // If we don't have a reactName, then we're dealing with
1091
- // an event type that React does not know about (i.e. a custom event).
1092
- // We need to register an event config for this or the SimpleEventPlugin
1093
- // will not appropriately provide a SyntheticEvent, so we use out empty
1094
- // dispatch config for custom events.
1095
- if (reactName === undefined) {
1096
- topLevelEventsToReactNames.set(type, null);
1097
- }
1098
-}
1099
-
1100
-export function getListenerMapKey(
1050
+export function getListenerSetKey(
1051
domEventName: DOMEventName,
1052
capture: boolean,
1053
): string {
packages/react-dom/src/events/EventRegistry.js
+8
-5
@@ -9,10 +9,15 @@
9
10
import type {DOMEventName} from './DOMEventNames';
11
12
-import {enableEagerRootListeners} from 'shared/ReactFeatureFlags';
12
+import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
13
14
export const allNativeEvents: Set<DOMEventName> = new Set();
15
16
+if (enableCreateEventHandleAPI) {
17
+ allNativeEvents.add('beforeblur');
18
+ allNativeEvents.add('afterblur');
19
+}
20
+
21
/**
22
* Mapping from registration name to event name
23
*/
@@ -60,9 +65,7 @@ export function registerDirectEvent(
65
}
66
}
67
63
- if (enableEagerRootListeners) {
64
- for (let i = 0; i < dependencies.length; i++) {
65
- allNativeEvents.add(dependencies[i]);
66
- }
68
+ for (let i = 0; i < dependencies.length; i++) {
69
+ allNativeEvents.add(dependencies[i]);
70
}
71
}
packages/react-dom/src/events/ReactDOMEventListener.js
+1
-6
@@ -8,7 +8,6 @@
8
*/
9
10
import type {AnyNativeEvent} from '../events/PluginModuleType';
11
-import type {EventPriority} from 'shared/ReactTypes';
11
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
12
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
13
import type {DOMEventName} from '../events/DOMEventNames';
@@ -96,12 +95,8 @@ export function createEventListenerWrapperWithPriority(
95
targetContainer: EventTarget,
96
domEventName: DOMEventName,
97
eventSystemFlags: EventSystemFlags,
99
- priority?: EventPriority,
98
): Function {
101
- const eventPriority =
102
- priority === undefined
103
- ? getEventPriorityForPluginSystem(domEventName)
104
- : priority;
99
+ const eventPriority = getEventPriorityForPluginSystem(domEventName);
100
let listenerWrapper;
101
switch (eventPriority) {
102
case DiscreteEvent:
packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+23
-213
@@ -1949,106 +1949,6 @@ describe('DOMPluginEventSystem', () => {
1949
expect(log).toEqual([{counter: 1}]);
1950
});
1951
1952
- // @gate experimental
1953
- it('should correctly work for a basic "click" listener that upgrades', () => {
1954
- const clickEvent = jest.fn();
1955
- const buttonRef = React.createRef();
1956
- const button2Ref = React.createRef();
1957
- const setClick1 = ReactDOM.unstable_createEventHandle('click', {
1958
- passive: false,
1959
- });
1960
- const setClick2 = ReactDOM.unstable_createEventHandle('click', {
1961
- passive: true,
1962
- });
1963
-
1964
- function Test2() {
1965
- React.useEffect(() => {
1966
- return setClick1(button2Ref.current, clickEvent);
1967
- });
1968
-
1969
- return <button ref={button2Ref}>Click me!</button>;
1970
- }
1971
-
1972
- function Test({extra}) {
1973
- React.useEffect(() => {
1974
- return setClick2(buttonRef.current, clickEvent);
1975
- });
1976
-
1977
- return (
1978
- <>
1979
- <button ref={buttonRef}>Click me!</button>
1980
- {extra && <Test2 />}
1981
- </>
1982
- );
1983
- }
1984
-
1985
- ReactDOM.render(<Test />, container);
1986
- Scheduler.unstable_flushAll();
1987
-
1988
- let button = buttonRef.current;
1989
- dispatchClickEvent(button);
1990
- expect(clickEvent).toHaveBeenCalledTimes(1);
1991
-
1992
- ReactDOM.render(<Test extra={true} />, container);
1993
- Scheduler.unstable_flushAll();
1994
-
1995
- clickEvent.mockClear();
1996
-
1997
- button = button2Ref.current;
1998
- dispatchClickEvent(button);
1999
- expect(clickEvent).toHaveBeenCalledTimes(1);
2000
- });
2001
-
2002
- // @gate experimental
2003
- it('should correctly work for a basic "click" listener that upgrades #2', () => {
2004
- const clickEvent = jest.fn();
2005
- const buttonRef = React.createRef();
2006
- const button2Ref = React.createRef();
2007
- const setClick1 = ReactDOM.unstable_createEventHandle('click', {
2008
- passive: false,
2009
- });
2010
- const setClick2 = ReactDOM.unstable_createEventHandle('click', {
2011
- passive: undefined,
2012
- });
2013
-
2014
- function Test2() {
2015
- React.useEffect(() => {
2016
- return setClick1(button2Ref.current, clickEvent);
2017
- });
2018
-
2019
- return <button ref={button2Ref}>Click me!</button>;
2020
- }
2021
-
2022
- function Test({extra}) {
2023
- React.useEffect(() => {
2024
- return setClick2(buttonRef.current, clickEvent);
2025
- });
2026
-
2027
- return (
2028
- <>
2029
- <button ref={buttonRef}>Click me!</button>
2030
- {extra && <Test2 />}
2031
- </>
2032
- );
2033
- }
2034
-
2035
- ReactDOM.render(<Test />, container);
2036
- Scheduler.unstable_flushAll();
2037
-
2038
- let button = buttonRef.current;
2039
- dispatchClickEvent(button);
2040
- expect(clickEvent).toHaveBeenCalledTimes(1);
2041
-
2042
- ReactDOM.render(<Test extra={true} />, container);
2043
- Scheduler.unstable_flushAll();
2044
-
2045
- clickEvent.mockClear();
2046
-
2047
- button = button2Ref.current;
2048
- dispatchClickEvent(button);
2049
- expect(clickEvent).toHaveBeenCalledTimes(1);
2050
- });
2051
-
1952
// @gate experimental
1953
it('should correctly work for a basic "click" window listener', () => {
1954
const log = [];
@@ -2391,109 +2291,15 @@ describe('DOMPluginEventSystem', () => {
2291
});
2292
2293
// @gate experimental
2394
- it('handles propagation of custom user events', () => {
2395
- const buttonRef = React.createRef();
2396
- const divRef = React.createRef();
2397
- const log = [];
2398
- const onCustomEvent = jest.fn(e =>
2399
- log.push(['bubble', e.currentTarget]),
2294
+ it('does not support custom user events', () => {
2295
+ // With eager listeners, supporting custom events via this API doesn't make sense
2296
+ // because we can't know a full list of them ahead of time. Let's check we throw
2297
+ // since otherwise we'd end up with inconsistent behavior, like no portal bubbling.
2298
+ expect(() => {
2299
+ ReactDOM.unstable_createEventHandle('custom-event');
2300
+ }).toThrow(
2301
+ 'Cannot call unstable_createEventHandle with "custom-event", as it is not an event known to React.',
2302
);
2401
- const onCustomEventCapture = jest.fn(e =>
2402
- log.push(['capture', e.currentTarget]),
2403
- );
2404
-
2405
- let setCustomEventHandle;
2406
- if (gate(flags => flags.enableEagerRootListeners)) {
2407
- // With eager listeners, supporting custom events via this API doesn't make sense
2408
- // because we can't know a full list of them ahead of time. Let's check we throw
2409
- // since otherwise we'd end up with inconsistent behavior, like no portal bubbling.
2410
- expect(() => {
2411
- setCustomEventHandle = ReactDOM.unstable_createEventHandle(
2412
- 'custom-event',
2413
- );
2414
- }).toThrow(
2415
- 'Cannot call unstable_createEventHandle with "custom-event", as it is not an event known to React.',
2416
- );
2417
- } else {
2418
- // Test that we get a warning when we don't provide an explicit priority
2419
- expect(() => {
2420
- setCustomEventHandle = ReactDOM.unstable_createEventHandle(
2421
- 'custom-event',
2422
- );
2423
- }).toWarnDev(
2424
- 'Warning: The event "custom-event" provided to createEventHandle() does not have a known priority type. ' +
2425
- 'It is recommended to provide a "priority" option to specify a priority.',
2426
- {withoutStack: true},
2427
- );
2428
-
2429
- setCustomEventHandle = ReactDOM.unstable_createEventHandle(
2430
- 'custom-event',
2431
- {
2432
- priority: 0, // Discrete
2433
- },
2434
- );
2435
-
2436
- const setCustomCaptureHandle = ReactDOM.unstable_createEventHandle(
2437
- 'custom-event',
2438
- {
2439
- capture: true,
2440
- priority: 0, // Discrete
2441
- },
2442
- );
2443
-
2444
- const Test = () => {
2445
- React.useEffect(() => {
2446
- const clearCustom1 = setCustomEventHandle(
2447
- buttonRef.current,
2448
- onCustomEvent,
2449
- );
2450
- const clearCustom2 = setCustomCaptureHandle(
2451
- buttonRef.current,
2452
- onCustomEventCapture,
2453
- );
2454
- const clearCustom3 = setCustomEventHandle(
2455
- divRef.current,
2456
- onCustomEvent,
2457
- );
2458
- const clearCustom4 = setCustomCaptureHandle(
2459
- divRef.current,
2460
- onCustomEventCapture,
2461
- );
2462
-
2463
- return () => {
2464
- clearCustom1();
2465
- clearCustom2();
2466
- clearCustom3();
2467
- clearCustom4();
2468
- };
2469
- });
2470
-
2471
- return (
2472
- <button ref={buttonRef}>
2473
- <div ref={divRef}>Click me!</div>
2474
- </button>
2475
- );
2476
- };
2477
-
2478
- ReactDOM.render(<Test />, container);
2479
- Scheduler.unstable_flushAll();
2480
-
2481
- const buttonElement = buttonRef.current;
2482
- dispatchEvent(buttonElement, 'custom-event');
2483
- expect(onCustomEvent).toHaveBeenCalledTimes(1);
2484
- expect(onCustomEventCapture).toHaveBeenCalledTimes(1);
2485
- expect(log[0]).toEqual(['capture', buttonElement]);
2486
- expect(log[1]).toEqual(['bubble', buttonElement]);
2487
-
2488
- const divElement = divRef.current;
2489
- dispatchEvent(divElement, 'custom-event');
2490
- expect(onCustomEvent).toHaveBeenCalledTimes(3);
2491
- expect(onCustomEventCapture).toHaveBeenCalledTimes(3);
2492
- expect(log[2]).toEqual(['capture', buttonElement]);
2493
- expect(log[3]).toEqual(['capture', divElement]);
2494
- expect(log[4]).toEqual(['bubble', divElement]);
2495
- expect(log[5]).toEqual(['bubble', buttonElement]);
2496
- }
2303
});
2304
2305
// @gate experimental
@@ -3211,12 +3017,14 @@ describe('DOMPluginEventSystem', () => {
3017
});
3018
3019
// @gate experimental
3214
- it('should be able to register non-passive handlers for events affected by the intervention', () => {
3020
+ it('should be able to register handlers for events affected by the intervention', () => {
3021
const rootContainer = document.createElement('div');
3022
container.appendChild(rootContainer);
3023
3024
+ const allEvents = [];
3025
const defaultPreventedEvents = [];
3026
const handler = e => {
3027
+ allEvents.push(e.type);
3028
if (e.defaultPrevented) defaultPreventedEvents.push(e.type);
3029
};
3030
@@ -3227,15 +3035,11 @@ describe('DOMPluginEventSystem', () => {
3035
const ref = React.createRef();
3036
const setTouchStart = ReactDOM.unstable_createEventHandle(
3037
'touchstart',
3230
- {passive: false},
3038
);
3039
const setTouchMove = ReactDOM.unstable_createEventHandle(
3040
'touchmove',
3234
- {passive: false},
3041
);
3236
- const setWheel = ReactDOM.unstable_createEventHandle('wheel', {
3237
- passive: false,
3238
- });
3042
+ const setWheel = ReactDOM.unstable_createEventHandle('wheel');
3043
3044
function Component() {
3045
React.useEffect(() => {
@@ -3264,11 +3068,17 @@ describe('DOMPluginEventSystem', () => {
3068
dispatchEvent(ref.current, 'touchmove');
3069
dispatchEvent(ref.current, 'wheel');
3070
3267
- expect(defaultPreventedEvents).toEqual([
3268
- 'touchstart',
3269
- 'touchmove',
3270
- 'wheel',
3271
- ]);
3071
+ expect(allEvents).toEqual(['touchstart', 'touchmove', 'wheel']);
3072
+ // These events are passive by default, so we can't preventDefault.
3073
+ if (gate(flags => flags.enablePassiveEventIntervention)) {
3074
+ expect(defaultPreventedEvents).toEqual([]);
3075
+ } else {
3076
+ expect(defaultPreventedEvents).toEqual([
3077
+ 'touchstart',
3078
+ 'touchmove',
3079
+ 'wheel',
3080
+ ]);
3081
+ }
3082
});
3083
});
3084
});
packages/react-dom/src/events/plugins/SelectEventPlugin.js
+4
-4
@@ -22,7 +22,7 @@ import {registerTwoPhaseEvent} from '../EventRegistry';
22
import getActiveElement from '../../client/getActiveElement';
23
import {
24
getNodeFromInstance,
25
- getEventListenerMap,
25
+ getEventListenerSet,
26
} from '../../client/ReactDOMComponentTree';
27
import {hasSelectionCapabilities} from '../../client/ReactInputSelection';
28
import {DOCUMENT_NODE} from '../../shared/HTMLNodeType';
@@ -154,7 +154,7 @@ function extractEvents(
154
targetContainer: EventTarget,
155
) {
156
if (!enableEagerRootListeners) {
157
- const eventListenerMap = getEventListenerMap(targetContainer);
157
+ const eventListenerSet = getEventListenerSet(targetContainer);
158
// Track whether all listeners exists for this plugin. If none exist, we do
159
// not extract events. See #3639.
160
if (
@@ -163,8 +163,8 @@ function extractEvents(
163
// event attached from the onChange plugin and we don't expose an
164
// onSelectionChange event from React.
165
domEventName !== 'selectionchange' &&
166
- !eventListenerMap.has('onSelect') &&
167
- !eventListenerMap.has('onSelectCapture')
166
+ !eventListenerSet.has('onSelect') &&
167
+ !eventListenerSet.has('onSelectCapture')
168
) {
169
return;
170
}
packages/react-interactions/events/src/dom/create-event-handle/Focus.js
+10
-43
@@ -37,32 +37,6 @@ const isMac =
37
? /^Mac/.test(window.navigator.platform)
38
: false;
39
40
-const canUseDOM: boolean = !!(
41
- typeof window !== 'undefined' &&
42
- typeof window.document !== 'undefined' &&
43
- typeof window.document.createElement !== 'undefined'
44
-);
45
-
46
-let passiveBrowserEventsSupported = false;
47
-
48
-// Check if browser support events with passive listeners
49
-// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
50
-if (canUseDOM) {
51
- try {
52
- const options = {};
53
- // $FlowFixMe: Ignore Flow complaining about needing a value
54
- Object.defineProperty(options, 'passive', {
55
- get: function() {
56
- passiveBrowserEventsSupported = true;
57
- },
58
- });
59
- window.addEventListener('test', options, options);
60
- window.removeEventListener('test', options, options);
61
- } catch (e) {
62
- passiveBrowserEventsSupported = false;
63
- }
64
-}
65
-
40
const hasPointerEvents =
41
typeof window !== 'undefined' && window.PointerEvent != null;
42
@@ -78,20 +52,13 @@ const globalFocusVisibleEvents = hasPointerEvents
52
'touchend',
53
];
54
81
-const passiveObject = {passive: true};
82
-const passiveObjectWithPriority = {passive: true, priority: 0};
83
-
55
// Global state for tracking focus visible and emulation of mouse
56
let isGlobalFocusVisible = true;
57
let hasTrackedGlobalFocusVisible = false;
58
59
function trackGlobalFocusVisible() {
60
globalFocusVisibleEvents.forEach(type => {
90
- window.addEventListener(
91
- type,
92
- handleGlobalFocusVisibleEvent,
93
- passiveBrowserEventsSupported ? {capture: true, passive: true} : true,
94
- );
61
+ window.addEventListener(type, handleGlobalFocusVisibleEvent, true);
62
});
63
}
64
@@ -171,9 +138,9 @@ function setFocusVisibleListeners(
138
139
function useFocusVisibleInputHandles() {
140
return [
174
- useEvent('mousedown', passiveObject),
175
- useEvent(hasPointerEvents ? 'pointerdown' : 'touchstart', passiveObject),
176
- useEvent('keydown', passiveObject),
141
+ useEvent('mousedown'),
142
+ useEvent(hasPointerEvents ? 'pointerdown' : 'touchstart'),
143
+ useEvent('keydown'),
144
];
145
}
146
@@ -200,8 +167,8 @@ export function useFocus(
167
const stateRef = useRef<null | {isFocused: boolean, isFocusVisible: boolean}>(
168
{isFocused: false, isFocusVisible: false},
169
);
203
- const focusHandle = useEvent('focusin', passiveObjectWithPriority);
204
- const blurHandle = useEvent('focusout', passiveObjectWithPriority);
170
+ const focusHandle = useEvent('focusin');
171
+ const blurHandle = useEvent('focusout');
172
const focusVisibleHandles = useFocusVisibleInputHandles();
173
174
useLayoutEffect(() => {
@@ -297,10 +264,10 @@ export function useFocusWithin<T>(
264
const stateRef = useRef<null | {isFocused: boolean, isFocusVisible: boolean}>(
265
{isFocused: false, isFocusVisible: false},
266
);
300
- const focusHandle = useEvent('focusin', passiveObjectWithPriority);
301
- const blurHandle = useEvent('focusout', passiveObjectWithPriority);
302
- const afterBlurHandle = useEvent('afterblur', passiveObject);
303
- const beforeBlurHandle = useEvent('beforeblur', passiveObject);
267
+ const focusHandle = useEvent('focusin');
268
+ const blurHandle = useEvent('focusout');
269
+ const afterBlurHandle = useEvent('afterblur');
270
+ const beforeBlurHandle = useEvent('beforeblur');
271
const focusVisibleHandles = useFocusVisibleInputHandles();
272
273
const useFocusWithinRef = useCallback(
packages/react-interactions/events/src/dom/create-event-handle/useEvent.js
-2
@@ -25,8 +25,6 @@ export default function useEvent(
25
event: string,
26
options?: {|
27
capture?: boolean,
28
- passive?: boolean,
29
- priority?: 0 | 1 | 2,
28
|},
29
): UseEventHandle {
30
const handleRef = useRef<UseEventHandle | null>(null);