/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
1 004 строки
30 KB
Jan Kassens
Update Flow to 0.257 (#34253)
21 авг 2025, 20:30
Не верифицирован
21 авг 2025, 20:30
ec5dd0a
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and 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 {DOMEventName} from './DOMEventNames'; import type {EventSystemFlags} from './EventSystemFlags'; import type {AnyNativeEvent} from './PluginModuleType'; import type { KnownReactSyntheticEvent, ReactSyntheticEvent, } from './ReactSyntheticEventType'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import {allNativeEvents} from './EventRegistry'; import { SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE, IS_LEGACY_FB_SUPPORT_MODE, SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS, IS_CAPTURE_PHASE, IS_EVENT_HANDLE_NON_MANAGED_NODE, IS_NON_DELEGATED, } from './EventSystemFlags'; import {isReplayingEvent} from './CurrentReplayingEvent'; import { HostRoot, HostPortal, HostComponent, HostHoistable, HostSingleton, HostText, ScopeComponent, } from 'react-reconciler/src/ReactWorkTags'; import {getLowestCommonAncestor} from 'react-reconciler/src/ReactFiberTreeReflection'; import getEventTarget from './getEventTarget'; import { getClosestInstanceFromNode, getEventListenerSet, getEventHandlerListeners, } from '../client/ReactDOMComponentTree'; import {COMMENT_NODE, DOCUMENT_NODE} from '../client/HTMLNodeType'; import {batchedUpdates} from './ReactDOMUpdateBatching'; import getListener from './getListener'; import {passiveBrowserEventsSupported} from './checkPassiveEvents'; import { enableLegacyFBSupport, enableCreateEventHandleAPI, enableScopeAPI, disableCommentsAsDOMContainers, enableScrollEndPolyfill, } from 'shared/ReactFeatureFlags'; import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener'; import { removeEventListener, addEventCaptureListener, addEventBubbleListener, addEventBubbleListenerWithPassiveFlag, addEventCaptureListenerWithPassiveFlag, } from './EventListener'; import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin'; import * as ChangeEventPlugin from './plugins/ChangeEventPlugin'; import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin'; import * as SelectEventPlugin from './plugins/SelectEventPlugin'; import * as SimpleEventPlugin from './plugins/SimpleEventPlugin'; import * as FormActionEventPlugin from './plugins/FormActionEventPlugin'; import * as ScrollEndEventPlugin from './plugins/ScrollEndEventPlugin'; import reportGlobalError from 'shared/reportGlobalError'; import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber'; type DispatchListener = { instance: null | Fiber, listener: Function, currentTarget: EventTarget, }; type DispatchEntry = { event: ReactSyntheticEvent, listeners: Array<DispatchListener>, }; export type DispatchQueue = Array<DispatchEntry>; // TODO: remove top-level side effect. SimpleEventPlugin.registerEvents(); EnterLeaveEventPlugin.registerEvents(); ChangeEventPlugin.registerEvents(); SelectEventPlugin.registerEvents(); BeforeInputEventPlugin.registerEvents(); if (enableScrollEndPolyfill) { ScrollEndEventPlugin.registerEvents(); } function extractEvents( dispatchQueue: DispatchQueue, domEventName: DOMEventName, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: null | EventTarget, eventSystemFlags: EventSystemFlags, targetContainer: EventTarget, ) { // TODO: we should remove the concept of a "SimpleEventPlugin". // This is the basic functionality of the event system. All // the other plugins are essentially polyfills. So the plugin // should probably be inlined somewhere and have its logic // be core the to event system. This would potentially allow // us to ship builds of React without the polyfilled plugins below. SimpleEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); const shouldProcessPolyfillPlugins = (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0; // We don't process these events unless we are in the // event's native "bubble" phase, which means that we're // not in the capture phase. That's because we emulate // the capture phase here still. This is a trade-off, // because in an ideal world we would not emulate and use // the phases properly, like we do with the SimpleEvent // plugin. However, the plugins below either expect // emulation (EnterLeave) or use state localized to that // plugin (BeforeInput, Change, Select). The state in // these modules complicates things, as you'll essentially // get the case where the capture phase event might change // state, only for the following bubble event to come in // later and not trigger anything as the state now // invalidates the heuristics of the event plugin. We // could alter all these plugins to work in such ways, but // that might cause other unknown side-effects that we // can't foresee right now. if (shouldProcessPolyfillPlugins) { EnterLeaveEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); ChangeEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); SelectEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); BeforeInputEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); FormActionEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); } if (enableScrollEndPolyfill) { ScrollEndEventPlugin.extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); } } // List of events that need to be individually attached to media elements. export const mediaEventTypes: Array<DOMEventName> = [ 'abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'encrypted', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 'resize', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting', ]; // We should not delegate these events to the container, but rather // set them on the actual target element itself. This is primarily // because these events do not consistently bubble in the DOM. export const nonDelegatedEvents: Set<DOMEventName> = new Set([ 'beforetoggle', 'cancel', 'close', 'invalid', 'load', 'scroll', 'scrollend', 'toggle', // In order to reduce bytes, we insert the above array of media events // into this Set. Note: the "error" event isn't an exclusive media event, // and can occur on other elements too. Rather than duplicate that event, // we just take it from the media events array. ...mediaEventTypes, ]); function executeDispatch( event: ReactSyntheticEvent, listener: Function, currentTarget: EventTarget, ): void { event.currentTarget = currentTarget; try { listener(event); } catch (error) { reportGlobalError(error); } event.currentTarget = null; } function processDispatchQueueItemsInOrder( event: ReactSyntheticEvent, dispatchListeners: Array<DispatchListener>, inCapturePhase: boolean, ): void { let previousInstance; if (inCapturePhase) { for (let i = dispatchListeners.length - 1; i >= 0; i--) { const {instance, currentTarget, listener} = dispatchListeners[i]; if (instance !== previousInstance && event.isPropagationStopped()) { return; } if (__DEV__ && instance !== null) { runWithFiberInDEV( instance, executeDispatch, event, listener, currentTarget, ); } else { executeDispatch(event, listener, currentTarget); } previousInstance = instance; } } else { for (let i = 0; i < dispatchListeners.length; i++) { const {instance, currentTarget, listener} = dispatchListeners[i]; if (instance !== previousInstance && event.isPropagationStopped()) { return; } if (__DEV__ && instance !== null) { runWithFiberInDEV( instance, executeDispatch, event, listener, currentTarget, ); } else { executeDispatch(event, listener, currentTarget); } previousInstance = instance; } } } export function processDispatchQueue( dispatchQueue: DispatchQueue, eventSystemFlags: EventSystemFlags, ): void { const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0; for (let i = 0; i < dispatchQueue.length; i++) { const {event, listeners} = dispatchQueue[i]; processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling. } } function dispatchEventsForPlugins( domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, targetInst: null | Fiber, targetContainer: EventTarget, ): void { const nativeEventTarget = getEventTarget(nativeEvent); const dispatchQueue: DispatchQueue = []; extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); processDispatchQueue(dispatchQueue, eventSystemFlags); } export function listenToNonDelegatedEvent( domEventName: DOMEventName, targetElement: Element, ): void { if (__DEV__) { if (!nonDelegatedEvents.has(domEventName)) { console.error( 'Did not expect a listenToNonDelegatedEvent() call for "%s". ' + 'This is a bug in React. Please file an issue.', domEventName, ); } } const isCapturePhaseListener = false; const listenerSet = getEventListenerSet(targetElement); const listenerSetKey = getListenerSetKey( domEventName, isCapturePhaseListener, ); if (!listenerSet.has(listenerSetKey)) { addTrappedEventListener( targetElement, domEventName, IS_NON_DELEGATED, isCapturePhaseListener, ); listenerSet.add(listenerSetKey); } } export function listenToNativeEvent( domEventName: DOMEventName, isCapturePhaseListener: boolean, target: EventTarget, ): void { if (__DEV__) { if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) { console.error( 'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' + 'This is a bug in React. Please file an issue.', domEventName, ); } } let eventSystemFlags = 0; if (isCapturePhaseListener) { eventSystemFlags |= IS_CAPTURE_PHASE; } addTrappedEventListener( target, domEventName, eventSystemFlags, isCapturePhaseListener, ); } // This is only used by createEventHandle when the // target is not a DOM element. E.g. window. export function listenToNativeEventForNonManagedEventTarget( domEventName: DOMEventName, isCapturePhaseListener: boolean, target: EventTarget, ): void { let eventSystemFlags: number = IS_EVENT_HANDLE_NON_MANAGED_NODE; const listenerSet = getEventListenerSet(target); const listenerSetKey = getListenerSetKey( domEventName, isCapturePhaseListener, ); if (!listenerSet.has(listenerSetKey)) { if (isCapturePhaseListener) { eventSystemFlags |= IS_CAPTURE_PHASE; } addTrappedEventListener( target, domEventName, eventSystemFlags, isCapturePhaseListener, ); listenerSet.add(listenerSetKey); } } const listeningMarker = '_reactListening' + Math.random().toString(36).slice(2); export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { if (!(rootContainerElement: any)[listeningMarker]) { (rootContainerElement: any)[listeningMarker] = true; allNativeEvents.forEach(domEventName => { // We handle selectionchange separately because it // doesn't bubble and needs to be on the document. if (domEventName !== 'selectionchange') { if (!nonDelegatedEvents.has(domEventName)) { listenToNativeEvent(domEventName, false, rootContainerElement); } listenToNativeEvent(domEventName, true, rootContainerElement); } }); const ownerDocument = (rootContainerElement: any).nodeType === DOCUMENT_NODE ? rootContainerElement : (rootContainerElement: any).ownerDocument; if (ownerDocument !== null) { // The selectionchange event also needs deduplication // but it is attached to the document. if (!(ownerDocument: any)[listeningMarker]) { (ownerDocument: any)[listeningMarker] = true; listenToNativeEvent('selectionchange', false, ownerDocument); } } } } function addTrappedEventListener( targetContainer: EventTarget, domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, isCapturePhaseListener: boolean, isDeferredListenerForLegacyFBSupport?: boolean, ) { let listener = createEventListenerWrapperWithPriority( targetContainer, domEventName, eventSystemFlags, ); // If passive option is not supported, then the event will be // active and not passive. let isPassiveListener: void | boolean = undefined; if (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 = enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport ? (targetContainer: any).ownerDocument : targetContainer; let unsubscribeListener; // When legacyFBSupport is enabled, it's for when we // want to add a one time event listener to a container. // This should only be used with enableLegacyFBSupport // due to requirement to provide compatibility with // internal FB www event tooling. This works by removing // the event listener as soon as it is invoked. We could // also attempt to use the {once: true} param on // addEventListener, but that requires support and some // browsers do not support this today, and given this is // to support legacy code patterns, it's likely they'll // need support for such browsers. if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) { const originalListener = listener; // $FlowFixMe[missing-this-annot] listener = function (...p) { removeEventListener( targetContainer, domEventName, unsubscribeListener, isCapturePhaseListener, ); return originalListener.apply(this, p); }; } // TODO: There are too many combinations here. Consolidate them. if (isCapturePhaseListener) { if (isPassiveListener !== undefined) { unsubscribeListener = addEventCaptureListenerWithPassiveFlag( targetContainer, domEventName, listener, isPassiveListener, ); } else { unsubscribeListener = addEventCaptureListener( targetContainer, domEventName, listener, ); } } else { if (isPassiveListener !== undefined) { unsubscribeListener = addEventBubbleListenerWithPassiveFlag( targetContainer, domEventName, listener, isPassiveListener, ); } else { unsubscribeListener = addEventBubbleListener( targetContainer, domEventName, listener, ); } } } function deferClickToDocumentForLegacyFBSupport( domEventName: DOMEventName, targetContainer: EventTarget, ): void { // We defer all click events with legacy FB support mode on. // This means we add a one time event listener to trigger // after the FB delegated listeners fire. const isDeferredListenerForLegacyFBSupport = true; addTrappedEventListener( targetContainer, domEventName, IS_LEGACY_FB_SUPPORT_MODE, false, isDeferredListenerForLegacyFBSupport, ); } function isMatchingRootContainer( grandContainer: Element, targetContainer: EventTarget, ): boolean { return ( grandContainer === targetContainer || (!disableCommentsAsDOMContainers && grandContainer.nodeType === COMMENT_NODE && grandContainer.parentNode === targetContainer) ); } export function dispatchEventForPluginEventSystem( domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, targetInst: null | Fiber, targetContainer: EventTarget, ): void { let ancestorInst = targetInst; if ( (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && (eventSystemFlags & IS_NON_DELEGATED) === 0 ) { const targetContainerNode = ((targetContainer: any): Node); // If we are using the legacy FB support flag, we // defer the event to the null with a one // time event listener so we can defer the event. if ( enableLegacyFBSupport && // If our event flags match the required flags for entering // FB legacy mode and we are processing the "click" event, // then we can defer the event to the "document", to allow // for legacy FB support, where the expected behavior was to // match React < 16 behavior of delegated clicks to the doc. domEventName === 'click' && (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 && !isReplayingEvent(nativeEvent) ) { deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer); return; } if (targetInst !== null) { // The below logic attempts to work out if we need to change // the target fiber to a different ancestor. We had similar logic // in the legacy event system, except the big difference between // systems is that the modern event system now has an event listener // attached to each React Root and React Portal Root. Together, // the DOM nodes representing these roots are the "rootContainer". // To figure out which ancestor instance we should use, we traverse // up the fiber tree from the target instance and attempt to find // root boundaries that match that of our current "rootContainer". // If we find that "rootContainer", we find the parent fiber // sub-tree for that root and make that our ancestor instance. let node: null | Fiber = targetInst; mainLoop: while (true) { if (node === null) { return; } const nodeTag = node.tag; if (nodeTag === HostRoot || nodeTag === HostPortal) { let container = node.stateNode.containerInfo; if (isMatchingRootContainer(container, targetContainerNode)) { break; } if (nodeTag === HostPortal) { // The target is a portal, but it's not the rootContainer we're looking for. // Normally portals handle their own events all the way down to the root. // So we should be able to stop now. However, we don't know if this portal // was part of *our* root. let grandNode = node.return; while (grandNode !== null) { const grandTag = grandNode.tag; if (grandTag === HostRoot || grandTag === HostPortal) { const grandContainer = grandNode.stateNode.containerInfo; if ( isMatchingRootContainer(grandContainer, targetContainerNode) ) { // This is the rootContainer we're looking for and we found it as // a parent of the Portal. That means we can ignore it because the // Portal will bubble through to us. return; } } grandNode = grandNode.return; } } // Now we need to find it's corresponding host fiber in the other // tree. To do this we can use getClosestInstanceFromNode, but we // need to validate that the fiber is a host instance, otherwise // we need to traverse up through the DOM till we find the correct // node that is from the other tree. while (container !== null) { const parentNode = getClosestInstanceFromNode(container); if (parentNode === null) { return; } const parentTag = parentNode.tag; if ( parentTag === HostComponent || parentTag === HostText || parentTag === HostHoistable || parentTag === HostSingleton ) { node = ancestorInst = parentNode; continue mainLoop; } container = container.parentNode; } } node = node.return; } } } batchedUpdates(() => dispatchEventsForPlugins( domEventName, eventSystemFlags, nativeEvent, ancestorInst, targetContainer, ), ); } function createDispatchListener( instance: null | Fiber, listener: Function, currentTarget: EventTarget, ): DispatchListener { return { instance, listener, currentTarget, }; } export function accumulateSinglePhaseListeners( targetFiber: Fiber | null, reactName: string | null, nativeEventType: string, inCapturePhase: boolean, accumulateTargetOnly: boolean, nativeEvent: AnyNativeEvent, ): Array<DispatchListener> { const captureName = reactName !== null ? reactName + 'Capture' : null; const reactEventName = inCapturePhase ? captureName : reactName; let listeners: Array<DispatchListener> = []; let instance = targetFiber; let lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path. while (instance !== null) { const {stateNode, tag} = instance; // Handle listeners that are on HostComponents (i.e. <div>) if ( (tag === HostComponent || tag === HostHoistable || tag === HostSingleton) && stateNode !== null ) { lastHostComponent = stateNode; // createEventHandle listeners if (enableCreateEventHandleAPI) { const eventHandlerListeners = getEventHandlerListeners(lastHostComponent); if (eventHandlerListeners !== null) { eventHandlerListeners.forEach(entry => { if ( entry.type === nativeEventType && entry.capture === inCapturePhase ) { listeners.push( createDispatchListener( instance, entry.callback, (lastHostComponent: any), ), ); } }); } } // Standard React on* listeners, i.e. onClick or onClickCapture if (reactEventName !== null) { const listener = getListener(instance, reactEventName); if (listener != null) { listeners.push( createDispatchListener(instance, listener, lastHostComponent), ); } } } else if ( enableCreateEventHandleAPI && enableScopeAPI && tag === ScopeComponent && lastHostComponent !== null && stateNode !== null ) { // Scopes const reactScopeInstance = stateNode; const eventHandlerListeners = getEventHandlerListeners(reactScopeInstance); if (eventHandlerListeners !== null) { eventHandlerListeners.forEach(entry => { if ( entry.type === nativeEventType && entry.capture === inCapturePhase ) { listeners.push( createDispatchListener( instance, entry.callback, (lastHostComponent: any), ), ); } }); } } // If we are only accumulating events for the target, then we don't // continue to propagate through the React fiber tree to find other // listeners. if (accumulateTargetOnly) { break; } // If we are processing the onBeforeBlur event, then we need to take // into consideration that part of the React tree might have been hidden // or deleted (as we're invoking this event during commit). We can find // this out by checking if intercept fiber set on the event matches the // current instance fiber. In which case, we should clear all existing // listeners. if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') { // $FlowFixMe[prop-missing] internal field const detachedInterceptFiber = nativeEvent._detachedInterceptFiber; if ( detachedInterceptFiber !== null && (detachedInterceptFiber === instance || detachedInterceptFiber === instance.alternate) ) { listeners = []; } } instance = instance.return; } return listeners; } // We should only use this function for: // - BeforeInputEventPlugin // - ChangeEventPlugin // - SelectEventPlugin // - ScrollEndEventPlugin // This is because we only process these plugins // in the bubble phase, so we need to accumulate two // phase event listeners (via emulation). export function accumulateTwoPhaseListeners( targetFiber: Fiber | null, reactName: string, ): Array<DispatchListener> { const captureName = reactName + 'Capture'; const listeners: Array<DispatchListener> = []; let instance = targetFiber; // Accumulate all instances and listeners via the target -> root path. while (instance !== null) { const {stateNode, tag} = instance; // Handle listeners that are on HostComponents (i.e. <div>) if ( (tag === HostComponent || tag === HostHoistable || tag === HostSingleton) && stateNode !== null ) { const currentTarget = stateNode; const captureListener = getListener(instance, captureName); if (captureListener != null) { listeners.unshift( createDispatchListener(instance, captureListener, currentTarget), ); } const bubbleListener = getListener(instance, reactName); if (bubbleListener != null) { listeners.push( createDispatchListener(instance, bubbleListener, currentTarget), ); } } if (instance.tag === HostRoot) { return listeners; } instance = instance.return; } // If we didn't reach the root it means we're unmounted and shouldn't // dispatch any events on the target. return []; } function getParent(inst: Fiber | null): Fiber | null { if (inst === null) { return null; } do { // $FlowFixMe[incompatible-use] found when upgrading Flow inst = inst.return; // TODO: If this is a HostRoot we might want to bail out. // That is depending on if we want nested subtrees (layers) to bubble // events to their parent. We could also go through parentNode on the // host node but that wouldn't work for React Native and doesn't let us // do the portal feature. } while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton); if (inst) { return inst; } return null; } function accumulateEnterLeaveListenersForEvent( dispatchQueue: DispatchQueue, event: KnownReactSyntheticEvent, target: Fiber, common: Fiber | null, inCapturePhase: boolean, ): void { const registrationName = event._reactName; const listeners: Array<DispatchListener> = []; let instance: null | Fiber = target; while (instance !== null) { if (instance === common) { break; } const {alternate, stateNode, tag} = instance; if (alternate !== null && alternate === common) { break; } if ( (tag === HostComponent || tag === HostHoistable || tag === HostSingleton) && stateNode !== null ) { const currentTarget = stateNode; if (inCapturePhase) { const captureListener = getListener(instance, registrationName); if (captureListener != null) { listeners.unshift( createDispatchListener(instance, captureListener, currentTarget), ); } } else if (!inCapturePhase) { const bubbleListener = getListener(instance, registrationName); if (bubbleListener != null) { listeners.push( createDispatchListener(instance, bubbleListener, currentTarget), ); } } } instance = instance.return; } if (listeners.length !== 0) { dispatchQueue.push({event, listeners}); } } // We should only use this function for: // - EnterLeaveEventPlugin // This is because we only process this plugin // in the bubble phase, so we need to accumulate two // phase event listeners. export function accumulateEnterLeaveTwoPhaseListeners( dispatchQueue: DispatchQueue, leaveEvent: KnownReactSyntheticEvent, enterEvent: null | KnownReactSyntheticEvent, from: Fiber | null, to: Fiber | null, ): void { const common = from && to ? getLowestCommonAncestor(from, to, getParent) : null; if (from !== null) { accumulateEnterLeaveListenersForEvent( dispatchQueue, leaveEvent, from, common, false, ); } if (to !== null && enterEvent !== null) { accumulateEnterLeaveListenersForEvent( dispatchQueue, enterEvent, to, common, true, ); } } export function accumulateEventHandleNonManagedNodeListeners( reactEventType: DOMEventName, currentTarget: EventTarget, inCapturePhase: boolean, ): Array<DispatchListener> { const listeners: Array<DispatchListener> = []; const eventListeners = getEventHandlerListeners(currentTarget); if (eventListeners !== null) { eventListeners.forEach(entry => { if (entry.type === reactEventType && entry.capture === inCapturePhase) { listeners.push( createDispatchListener(null, entry.callback, currentTarget), ); } }); } return listeners; } export function getListenerSetKey( domEventName: DOMEventName, capture: boolean, ): string { return `${domEventName}__${capture ? 'capture' : 'bubble'}`; }