packages/react-dom-bindings/src/events/DOMPluginEventSystem.js JAVASCRIPT 1,007 lines View on github.com → Search inside
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}

Code quality findings 78

Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (instance !== previousInstance && event.isPropagationStopped()) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (__DEV__ && instance !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (instance !== previousInstance && event.isPropagationStopped()) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (__DEV__ && instance !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (domEventName !== 'selectionchange') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(rootContainerElement as any).nodeType === DOCUMENT_NODE
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (ownerDocument !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
domEventName === 'touchstart' ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
domEventName === 'touchmove' ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
domEventName === 'wheel'
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (isPassiveListener !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (isPassiveListener !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
grandContainer === targetContainer ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
grandContainer.nodeType === COMMENT_NODE &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
grandContainer.parentNode === targetContainer)
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(eventSystemFlags & IS_NON_DELEGATED) === 0
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
domEventName === 'click' &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (targetInst !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (node === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nodeTag === HostRoot || nodeTag === HostPortal) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (nodeTag === HostPortal) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (grandNode !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (grandTag === HostRoot || grandTag === HostPortal) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (container !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (parentNode === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentTag === HostComponent ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentTag === HostText ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentTag === HostHoistable ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
parentTag === HostSingleton
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const captureName = reactName !== null ? reactName + 'Capture' : null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (instance !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(tag === HostComponent ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === HostHoistable ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === HostSingleton) &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
stateNode !== null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (eventHandlerListeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
entry.type === nativeEventType &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
entry.capture === inCapturePhase
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (reactEventName !== null) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (listener != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === ScopeComponent &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
lastHostComponent !== null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
stateNode !== null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (eventHandlerListeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
entry.type === nativeEventType &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
entry.capture === inCapturePhase
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
detachedInterceptFiber !== null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(detachedInterceptFiber === instance ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
detachedInterceptFiber === instance.alternate)
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (instance !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(tag === HostComponent ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === HostHoistable ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === HostSingleton) &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
stateNode !== null
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (captureListener != null) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (bubbleListener != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (instance.tag === HostRoot) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (inst === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
while (instance !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (instance === common) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (alternate !== null && alternate === common) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(tag === HostComponent ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === HostHoistable ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
tag === HostSingleton) &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
stateNode !== null
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (captureListener != null) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (bubbleListener != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (listeners.length !== 0) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (from !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (to !== null && enterEvent !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (eventListeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (entry.type === reactEventType && entry.capture === inCapturePhase) {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.