diff --git a/packages/react-dom/src/events/SimpleEventPlugin.js b/packages/react-dom/src/events/SimpleEventPlugin.js index a07b7517a5..a2a0c9332f 100644 --- a/packages/react-dom/src/events/SimpleEventPlugin.js +++ b/packages/react-dom/src/events/SimpleEventPlugin.js @@ -37,6 +37,9 @@ import SyntheticUIEvent from './SyntheticUIEvent'; import SyntheticWheelEvent from './SyntheticWheelEvent'; import getEventCharCode from './getEventCharCode'; import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners'; +import accumulateEventTargetListeners from './accumulateEventTargetListeners'; +import {IS_TARGET_EVENT_ONLY} from 'legacy-events/EventSystemFlags'; +import {enableUseEventAPI} from 'shared/ReactFeatureFlags'; // Only used in DEV for exhaustiveness validation. const knownHTMLTopLevelTypes: Array = [ @@ -197,7 +200,23 @@ const SimpleEventPlugin: PluginModule = { nativeEvent, nativeEventTarget, ); - accumulateTwoPhaseListeners(event, true, eventSystemFlags, targetContainer); + + // For TargetEvent only accumulation, we do not traverse through + // the React tree looking for managed React DOM elements that have + // events. Instead we only check the EventTarget Store Map to see + // if the container has listeners for the particular phase we're + // interested in. This is because we attach the native event listener + // only in the given phase. + if ( + enableUseEventAPI && + eventSystemFlags !== undefined && + eventSystemFlags & IS_TARGET_EVENT_ONLY && + targetContainer != null + ) { + accumulateEventTargetListeners(event, targetContainer); + } else { + accumulateTwoPhaseListeners(event, true); + } return event; }, }; diff --git a/packages/react-dom/src/events/accumulateEventTargetListeners.js b/packages/react-dom/src/events/accumulateEventTargetListeners.js new file mode 100644 index 0000000000..70059c8f88 --- /dev/null +++ b/packages/react-dom/src/events/accumulateEventTargetListeners.js @@ -0,0 +1,55 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; +import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType'; + +import {eventTargetEventListenerStore} from './DOMModernPluginEventSystem'; + +export default function accumulateEventTargetListeners( + event: ReactSyntheticEvent, + targetContainer: EventTarget, +): void { + const dispatchListeners = []; + const dispatchInstances = []; + const eventTypeMap = eventTargetEventListenerStore.get(targetContainer); + if (eventTypeMap !== undefined) { + const type = ((event.type: any): DOMTopLevelEventType); + const listeners = eventTypeMap.get(type); + if (listeners !== undefined) { + const isCapturePhase = (event: any).eventPhase === 1; + + if (isCapturePhase) { + const captureListeners = Array.from(listeners.captured); + + for (let i = captureListeners.length - 1; i >= 0; i--) { + const listener = captureListeners[i]; + const {callback} = listener; + dispatchListeners.push(callback); + dispatchInstances.push(targetContainer); + } + } else { + const bubbleListeners = Array.from(listeners.bubbled); + + for (let i = 0; i < bubbleListeners.length; i++) { + const listener = bubbleListeners[i]; + const {callback} = listener; + dispatchListeners.push(callback); + dispatchInstances.push(targetContainer); + } + } + } + } + // To prevent allocation to the event unless we actually + // have listeners we check the length of one of the arrays. + if (dispatchListeners.length > 0) { + event._dispatchListeners = dispatchListeners; + event._dispatchInstances = dispatchInstances; + } +} diff --git a/packages/react-dom/src/events/accumulateTwoPhaseListeners.js b/packages/react-dom/src/events/accumulateTwoPhaseListeners.js index 39f0148b53..de096ca690 100644 --- a/packages/react-dom/src/events/accumulateTwoPhaseListeners.js +++ b/packages/react-dom/src/events/accumulateTwoPhaseListeners.js @@ -8,7 +8,6 @@ */ import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes'; -import type {EventSystemFlags} from 'legacy-events/EventSystemFlags'; import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType'; import { @@ -23,162 +22,114 @@ import { import getListener from 'legacy-events/getListener'; import {getListenersFromTarget} from '../client/ReactDOMComponentTree'; -import {IS_TARGET_EVENT_ONLY} from 'legacy-events/EventSystemFlags'; -import { - eventTargetEventListenerStore, - reactScopeListenerStore, -} from './DOMModernPluginEventSystem'; +import {reactScopeListenerStore} from './DOMModernPluginEventSystem'; export default function accumulateTwoPhaseListeners( event: ReactSyntheticEvent, accumulateUseEventListeners?: boolean, - eventSystemFlags?: EventSystemFlags, - targetContainer?: null | EventTarget, ): void { const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames; const dispatchListeners = []; const dispatchInstances = []; - // For TargetEvent only accumulation, we do not traverse through - // the React tree looking for managed React DOM elements that have - // events. Instead we only check the EventTarget Store Map to see - // if the container has listeners for the particular phase we're - // interested in. This is because we attach the native event listener - // only in the given phase. - if ( - enableUseEventAPI && - accumulateUseEventListeners && - eventSystemFlags !== undefined && - eventSystemFlags & IS_TARGET_EVENT_ONLY && - targetContainer != null - ) { - const eventTypeMap = eventTargetEventListenerStore.get(targetContainer); - if (eventTypeMap !== undefined) { - const type = ((event.type: any): DOMTopLevelEventType); - const listeners = eventTypeMap.get(type); - if (listeners !== undefined) { - const isCapturePhase = (event: any).eventPhase === 1; + const {bubbled, captured} = phasedRegistrationNames; + // If we are not handling EventTarget only phase, then we're doing the + // usual two phase accumulation using the React fiber tree to pick up + // all relevant useEvent and on* prop events. + let node = event._targetInst; + let lastHostComponent = null; - if (isCapturePhase) { - const captureListeners = Array.from(listeners.captured); + // Accumulate all instances and listeners via the target -> root path. + while (node !== null) { + const {stateNode: instance, tag} = node; + // Handle listeners that are on HostComponents (i.e.
) + if (instance !== null && tag === HostComponent) { + lastHostComponent = instance; + // For useEvent listenrs + if ( + enableModernEventSystem && + enableUseEventAPI && + accumulateUseEventListeners + ) { + // useEvent event listeners + const targetType = event.type; + const listeners = getListenersFromTarget(instance); - for (let i = captureListeners.length - 1; i >= 0; i--) { - const listener = captureListeners[i]; - const {callback} = listener; - dispatchListeners.push(callback); - dispatchInstances.push(targetContainer); - } - } else { - const bubbleListeners = Array.from(listeners.bubbled); - - for (let i = 0; i < bubbleListeners.length; i++) { - const listener = bubbleListeners[i]; - const {callback} = listener; - dispatchListeners.push(callback); - dispatchInstances.push(targetContainer); - } - } - } - } - } else { - const {bubbled, captured} = phasedRegistrationNames; - // If we are not handling EventTarget only phase, then we're doing the - // usual two phase accumulation using the React fiber tree to pick up - // all relevant useEvent and on* prop events. - let node = event._targetInst; - let lastHostComponent = null; - - // Accumulate all instances and listeners via the target -> root path. - while (node !== null) { - const {stateNode: instance, tag} = node; - // Handle listeners that are on HostComponents (i.e.
) - if (instance !== null && tag === HostComponent) { - lastHostComponent = instance; - // For useEvent listenrs - if ( - enableModernEventSystem && - enableUseEventAPI && - accumulateUseEventListeners - ) { - // useEvent event listeners - const targetType = event.type; - const listeners = getListenersFromTarget(instance); - - if (listeners !== null) { - const listenersArr = Array.from(listeners); - for (let i = 0; i < listenersArr.length; i++) { - const listener = listenersArr[i]; - const { - callback, - event: {capture, type}, - } = listener; - if (type === targetType) { - if (capture === true) { - dispatchListeners.unshift(callback); - dispatchInstances.unshift(node); - } else { - dispatchListeners.push(callback); - dispatchInstances.push(node); - } + if (listeners !== null) { + const listenersArr = Array.from(listeners); + for (let i = 0; i < listenersArr.length; i++) { + const listener = listenersArr[i]; + const { + callback, + event: {capture, type}, + } = listener; + if (type === targetType) { + if (capture === true) { + dispatchListeners.unshift(callback); + dispatchInstances.unshift(node); + } else { + dispatchListeners.push(callback); + dispatchInstances.push(node); } } } } - // Standard React on* listeners, i.e. onClick prop - if (captured !== null) { - const captureListener = getListener(node, captured); - if (captureListener != null) { - // Capture listeners/instances should go at the start, so we - // unshift them to the start of the array. - dispatchListeners.unshift(captureListener); - dispatchInstances.unshift(node); - } - } - if (bubbled !== null) { - const bubbleListener = getListener(node, bubbled); - if (bubbleListener != null) { - // Bubble listeners/instances should go at the end, so we - // push them to the end of the array. - dispatchListeners.push(bubbleListener); - dispatchInstances.push(node); - } + } + // Standard React on* listeners, i.e. onClick prop + if (captured !== null) { + const captureListener = getListener(node, captured); + if (captureListener != null) { + // Capture listeners/instances should go at the start, so we + // unshift them to the start of the array. + dispatchListeners.unshift(captureListener); + dispatchInstances.unshift(node); } } - if ( - enableModernEventSystem && - enableUseEventAPI && - enableScopeAPI && - accumulateUseEventListeners && - tag === ScopeComponent && - lastHostComponent !== null - ) { - const reactScope = instance.methods; - const eventTypeMap = reactScopeListenerStore.get(reactScope); - if (eventTypeMap !== undefined) { - const type = ((event.type: any): DOMTopLevelEventType); - const listeners = eventTypeMap.get(type); - if (listeners !== undefined) { - const captureListeners = Array.from(listeners.captured); - const bubbleListeners = Array.from(listeners.bubbled); - - for (let i = 0; i < captureListeners.length; i++) { - const listener = captureListeners[i]; - const {callback} = listener; - dispatchListeners.unshift(callback); - dispatchInstances.unshift(((lastHostComponent: any): Element)); - } - for (let i = 0; i < bubbleListeners.length; i++) { - const listener = bubbleListeners[i]; - const {callback} = listener; - dispatchListeners.push(callback); - dispatchInstances.push(((lastHostComponent: any): Element)); - } - } + if (bubbled !== null) { + const bubbleListener = getListener(node, bubbled); + if (bubbleListener != null) { + // Bubble listeners/instances should go at the end, so we + // push them to the end of the array. + dispatchListeners.push(bubbleListener); + dispatchInstances.push(node); } } - node = node.return; } + if ( + enableModernEventSystem && + enableUseEventAPI && + enableScopeAPI && + accumulateUseEventListeners && + tag === ScopeComponent && + lastHostComponent !== null + ) { + const reactScope = instance.methods; + const eventTypeMap = reactScopeListenerStore.get(reactScope); + if (eventTypeMap !== undefined) { + const type = ((event.type: any): DOMTopLevelEventType); + const listeners = eventTypeMap.get(type); + if (listeners !== undefined) { + const captureListeners = Array.from(listeners.captured); + const bubbleListeners = Array.from(listeners.bubbled); + + for (let i = 0; i < captureListeners.length; i++) { + const listener = captureListeners[i]; + const {callback} = listener; + dispatchListeners.unshift(callback); + dispatchInstances.unshift(((lastHostComponent: any): Element)); + } + for (let i = 0; i < bubbleListeners.length; i++) { + const listener = bubbleListeners[i]; + const {callback} = listener; + dispatchListeners.push(callback); + dispatchInstances.push(((lastHostComponent: any): Element)); + } + } + } + } + node = node.return; } + // To prevent allocation to the event unless we actually // have listeners we check the length of one of the arrays. if (dispatchListeners.length > 0) {