packages/react-dom-bindings/src/events/DOMPluginEventSystem.js JAVASCRIPT 1,011 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      ? // 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}

Code quality findings 79

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
(targetContainer as any).nodeType === DOCUMENT_NODE
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.