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 ? (targetContainer as any).ownerDocument495 : targetContainer;496497 let unsubscribeListener;498 // When legacyFBSupport is enabled, it's for when we499 // want to add a one time event listener to a container.500 // This should only be used with enableLegacyFBSupport501 // due to requirement to provide compatibility with502 // internal FB www event tooling. This works by removing503 // the event listener as soon as it is invoked. We could504 // also attempt to use the {once: true} param on505 // addEventListener, but that requires support and some506 // browsers do not support this today, and given this is507 // to support legacy code patterns, it's likely they'll508 // need support for such browsers.509 if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {510 const originalListener = listener;511 // $FlowFixMe[missing-this-annot]512 listener = function (...p) {513 removeEventListener(514 targetContainer,515 domEventName,516 unsubscribeListener,517 isCapturePhaseListener,518 );519 return originalListener.apply(this, p);520 };521 }522 // TODO: There are too many combinations here. Consolidate them.523 if (isCapturePhaseListener) {524 if (isPassiveListener !== undefined) {525 unsubscribeListener = addEventCaptureListenerWithPassiveFlag(526 targetContainer,527 domEventName,528 listener,529 isPassiveListener,530 );531 } else {532 unsubscribeListener = addEventCaptureListener(533 targetContainer,534 domEventName,535 listener,536 );537 }538 } else {539 if (isPassiveListener !== undefined) {540 unsubscribeListener = addEventBubbleListenerWithPassiveFlag(541 targetContainer,542 domEventName,543 listener,544 isPassiveListener,545 );546 } else {547 unsubscribeListener = addEventBubbleListener(548 targetContainer,549 domEventName,550 listener,551 );552 }553 }554}555556function deferClickToDocumentForLegacyFBSupport(557 domEventName: DOMEventName,558 targetContainer: EventTarget,559): void {560 // We defer all click events with legacy FB support mode on.561 // This means we add a one time event listener to trigger562 // after the FB delegated listeners fire.563 const isDeferredListenerForLegacyFBSupport = true;564 addTrappedEventListener(565 targetContainer,566 domEventName,567 IS_LEGACY_FB_SUPPORT_MODE,568 false,569 isDeferredListenerForLegacyFBSupport,570 );571}572573function isMatchingRootContainer(574 grandContainer: Element,575 targetContainer: EventTarget,576): boolean {577 return (578 grandContainer === targetContainer ||579 (!disableCommentsAsDOMContainers &&580 grandContainer.nodeType === COMMENT_NODE &&581 grandContainer.parentNode === targetContainer)582 );583}584585export function dispatchEventForPluginEventSystem(586 domEventName: DOMEventName,587 eventSystemFlags: EventSystemFlags,588 nativeEvent: AnyNativeEvent,589 targetInst: null | Fiber,590 targetContainer: EventTarget,591): void {592 let ancestorInst = targetInst;593 if (594 (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&595 (eventSystemFlags & IS_NON_DELEGATED) === 0596 ) {597 const targetContainerNode = targetContainer as any as Node;598599 // If we are using the legacy FB support flag, we600 // defer the event to the null with a one601 // time event listener so we can defer the event.602 if (603 enableLegacyFBSupport &&604 // If our event flags match the required flags for entering605 // FB legacy mode and we are processing the "click" event,606 // then we can defer the event to the "document", to allow607 // for legacy FB support, where the expected behavior was to608 // match React < 16 behavior of delegated clicks to the doc.609 domEventName === 'click' &&610 (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 &&611 !isReplayingEvent(nativeEvent)612 ) {613 deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer);614 return;615 }616 if (targetInst !== null) {617 // The below logic attempts to work out if we need to change618 // the target fiber to a different ancestor. We had similar logic619 // in the legacy event system, except the big difference between620 // systems is that the modern event system now has an event listener621 // attached to each React Root and React Portal Root. Together,622 // the DOM nodes representing these roots are the "rootContainer".623 // To figure out which ancestor instance we should use, we traverse624 // up the fiber tree from the target instance and attempt to find625 // root boundaries that match that of our current "rootContainer".626 // If we find that "rootContainer", we find the parent fiber627 // sub-tree for that root and make that our ancestor instance.628 let node: null | Fiber = targetInst;629630 mainLoop: while (true) {631 if (node === null) {632 return;633 }634 const nodeTag = node.tag;635 if (nodeTag === HostRoot || nodeTag === HostPortal) {636 let container = node.stateNode.containerInfo;637 if (isMatchingRootContainer(container, targetContainerNode)) {638 break;639 }640 if (nodeTag === HostPortal) {641 // The target is a portal, but it's not the rootContainer we're looking for.642 // Normally portals handle their own events all the way down to the root.643 // So we should be able to stop now. However, we don't know if this portal644 // was part of *our* root.645 let grandNode = node.return;646 while (grandNode !== null) {647 const grandTag = grandNode.tag;648 if (grandTag === HostRoot || grandTag === HostPortal) {649 const grandContainer = grandNode.stateNode.containerInfo;650 if (651 isMatchingRootContainer(grandContainer, targetContainerNode)652 ) {653 // This is the rootContainer we're looking for and we found it as654 // a parent of the Portal. That means we can ignore it because the655 // Portal will bubble through to us.656 return;657 }658 }659 grandNode = grandNode.return;660 }661 }662 // Now we need to find it's corresponding host fiber in the other663 // tree. To do this we can use getClosestInstanceFromNode, but we664 // need to validate that the fiber is a host instance, otherwise665 // we need to traverse up through the DOM till we find the correct666 // node that is from the other tree.667 while (container !== null) {668 const parentNode = getClosestInstanceFromNode(container);669 if (parentNode === null) {670 return;671 }672 const parentTag = parentNode.tag;673 if (674 parentTag === HostComponent ||675 parentTag === HostText ||676 parentTag === HostHoistable ||677 parentTag === HostSingleton678 ) {679 node = ancestorInst = parentNode;680 continue mainLoop;681 }682 container = container.parentNode;683 }684 }685 node = node.return;686 }687 }688 }689690 batchedUpdates(() =>691 dispatchEventsForPlugins(692 domEventName,693 eventSystemFlags,694 nativeEvent,695 ancestorInst,696 targetContainer,697 ),698 );699}700701function createDispatchListener(702 instance: null | Fiber,703 listener: Function,704 currentTarget: EventTarget,705): DispatchListener {706 return {707 instance,708 listener,709 currentTarget,710 };711}712713export function accumulateSinglePhaseListeners(714 targetFiber: Fiber | null,715 reactName: string | null,716 nativeEventType: string,717 inCapturePhase: boolean,718 accumulateTargetOnly: boolean,719 nativeEvent: AnyNativeEvent,720): Array<DispatchListener> {721 const captureName = reactName !== null ? reactName + 'Capture' : null;722 const reactEventName = inCapturePhase ? captureName : reactName;723 let listeners: Array<DispatchListener> = [];724725 let instance = targetFiber;726 let lastHostComponent = null;727728 // Accumulate all instances and listeners via the target -> root path.729 while (instance !== null) {730 const {stateNode, tag} = instance;731 // Handle listeners that are on HostComponents (i.e. <div>)732 if (733 (tag === HostComponent ||734 tag === HostHoistable ||735 tag === HostSingleton) &&736 stateNode !== null737 ) {738 lastHostComponent = stateNode;739740 // createEventHandle listeners741 if (enableCreateEventHandleAPI) {742 const eventHandlerListeners =743 getEventHandlerListeners(lastHostComponent);744 if (eventHandlerListeners !== null) {745 eventHandlerListeners.forEach(entry => {746 if (747 entry.type === nativeEventType &&748 entry.capture === inCapturePhase749 ) {750 listeners.push(751 createDispatchListener(752 instance,753 entry.callback,754 lastHostComponent as any,755 ),756 );757 }758 });759 }760 }761762 // Standard React on* listeners, i.e. onClick or onClickCapture763 if (reactEventName !== null) {764 const listener = getListener(instance, reactEventName);765 if (listener != null) {766 listeners.push(767 createDispatchListener(instance, listener, lastHostComponent),768 );769 }770 }771 } else if (772 enableCreateEventHandleAPI &&773 enableScopeAPI &&774 tag === ScopeComponent &&775 lastHostComponent !== null &&776 stateNode !== null777 ) {778 // Scopes779 const reactScopeInstance = stateNode;780 const eventHandlerListeners =781 getEventHandlerListeners(reactScopeInstance);782 if (eventHandlerListeners !== null) {783 eventHandlerListeners.forEach(entry => {784 if (785 entry.type === nativeEventType &&786 entry.capture === inCapturePhase787 ) {788 listeners.push(789 createDispatchListener(790 instance,791 entry.callback,792 lastHostComponent as any,793 ),794 );795 }796 });797 }798 }799 // If we are only accumulating events for the target, then we don't800 // continue to propagate through the React fiber tree to find other801 // listeners.802 if (accumulateTargetOnly) {803 break;804 }805 // If we are processing the onBeforeBlur event, then we need to take806 // into consideration that part of the React tree might have been hidden807 // or deleted (as we're invoking this event during commit). We can find808 // this out by checking if intercept fiber set on the event matches the809 // current instance fiber. In which case, we should clear all existing810 // listeners.811 if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {812 // $FlowFixMe[prop-missing] internal field813 const detachedInterceptFiber = nativeEvent._detachedInterceptFiber;814 if (815 detachedInterceptFiber !== null &&816 (detachedInterceptFiber === instance ||817 detachedInterceptFiber === instance.alternate)818 ) {819 listeners = [];820 }821 }822 instance = instance.return;823 }824 return listeners;825}826827// We should only use this function for:828// - BeforeInputEventPlugin829// - ChangeEventPlugin830// - SelectEventPlugin831// - ScrollEndEventPlugin832// This is because we only process these plugins833// in the bubble phase, so we need to accumulate two834// phase event listeners (via emulation).835export function accumulateTwoPhaseListeners(836 targetFiber: Fiber | null,837 reactName: string,838): Array<DispatchListener> {839 const captureName = reactName + 'Capture';840 const listeners: Array<DispatchListener> = [];841 let instance = targetFiber;842843 // Accumulate all instances and listeners via the target -> root path.844 while (instance !== null) {845 const {stateNode, tag} = instance;846 // Handle listeners that are on HostComponents (i.e. <div>)847 if (848 (tag === HostComponent ||849 tag === HostHoistable ||850 tag === HostSingleton) &&851 stateNode !== null852 ) {853 const currentTarget = stateNode;854 const captureListener = getListener(instance, captureName);855 if (captureListener != null) {856 listeners.unshift(857 createDispatchListener(instance, captureListener, currentTarget),858 );859 }860 const bubbleListener = getListener(instance, reactName);861 if (bubbleListener != null) {862 listeners.push(863 createDispatchListener(instance, bubbleListener, currentTarget),864 );865 }866 }867 if (instance.tag === HostRoot) {868 return listeners;869 }870 instance = instance.return;871 }872 // If we didn't reach the root it means we're unmounted and shouldn't873 // dispatch any events on the target.874 return [];875}876877function getParent(inst: Fiber | null): Fiber | null {878 if (inst === null) {879 return null;880 }881 do {882 // $FlowFixMe[incompatible-use] found when upgrading Flow883 inst = inst.return;884 // TODO: If this is a HostRoot we might want to bail out.885 // That is depending on if we want nested subtrees (layers) to bubble886 // events to their parent. We could also go through parentNode on the887 // host node but that wouldn't work for React Native and doesn't let us888 // do the portal feature.889 } while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);890 if (inst) {891 return inst;892 }893 return null;894}895896function accumulateEnterLeaveListenersForEvent(897 dispatchQueue: DispatchQueue,898 event: KnownReactSyntheticEvent,899 target: Fiber,900 common: Fiber | null,901 inCapturePhase: boolean,902): void {903 const registrationName = event._reactName;904 const listeners: Array<DispatchListener> = [];905906 let instance: null | Fiber = target;907 while (instance !== null) {908 if (instance === common) {909 break;910 }911 const {alternate, stateNode, tag} = instance;912 if (alternate !== null && alternate === common) {913 break;914 }915 if (916 (tag === HostComponent ||917 tag === HostHoistable ||918 tag === HostSingleton) &&919 stateNode !== null920 ) {921 const currentTarget = stateNode;922 if (inCapturePhase) {923 const captureListener = getListener(instance, registrationName);924 if (captureListener != null) {925 listeners.unshift(926 createDispatchListener(instance, captureListener, currentTarget),927 );928 }929 // $FlowFixMe[constant-condition]930 } else if (!inCapturePhase) {931 const bubbleListener = getListener(instance, registrationName);932 if (bubbleListener != null) {933 listeners.push(934 createDispatchListener(instance, bubbleListener, currentTarget),935 );936 }937 }938 }939 instance = instance.return;940 }941 if (listeners.length !== 0) {942 dispatchQueue.push({event, listeners});943 }944}945946// We should only use this function for:947// - EnterLeaveEventPlugin948// This is because we only process this plugin949// in the bubble phase, so we need to accumulate two950// phase event listeners.951export function accumulateEnterLeaveTwoPhaseListeners(952 dispatchQueue: DispatchQueue,953 leaveEvent: KnownReactSyntheticEvent,954 enterEvent: null | KnownReactSyntheticEvent,955 from: Fiber | null,956 to: Fiber | null,957): void {958 const common =959 from && to ? getLowestCommonAncestor(from, to, getParent) : null;960961 if (from !== null) {962 accumulateEnterLeaveListenersForEvent(963 dispatchQueue,964 leaveEvent,965 from,966 common,967 false,968 );969 }970 if (to !== null && enterEvent !== null) {971 accumulateEnterLeaveListenersForEvent(972 dispatchQueue,973 enterEvent,974 to,975 common,976 true,977 );978 }979}980981export function accumulateEventHandleNonManagedNodeListeners(982 reactEventType: DOMEventName,983 currentTarget: EventTarget,984 inCapturePhase: boolean,985): Array<DispatchListener> {986 const listeners: Array<DispatchListener> = [];987988 const eventListeners = getEventHandlerListeners(currentTarget);989 if (eventListeners !== null) {990 eventListeners.forEach(entry => {991 if (entry.type === reactEventType && entry.capture === inCapturePhase) {992 listeners.push(993 createDispatchListener(null, entry.callback, currentTarget),994 );995 }996 });997 }998 return listeners;999}10001001export function getListenerSetKey(1002 domEventName: DOMEventName,1003 capture: boolean,1004): string {1005 return `${domEventName}__${capture ? 'capture' : 'bubble'}`;1006}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.