From 68e33ad7c04965287a3682de61defb68a3f761d0 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 19 Aug 2020 18:42:33 +0100 Subject: [PATCH] Keep onTouchStart, onTouchMove, and onWheel passive (#19654) * Keep onTouchStart, onTouchMove, and onWheel passive * Put it behind a feature flag on WWW --- .../src/events/DOMPluginEventSystem.js | 20 +++++++++- .../src/events/checkPassiveEvents.js | 3 +- .../__tests__/SimpleEventPlugin-test.js | 39 +++++++++++++++++++ packages/shared/ReactFeatureFlags.js | 3 ++ .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.native.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.testing.js | 1 + .../forks/ReactFeatureFlags.testing.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 13 files changed, 70 insertions(+), 4 deletions(-) diff --git a/packages/react-dom/src/events/DOMPluginEventSystem.js b/packages/react-dom/src/events/DOMPluginEventSystem.js index eea047b959879..6a216ace127c4 100644 --- a/packages/react-dom/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMPluginEventSystem.js @@ -53,6 +53,7 @@ import { enableLegacyFBSupport, enableCreateEventHandleAPI, enableScopeAPI, + enablePassiveEventIntervention, } from 'shared/ReactFeatureFlags'; import { invokeGuardedCallbackAndCatchFirstError, @@ -342,6 +343,21 @@ export function listenToNativeEvent( if (domEventName === 'selectionchange') { target = (rootContainerElement: any).ownerDocument; } + if (enablePassiveEventIntervention && isPassiveListener === undefined) { + // Browsers introduced an intervention, making these events + // passive by default on document. React doesn't bind them + // to document anymore, but changing this now would undo + // the performance wins from the change. So we emulate + // the existing behavior manually on the roots now. + // https://github.com/facebook/react/issues/19651 + if ( + domEventName === 'touchstart' || + domEventName === 'touchmove' || + domEventName === 'wheel' + ) { + isPassiveListener = true; + } + } // If the event can be delegated (or is capture phase), we can // register it to the root container. Otherwise, we should // register the event to the target element and mark it as @@ -506,7 +522,7 @@ function addTrappedEventListener( }; } if (isCapturePhaseListener) { - if (enableCreateEventHandleAPI && isPassiveListener !== undefined) { + if (isPassiveListener !== undefined) { unsubscribeListener = addEventCaptureListenerWithPassiveFlag( targetContainer, domEventName, @@ -521,7 +537,7 @@ function addTrappedEventListener( ); } } else { - if (enableCreateEventHandleAPI && isPassiveListener !== undefined) { + if (isPassiveListener !== undefined) { unsubscribeListener = addEventBubbleListenerWithPassiveFlag( targetContainer, domEventName, diff --git a/packages/react-dom/src/events/checkPassiveEvents.js b/packages/react-dom/src/events/checkPassiveEvents.js index 415a13395c736..12680a885ebde 100644 --- a/packages/react-dom/src/events/checkPassiveEvents.js +++ b/packages/react-dom/src/events/checkPassiveEvents.js @@ -8,13 +8,12 @@ */ import {canUseDOM} from 'shared/ExecutionEnvironment'; -import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags'; export let passiveBrowserEventsSupported = false; // Check if browser support events with passive listeners // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support -if (enableCreateEventHandleAPI && canUseDOM) { +if (canUseDOM) { try { const options = {}; // $FlowFixMe: Ignore Flow complaining about needing a value diff --git a/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js index 4ed062404e52f..d8eb2c9219e8d 100644 --- a/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js +++ b/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js @@ -504,5 +504,44 @@ describe('SimpleEventPlugin', function() { expect(onClick).toHaveBeenCalledTimes(0); }); + + it('registers passive handlers for events affected by the intervention', () => { + container = document.createElement('div'); + + const passiveEvents = []; + const nativeAddEventListener = container.addEventListener; + container.addEventListener = function(type, fn, options) { + if (options !== null && typeof options === 'object') { + if (options.passive) { + passiveEvents.push(type); + } + } + return nativeAddEventListener.apply(this, arguments); + }; + + ReactDOM.render( +
{}} + onTouchMove={() => {}} + onWheel={() => {}} + // A few events that should be unaffected: + onClick={() => {}} + onScroll={() => {}} + onTouchEnd={() => {}} + onChange={() => {}} + onPointerDown={() => {}} + onPointerMove={() => {}} + />, + container, + ); + + if (gate(flags => flags.enablePassiveEventIntervention)) { + expect(passiveEvents).toEqual(['touchstart', 'touchmove', 'wheel']); + } else { + expect(passiveEvents).toEqual([]); + } + }); }); }); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 2ae1805af95c1..81975c25896ce 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -132,3 +132,6 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; + +// https://github.com/facebook/react/pull/19654 +export const enablePassiveEventIntervention = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 6b61818c6d044..49f542446f36f 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -49,6 +49,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index c8598c23cd924..a050c69e1e1a3 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -48,6 +48,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 47a729c1d0a09..f2a1adc1b4d70 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -48,6 +48,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 4af72e9d901a7..baee0a991e580 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -48,6 +48,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 7c25d50d07de9..3ede445fa3ddc 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -48,6 +48,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index 5e530afbd9db7..acfafaadb255b 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -48,6 +48,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index e11f9f9d13381..ccda44cf343d3 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -48,6 +48,7 @@ export const enableNewReconciler = false; export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = true; +export const enablePassiveEventIntervention = true; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index c7df70d192989..1397d33d53bd9 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -19,6 +19,7 @@ export const enableFilterEmptyStringAttributesDOM = __VARIANT__; export const enableLegacyFBSupport = __VARIANT__; export const decoupleUpdatePriorityFromScheduler = __VARIANT__; export const skipUnmountedBoundaries = __VARIANT__; +export const enablePassiveEventIntervention = __VARIANT__; // Enable this flag to help with concurrent mode debugging. // It logs information to the console about React scheduling, rendering, and commit phases. diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 35f5767413fc3..74d4105ea41ad 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -27,6 +27,7 @@ export const { decoupleUpdatePriorityFromScheduler, enableDebugTracing, skipUnmountedBoundaries, + enablePassiveEventIntervention, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build.