Use strict equality (===) to prevent type coercion bugs
(eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {DOMEventName} from './DOMEventNames';11import type {EventSystemFlags} from './EventSystemFlags';12import type {AnyNativeEvent} from './PluginModuleType';13import type {14 KnownReactSyntheticEvent,15 ReactSyntheticEvent,16} from './ReactSyntheticEventType';17import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';1819import {allNativeEvents} from './EventRegistry';20import {21 SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE,22 IS_LEGACY_FB_SUPPORT_MODE,23 SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS,24 IS_CAPTURE_PHASE,25 IS_EVENT_HANDLE_NON_MANAGED_NODE,26 IS_NON_DELEGATED,27} from './EventSystemFlags';28import {isReplayingEvent} from './CurrentReplayingEvent';2930import {31 HostRoot,32 HostPortal,33 HostComponent,34 HostHoistable,35 HostSingleton,36 HostText,37 ScopeComponent,38} from 'react-reconciler/src/ReactWorkTags';39import {getLowestCommonAncestor} from 'react-reconciler/src/ReactFiberTreeReflection';4041import getEventTarget from './getEventTarget';42import {43 getClosestInstanceFromNode,44 getEventListenerSet,45 getEventHandlerListeners,46} from '../client/ReactDOMComponentTree';47import {COMMENT_NODE, DOCUMENT_NODE} from '../client/HTMLNodeType';48import {batchedUpdates} from './ReactDOMUpdateBatching';49import getListener from './getListener';50import {passiveBrowserEventsSupported} from './checkPassiveEvents';5152import {53 enableLegacyFBSupport,54 enableCreateEventHandleAPI,55 enableScopeAPI,56 disableCommentsAsDOMContainers,57 enableScrollEndPolyfill,58} from 'shared/ReactFeatureFlags';59import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener';60import {61 removeEventListener,62 addEventCaptureListener,63 addEventBubbleListener,64 addEventBubbleListenerWithPassiveFlag,65 addEventCaptureListenerWithPassiveFlag,66} from './EventListener';67import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';68import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';69import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';70import * as SelectEventPlugin from './plugins/SelectEventPlugin';71import * as SimpleEventPlugin from './plugins/SimpleEventPlugin';72import * as FormActionEventPlugin from './plugins/FormActionEventPlugin';73import * as ScrollEndEventPlugin from './plugins/ScrollEndEventPlugin';7475import reportGlobalError from 'shared/reportGlobalError';7677import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';7879type DispatchListener = {80 instance: null | Fiber,81 listener: Function,82 currentTarget: EventTarget,83};8485type DispatchEntry = {86 event: ReactSyntheticEvent,87 listeners: Array<DispatchListener>,88};8990export type DispatchQueue = Array<DispatchEntry>;9192// TODO: remove top-level side effect.93SimpleEventPlugin.registerEvents();94EnterLeaveEventPlugin.registerEvents();95ChangeEventPlugin.registerEvents();96SelectEventPlugin.registerEvents();97BeforeInputEventPlugin.registerEvents();98if (enableScrollEndPolyfill) {99 ScrollEndEventPlugin.registerEvents();100}101102function extractEvents(103 dispatchQueue: DispatchQueue,104 domEventName: DOMEventName,105 targetInst: null | Fiber,106 nativeEvent: AnyNativeEvent,107 nativeEventTarget: null | EventTarget,108 eventSystemFlags: EventSystemFlags,109 targetContainer: EventTarget,110) {111 // TODO: we should remove the concept of a "SimpleEventPlugin".112 // This is the basic functionality of the event system. All113 // the other plugins are essentially polyfills. So the plugin114 // should probably be inlined somewhere and have its logic115 // be core the to event system. This would potentially allow116 // us to ship builds of React without the polyfilled plugins below.117 SimpleEventPlugin.extractEvents(118 dispatchQueue,119 domEventName,120 targetInst,121 nativeEvent,122 nativeEventTarget,123 eventSystemFlags,124 targetContainer,125 );126 const shouldProcessPolyfillPlugins =127 (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;128 // We don't process these events unless we are in the129 // event's native "bubble" phase, which means that we're130 // not in the capture phase. That's because we emulate131 // the capture phase here still. This is a trade-off,132 // because in an ideal world we would not emulate and use133 // the phases properly, like we do with the SimpleEvent134 // plugin. However, the plugins below either expect135 // emulation (EnterLeave) or use state localized to that136 // plugin (BeforeInput, Change, Select). The state in137 // these modules complicates things, as you'll essentially138 // get the case where the capture phase event might change139 // state, only for the following bubble event to come in140 // later and not trigger anything as the state now141 // invalidates the heuristics of the event plugin. We142 // could alter all these plugins to work in such ways, but143 // that might cause other unknown side-effects that we144 // can't foresee right now.145 if (shouldProcessPolyfillPlugins) {146 EnterLeaveEventPlugin.extractEvents(147 dispatchQueue,148 domEventName,149 targetInst,150 nativeEvent,151 nativeEventTarget,152 eventSystemFlags,153 targetContainer,154 );155 ChangeEventPlugin.extractEvents(156 dispatchQueue,157 domEventName,158 targetInst,159 nativeEvent,160 nativeEventTarget,161 eventSystemFlags,162 targetContainer,163 );164 SelectEventPlugin.extractEvents(165 dispatchQueue,166 domEventName,167 targetInst,168 nativeEvent,169 nativeEventTarget,170 eventSystemFlags,171 targetContainer,172 );173 BeforeInputEventPlugin.extractEvents(174 dispatchQueue,175 domEventName,176 targetInst,177 nativeEvent,178 nativeEventTarget,179 eventSystemFlags,180 targetContainer,181 );182 FormActionEventPlugin.extractEvents(183 dispatchQueue,184 domEventName,185 targetInst,186 nativeEvent,187 nativeEventTarget,188 eventSystemFlags,189 targetContainer,190 );191 }192 if (enableScrollEndPolyfill) {193 ScrollEndEventPlugin.extractEvents(194 dispatchQueue,195 domEventName,196 targetInst,197 nativeEvent,198 nativeEventTarget,199 eventSystemFlags,200 targetContainer,201 );202 }203}204205// List of events that need to be individually attached to media elements.206export const mediaEventTypes: Array<DOMEventName> = [207 'abort',208 'canplay',209 'canplaythrough',210 'durationchange',211 'emptied',212 'encrypted',213 'ended',214 'error',215 'loadeddata',216 'loadedmetadata',217 'loadstart',218 'pause',219 'play',220 'playing',221 'progress',222 'ratechange',223 'resize',224 'seeked',225 'seeking',226 'stalled',227 'suspend',228 'timeupdate',229 'volumechange',230 'waiting',231];232233// We should not delegate these events to the container, but rather234// set them on the actual target element itself. This is primarily235// because these events do not consistently bubble in the DOM.236export const nonDelegatedEvents: Set<DOMEventName> = new Set([237 'beforetoggle',238 'cancel',239 'close',240 'invalid',241 'load',242 'scroll',243 'scrollend',244 'toggle',245 // In order to reduce bytes, we insert the above array of media events246 // into this Set. Note: the "error" event isn't an exclusive media event,247 // and can occur on other elements too. Rather than duplicate that event,248 // we just take it from the media events array.249 ...mediaEventTypes,250]);251252function executeDispatch(253 event: ReactSyntheticEvent,254 listener: Function,255 currentTarget: EventTarget,256): void {257 event.currentTarget = currentTarget;258 try {259 listener(event);260 } catch (error) {261 reportGlobalError(error);262 }263 event.currentTarget = null;264}265266function processDispatchQueueItemsInOrder(267 event: ReactSyntheticEvent,268 dispatchListeners: Array<DispatchListener>,269 inCapturePhase: boolean,270): void {271 let previousInstance;272 if (inCapturePhase) {273 for (let i = dispatchListeners.length - 1; i >= 0; i--) {274 const {instance, currentTarget, listener} = dispatchListeners[i];275 if (instance !== previousInstance && event.isPropagationStopped()) {276 return;277 }278 if (__DEV__ && instance !== null) {279 runWithFiberInDEV(280 instance,281 executeDispatch,282 event,283 listener,284 currentTarget,285 );286 } else {287 executeDispatch(event, listener, currentTarget);288 }289 previousInstance = instance;290 }291 } else {292 for (let i = 0; i < dispatchListeners.length; i++) {293 const {instance, currentTarget, listener} = dispatchListeners[i];294 if (instance !== previousInstance && event.isPropagationStopped()) {295 return;296 }297 if (__DEV__ && instance !== null) {298 runWithFiberInDEV(299 instance,300 executeDispatch,301 event,302 listener,303 currentTarget,304 );305 } else {306 executeDispatch(event, listener, currentTarget);307 }308 previousInstance = instance;309 }310 }311}312313export function processDispatchQueue(314 dispatchQueue: DispatchQueue,315 eventSystemFlags: EventSystemFlags,316): void {317 const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;318 for (let i = 0; i < dispatchQueue.length; i++) {319 const {event, listeners} = dispatchQueue[i];320 processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);321 // event system doesn't use pooling.322 }323}324325function dispatchEventsForPlugins(326 domEventName: DOMEventName,327 eventSystemFlags: EventSystemFlags,328 nativeEvent: AnyNativeEvent,329 targetInst: null | Fiber,330 targetContainer: EventTarget,331): void {332 const nativeEventTarget = getEventTarget(nativeEvent);333 const dispatchQueue: DispatchQueue = [];334 extractEvents(335 dispatchQueue,336 domEventName,337 targetInst,338 nativeEvent,339 nativeEventTarget,340 eventSystemFlags,341 targetContainer,342 );343 processDispatchQueue(dispatchQueue, eventSystemFlags);344}345346export function listenToNonDelegatedEvent(347 domEventName: DOMEventName,348 targetElement: Element,349): void {350 if (__DEV__) {351 if (!nonDelegatedEvents.has(domEventName)) {352 console.error(353 'Did not expect a listenToNonDelegatedEvent() call for "%s". ' +354 'This is a bug in React. Please file an issue.',355 domEventName,356 );357 }358 }359 const isCapturePhaseListener = false;360 const listenerSet = getEventListenerSet(targetElement);361 const listenerSetKey = getListenerSetKey(362 domEventName,363 isCapturePhaseListener,364 );365 if (!listenerSet.has(listenerSetKey)) {366 addTrappedEventListener(367 targetElement,368 domEventName,369 IS_NON_DELEGATED,370 isCapturePhaseListener,371 );372 listenerSet.add(listenerSetKey);373 }374}375376export function listenToNativeEvent(377 domEventName: DOMEventName,378 isCapturePhaseListener: boolean,379 target: EventTarget,380): void {381 if (__DEV__) {382 if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {383 console.error(384 'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +385 'This is a bug in React. Please file an issue.',386 domEventName,387 );388 }389 }390391 let eventSystemFlags = 0;392 if (isCapturePhaseListener) {393 eventSystemFlags |= IS_CAPTURE_PHASE;394 }395 addTrappedEventListener(396 target,397 domEventName,398 eventSystemFlags,399 isCapturePhaseListener,400 );401}402403// This is only used by createEventHandle when the404// target is not a DOM element. E.g. window.405export function listenToNativeEventForNonManagedEventTarget(406 domEventName: DOMEventName,407 isCapturePhaseListener: boolean,408 target: EventTarget,409): void {410 let eventSystemFlags: number = IS_EVENT_HANDLE_NON_MANAGED_NODE;411 const listenerSet = getEventListenerSet(target);412 const listenerSetKey = getListenerSetKey(413 domEventName,414 isCapturePhaseListener,415 );416 if (!listenerSet.has(listenerSetKey)) {417 if (isCapturePhaseListener) {418 eventSystemFlags |= IS_CAPTURE_PHASE;419 }420 addTrappedEventListener(421 target,422 domEventName,423 eventSystemFlags,424 isCapturePhaseListener,425 );426 listenerSet.add(listenerSetKey);427 }428}429430const listeningMarker = '_reactListening' + Math.random().toString(36).slice(2);431432export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {433 if (!(rootContainerElement as any)[listeningMarker]) {434 (rootContainerElement as any)[listeningMarker] = true;435 allNativeEvents.forEach(domEventName => {436 // We handle selectionchange separately because it437 // doesn't bubble and needs to be on the document.438 if (domEventName !== 'selectionchange') {439 if (!nonDelegatedEvents.has(domEventName)) {440 listenToNativeEvent(domEventName, false, rootContainerElement);441 }442 listenToNativeEvent(domEventName, true, rootContainerElement);443 }444 });445 const ownerDocument =446 (rootContainerElement as any).nodeType === DOCUMENT_NODE447 ? rootContainerElement448 : (rootContainerElement as any).ownerDocument;449 // $FlowFixMe[invalid-compare]450 if (ownerDocument !== null) {451 // The selectionchange event also needs deduplication452 // but it is attached to the document.453 if (!(ownerDocument as any)[listeningMarker]) {454 (ownerDocument as any)[listeningMarker] = true;455 listenToNativeEvent('selectionchange', false, ownerDocument);456 }457 }458 }459}460461function addTrappedEventListener(462 targetContainer: EventTarget,463 domEventName: DOMEventName,464 eventSystemFlags: EventSystemFlags,465 isCapturePhaseListener: boolean,466 isDeferredListenerForLegacyFBSupport?: boolean,467) {468 let listener = createEventListenerWrapperWithPriority(469 targetContainer,470 domEventName,471 eventSystemFlags,472 );473 // If passive option is not supported, then the event will be474 // active and not passive.475 let isPassiveListener: void | boolean = undefined;476 if (passiveBrowserEventsSupported) {477 // Browsers introduced an intervention, making these events478 // passive by default on document. React doesn't bind them479 // to document anymore, but changing this now would undo480 // the performance wins from the change. So we emulate481 // the existing behavior manually on the roots now.482 // https://github.com/facebook/react/issues/19651483 if (484 domEventName === 'touchstart' ||485 domEventName === 'touchmove' ||486 domEventName === 'wheel'487 ) {488 isPassiveListener = true;489 }490 }491492 targetContainer =493 enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport494 ? // A Document container's ownerDocument is null, so it must be used495 // as the deferral target itself.496 (targetContainer as any).nodeType === DOCUMENT_NODE497 ? targetContainer498 : (targetContainer as any).ownerDocument499 : targetContainer;500501 let unsubscribeListener;502 // When legacyFBSupport is enabled, it's for when we503 // want to add a one time event listener to a container.504 // This should only be used with enableLegacyFBSupport505 // due to requirement to provide compatibility with506 // internal FB www event tooling. This works by removing507 // the event listener as soon as it is invoked. We could508 // also attempt to use the {once: true} param on509 // addEventListener, but that requires support and some510 // browsers do not support this today, and given this is511 // to support legacy code patterns, it's likely they'll512 // need support for such browsers.513 if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {514 const originalListener = listener;515 // $FlowFixMe[missing-this-annot]516 listener = function (...p) {517 removeEventListener(518 targetContainer,519 domEventName,520 unsubscribeListener,521 isCapturePhaseListener,522 );523 return originalListener.apply(this, p);524 };525 }526 // TODO: There are too many combinations here. Consolidate them.527 if (isCapturePhaseListener) {528 if (isPassiveListener !== undefined) {529 unsubscribeListener = addEventCaptureListenerWithPassiveFlag(530 targetContainer,531 domEventName,532 listener,533 isPassiveListener,534 );535 } else {536 unsubscribeListener = addEventCaptureListener(537 targetContainer,538 domEventName,539 listener,540 );541 }542 } else {543 if (isPassiveListener !== undefined) {544 unsubscribeListener = addEventBubbleListenerWithPassiveFlag(545 targetContainer,546 domEventName,547 listener,548 isPassiveListener,549 );550 } else {551 unsubscribeListener = addEventBubbleListener(552 targetContainer,553 domEventName,554 listener,555 );556 }557 }558}559560function deferClickToDocumentForLegacyFBSupport(561 domEventName: DOMEventName,562 targetContainer: EventTarget,563): void {564 // We defer all click events with legacy FB support mode on.565 // This means we add a one time event listener to trigger566 // after the FB delegated listeners fire.567 const isDeferredListenerForLegacyFBSupport = true;568 addTrappedEventListener(569 targetContainer,570 domEventName,571 IS_LEGACY_FB_SUPPORT_MODE,572 false,573 isDeferredListenerForLegacyFBSupport,574 );575}576577function isMatchingRootContainer(578 grandContainer: Element,579 targetContainer: EventTarget,580): boolean {581 return (582 grandContainer === targetContainer ||583 (!disableCommentsAsDOMContainers &&584 grandContainer.nodeType === COMMENT_NODE &&585 grandContainer.parentNode === targetContainer)586 );587}588589export function dispatchEventForPluginEventSystem(590 domEventName: DOMEventName,591 eventSystemFlags: EventSystemFlags,592 nativeEvent: AnyNativeEvent,593 targetInst: null | Fiber,594 targetContainer: EventTarget,595): void {596 let ancestorInst = targetInst;597 if (598 (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&599 (eventSystemFlags & IS_NON_DELEGATED) === 0600 ) {601 const targetContainerNode = targetContainer as any as Node;602603 // If we are using the legacy FB support flag, we604 // defer the event to the null with a one605 // time event listener so we can defer the event.606 if (607 enableLegacyFBSupport &&608 // If our event flags match the required flags for entering609 // FB legacy mode and we are processing the "click" event,610 // then we can defer the event to the "document", to allow611 // for legacy FB support, where the expected behavior was to612 // match React < 16 behavior of delegated clicks to the doc.613 domEventName === 'click' &&614 (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 &&615 !isReplayingEvent(nativeEvent)616 ) {617 deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer);618 return;619 }620 if (targetInst !== null) {621 // The below logic attempts to work out if we need to change622 // the target fiber to a different ancestor. We had similar logic623 // in the legacy event system, except the big difference between624 // systems is that the modern event system now has an event listener625 // attached to each React Root and React Portal Root. Together,626 // the DOM nodes representing these roots are the "rootContainer".627 // To figure out which ancestor instance we should use, we traverse628 // up the fiber tree from the target instance and attempt to find629 // root boundaries that match that of our current "rootContainer".630 // If we find that "rootContainer", we find the parent fiber631 // sub-tree for that root and make that our ancestor instance.632 let node: null | Fiber = targetInst;633634 mainLoop: while (true) {635 if (node === null) {636 return;637 }638 const nodeTag = node.tag;639 if (nodeTag === HostRoot || nodeTag === HostPortal) {640 let container = node.stateNode.containerInfo;641 if (isMatchingRootContainer(container, targetContainerNode)) {642 break;643 }644 if (nodeTag === HostPortal) {645 // The target is a portal, but it's not the rootContainer we're looking for.646 // Normally portals handle their own events all the way down to the root.647 // So we should be able to stop now. However, we don't know if this portal648 // was part of *our* root.649 let grandNode = node.return;650 while (grandNode !== null) {651 const grandTag = grandNode.tag;652 if (grandTag === HostRoot || grandTag === HostPortal) {653 const grandContainer = grandNode.stateNode.containerInfo;654 if (655 isMatchingRootContainer(grandContainer, targetContainerNode)656 ) {657 // This is the rootContainer we're looking for and we found it as658 // a parent of the Portal. That means we can ignore it because the659 // Portal will bubble through to us.660 return;661 }662 }663 grandNode = grandNode.return;664 }665 }666 // Now we need to find it's corresponding host fiber in the other667 // tree. To do this we can use getClosestInstanceFromNode, but we668 // need to validate that the fiber is a host instance, otherwise669 // we need to traverse up through the DOM till we find the correct670 // node that is from the other tree.671 while (container !== null) {672 const parentNode = getClosestInstanceFromNode(container);673 if (parentNode === null) {674 return;675 }676 const parentTag = parentNode.tag;677 if (678 parentTag === HostComponent ||679 parentTag === HostText ||680 parentTag === HostHoistable ||681 parentTag === HostSingleton682 ) {683 node = ancestorInst = parentNode;684 continue mainLoop;685 }686 container = container.parentNode;687 }688 }689 node = node.return;690 }691 }692 }693694 batchedUpdates(() =>695 dispatchEventsForPlugins(696 domEventName,697 eventSystemFlags,698 nativeEvent,699 ancestorInst,700 targetContainer,701 ),702 );703}704705function createDispatchListener(706 instance: null | Fiber,707 listener: Function,708 currentTarget: EventTarget,709): DispatchListener {710 return {711 instance,712 listener,713 currentTarget,714 };715}716717export function accumulateSinglePhaseListeners(718 targetFiber: Fiber | null,719 reactName: string | null,720 nativeEventType: string,721 inCapturePhase: boolean,722 accumulateTargetOnly: boolean,723 nativeEvent: AnyNativeEvent,724): Array<DispatchListener> {725 const captureName = reactName !== null ? reactName + 'Capture' : null;726 const reactEventName = inCapturePhase ? captureName : reactName;727 let listeners: Array<DispatchListener> = [];728729 let instance = targetFiber;730 let lastHostComponent = null;731732 // Accumulate all instances and listeners via the target -> root path.733 while (instance !== null) {734 const {stateNode, tag} = instance;735 // Handle listeners that are on HostComponents (i.e. <div>)736 if (737 (tag === HostComponent ||738 tag === HostHoistable ||739 tag === HostSingleton) &&740 stateNode !== null741 ) {742 lastHostComponent = stateNode;743744 // createEventHandle listeners745 if (enableCreateEventHandleAPI) {746 const eventHandlerListeners =747 getEventHandlerListeners(lastHostComponent);748 if (eventHandlerListeners !== null) {749 eventHandlerListeners.forEach(entry => {750 if (751 entry.type === nativeEventType &&752 entry.capture === inCapturePhase753 ) {754 listeners.push(755 createDispatchListener(756 instance,757 entry.callback,758 lastHostComponent as any,759 ),760 );761 }762 });763 }764 }765766 // Standard React on* listeners, i.e. onClick or onClickCapture767 if (reactEventName !== null) {768 const listener = getListener(instance, reactEventName);769 if (listener != null) {770 listeners.push(771 createDispatchListener(instance, listener, lastHostComponent),772 );773 }774 }775 } else if (776 enableCreateEventHandleAPI &&777 enableScopeAPI &&778 tag === ScopeComponent &&779 lastHostComponent !== null &&780 stateNode !== null781 ) {782 // Scopes783 const reactScopeInstance = stateNode;784 const eventHandlerListeners =785 getEventHandlerListeners(reactScopeInstance);786 if (eventHandlerListeners !== null) {787 eventHandlerListeners.forEach(entry => {788 if (789 entry.type === nativeEventType &&790 entry.capture === inCapturePhase791 ) {792 listeners.push(793 createDispatchListener(794 instance,795 entry.callback,796 lastHostComponent as any,797 ),798 );799 }800 });801 }802 }803 // If we are only accumulating events for the target, then we don't804 // continue to propagate through the React fiber tree to find other805 // listeners.806 if (accumulateTargetOnly) {807 break;808 }809 // If we are processing the onBeforeBlur event, then we need to take810 // into consideration that part of the React tree might have been hidden811 // or deleted (as we're invoking this event during commit). We can find812 // this out by checking if intercept fiber set on the event matches the813 // current instance fiber. In which case, we should clear all existing814 // listeners.815 if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {816 // $FlowFixMe[prop-missing] internal field817 const detachedInterceptFiber = nativeEvent._detachedInterceptFiber;818 if (819 detachedInterceptFiber !== null &&820 (detachedInterceptFiber === instance ||821 detachedInterceptFiber === instance.alternate)822 ) {823 listeners = [];824 }825 }826 instance = instance.return;827 }828 return listeners;829}830831// We should only use this function for:832// - BeforeInputEventPlugin833// - ChangeEventPlugin834// - SelectEventPlugin835// - ScrollEndEventPlugin836// This is because we only process these plugins837// in the bubble phase, so we need to accumulate two838// phase event listeners (via emulation).839export function accumulateTwoPhaseListeners(840 targetFiber: Fiber | null,841 reactName: string,842): Array<DispatchListener> {843 const captureName = reactName + 'Capture';844 const listeners: Array<DispatchListener> = [];845 let instance = targetFiber;846847 // Accumulate all instances and listeners via the target -> root path.848 while (instance !== null) {849 const {stateNode, tag} = instance;850 // Handle listeners that are on HostComponents (i.e. <div>)851 if (852 (tag === HostComponent ||853 tag === HostHoistable ||854 tag === HostSingleton) &&855 stateNode !== null856 ) {857 const currentTarget = stateNode;858 const captureListener = getListener(instance, captureName);859 if (captureListener != null) {860 listeners.unshift(861 createDispatchListener(instance, captureListener, currentTarget),862 );863 }864 const bubbleListener = getListener(instance, reactName);865 if (bubbleListener != null) {866 listeners.push(867 createDispatchListener(instance, bubbleListener, currentTarget),868 );869 }870 }871 if (instance.tag === HostRoot) {872 return listeners;873 }874 instance = instance.return;875 }876 // If we didn't reach the root it means we're unmounted and shouldn't877 // dispatch any events on the target.878 return [];879}880881function getParent(inst: Fiber | null): Fiber | null {882 if (inst === null) {883 return null;884 }885 do {886 // $FlowFixMe[incompatible-use] found when upgrading Flow887 inst = inst.return;888 // TODO: If this is a HostRoot we might want to bail out.889 // That is depending on if we want nested subtrees (layers) to bubble890 // events to their parent. We could also go through parentNode on the891 // host node but that wouldn't work for React Native and doesn't let us892 // do the portal feature.893 } while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);894 if (inst) {895 return inst;896 }897 return null;898}899900function accumulateEnterLeaveListenersForEvent(901 dispatchQueue: DispatchQueue,902 event: KnownReactSyntheticEvent,903 target: Fiber,904 common: Fiber | null,905 inCapturePhase: boolean,906): void {907 const registrationName = event._reactName;908 const listeners: Array<DispatchListener> = [];909910 let instance: null | Fiber = target;911 while (instance !== null) {912 if (instance === common) {913 break;914 }915 const {alternate, stateNode, tag} = instance;916 if (alternate !== null && alternate === common) {917 break;918 }919 if (920 (tag === HostComponent ||921 tag === HostHoistable ||922 tag === HostSingleton) &&923 stateNode !== null924 ) {925 const currentTarget = stateNode;926 if (inCapturePhase) {927 const captureListener = getListener(instance, registrationName);928 if (captureListener != null) {929 listeners.unshift(930 createDispatchListener(instance, captureListener, currentTarget),931 );932 }933 // $FlowFixMe[constant-condition]934 } else if (!inCapturePhase) {935 const bubbleListener = getListener(instance, registrationName);936 if (bubbleListener != null) {937 listeners.push(938 createDispatchListener(instance, bubbleListener, currentTarget),939 );940 }941 }942 }943 instance = instance.return;944 }945 if (listeners.length !== 0) {946 dispatchQueue.push({event, listeners});947 }948}949950// We should only use this function for:951// - EnterLeaveEventPlugin952// This is because we only process this plugin953// in the bubble phase, so we need to accumulate two954// phase event listeners.955export function accumulateEnterLeaveTwoPhaseListeners(956 dispatchQueue: DispatchQueue,957 leaveEvent: KnownReactSyntheticEvent,958 enterEvent: null | KnownReactSyntheticEvent,959 from: Fiber | null,960 to: Fiber | null,961): void {962 const common =963 from && to ? getLowestCommonAncestor(from, to, getParent) : null;964965 if (from !== null) {966 accumulateEnterLeaveListenersForEvent(967 dispatchQueue,968 leaveEvent,969 from,970 common,971 false,972 );973 }974 if (to !== null && enterEvent !== null) {975 accumulateEnterLeaveListenersForEvent(976 dispatchQueue,977 enterEvent,978 to,979 common,980 true,981 );982 }983}984985export function accumulateEventHandleNonManagedNodeListeners(986 reactEventType: DOMEventName,987 currentTarget: EventTarget,988 inCapturePhase: boolean,989): Array<DispatchListener> {990 const listeners: Array<DispatchListener> = [];991992 const eventListeners = getEventHandlerListeners(currentTarget);993 if (eventListeners !== null) {994 eventListeners.forEach(entry => {995 if (entry.type === reactEventType && entry.capture === inCapturePhase) {996 listeners.push(997 createDispatchListener(null, entry.callback, currentTarget),998 );999 }1000 });1001 }1002 return listeners;1003}10041005export function getListenerSetKey(1006 domEventName: DOMEventName,1007 capture: boolean,1008): string {1009 return `${domEventName}__${capture ? 'capture' : 'bubble'}`;1010}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.