Keep onTouchStart, onTouchMove, and onWheel passive (#19654)
* Keep onTouchStart, onTouchMove, and onWheel passive * Put it behind a feature flag on WWW
Dan Abramov committed
Aug 19, 2020 at 18:42 UTC
dd651df05e5802a31484f269b689c17acf087299
13 files changed
+70
-4
packages/react-dom/src/events/DOMPluginEventSystem.js
+18
-2
@@ -53,6 +53,7 @@ import {
53
enableLegacyFBSupport,
54
enableCreateEventHandleAPI,
55
enableScopeAPI,
56
+ enablePassiveEventIntervention,
57
} from 'shared/ReactFeatureFlags';
58
import {
59
invokeGuardedCallbackAndCatchFirstError,
@@ -342,6 +343,21 @@ export function listenToNativeEvent(
343
if (domEventName === 'selectionchange') {
344
target = (rootContainerElement: any).ownerDocument;
345
}
346
+ if (enablePassiveEventIntervention && isPassiveListener === undefined) {
347
+ // Browsers introduced an intervention, making these events
348
+ // passive by default on document. React doesn't bind them
349
+ // to document anymore, but changing this now would undo
350
+ // the performance wins from the change. So we emulate
351
+ // the existing behavior manually on the roots now.
352
+ // https://github.com/facebook/react/issues/19651
353
+ if (
354
+ domEventName === 'touchstart' ||
355
+ domEventName === 'touchmove' ||
356
+ domEventName === 'wheel'
357
+ ) {
358
+ isPassiveListener = true;
359
+ }
360
+ }
361
// If the event can be delegated (or is capture phase), we can
362
// register it to the root container. Otherwise, we should
363
// register the event to the target element and mark it as
@@ -506,7 +522,7 @@ function addTrappedEventListener(
522
};
523
}
524
if (isCapturePhaseListener) {
509
- if (enableCreateEventHandleAPI && isPassiveListener !== undefined) {
525
+ if (isPassiveListener !== undefined) {
526
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
527
targetContainer,
528
domEventName,
@@ -521,7 +537,7 @@ function addTrappedEventListener(
537
);
538
}
539
} else {
524
- if (enableCreateEventHandleAPI && isPassiveListener !== undefined) {
540
+ if (isPassiveListener !== undefined) {
541
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
542
targetContainer,
543
domEventName,
packages/react-dom/src/events/checkPassiveEvents.js
+1
-2
@@ -8,13 +8,12 @@
8
*/
9
10
import {canUseDOM} from 'shared/ExecutionEnvironment';
11
-import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
11
12
export let passiveBrowserEventsSupported = false;
13
14
// Check if browser support events with passive listeners
15
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
17
-if (enableCreateEventHandleAPI && canUseDOM) {
16
+if (canUseDOM) {
17
try {
18
const options = {};
19
// $FlowFixMe: Ignore Flow complaining about needing a value
packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
+39
@@ -504,5 +504,44 @@ describe('SimpleEventPlugin', function() {
504
505
expect(onClick).toHaveBeenCalledTimes(0);
506
});
507
+
508
+ it('registers passive handlers for events affected by the intervention', () => {
509
+ container = document.createElement('div');
510
+
511
+ const passiveEvents = [];
512
+ const nativeAddEventListener = container.addEventListener;
513
+ container.addEventListener = function(type, fn, options) {
514
+ if (options !== null && typeof options === 'object') {
515
+ if (options.passive) {
516
+ passiveEvents.push(type);
517
+ }
518
+ }
519
+ return nativeAddEventListener.apply(this, arguments);
520
+ };
521
+
522
+ ReactDOM.render(
523
+ <div
524
+ // Affected by the intervention:
525
+ // https://github.com/facebook/react/issues/19651
526
+ onTouchStart={() => {}}
527
+ onTouchMove={() => {}}
528
+ onWheel={() => {}}
529
+ // A few events that should be unaffected:
530
+ onClick={() => {}}
531
+ onScroll={() => {}}
532
+ onTouchEnd={() => {}}
533
+ onChange={() => {}}
534
+ onPointerDown={() => {}}
535
+ onPointerMove={() => {}}
536
+ />,
537
+ container,
538
+ );
539
+
540
+ if (gate(flags => flags.enablePassiveEventIntervention)) {
541
+ expect(passiveEvents).toEqual(['touchstart', 'touchmove', 'wheel']);
542
+ } else {
543
+ expect(passiveEvents).toEqual([]);
544
+ }
545
+ });
546
});
547
});
packages/shared/ReactFeatureFlags.js
+3
@@ -132,3 +132,6 @@ export const deferRenderPhaseUpdateToNextBatch = true;
132
export const decoupleUpdatePriorityFromScheduler = false;
133
134
export const enableDiscreteEventFlushingChange = false;
135
+
136
+// https://github.com/facebook/react/pull/19654
137
+export const enablePassiveEventIntervention = true;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -49,6 +49,7 @@ export const enableNewReconciler = false;
49
export const deferRenderPhaseUpdateToNextBatch = true;
50
export const decoupleUpdatePriorityFromScheduler = false;
51
export const enableDiscreteEventFlushingChange = false;
52
+export const enablePassiveEventIntervention = true;
53
54
// Flow magic to verify the exports of this file match the original version.
55
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -48,6 +48,7 @@ export const enableNewReconciler = false;
48
export const deferRenderPhaseUpdateToNextBatch = true;
49
export const decoupleUpdatePriorityFromScheduler = false;
50
export const enableDiscreteEventFlushingChange = false;
51
+export const enablePassiveEventIntervention = true;
52
53
// Flow magic to verify the exports of this file match the original version.
54
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -48,6 +48,7 @@ export const enableNewReconciler = false;
48
export const deferRenderPhaseUpdateToNextBatch = true;
49
export const decoupleUpdatePriorityFromScheduler = false;
50
export const enableDiscreteEventFlushingChange = false;
51
+export const enablePassiveEventIntervention = true;
52
53
// Flow magic to verify the exports of this file match the original version.
54
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -48,6 +48,7 @@ export const enableNewReconciler = false;
48
export const deferRenderPhaseUpdateToNextBatch = true;
49
export const decoupleUpdatePriorityFromScheduler = false;
50
export const enableDiscreteEventFlushingChange = false;
51
+export const enablePassiveEventIntervention = true;
52
53
// Flow magic to verify the exports of this file match the original version.
54
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -48,6 +48,7 @@ export const enableNewReconciler = false;
48
export const deferRenderPhaseUpdateToNextBatch = true;
49
export const decoupleUpdatePriorityFromScheduler = false;
50
export const enableDiscreteEventFlushingChange = false;
51
+export const enablePassiveEventIntervention = true;
52
53
// Flow magic to verify the exports of this file match the original version.
54
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -48,6 +48,7 @@ export const enableNewReconciler = false;
48
export const deferRenderPhaseUpdateToNextBatch = true;
49
export const decoupleUpdatePriorityFromScheduler = false;
50
export const enableDiscreteEventFlushingChange = false;
51
+export const enablePassiveEventIntervention = true;
52
53
// Flow magic to verify the exports of this file match the original version.
54
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1
@@ -48,6 +48,7 @@ export const enableNewReconciler = false;
48
export const deferRenderPhaseUpdateToNextBatch = true;
49
export const decoupleUpdatePriorityFromScheduler = false;
50
export const enableDiscreteEventFlushingChange = true;
51
+export const enablePassiveEventIntervention = true;
52
53
// Flow magic to verify the exports of this file match the original version.
54
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -19,6 +19,7 @@ export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
19
export const enableLegacyFBSupport = __VARIANT__;
20
export const decoupleUpdatePriorityFromScheduler = __VARIANT__;
21
export const skipUnmountedBoundaries = __VARIANT__;
22
+export const enablePassiveEventIntervention = __VARIANT__;
23
24
// Enable this flag to help with concurrent mode debugging.
25
// It logs information to the console about React scheduling, rendering, and commit phases.
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -27,6 +27,7 @@ export const {
27
decoupleUpdatePriorityFromScheduler,
28
enableDebugTracing,
29
skipUnmountedBoundaries,
30
+ enablePassiveEventIntervention,
31
} = dynamicFeatureFlags;
32
33
// On WWW, __EXPERIMENTAL__ is used for a new modern build.