mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Enable eager listeners statically (#19983)
This commit is contained in:
@@ -1040,7 +1040,6 @@ describe('ReactDOMFiber', () => {
|
||||
expect(ops).toEqual([]);
|
||||
});
|
||||
|
||||
// @gate enableEagerRootListeners
|
||||
it('listens to events that do not exist in the Portal subtree', () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
|
||||
+5
-83
@@ -61,12 +61,7 @@ import {
|
||||
shouldRemoveAttribute,
|
||||
} from '../shared/DOMProperty';
|
||||
import assertValidProps from '../shared/assertValidProps';
|
||||
import {
|
||||
DOCUMENT_NODE,
|
||||
ELEMENT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
|
||||
import isCustomComponent from '../shared/isCustomComponent';
|
||||
import possibleStandardNames from '../shared/possibleStandardNames';
|
||||
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
|
||||
@@ -74,12 +69,8 @@ import {validateProperties as validateInputProperties} from '../shared/ReactDOMN
|
||||
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
|
||||
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import {enableTrustedTypesIntegration} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableTrustedTypesIntegration,
|
||||
enableEagerRootListeners,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
listenToReactEvent,
|
||||
mediaEventTypes,
|
||||
listenToNonDelegatedEvent,
|
||||
} from '../events/DOMPluginEventSystem';
|
||||
@@ -253,39 +244,6 @@ if (__DEV__) {
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureListeningTo(
|
||||
rootContainerInstance: Element | Node,
|
||||
reactPropEvent: string,
|
||||
targetElement: Element | null,
|
||||
): void {
|
||||
if (!enableEagerRootListeners) {
|
||||
// If we have a comment node, then use the parent node,
|
||||
// which should be an element.
|
||||
const rootContainerElement =
|
||||
rootContainerInstance.nodeType === COMMENT_NODE
|
||||
? rootContainerInstance.parentNode
|
||||
: rootContainerInstance;
|
||||
if (__DEV__) {
|
||||
if (
|
||||
rootContainerElement == null ||
|
||||
(rootContainerElement.nodeType !== ELEMENT_NODE &&
|
||||
// This is to support rendering into a ShadowRoot:
|
||||
rootContainerElement.nodeType !== DOCUMENT_FRAGMENT_NODE)
|
||||
) {
|
||||
console.error(
|
||||
'ensureListeningTo(): received a container that was not an element node. ' +
|
||||
'This is likely a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
listenToReactEvent(
|
||||
reactPropEvent,
|
||||
((rootContainerElement: any): Element),
|
||||
targetElement,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getOwnerDocumentFromRootContainer(
|
||||
rootContainerElement: Element | Document,
|
||||
): Document {
|
||||
@@ -364,9 +322,7 @@ function setInitialDOMProperties(
|
||||
if (__DEV__ && typeof nextProp !== 'function') {
|
||||
warnForInvalidEventListener(propKey, nextProp);
|
||||
}
|
||||
if (!enableEagerRootListeners) {
|
||||
ensureListeningTo(rootContainerElement, propKey, domElement);
|
||||
} else if (propKey === 'onScroll') {
|
||||
if (propKey === 'onScroll') {
|
||||
listenToNonDelegatedEvent('scroll', domElement);
|
||||
}
|
||||
}
|
||||
@@ -577,11 +533,6 @@ export function setInitialProperties(
|
||||
// We listen to this event in case to ensure emulated bubble
|
||||
// listeners still fire for the invalid event.
|
||||
listenToNonDelegatedEvent('invalid', domElement);
|
||||
if (!enableEagerRootListeners) {
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange', domElement);
|
||||
}
|
||||
break;
|
||||
case 'option':
|
||||
ReactDOMOptionValidateProps(domElement, rawProps);
|
||||
@@ -593,11 +544,6 @@ export function setInitialProperties(
|
||||
// We listen to this event in case to ensure emulated bubble
|
||||
// listeners still fire for the invalid event.
|
||||
listenToNonDelegatedEvent('invalid', domElement);
|
||||
if (!enableEagerRootListeners) {
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange', domElement);
|
||||
}
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMTextareaInitWrapperState(domElement, rawProps);
|
||||
@@ -605,11 +551,6 @@ export function setInitialProperties(
|
||||
// We listen to this event in case to ensure emulated bubble
|
||||
// listeners still fire for the invalid event.
|
||||
listenToNonDelegatedEvent('invalid', domElement);
|
||||
if (!enableEagerRootListeners) {
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange', domElement);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
props = rawProps;
|
||||
@@ -827,9 +768,7 @@ export function diffProperties(
|
||||
if (__DEV__ && typeof nextProp !== 'function') {
|
||||
warnForInvalidEventListener(propKey, nextProp);
|
||||
}
|
||||
if (!enableEagerRootListeners) {
|
||||
ensureListeningTo(rootContainerElement, propKey, domElement);
|
||||
} else if (propKey === 'onScroll') {
|
||||
if (propKey === 'onScroll') {
|
||||
listenToNonDelegatedEvent('scroll', domElement);
|
||||
}
|
||||
}
|
||||
@@ -983,11 +922,6 @@ export function diffHydratedProperties(
|
||||
// We listen to this event in case to ensure emulated bubble
|
||||
// listeners still fire for the invalid event.
|
||||
listenToNonDelegatedEvent('invalid', domElement);
|
||||
if (!enableEagerRootListeners) {
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange', domElement);
|
||||
}
|
||||
break;
|
||||
case 'option':
|
||||
ReactDOMOptionValidateProps(domElement, rawProps);
|
||||
@@ -997,22 +931,12 @@ export function diffHydratedProperties(
|
||||
// We listen to this event in case to ensure emulated bubble
|
||||
// listeners still fire for the invalid event.
|
||||
listenToNonDelegatedEvent('invalid', domElement);
|
||||
if (!enableEagerRootListeners) {
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange', domElement);
|
||||
}
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMTextareaInitWrapperState(domElement, rawProps);
|
||||
// We listen to this event in case to ensure emulated bubble
|
||||
// listeners still fire for the invalid event.
|
||||
listenToNonDelegatedEvent('invalid', domElement);
|
||||
if (!enableEagerRootListeners) {
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange', domElement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1079,9 +1003,7 @@ export function diffHydratedProperties(
|
||||
if (__DEV__ && typeof nextProp !== 'function') {
|
||||
warnForInvalidEventListener(propKey, nextProp);
|
||||
}
|
||||
if (!enableEagerRootListeners) {
|
||||
ensureListeningTo(rootContainerElement, propKey, domElement);
|
||||
} else if (propKey === 'onScroll') {
|
||||
if (propKey === 'onScroll') {
|
||||
listenToNonDelegatedEvent('scroll', domElement);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-84
@@ -16,23 +16,19 @@ import type {
|
||||
|
||||
import {allNativeEvents} from '../events/EventRegistry';
|
||||
import {
|
||||
getClosestInstanceFromNode,
|
||||
getEventHandlerListeners,
|
||||
setEventHandlerListeners,
|
||||
getFiberFromScopeInstance,
|
||||
doesTargetHaveEventHandle,
|
||||
addEventHandleToTarget,
|
||||
} from './ReactDOMComponentTree';
|
||||
import {ELEMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';
|
||||
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
|
||||
import {listenToNativeEvent} from '../events/DOMPluginEventSystem';
|
||||
|
||||
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../events/EventSystemFlags';
|
||||
|
||||
import {
|
||||
enableScopeAPI,
|
||||
enableCreateEventHandleAPI,
|
||||
enableEagerRootListeners,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
@@ -40,19 +36,6 @@ type EventHandleOptions = {|
|
||||
capture?: boolean,
|
||||
|};
|
||||
|
||||
function getNearestRootOrPortalContainer(node: Fiber): null | Element {
|
||||
while (node !== null) {
|
||||
const tag = node.tag;
|
||||
// Once we encounter a host container or root container
|
||||
// we can return their DOM instance.
|
||||
if (tag === HostRoot || tag === HostPortal) {
|
||||
return node.stateNode.containerInfo;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isValidEventTarget(target: EventTarget | ReactScopeInstance): boolean {
|
||||
return typeof (target: Object).addEventListener === 'function';
|
||||
}
|
||||
@@ -73,79 +56,15 @@ function createEventHandleListener(
|
||||
};
|
||||
}
|
||||
|
||||
function registerEventOnNearestTargetContainer(
|
||||
targetFiber: Fiber,
|
||||
domEventName: DOMEventName,
|
||||
isCapturePhaseListener: boolean,
|
||||
targetElement: Element | null,
|
||||
): void {
|
||||
if (!enableEagerRootListeners) {
|
||||
// If it is, find the nearest root or portal and make it
|
||||
// our event handle target container.
|
||||
let targetContainer = getNearestRootOrPortalContainer(targetFiber);
|
||||
if (targetContainer === null) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'ReactDOM.createEventHandle: setListener called on an target ' +
|
||||
'that did not have a corresponding root. This is likely a bug in React.',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (targetContainer.nodeType === COMMENT_NODE) {
|
||||
targetContainer = ((targetContainer.parentNode: any): Element);
|
||||
}
|
||||
listenToNativeEvent(
|
||||
domEventName,
|
||||
isCapturePhaseListener,
|
||||
targetContainer,
|
||||
targetElement,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function registerReactDOMEvent(
|
||||
target: EventTarget | ReactScopeInstance,
|
||||
domEventName: DOMEventName,
|
||||
isCapturePhaseListener: boolean,
|
||||
): void {
|
||||
// Check if the target is a DOM element.
|
||||
if ((target: any).nodeType === ELEMENT_NODE) {
|
||||
if (!enableEagerRootListeners) {
|
||||
const targetElement = ((target: any): Element);
|
||||
// Check if the DOM element is managed by React.
|
||||
const targetFiber = getClosestInstanceFromNode(targetElement);
|
||||
if (targetFiber === null) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'ReactDOM.createEventHandle: setListener called on an element ' +
|
||||
'target that is not managed by React. Ensure React rendered the DOM element.',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerEventOnNearestTargetContainer(
|
||||
targetFiber,
|
||||
domEventName,
|
||||
isCapturePhaseListener,
|
||||
targetElement,
|
||||
);
|
||||
}
|
||||
// Do nothing. We already attached all root listeners.
|
||||
} else if (enableScopeAPI && isReactScope(target)) {
|
||||
if (!enableEagerRootListeners) {
|
||||
const scopeTarget = ((target: any): ReactScopeInstance);
|
||||
const targetFiber = getFiberFromScopeInstance(scopeTarget);
|
||||
if (targetFiber === null) {
|
||||
// Scope is unmounted, do not proceed.
|
||||
return;
|
||||
}
|
||||
registerEventOnNearestTargetContainer(
|
||||
targetFiber,
|
||||
domEventName,
|
||||
isCapturePhaseListener,
|
||||
null,
|
||||
);
|
||||
}
|
||||
// Do nothing. We already attached all root listeners.
|
||||
} else if (isValidEventTarget(target)) {
|
||||
const eventTarget = ((target: any): EventTarget);
|
||||
// These are valid event targets, but they are also
|
||||
|
||||
+2
-10
@@ -67,13 +67,9 @@ import {
|
||||
enableFundamentalAPI,
|
||||
enableCreateEventHandleAPI,
|
||||
enableScopeAPI,
|
||||
enableEagerRootListeners,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {
|
||||
listenToReactEvent,
|
||||
listenToAllSupportedEvents,
|
||||
} from '../events/DOMPluginEventSystem';
|
||||
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
|
||||
|
||||
export type Type = string;
|
||||
export type Props = {
|
||||
@@ -1073,11 +1069,7 @@ export function makeOpaqueHydratingObject(
|
||||
}
|
||||
|
||||
export function preparePortalMount(portalInstance: Instance): void {
|
||||
if (enableEagerRootListeners) {
|
||||
listenToAllSupportedEvents(portalInstance);
|
||||
} else {
|
||||
listenToReactEvent('onMouseEnter', portalInstance, null);
|
||||
}
|
||||
listenToAllSupportedEvents(portalInstance);
|
||||
}
|
||||
|
||||
export function prepareScopeUpdate(
|
||||
|
||||
+3
-26
@@ -36,14 +36,12 @@ import {
|
||||
unmarkContainerAsRoot,
|
||||
} from './ReactDOMComponentTree';
|
||||
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
|
||||
import {eagerlyTrapReplayableEvents} from '../events/ReactDOMEventReplaying';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
import {ensureListeningTo} from './ReactDOMComponent';
|
||||
|
||||
import {
|
||||
createContainer,
|
||||
@@ -52,7 +50,6 @@ import {
|
||||
registerMutableSourceForHydration,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
import invariant from 'shared/invariant';
|
||||
import {enableEagerRootListeners} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
BlockingRoot,
|
||||
ConcurrentRoot,
|
||||
@@ -133,30 +130,10 @@ function createRootImpl(
|
||||
null;
|
||||
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
|
||||
markContainerAsRoot(root.current, container);
|
||||
const containerNodeType = container.nodeType;
|
||||
|
||||
if (enableEagerRootListeners) {
|
||||
const rootContainerElement =
|
||||
container.nodeType === COMMENT_NODE ? container.parentNode : container;
|
||||
listenToAllSupportedEvents(rootContainerElement);
|
||||
} else {
|
||||
if (hydrate && tag !== LegacyRoot) {
|
||||
const doc =
|
||||
containerNodeType === DOCUMENT_NODE
|
||||
? container
|
||||
: container.ownerDocument;
|
||||
// We need to cast this because Flow doesn't work
|
||||
// with the hoisted containerNodeType. If we inline
|
||||
// it, then Flow doesn't complain. We intentionally
|
||||
// hoist it to reduce code-size.
|
||||
eagerlyTrapReplayableEvents(container, ((doc: any): Document));
|
||||
} else if (
|
||||
containerNodeType !== DOCUMENT_FRAGMENT_NODE &&
|
||||
containerNodeType !== DOCUMENT_NODE
|
||||
) {
|
||||
ensureListeningTo(container, 'onMouseEnter', null);
|
||||
}
|
||||
}
|
||||
const rootContainerElement =
|
||||
container.nodeType === COMMENT_NODE ? container.parentNode : container;
|
||||
listenToAllSupportedEvents(rootContainerElement);
|
||||
|
||||
if (mutableSources) {
|
||||
for (let i = 0; i < mutableSources.length; i++) {
|
||||
|
||||
+20
-77
@@ -21,7 +21,7 @@ import type {
|
||||
} from './ReactSyntheticEventType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import {registrationNameDependencies, allNativeEvents} from './EventRegistry';
|
||||
import {allNativeEvents} from './EventRegistry';
|
||||
import {
|
||||
IS_CAPTURE_PHASE,
|
||||
IS_EVENT_HANDLE_NON_MANAGED_NODE,
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
enableLegacyFBSupport,
|
||||
enableCreateEventHandleAPI,
|
||||
enableScopeAPI,
|
||||
enableEagerRootListeners,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
invokeGuardedCallbackAndCatchFirstError,
|
||||
@@ -320,32 +319,30 @@ const listeningMarker =
|
||||
.slice(2);
|
||||
|
||||
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
|
||||
if (enableEagerRootListeners) {
|
||||
if ((rootContainerElement: any)[listeningMarker]) {
|
||||
// Performance optimization: don't iterate through events
|
||||
// for the same portal container or root node more than once.
|
||||
// TODO: once we remove the flag, we may be able to also
|
||||
// remove some of the bookkeeping maps used for laziness.
|
||||
return;
|
||||
}
|
||||
(rootContainerElement: any)[listeningMarker] = true;
|
||||
allNativeEvents.forEach(domEventName => {
|
||||
if (!nonDelegatedEvents.has(domEventName)) {
|
||||
listenToNativeEvent(
|
||||
domEventName,
|
||||
false,
|
||||
((rootContainerElement: any): Element),
|
||||
null,
|
||||
);
|
||||
}
|
||||
if ((rootContainerElement: any)[listeningMarker]) {
|
||||
// Performance optimization: don't iterate through events
|
||||
// for the same portal container or root node more than once.
|
||||
// TODO: once we remove the flag, we may be able to also
|
||||
// remove some of the bookkeeping maps used for laziness.
|
||||
return;
|
||||
}
|
||||
(rootContainerElement: any)[listeningMarker] = true;
|
||||
allNativeEvents.forEach(domEventName => {
|
||||
if (!nonDelegatedEvents.has(domEventName)) {
|
||||
listenToNativeEvent(
|
||||
domEventName,
|
||||
true,
|
||||
false,
|
||||
((rootContainerElement: any): Element),
|
||||
null,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
listenToNativeEvent(
|
||||
domEventName,
|
||||
true,
|
||||
((rootContainerElement: any): Element),
|
||||
null,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function listenToNativeEvent(
|
||||
@@ -411,60 +408,6 @@ export function listenToNativeEvent(
|
||||
}
|
||||
}
|
||||
|
||||
export function listenToReactEvent(
|
||||
reactEvent: string,
|
||||
rootContainerElement: Element,
|
||||
targetElement: Element | null,
|
||||
): void {
|
||||
if (!enableEagerRootListeners) {
|
||||
const dependencies = registrationNameDependencies[reactEvent];
|
||||
const dependenciesLength = dependencies.length;
|
||||
// If the dependencies length is 1, that means we're not using a polyfill
|
||||
// plugin like ChangeEventPlugin, BeforeInputPlugin, EnterLeavePlugin
|
||||
// and SelectEventPlugin. We always use the native bubble event phase for
|
||||
// these plugins and emulate two phase event dispatching. SimpleEventPlugin
|
||||
// always only has a single dependency and SimpleEventPlugin events also
|
||||
// use either the native capture event phase or bubble event phase, there
|
||||
// is no emulation (except for focus/blur, but that will be removed soon).
|
||||
const isPolyfillEventPlugin = dependenciesLength !== 1;
|
||||
|
||||
if (isPolyfillEventPlugin) {
|
||||
const listenerSet = getEventListenerSet(rootContainerElement);
|
||||
// When eager listeners are off, this Set has a dual purpose: it both
|
||||
// captures which native listeners we registered (e.g. "click__bubble")
|
||||
// and *React* lazy listeners (e.g. "onClick") so we don't do extra checks.
|
||||
// This second usage does not exist in the eager mode.
|
||||
if (!listenerSet.has(reactEvent)) {
|
||||
listenerSet.add(reactEvent);
|
||||
for (let i = 0; i < dependenciesLength; i++) {
|
||||
listenToNativeEvent(
|
||||
dependencies[i],
|
||||
false,
|
||||
rootContainerElement,
|
||||
targetElement,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const isCapturePhaseListener =
|
||||
reactEvent.substr(-7) === 'Capture' &&
|
||||
// Edge case: onGotPointerCapture and onLostPointerCapture
|
||||
// end with "Capture" but that's part of their event names.
|
||||
// The Capture versions would end with CaptureCapture.
|
||||
// So we have to check against that.
|
||||
// This check works because none of the events we support
|
||||
// end with "Pointer".
|
||||
reactEvent.substr(-14, 7) !== 'Pointer';
|
||||
listenToNativeEvent(
|
||||
dependencies[0],
|
||||
isCapturePhaseListener,
|
||||
rootContainerElement,
|
||||
targetElement,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addTrappedEventListener(
|
||||
targetContainer: EventTarget,
|
||||
domEventName: DOMEventName,
|
||||
|
||||
+8
-11
@@ -40,7 +40,6 @@ import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
|
||||
|
||||
import {
|
||||
enableLegacyFBSupport,
|
||||
enableEagerRootListeners,
|
||||
decoupleUpdatePriorityFromScheduler,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
@@ -188,16 +187,14 @@ export function dispatchEvent(
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
let allowReplay = true;
|
||||
if (enableEagerRootListeners) {
|
||||
// TODO: replaying capture phase events is currently broken
|
||||
// because we used to do it during top-level native bubble handlers
|
||||
// but now we use different bubble and capture handlers.
|
||||
// In eager mode, we attach capture listeners early, so we need
|
||||
// to filter them out until we fix the logic to handle them correctly.
|
||||
// This could've been outside the flag but I put it inside to reduce risk.
|
||||
allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
|
||||
}
|
||||
|
||||
// TODO: replaying capture phase events is currently broken
|
||||
// because we used to do it during top-level native bubble handlers
|
||||
// but now we use different bubble and capture handlers.
|
||||
// In eager mode, we attach capture listeners early, so we need
|
||||
// to filter them out until we fix the logic to handle them correctly.
|
||||
const allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
|
||||
|
||||
if (
|
||||
allowReplay &&
|
||||
hasQueuedDiscreteEvents() &&
|
||||
|
||||
+1
-45
@@ -14,10 +14,7 @@ import type {EventSystemFlags} from './EventSystemFlags';
|
||||
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {LanePriority} from 'react-reconciler/src/ReactFiberLane';
|
||||
|
||||
import {
|
||||
enableSelectiveHydration,
|
||||
enableEagerRootListeners,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableSelectiveHydration} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
unstable_runWithPriority as runWithPriority,
|
||||
unstable_scheduleCallback as scheduleCallback,
|
||||
@@ -85,7 +82,6 @@ type PointerEvent = Event & {
|
||||
};
|
||||
|
||||
import {IS_REPLAYED} from './EventSystemFlags';
|
||||
import {listenToNativeEvent} from './DOMPluginEventSystem';
|
||||
|
||||
type QueuedReplayableEvent = {|
|
||||
blockedOn: null | Container | SuspenseInstance,
|
||||
@@ -159,50 +155,10 @@ const discreteReplayableEvents: Array<DOMEventName> = [
|
||||
'submit',
|
||||
];
|
||||
|
||||
const continuousReplayableEvents: Array<DOMEventName> = [
|
||||
'dragenter',
|
||||
'dragleave',
|
||||
'focusin',
|
||||
'focusout',
|
||||
'mouseover',
|
||||
'mouseout',
|
||||
'pointerover',
|
||||
'pointerout',
|
||||
'gotpointercapture',
|
||||
'lostpointercapture',
|
||||
];
|
||||
|
||||
export function isReplayableDiscreteEvent(eventType: DOMEventName): boolean {
|
||||
return discreteReplayableEvents.indexOf(eventType) > -1;
|
||||
}
|
||||
|
||||
function trapReplayableEventForContainer(
|
||||
domEventName: DOMEventName,
|
||||
container: Container,
|
||||
) {
|
||||
// When the flag is on, we do this in a unified codepath elsewhere.
|
||||
if (!enableEagerRootListeners) {
|
||||
listenToNativeEvent(domEventName, false, ((container: any): Element), null);
|
||||
}
|
||||
}
|
||||
|
||||
export function eagerlyTrapReplayableEvents(
|
||||
container: Container,
|
||||
document: Document,
|
||||
) {
|
||||
// When the flag is on, we do this in a unified codepath elsewhere.
|
||||
if (!enableEagerRootListeners) {
|
||||
// Discrete
|
||||
discreteReplayableEvents.forEach(domEventName => {
|
||||
trapReplayableEventForContainer(domEventName, container);
|
||||
});
|
||||
// Continuous
|
||||
continuousReplayableEvents.forEach(domEventName => {
|
||||
trapReplayableEventForContainer(domEventName, container);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createQueuedReplayableEvent(
|
||||
blockedOn: null | Container | SuspenseInstance,
|
||||
domEventName: DOMEventName,
|
||||
|
||||
@@ -2643,7 +2643,7 @@ describe('DOMPluginEventSystem', () => {
|
||||
expect(log[5]).toEqual(['bubble', buttonElement]);
|
||||
});
|
||||
|
||||
// @gate experimental && enableEagerRootListeners
|
||||
// @gate experimental
|
||||
it('propagates known createEventHandle events through portals without inner listeners', () => {
|
||||
const buttonRef = React.createRef();
|
||||
const divRef = React.createRef();
|
||||
|
||||
+1
-22
@@ -16,14 +16,10 @@ import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
import {SyntheticEvent} from '../../events/SyntheticEvent';
|
||||
import isTextInputElement from '../isTextInputElement';
|
||||
import shallowEqual from 'shared/shallowEqual';
|
||||
import {enableEagerRootListeners} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {registerTwoPhaseEvent} from '../EventRegistry';
|
||||
import getActiveElement from '../../client/getActiveElement';
|
||||
import {
|
||||
getNodeFromInstance,
|
||||
getEventListenerSet,
|
||||
} from '../../client/ReactDOMComponentTree';
|
||||
import {getNodeFromInstance} from '../../client/ReactDOMComponentTree';
|
||||
import {hasSelectionCapabilities} from '../../client/ReactInputSelection';
|
||||
import {DOCUMENT_NODE} from '../../shared/HTMLNodeType';
|
||||
import {accumulateTwoPhaseListeners} from '../DOMPluginEventSystem';
|
||||
@@ -154,23 +150,6 @@ function extractEvents(
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
targetContainer: EventTarget,
|
||||
) {
|
||||
if (!enableEagerRootListeners) {
|
||||
const eventListenerSet = getEventListenerSet(targetContainer);
|
||||
// Track whether all listeners exists for this plugin. If none exist, we do
|
||||
// not extract events. See #3639.
|
||||
if (
|
||||
// If we are handling selectionchange, then we don't need to
|
||||
// check for the other dependencies, as selectionchange is only
|
||||
// event attached from the onChange plugin and we don't expose an
|
||||
// onSelectionChange event from React.
|
||||
domEventName !== 'selectionchange' &&
|
||||
!eventListenerSet.has('onSelect') &&
|
||||
!eventListenerSet.has('onSelectCapture')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
|
||||
|
||||
switch (domEventName) {
|
||||
|
||||
@@ -516,36 +516,16 @@ describe('SimpleEventPlugin', function() {
|
||||
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,
|
||||
);
|
||||
ReactDOM.render(<div />, container);
|
||||
|
||||
if (gate(flags => flags.enableEagerRootListeners)) {
|
||||
expect(passiveEvents).toEqual([
|
||||
'touchstart',
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'touchmove',
|
||||
'wheel',
|
||||
'wheel',
|
||||
]);
|
||||
} else {
|
||||
expect(passiveEvents).toEqual(['touchstart', 'touchmove', 'wheel']);
|
||||
}
|
||||
expect(passiveEvents).toEqual([
|
||||
'touchstart',
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'touchmove',
|
||||
'wheel',
|
||||
'wheel',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,6 +133,4 @@ export const decoupleUpdatePriorityFromScheduler = false;
|
||||
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
@@ -49,7 +49,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = false;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ export const enableNewReconciler = false;
|
||||
export const deferRenderPhaseUpdateToNextBatch = true;
|
||||
export const decoupleUpdatePriorityFromScheduler = false;
|
||||
export const enableDiscreteEventFlushingChange = true;
|
||||
export const enableEagerRootListeners = true;
|
||||
|
||||
export const enableDoubleInvokingEffects = false;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
|
||||
export const enableLegacyFBSupport = __VARIANT__;
|
||||
export const decoupleUpdatePriorityFromScheduler = __VARIANT__;
|
||||
export const skipUnmountedBoundaries = __VARIANT__;
|
||||
export const enableEagerRootListeners = !__VARIANT__;
|
||||
|
||||
// Enable this flag to help with concurrent mode debugging.
|
||||
// It logs information to the console about React scheduling, rendering, and commit phases.
|
||||
|
||||
@@ -27,7 +27,6 @@ export const {
|
||||
decoupleUpdatePriorityFromScheduler,
|
||||
enableDebugTracing,
|
||||
skipUnmountedBoundaries,
|
||||
enableEagerRootListeners,
|
||||
enableDoubleInvokingEffects,
|
||||
} = dynamicFeatureFlags;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user