Keep onTouchStart, onTouchMove, and onWheel passive (#19654)

* Keep onTouchStart, onTouchMove, and onWheel passive

* Put it behind a feature flag on WWW
This commit is contained in:
Dan Abramov
2020-08-19 18:42:33 +01:00
committed by GitHub
parent 87b3e2d257
commit dd651df05e
13 changed files with 70 additions and 4 deletions
+18 -2
View File
@@ -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,
+1 -2
View File
@@ -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
@@ -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(
<div
// Affected by the intervention:
// https://github.com/facebook/react/issues/19651
onTouchStart={() => {}}
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([]);
}
});
});
});
+3
View File
@@ -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;
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -27,6 +27,7 @@ export const {
decoupleUpdatePriorityFromScheduler,
enableDebugTracing,
skipUnmountedBoundaries,
enablePassiveEventIntervention,
} = dynamicFeatureFlags;
// On WWW, __EXPERIMENTAL__ is used for a new modern build.