diff --git a/packages/legacy-events/ReactSyntheticEventType.js b/packages/legacy-events/ReactSyntheticEventType.js index 51929b8da2..ff9927fbe5 100644 --- a/packages/legacy-events/ReactSyntheticEventType.js +++ b/packages/legacy-events/ReactSyntheticEventType.js @@ -31,4 +31,7 @@ export type ReactSyntheticEvent = {| nativeEventTarget: EventTarget, ) => ReactSyntheticEvent, isPersistent: () => boolean, + _dispatchInstances: null | Array, + _dispatchListeners: null | Array, + _targetInst: null | Fiber, |}; diff --git a/packages/legacy-events/SyntheticEvent.js b/packages/legacy-events/SyntheticEvent.js index ca7a5cf407..c763ce4520 100644 --- a/packages/legacy-events/SyntheticEvent.js +++ b/packages/legacy-events/SyntheticEvent.js @@ -76,6 +76,8 @@ function SyntheticEvent( this.dispatchConfig = dispatchConfig; this._targetInst = targetInst; this.nativeEvent = nativeEvent; + this._dispatchListeners = null; + this._dispatchInstances = null; const Interface = this.constructor.Interface; for (const propName in Interface) { diff --git a/packages/react-dom/src/events/BeforeInputEventPlugin.js b/packages/react-dom/src/events/BeforeInputEventPlugin.js index d881f05241..8e48ccc88f 100644 --- a/packages/react-dom/src/events/BeforeInputEventPlugin.js +++ b/packages/react-dom/src/events/BeforeInputEventPlugin.js @@ -7,7 +7,6 @@ import type {TopLevelType} from 'legacy-events/TopLevelEventTypes'; -import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import { @@ -29,6 +28,7 @@ import { } from './FallbackCompositionState'; import SyntheticCompositionEvent from './SyntheticCompositionEvent'; import SyntheticInputEvent from './SyntheticInputEvent'; +import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem'; const END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space const START_KEYCODE = 229; @@ -276,7 +276,7 @@ function extractCompositionEvent( } } - accumulateTwoPhaseDispatchesSingle(event); + accumulateTwoPhaseListeners(event); return event; } @@ -437,7 +437,7 @@ function extractBeforeInputEvent( ); event.data = chars; - accumulateTwoPhaseDispatchesSingle(event); + accumulateTwoPhaseListeners(event); return event; } diff --git a/packages/react-dom/src/events/ChangeEventPlugin.js b/packages/react-dom/src/events/ChangeEventPlugin.js index 77564189cf..5d59435373 100644 --- a/packages/react-dom/src/events/ChangeEventPlugin.js +++ b/packages/react-dom/src/events/ChangeEventPlugin.js @@ -6,7 +6,6 @@ */ import {runEventsInBatch} from 'legacy-events/EventBatching'; -import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators'; import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent'; import {batchedUpdates} from 'legacy-events/ReactGenericBatching'; import SyntheticEvent from 'legacy-events/SyntheticEvent'; @@ -28,6 +27,8 @@ import isEventSupported from './isEventSupported'; import {getNodeFromInstance} from '../client/ReactDOMComponentTree'; import {updateValueIfChanged} from '../client/inputValueTracking'; import {setDefaultValue} from '../client/ReactDOMInput'; +import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem'; + import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags'; const eventTypes = { @@ -59,7 +60,7 @@ function createAndAccumulateChangeEvent(inst, nativeEvent, target) { event.type = 'change'; // Flag this event loop as needing state restore. enqueueStateRestore(target); - accumulateTwoPhaseDispatchesSingle(event); + accumulateTwoPhaseListeners(event); return event; } /** diff --git a/packages/react-dom/src/events/DOMModernPluginEventSystem.js b/packages/react-dom/src/events/DOMModernPluginEventSystem.js index 368edd6440..0f566d0812 100644 --- a/packages/react-dom/src/events/DOMModernPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMModernPluginEventSystem.js @@ -19,8 +19,9 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry'; import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching'; import {executeDispatchesInOrder} from 'legacy-events/EventPluginUtils'; import {plugins} from 'legacy-events/EventPluginRegistry'; +import getListener from 'legacy-events/getListener'; -import {HostRoot, HostPortal} from 'shared/ReactWorkTags'; +import {HostRoot, HostPortal, HostComponent} from 'shared/ReactWorkTags'; import {addTrappedEventListener} from './ReactDOMEventListener'; import getEventTarget from './getEventTarget'; @@ -305,3 +306,46 @@ export function attachElementListener(listener: ReactDOMListener): void { export function detachElementListener(listener: ReactDOMListener): void { // TODO } + +export function accumulateTwoPhaseListeners(event: ReactSyntheticEvent): void { + const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames; + if (phasedRegistrationNames == null) { + return; + } + const {bubbled, captured} = phasedRegistrationNames; + const dispatchListeners = []; + const dispatchInstances = []; + let node = event._targetInst; + let hasListeners = false; + + // Accumulate all instances and listeners via the target -> root path. + while (node !== null) { + // We only care for listeners that are on HostComponents (i.e.
) + if (node.tag === HostComponent) { + // Standard React on* listeners, i.e. onClick prop + const captureListener = getListener(node, captured); + if (captureListener != null) { + hasListeners = true; + // Capture listeners/instances should go at the start, so we + // unshift them to the start of the array. + dispatchListeners.unshift(captureListener); + dispatchInstances.unshift(node); + } + const bubbleListener = getListener(node, bubbled); + if (bubbleListener != null) { + hasListeners = true; + // 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; + } + // To prevent allocation to the event unless we actually + // have listeners we use the flag we would have set above. + if (hasListeners) { + event._dispatchListeners = dispatchListeners; + event._dispatchInstances = dispatchInstances; + } +} diff --git a/packages/react-dom/src/events/SelectEventPlugin.js b/packages/react-dom/src/events/SelectEventPlugin.js index 5f1d468fd4..840251506a 100644 --- a/packages/react-dom/src/events/SelectEventPlugin.js +++ b/packages/react-dom/src/events/SelectEventPlugin.js @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import SyntheticEvent from 'legacy-events/SyntheticEvent'; import isTextInputElement from 'shared/isTextInputElement'; @@ -27,6 +26,7 @@ import {getNodeFromInstance} from '../client/ReactDOMComponentTree'; import {hasSelectionCapabilities} from '../client/ReactInputSelection'; import {DOCUMENT_NODE} from '../shared/HTMLNodeType'; import {isListeningToAllDependencies} from './DOMEventListenerMap'; +import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem'; const skipSelectionChangeEvent = canUseDOM && 'documentMode' in document && document.documentMode <= 11; @@ -135,7 +135,7 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) { syntheticEvent.type = 'select'; syntheticEvent.target = activeElement; - accumulateTwoPhaseDispatchesSingle(syntheticEvent); + accumulateTwoPhaseListeners(syntheticEvent); return syntheticEvent; } diff --git a/packages/react-dom/src/events/SimpleEventPlugin.js b/packages/react-dom/src/events/SimpleEventPlugin.js index 21fbdfd6c0..3b924eec0a 100644 --- a/packages/react-dom/src/events/SimpleEventPlugin.js +++ b/packages/react-dom/src/events/SimpleEventPlugin.js @@ -16,7 +16,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber'; import type {PluginModule} from 'legacy-events/PluginModuleType'; import type {EventSystemFlags} from 'legacy-events/EventSystemFlags'; -import {accumulateTwoPhaseDispatchesSingle} from 'legacy-events/EventPropagators'; import SyntheticEvent from 'legacy-events/SyntheticEvent'; import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes'; @@ -37,6 +36,7 @@ import SyntheticTransitionEvent from './SyntheticTransitionEvent'; import SyntheticUIEvent from './SyntheticUIEvent'; import SyntheticWheelEvent from './SyntheticWheelEvent'; import getEventCharCode from './getEventCharCode'; +import {accumulateTwoPhaseListeners} from './DOMModernPluginEventSystem'; // Only used in DEV for exhaustiveness validation. const knownHTMLTopLevelTypes: Array = [ @@ -191,7 +191,7 @@ const SimpleEventPlugin: PluginModule = { nativeEvent, nativeEventTarget, ); - accumulateTwoPhaseDispatchesSingle(event); + accumulateTwoPhaseListeners(event); return event; }, };