mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Events] Make passiveness and priority non-configurable (#19807)
This commit is contained in:
parent
ebb2253428
commit
11ee82df45
|
|
@ -20,7 +20,6 @@ import type {
|
|||
SuspenseInstance,
|
||||
Props,
|
||||
} from './ReactDOMHostConfig';
|
||||
import type {DOMEventName} from '../events/DOMEventNames';
|
||||
|
||||
import {
|
||||
HostComponent,
|
||||
|
|
@ -44,16 +43,6 @@ const internalEventHandlersKey = '__reactEvents$' + randomKey;
|
|||
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
|
||||
const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
|
||||
|
||||
export type ElementListenerMap = Map<
|
||||
DOMEventName | string,
|
||||
ElementListenerMapEntry | null,
|
||||
>;
|
||||
|
||||
export type ElementListenerMapEntry = {
|
||||
passive: void | boolean,
|
||||
listener: any => void,
|
||||
};
|
||||
|
||||
export function precacheFiberNode(
|
||||
hostInst: Fiber,
|
||||
node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
|
||||
|
|
@ -207,12 +196,12 @@ export function updateFiberProps(
|
|||
(node: any)[internalPropsKey] = props;
|
||||
}
|
||||
|
||||
export function getEventListenerMap(node: EventTarget): ElementListenerMap {
|
||||
let elementListenerMap = (node: any)[internalEventHandlersKey];
|
||||
if (elementListenerMap === undefined) {
|
||||
elementListenerMap = (node: any)[internalEventHandlersKey] = new Map();
|
||||
export function getEventListenerSet(node: EventTarget): Set<string> {
|
||||
let elementListenerSet = (node: any)[internalEventHandlersKey];
|
||||
if (elementListenerSet === undefined) {
|
||||
elementListenerSet = (node: any)[internalEventHandlersKey] = new Set();
|
||||
}
|
||||
return elementListenerMap;
|
||||
return elementListenerSet;
|
||||
}
|
||||
|
||||
export function getFiberFromScopeInstance(
|
||||
|
|
|
|||
173
packages/react-dom/src/client/ReactDOMEventHandle.js
vendored
173
packages/react-dom/src/client/ReactDOMEventHandle.js
vendored
|
|
@ -8,13 +8,12 @@
|
|||
*/
|
||||
|
||||
import type {DOMEventName} from '../events/DOMEventNames';
|
||||
import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes';
|
||||
import type {ReactScopeInstance} from 'shared/ReactTypes';
|
||||
import type {
|
||||
ReactDOMEventHandle,
|
||||
ReactDOMEventHandleListener,
|
||||
} from '../shared/ReactDOMTypes';
|
||||
|
||||
import {getEventPriorityForListenerSystem} from '../events/DOMEventProperties';
|
||||
import {allNativeEvents} from '../events/EventRegistry';
|
||||
import {
|
||||
getClosestInstanceFromNode,
|
||||
|
|
@ -25,10 +24,7 @@ import {
|
|||
addEventHandleToTarget,
|
||||
} from './ReactDOMComponentTree';
|
||||
import {ELEMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';
|
||||
import {
|
||||
listenToNativeEvent,
|
||||
addEventTypeToDispatchConfig,
|
||||
} from '../events/DOMPluginEventSystem';
|
||||
import {listenToNativeEvent} from '../events/DOMPluginEventSystem';
|
||||
|
||||
import {HostRoot, HostPortal} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../events/EventSystemFlags';
|
||||
|
|
@ -42,8 +38,6 @@ import invariant from 'shared/invariant';
|
|||
|
||||
type EventHandleOptions = {|
|
||||
capture?: boolean,
|
||||
passive?: boolean,
|
||||
priority?: EventPriority,
|
||||
|};
|
||||
|
||||
function getNearestRootOrPortalContainer(node: Fiber): null | Element {
|
||||
|
|
@ -82,76 +76,76 @@ function createEventHandleListener(
|
|||
function registerEventOnNearestTargetContainer(
|
||||
targetFiber: Fiber,
|
||||
domEventName: DOMEventName,
|
||||
isPassiveListener: boolean | void,
|
||||
listenerPriority: EventPriority | void,
|
||||
isCapturePhaseListener: boolean,
|
||||
targetElement: Element | null,
|
||||
): void {
|
||||
// If it is, find the nearest root or portal and make it
|
||||
// our event handle target container.
|
||||
let targetContainer = getNearestRootOrPortalContainer(targetFiber);
|
||||
if (targetContainer === null) {
|
||||
invariant(
|
||||
false,
|
||||
'ReactDOM.createEventHandle: setListener called on an target ' +
|
||||
'that did not have a corresponding root. This is likely a bug in React.',
|
||||
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,
|
||||
);
|
||||
}
|
||||
if (targetContainer.nodeType === COMMENT_NODE) {
|
||||
targetContainer = ((targetContainer.parentNode: any): Element);
|
||||
}
|
||||
listenToNativeEvent(
|
||||
domEventName,
|
||||
isCapturePhaseListener,
|
||||
targetContainer,
|
||||
targetElement,
|
||||
isPassiveListener,
|
||||
listenerPriority,
|
||||
);
|
||||
}
|
||||
|
||||
function registerReactDOMEvent(
|
||||
target: EventTarget | ReactScopeInstance,
|
||||
domEventName: DOMEventName,
|
||||
isPassiveListener: boolean | void,
|
||||
isCapturePhaseListener: boolean,
|
||||
listenerPriority: EventPriority | void,
|
||||
): void {
|
||||
// Check if the target is a DOM element.
|
||||
if ((target: any).nodeType === ELEMENT_NODE) {
|
||||
const targetElement = ((target: any): Element);
|
||||
// Check if the DOM element is managed by React.
|
||||
const targetFiber = getClosestInstanceFromNode(targetElement);
|
||||
if (targetFiber === null) {
|
||||
invariant(
|
||||
false,
|
||||
'ReactDOM.createEventHandle: setListener called on an element ' +
|
||||
'target that is not managed by React. Ensure React rendered the DOM element.',
|
||||
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,
|
||||
);
|
||||
}
|
||||
registerEventOnNearestTargetContainer(
|
||||
targetFiber,
|
||||
domEventName,
|
||||
isPassiveListener,
|
||||
listenerPriority,
|
||||
isCapturePhaseListener,
|
||||
targetElement,
|
||||
);
|
||||
} else if (enableScopeAPI && isReactScope(target)) {
|
||||
const scopeTarget = ((target: any): ReactScopeInstance);
|
||||
const targetFiber = getFiberFromScopeInstance(scopeTarget);
|
||||
if (targetFiber === null) {
|
||||
// Scope is unmounted, do not proceed.
|
||||
return;
|
||||
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,
|
||||
);
|
||||
}
|
||||
registerEventOnNearestTargetContainer(
|
||||
targetFiber,
|
||||
domEventName,
|
||||
isPassiveListener,
|
||||
listenerPriority,
|
||||
isCapturePhaseListener,
|
||||
null,
|
||||
);
|
||||
} else if (isValidEventTarget(target)) {
|
||||
const eventTarget = ((target: any): EventTarget);
|
||||
// These are valid event targets, but they are also
|
||||
|
|
@ -161,8 +155,6 @@ function registerReactDOMEvent(
|
|||
isCapturePhaseListener,
|
||||
eventTarget,
|
||||
null,
|
||||
isPassiveListener,
|
||||
listenerPriority,
|
||||
IS_EVENT_HANDLE_NON_MANAGED_NODE,
|
||||
);
|
||||
} else {
|
||||
|
|
@ -181,46 +173,27 @@ export function createEventHandle(
|
|||
if (enableCreateEventHandleAPI) {
|
||||
const domEventName = ((type: any): DOMEventName);
|
||||
|
||||
if (enableEagerRootListeners) {
|
||||
// We cannot support arbitrary native events with eager root listeners
|
||||
// because the eager strategy relies on knowing the whole list ahead of time.
|
||||
// If we wanted to support this, we'd have to add code to keep track
|
||||
// (or search) for all portal and root containers, and lazily add listeners
|
||||
// to them whenever we see a previously unknown event. This seems like a lot
|
||||
// of complexity for something we don't even have a particular use case for.
|
||||
// Unfortunately, the downside of this invariant is that *removing* a native
|
||||
// event from the list of known events has now become a breaking change for
|
||||
// any code relying on the createEventHandle API.
|
||||
invariant(
|
||||
allNativeEvents.has(domEventName) ||
|
||||
domEventName === 'beforeblur' ||
|
||||
domEventName === 'afterblur',
|
||||
'Cannot call unstable_createEventHandle with "%s", as it is not an event known to React.',
|
||||
domEventName,
|
||||
);
|
||||
}
|
||||
// We cannot support arbitrary native events with eager root listeners
|
||||
// because the eager strategy relies on knowing the whole list ahead of time.
|
||||
// If we wanted to support this, we'd have to add code to keep track
|
||||
// (or search) for all portal and root containers, and lazily add listeners
|
||||
// to them whenever we see a previously unknown event. This seems like a lot
|
||||
// of complexity for something we don't even have a particular use case for.
|
||||
// Unfortunately, the downside of this invariant is that *removing* a native
|
||||
// event from the list of known events has now become a breaking change for
|
||||
// any code relying on the createEventHandle API.
|
||||
invariant(
|
||||
allNativeEvents.has(domEventName),
|
||||
'Cannot call unstable_createEventHandle with "%s", as it is not an event known to React.',
|
||||
domEventName,
|
||||
);
|
||||
|
||||
let isCapturePhaseListener = false;
|
||||
let isPassiveListener = undefined; // Undefined means to use the browser default
|
||||
let listenerPriority;
|
||||
|
||||
if (options != null) {
|
||||
const optionsCapture = options.capture;
|
||||
const optionsPassive = options.passive;
|
||||
const optionsPriority = options.priority;
|
||||
|
||||
if (typeof optionsCapture === 'boolean') {
|
||||
isCapturePhaseListener = optionsCapture;
|
||||
}
|
||||
if (typeof optionsPassive === 'boolean') {
|
||||
isPassiveListener = optionsPassive;
|
||||
}
|
||||
if (typeof optionsPriority === 'number') {
|
||||
listenerPriority = optionsPriority;
|
||||
}
|
||||
}
|
||||
if (listenerPriority === undefined) {
|
||||
listenerPriority = getEventPriorityForListenerSystem(domEventName);
|
||||
}
|
||||
|
||||
const eventHandle = (
|
||||
|
|
@ -234,15 +207,7 @@ export function createEventHandle(
|
|||
);
|
||||
if (!doesTargetHaveEventHandle(target, eventHandle)) {
|
||||
addEventHandleToTarget(target, eventHandle);
|
||||
registerReactDOMEvent(
|
||||
target,
|
||||
domEventName,
|
||||
isPassiveListener,
|
||||
isCapturePhaseListener,
|
||||
listenerPriority,
|
||||
);
|
||||
// Add the event to our known event types list.
|
||||
addEventTypeToDispatchConfig(domEventName);
|
||||
registerReactDOMEvent(target, domEventName, isCapturePhaseListener);
|
||||
}
|
||||
const listener = createEventHandleListener(
|
||||
domEventName,
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ const otherDiscreteEvents: Array<DOMEventName> = [
|
|||
];
|
||||
|
||||
if (enableCreateEventHandleAPI) {
|
||||
// Special case: these two events don't have on* React handler
|
||||
// and are only accessible via the createEventHandle API.
|
||||
topLevelEventsToReactNames.set('beforeblur', null);
|
||||
topLevelEventsToReactNames.set('afterblur', null);
|
||||
otherDiscreteEvents.push('beforeblur', 'afterblur');
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +206,7 @@ export function getEventPriorityForListenerSystem(
|
|||
if (__DEV__) {
|
||||
console.warn(
|
||||
'The event "%s" provided to createEventHandle() does not have a known priority type.' +
|
||||
' It is recommended to provide a "priority" option to specify a priority.',
|
||||
' This is likely a bug in React.',
|
||||
type,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import type {
|
|||
KnownReactSyntheticEvent,
|
||||
ReactSyntheticEvent,
|
||||
} from './ReactSyntheticEventType';
|
||||
import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import {registrationNameDependencies, allNativeEvents} from './EventRegistry';
|
||||
|
|
@ -41,7 +39,7 @@ import {
|
|||
import getEventTarget from './getEventTarget';
|
||||
import {
|
||||
getClosestInstanceFromNode,
|
||||
getEventListenerMap,
|
||||
getEventListenerSet,
|
||||
getEventHandlerListeners,
|
||||
} from '../client/ReactDOMComponentTree';
|
||||
import {COMMENT_NODE} from '../shared/HTMLNodeType';
|
||||
|
|
@ -69,7 +67,6 @@ import {
|
|||
addEventBubbleListenerWithPassiveFlag,
|
||||
addEventCaptureListenerWithPassiveFlag,
|
||||
} from './EventListener';
|
||||
import {topLevelEventsToReactNames} from './DOMEventProperties';
|
||||
import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';
|
||||
import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
|
||||
import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';
|
||||
|
|
@ -296,36 +293,24 @@ function dispatchEventsForPlugins(
|
|||
processDispatchQueue(dispatchQueue, eventSystemFlags);
|
||||
}
|
||||
|
||||
function shouldUpgradeListener(
|
||||
listenerEntry: void | ElementListenerMapEntry,
|
||||
passive: void | boolean,
|
||||
): boolean {
|
||||
return (
|
||||
listenerEntry !== undefined && listenerEntry.passive === true && !passive
|
||||
);
|
||||
}
|
||||
|
||||
export function listenToNonDelegatedEvent(
|
||||
domEventName: DOMEventName,
|
||||
targetElement: Element,
|
||||
): void {
|
||||
const isCapturePhaseListener = false;
|
||||
const listenerMap = getEventListenerMap(targetElement);
|
||||
const listenerMapKey = getListenerMapKey(
|
||||
const listenerSet = getEventListenerSet(targetElement);
|
||||
const listenerSetKey = getListenerSetKey(
|
||||
domEventName,
|
||||
isCapturePhaseListener,
|
||||
);
|
||||
const listenerEntry = ((listenerMap.get(
|
||||
listenerMapKey,
|
||||
): any): ElementListenerMapEntry | void);
|
||||
if (listenerEntry === undefined) {
|
||||
const listener = addTrappedEventListener(
|
||||
if (!listenerSet.has(listenerSetKey)) {
|
||||
addTrappedEventListener(
|
||||
targetElement,
|
||||
domEventName,
|
||||
IS_NON_DELEGATED,
|
||||
isCapturePhaseListener,
|
||||
);
|
||||
listenerMap.set(listenerMapKey, {passive: false, listener});
|
||||
listenerSet.add(listenerSetKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,8 +354,6 @@ export function listenToNativeEvent(
|
|||
isCapturePhaseListener: boolean,
|
||||
rootContainerElement: EventTarget,
|
||||
targetElement: Element | null,
|
||||
isPassiveListener?: boolean,
|
||||
listenerPriority?: EventPriority,
|
||||
eventSystemFlags?: EventSystemFlags = 0,
|
||||
): void {
|
||||
let target = rootContainerElement;
|
||||
|
|
@ -384,21 +367,6 @@ export function listenToNativeEvent(
|
|||
) {
|
||||
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
|
||||
|
|
@ -423,42 +391,24 @@ export function listenToNativeEvent(
|
|||
eventSystemFlags |= IS_NON_DELEGATED;
|
||||
target = targetElement;
|
||||
}
|
||||
const listenerMap = getEventListenerMap(target);
|
||||
const listenerMapKey = getListenerMapKey(
|
||||
const listenerSet = getEventListenerSet(target);
|
||||
const listenerSetKey = getListenerSetKey(
|
||||
domEventName,
|
||||
isCapturePhaseListener,
|
||||
);
|
||||
const listenerEntry = ((listenerMap.get(
|
||||
listenerMapKey,
|
||||
): any): ElementListenerMapEntry | void);
|
||||
const shouldUpgrade = shouldUpgradeListener(listenerEntry, isPassiveListener);
|
||||
|
||||
// If the listener entry is empty or we should upgrade, then
|
||||
// we need to trap an event listener onto the target.
|
||||
if (listenerEntry === undefined || shouldUpgrade) {
|
||||
// If we should upgrade, then we need to remove the existing trapped
|
||||
// event listener for the target container.
|
||||
if (shouldUpgrade) {
|
||||
removeEventListener(
|
||||
target,
|
||||
domEventName,
|
||||
((listenerEntry: any): ElementListenerMapEntry).listener,
|
||||
isCapturePhaseListener,
|
||||
);
|
||||
}
|
||||
if (!listenerSet.has(listenerSetKey)) {
|
||||
if (isCapturePhaseListener) {
|
||||
eventSystemFlags |= IS_CAPTURE_PHASE;
|
||||
}
|
||||
const listener = addTrappedEventListener(
|
||||
addTrappedEventListener(
|
||||
target,
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
isCapturePhaseListener,
|
||||
false,
|
||||
isPassiveListener,
|
||||
listenerPriority,
|
||||
);
|
||||
listenerMap.set(listenerMapKey, {passive: isPassiveListener, listener});
|
||||
listenerSet.add(listenerSetKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -480,11 +430,13 @@ export function listenToReactEvent(
|
|||
const isPolyfillEventPlugin = dependenciesLength !== 1;
|
||||
|
||||
if (isPolyfillEventPlugin) {
|
||||
const listenerMap = getEventListenerMap(rootContainerElement);
|
||||
// For optimization, we register plugins on the listener map, so we
|
||||
// don't need to check each of their dependencies each time.
|
||||
if (!listenerMap.has(reactEvent)) {
|
||||
listenerMap.set(reactEvent, null);
|
||||
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],
|
||||
|
|
@ -520,19 +472,29 @@ function addTrappedEventListener(
|
|||
eventSystemFlags: EventSystemFlags,
|
||||
isCapturePhaseListener: boolean,
|
||||
isDeferredListenerForLegacyFBSupport?: boolean,
|
||||
isPassiveListener?: boolean,
|
||||
listenerPriority?: EventPriority,
|
||||
): any => void {
|
||||
) {
|
||||
let listener = createEventListenerWrapperWithPriority(
|
||||
targetContainer,
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
listenerPriority,
|
||||
);
|
||||
// If passive option is not supported, then the event will be
|
||||
// active and not passive.
|
||||
if (isPassiveListener === true && !passiveBrowserEventsSupported) {
|
||||
isPassiveListener = false;
|
||||
let isPassiveListener = undefined;
|
||||
if (enablePassiveEventIntervention && passiveBrowserEventsSupported) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
targetContainer =
|
||||
|
|
@ -564,6 +526,7 @@ function addTrappedEventListener(
|
|||
return originalListener.apply(this, p);
|
||||
};
|
||||
}
|
||||
// TODO: There are too many combinations here. Consolidate them.
|
||||
if (isCapturePhaseListener) {
|
||||
if (isPassiveListener !== undefined) {
|
||||
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
|
||||
|
|
@ -595,7 +558,6 @@ function addTrappedEventListener(
|
|||
);
|
||||
}
|
||||
}
|
||||
return unsubscribeListener;
|
||||
}
|
||||
|
||||
function deferClickToDocumentForLegacyFBSupport(
|
||||
|
|
@ -1085,19 +1047,7 @@ export function accumulateEventHandleNonManagedNodeListeners(
|
|||
}
|
||||
}
|
||||
|
||||
export function addEventTypeToDispatchConfig(type: DOMEventName): void {
|
||||
const reactName = topLevelEventsToReactNames.get(type);
|
||||
// If we don't have a reactName, then we're dealing with
|
||||
// an event type that React does not know about (i.e. a custom event).
|
||||
// We need to register an event config for this or the SimpleEventPlugin
|
||||
// will not appropriately provide a SyntheticEvent, so we use out empty
|
||||
// dispatch config for custom events.
|
||||
if (reactName === undefined) {
|
||||
topLevelEventsToReactNames.set(type, null);
|
||||
}
|
||||
}
|
||||
|
||||
export function getListenerMapKey(
|
||||
export function getListenerSetKey(
|
||||
domEventName: DOMEventName,
|
||||
capture: boolean,
|
||||
): string {
|
||||
|
|
|
|||
13
packages/react-dom/src/events/EventRegistry.js
vendored
13
packages/react-dom/src/events/EventRegistry.js
vendored
|
|
@ -9,10 +9,15 @@
|
|||
|
||||
import type {DOMEventName} from './DOMEventNames';
|
||||
|
||||
import {enableEagerRootListeners} from 'shared/ReactFeatureFlags';
|
||||
import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
export const allNativeEvents: Set<DOMEventName> = new Set();
|
||||
|
||||
if (enableCreateEventHandleAPI) {
|
||||
allNativeEvents.add('beforeblur');
|
||||
allNativeEvents.add('afterblur');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping from registration name to event name
|
||||
*/
|
||||
|
|
@ -60,9 +65,7 @@ export function registerDirectEvent(
|
|||
}
|
||||
}
|
||||
|
||||
if (enableEagerRootListeners) {
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
allNativeEvents.add(dependencies[i]);
|
||||
}
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
allNativeEvents.add(dependencies[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
*/
|
||||
|
||||
import type {AnyNativeEvent} from '../events/PluginModuleType';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
|
||||
import type {DOMEventName} from '../events/DOMEventNames';
|
||||
|
|
@ -96,12 +95,8 @@ export function createEventListenerWrapperWithPriority(
|
|||
targetContainer: EventTarget,
|
||||
domEventName: DOMEventName,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
priority?: EventPriority,
|
||||
): Function {
|
||||
const eventPriority =
|
||||
priority === undefined
|
||||
? getEventPriorityForPluginSystem(domEventName)
|
||||
: priority;
|
||||
const eventPriority = getEventPriorityForPluginSystem(domEventName);
|
||||
let listenerWrapper;
|
||||
switch (eventPriority) {
|
||||
case DiscreteEvent:
|
||||
|
|
|
|||
|
|
@ -1949,106 +1949,6 @@ describe('DOMPluginEventSystem', () => {
|
|||
expect(log).toEqual([{counter: 1}]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should correctly work for a basic "click" listener that upgrades', () => {
|
||||
const clickEvent = jest.fn();
|
||||
const buttonRef = React.createRef();
|
||||
const button2Ref = React.createRef();
|
||||
const setClick1 = ReactDOM.unstable_createEventHandle('click', {
|
||||
passive: false,
|
||||
});
|
||||
const setClick2 = ReactDOM.unstable_createEventHandle('click', {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
function Test2() {
|
||||
React.useEffect(() => {
|
||||
return setClick1(button2Ref.current, clickEvent);
|
||||
});
|
||||
|
||||
return <button ref={button2Ref}>Click me!</button>;
|
||||
}
|
||||
|
||||
function Test({extra}) {
|
||||
React.useEffect(() => {
|
||||
return setClick2(buttonRef.current, clickEvent);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<button ref={buttonRef}>Click me!</button>
|
||||
{extra && <Test2 />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
let button = buttonRef.current;
|
||||
dispatchClickEvent(button);
|
||||
expect(clickEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<Test extra={true} />, container);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
clickEvent.mockClear();
|
||||
|
||||
button = button2Ref.current;
|
||||
dispatchClickEvent(button);
|
||||
expect(clickEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should correctly work for a basic "click" listener that upgrades #2', () => {
|
||||
const clickEvent = jest.fn();
|
||||
const buttonRef = React.createRef();
|
||||
const button2Ref = React.createRef();
|
||||
const setClick1 = ReactDOM.unstable_createEventHandle('click', {
|
||||
passive: false,
|
||||
});
|
||||
const setClick2 = ReactDOM.unstable_createEventHandle('click', {
|
||||
passive: undefined,
|
||||
});
|
||||
|
||||
function Test2() {
|
||||
React.useEffect(() => {
|
||||
return setClick1(button2Ref.current, clickEvent);
|
||||
});
|
||||
|
||||
return <button ref={button2Ref}>Click me!</button>;
|
||||
}
|
||||
|
||||
function Test({extra}) {
|
||||
React.useEffect(() => {
|
||||
return setClick2(buttonRef.current, clickEvent);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<button ref={buttonRef}>Click me!</button>
|
||||
{extra && <Test2 />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
let button = buttonRef.current;
|
||||
dispatchClickEvent(button);
|
||||
expect(clickEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
ReactDOM.render(<Test extra={true} />, container);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
clickEvent.mockClear();
|
||||
|
||||
button = button2Ref.current;
|
||||
dispatchClickEvent(button);
|
||||
expect(clickEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should correctly work for a basic "click" window listener', () => {
|
||||
const log = [];
|
||||
|
|
@ -2391,109 +2291,15 @@ describe('DOMPluginEventSystem', () => {
|
|||
});
|
||||
|
||||
// @gate experimental
|
||||
it('handles propagation of custom user events', () => {
|
||||
const buttonRef = React.createRef();
|
||||
const divRef = React.createRef();
|
||||
const log = [];
|
||||
const onCustomEvent = jest.fn(e =>
|
||||
log.push(['bubble', e.currentTarget]),
|
||||
it('does not support custom user events', () => {
|
||||
// With eager listeners, supporting custom events via this API doesn't make sense
|
||||
// because we can't know a full list of them ahead of time. Let's check we throw
|
||||
// since otherwise we'd end up with inconsistent behavior, like no portal bubbling.
|
||||
expect(() => {
|
||||
ReactDOM.unstable_createEventHandle('custom-event');
|
||||
}).toThrow(
|
||||
'Cannot call unstable_createEventHandle with "custom-event", as it is not an event known to React.',
|
||||
);
|
||||
const onCustomEventCapture = jest.fn(e =>
|
||||
log.push(['capture', e.currentTarget]),
|
||||
);
|
||||
|
||||
let setCustomEventHandle;
|
||||
if (gate(flags => flags.enableEagerRootListeners)) {
|
||||
// With eager listeners, supporting custom events via this API doesn't make sense
|
||||
// because we can't know a full list of them ahead of time. Let's check we throw
|
||||
// since otherwise we'd end up with inconsistent behavior, like no portal bubbling.
|
||||
expect(() => {
|
||||
setCustomEventHandle = ReactDOM.unstable_createEventHandle(
|
||||
'custom-event',
|
||||
);
|
||||
}).toThrow(
|
||||
'Cannot call unstable_createEventHandle with "custom-event", as it is not an event known to React.',
|
||||
);
|
||||
} else {
|
||||
// Test that we get a warning when we don't provide an explicit priority
|
||||
expect(() => {
|
||||
setCustomEventHandle = ReactDOM.unstable_createEventHandle(
|
||||
'custom-event',
|
||||
);
|
||||
}).toWarnDev(
|
||||
'Warning: The event "custom-event" provided to createEventHandle() does not have a known priority type. ' +
|
||||
'It is recommended to provide a "priority" option to specify a priority.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
setCustomEventHandle = ReactDOM.unstable_createEventHandle(
|
||||
'custom-event',
|
||||
{
|
||||
priority: 0, // Discrete
|
||||
},
|
||||
);
|
||||
|
||||
const setCustomCaptureHandle = ReactDOM.unstable_createEventHandle(
|
||||
'custom-event',
|
||||
{
|
||||
capture: true,
|
||||
priority: 0, // Discrete
|
||||
},
|
||||
);
|
||||
|
||||
const Test = () => {
|
||||
React.useEffect(() => {
|
||||
const clearCustom1 = setCustomEventHandle(
|
||||
buttonRef.current,
|
||||
onCustomEvent,
|
||||
);
|
||||
const clearCustom2 = setCustomCaptureHandle(
|
||||
buttonRef.current,
|
||||
onCustomEventCapture,
|
||||
);
|
||||
const clearCustom3 = setCustomEventHandle(
|
||||
divRef.current,
|
||||
onCustomEvent,
|
||||
);
|
||||
const clearCustom4 = setCustomCaptureHandle(
|
||||
divRef.current,
|
||||
onCustomEventCapture,
|
||||
);
|
||||
|
||||
return () => {
|
||||
clearCustom1();
|
||||
clearCustom2();
|
||||
clearCustom3();
|
||||
clearCustom4();
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<button ref={buttonRef}>
|
||||
<div ref={divRef}>Click me!</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
const buttonElement = buttonRef.current;
|
||||
dispatchEvent(buttonElement, 'custom-event');
|
||||
expect(onCustomEvent).toHaveBeenCalledTimes(1);
|
||||
expect(onCustomEventCapture).toHaveBeenCalledTimes(1);
|
||||
expect(log[0]).toEqual(['capture', buttonElement]);
|
||||
expect(log[1]).toEqual(['bubble', buttonElement]);
|
||||
|
||||
const divElement = divRef.current;
|
||||
dispatchEvent(divElement, 'custom-event');
|
||||
expect(onCustomEvent).toHaveBeenCalledTimes(3);
|
||||
expect(onCustomEventCapture).toHaveBeenCalledTimes(3);
|
||||
expect(log[2]).toEqual(['capture', buttonElement]);
|
||||
expect(log[3]).toEqual(['capture', divElement]);
|
||||
expect(log[4]).toEqual(['bubble', divElement]);
|
||||
expect(log[5]).toEqual(['bubble', buttonElement]);
|
||||
}
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
|
|
@ -3211,12 +3017,14 @@ describe('DOMPluginEventSystem', () => {
|
|||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should be able to register non-passive handlers for events affected by the intervention', () => {
|
||||
it('should be able to register handlers for events affected by the intervention', () => {
|
||||
const rootContainer = document.createElement('div');
|
||||
container.appendChild(rootContainer);
|
||||
|
||||
const allEvents = [];
|
||||
const defaultPreventedEvents = [];
|
||||
const handler = e => {
|
||||
allEvents.push(e.type);
|
||||
if (e.defaultPrevented) defaultPreventedEvents.push(e.type);
|
||||
};
|
||||
|
||||
|
|
@ -3227,15 +3035,11 @@ describe('DOMPluginEventSystem', () => {
|
|||
const ref = React.createRef();
|
||||
const setTouchStart = ReactDOM.unstable_createEventHandle(
|
||||
'touchstart',
|
||||
{passive: false},
|
||||
);
|
||||
const setTouchMove = ReactDOM.unstable_createEventHandle(
|
||||
'touchmove',
|
||||
{passive: false},
|
||||
);
|
||||
const setWheel = ReactDOM.unstable_createEventHandle('wheel', {
|
||||
passive: false,
|
||||
});
|
||||
const setWheel = ReactDOM.unstable_createEventHandle('wheel');
|
||||
|
||||
function Component() {
|
||||
React.useEffect(() => {
|
||||
|
|
@ -3264,11 +3068,17 @@ describe('DOMPluginEventSystem', () => {
|
|||
dispatchEvent(ref.current, 'touchmove');
|
||||
dispatchEvent(ref.current, 'wheel');
|
||||
|
||||
expect(defaultPreventedEvents).toEqual([
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'wheel',
|
||||
]);
|
||||
expect(allEvents).toEqual(['touchstart', 'touchmove', 'wheel']);
|
||||
// These events are passive by default, so we can't preventDefault.
|
||||
if (gate(flags => flags.enablePassiveEventIntervention)) {
|
||||
expect(defaultPreventedEvents).toEqual([]);
|
||||
} else {
|
||||
expect(defaultPreventedEvents).toEqual([
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'wheel',
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {registerTwoPhaseEvent} from '../EventRegistry';
|
|||
import getActiveElement from '../../client/getActiveElement';
|
||||
import {
|
||||
getNodeFromInstance,
|
||||
getEventListenerMap,
|
||||
getEventListenerSet,
|
||||
} from '../../client/ReactDOMComponentTree';
|
||||
import {hasSelectionCapabilities} from '../../client/ReactInputSelection';
|
||||
import {DOCUMENT_NODE} from '../../shared/HTMLNodeType';
|
||||
|
|
@ -154,7 +154,7 @@ function extractEvents(
|
|||
targetContainer: EventTarget,
|
||||
) {
|
||||
if (!enableEagerRootListeners) {
|
||||
const eventListenerMap = getEventListenerMap(targetContainer);
|
||||
const eventListenerSet = getEventListenerSet(targetContainer);
|
||||
// Track whether all listeners exists for this plugin. If none exist, we do
|
||||
// not extract events. See #3639.
|
||||
if (
|
||||
|
|
@ -163,8 +163,8 @@ function extractEvents(
|
|||
// event attached from the onChange plugin and we don't expose an
|
||||
// onSelectionChange event from React.
|
||||
domEventName !== 'selectionchange' &&
|
||||
!eventListenerMap.has('onSelect') &&
|
||||
!eventListenerMap.has('onSelectCapture')
|
||||
!eventListenerSet.has('onSelect') &&
|
||||
!eventListenerSet.has('onSelectCapture')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,32 +37,6 @@ const isMac =
|
|||
? /^Mac/.test(window.navigator.platform)
|
||||
: false;
|
||||
|
||||
const canUseDOM: boolean = !!(
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window.document !== 'undefined' &&
|
||||
typeof window.document.createElement !== 'undefined'
|
||||
);
|
||||
|
||||
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 (canUseDOM) {
|
||||
try {
|
||||
const options = {};
|
||||
// $FlowFixMe: Ignore Flow complaining about needing a value
|
||||
Object.defineProperty(options, 'passive', {
|
||||
get: function() {
|
||||
passiveBrowserEventsSupported = true;
|
||||
},
|
||||
});
|
||||
window.addEventListener('test', options, options);
|
||||
window.removeEventListener('test', options, options);
|
||||
} catch (e) {
|
||||
passiveBrowserEventsSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
const hasPointerEvents =
|
||||
typeof window !== 'undefined' && window.PointerEvent != null;
|
||||
|
||||
|
|
@ -78,20 +52,13 @@ const globalFocusVisibleEvents = hasPointerEvents
|
|||
'touchend',
|
||||
];
|
||||
|
||||
const passiveObject = {passive: true};
|
||||
const passiveObjectWithPriority = {passive: true, priority: 0};
|
||||
|
||||
// Global state for tracking focus visible and emulation of mouse
|
||||
let isGlobalFocusVisible = true;
|
||||
let hasTrackedGlobalFocusVisible = false;
|
||||
|
||||
function trackGlobalFocusVisible() {
|
||||
globalFocusVisibleEvents.forEach(type => {
|
||||
window.addEventListener(
|
||||
type,
|
||||
handleGlobalFocusVisibleEvent,
|
||||
passiveBrowserEventsSupported ? {capture: true, passive: true} : true,
|
||||
);
|
||||
window.addEventListener(type, handleGlobalFocusVisibleEvent, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -171,9 +138,9 @@ function setFocusVisibleListeners(
|
|||
|
||||
function useFocusVisibleInputHandles() {
|
||||
return [
|
||||
useEvent('mousedown', passiveObject),
|
||||
useEvent(hasPointerEvents ? 'pointerdown' : 'touchstart', passiveObject),
|
||||
useEvent('keydown', passiveObject),
|
||||
useEvent('mousedown'),
|
||||
useEvent(hasPointerEvents ? 'pointerdown' : 'touchstart'),
|
||||
useEvent('keydown'),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -200,8 +167,8 @@ export function useFocus(
|
|||
const stateRef = useRef<null | {isFocused: boolean, isFocusVisible: boolean}>(
|
||||
{isFocused: false, isFocusVisible: false},
|
||||
);
|
||||
const focusHandle = useEvent('focusin', passiveObjectWithPriority);
|
||||
const blurHandle = useEvent('focusout', passiveObjectWithPriority);
|
||||
const focusHandle = useEvent('focusin');
|
||||
const blurHandle = useEvent('focusout');
|
||||
const focusVisibleHandles = useFocusVisibleInputHandles();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
|
@ -297,10 +264,10 @@ export function useFocusWithin<T>(
|
|||
const stateRef = useRef<null | {isFocused: boolean, isFocusVisible: boolean}>(
|
||||
{isFocused: false, isFocusVisible: false},
|
||||
);
|
||||
const focusHandle = useEvent('focusin', passiveObjectWithPriority);
|
||||
const blurHandle = useEvent('focusout', passiveObjectWithPriority);
|
||||
const afterBlurHandle = useEvent('afterblur', passiveObject);
|
||||
const beforeBlurHandle = useEvent('beforeblur', passiveObject);
|
||||
const focusHandle = useEvent('focusin');
|
||||
const blurHandle = useEvent('focusout');
|
||||
const afterBlurHandle = useEvent('afterblur');
|
||||
const beforeBlurHandle = useEvent('beforeblur');
|
||||
const focusVisibleHandles = useFocusVisibleInputHandles();
|
||||
|
||||
const useFocusWithinRef = useCallback(
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ export default function useEvent(
|
|||
event: string,
|
||||
options?: {|
|
||||
capture?: boolean,
|
||||
passive?: boolean,
|
||||
priority?: 0 | 1 | 2,
|
||||
|},
|
||||
): UseEventHandle {
|
||||
const handleRef = useRef<UseEventHandle | null>(null);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user