packages/react-devtools-shared/src/backend/fiber/renderer.js JAVASCRIPT 8,152 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 8,152.
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 {11  Thenable,12  ReactComponentInfo,13  ReactDebugInfo,14  ReactAsyncInfo,15  ReactIOInfo,16  ReactStackTrace,17  ReactCallSite,18  Wakeable,19} from 'shared/ReactTypes';2021import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';2223import {24  ComponentFilterDisplayName,25  ComponentFilterElementType,26  ComponentFilterHOC,27  ComponentFilterLocation,28  ComponentFilterEnvironmentName,29  ComponentFilterActivitySlice,30  ElementTypeClass,31  ElementTypeContext,32  ElementTypeFunction,33  ElementTypeForwardRef,34  ElementTypeHostComponent,35  ElementTypeMemo,36  ElementTypeOtherOrUnknown,37  ElementTypeProfiler,38  ElementTypeRoot,39  ElementTypeSuspense,40  ElementTypeSuspenseList,41  ElementTypeTracingMarker,42  ElementTypeViewTransition,43  ElementTypeActivity,44  ElementTypeVirtual,45  StrictMode,46  ActivityHiddenMode,47  ActivityVisibleMode,48} from 'react-devtools-shared/src/frontend/types';49import {50  deletePathInObject,51  getInObject,52  getUID,53  renamePathInObject,54  setInObject,55  utfEncodeString,56} from 'react-devtools-shared/src/utils';57import {58  formatConsoleArgumentsToSingleString,59  formatDurationToMicrosecondsGranularity,60  gte,61  serializeToString,62} from 'react-devtools-shared/src/backend/utils';63import {64  extractLocationFromComponentStack,65  extractLocationFromOwnerStack,66  parseStackTrace,67} from 'react-devtools-shared/src/backend/utils/parseStackTrace';68import {69  cleanForBridge,70  copyWithDelete,71  copyWithRename,72  copyWithSet,73  getEffectDurations,74} from '../utils';75import {76  __DEBUG__,77  PROFILING_FLAG_BASIC_SUPPORT,78  PROFILING_FLAG_TIMELINE_SUPPORT,79  PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT,80  TREE_OPERATION_ADD,81  TREE_OPERATION_REMOVE,82  TREE_OPERATION_REORDER_CHILDREN,83  TREE_OPERATION_SET_SUBTREE_MODE,84  TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,85  TREE_OPERATION_UPDATE_TREE_BASE_DURATION,86  TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE,87  SUSPENSE_TREE_OPERATION_ADD,88  SUSPENSE_TREE_OPERATION_REMOVE,89  SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,90  SUSPENSE_TREE_OPERATION_RESIZE,91  SUSPENSE_TREE_OPERATION_SUSPENDERS,92  UNKNOWN_SUSPENDERS_NONE,93  UNKNOWN_SUSPENDERS_REASON_PRODUCTION,94  UNKNOWN_SUSPENDERS_REASON_OLD_VERSION,95  UNKNOWN_SUSPENDERS_REASON_THROWN_PROMISE,96} from '../../constants';97import {inspectHooksOfFiber} from 'react-debug-tools';98import {99  CONCURRENT_MODE_NUMBER,100  CONCURRENT_MODE_SYMBOL_STRING,101  DEPRECATED_ASYNC_MODE_SYMBOL_STRING,102  PROVIDER_NUMBER,103  PROVIDER_SYMBOL_STRING,104  CONTEXT_NUMBER,105  CONTEXT_SYMBOL_STRING,106  CONSUMER_SYMBOL_STRING,107  STRICT_MODE_NUMBER,108  STRICT_MODE_SYMBOL_STRING,109  PROFILER_NUMBER,110  PROFILER_SYMBOL_STRING,111  LAZY_SYMBOL_STRING,112  REACT_OPTIMISTIC_KEY,113} from '../shared/ReactSymbols';114import {enableStyleXFeatures} from 'react-devtools-feature-flags';115116import {componentInfoToComponentLogsMap} from '../shared/DevToolsServerComponentLogs';117118import {getIODescription} from 'shared/ReactIODescription';119120import {121  getPublicInstance,122  getNativeTag,123  getCurrentTime,124} from 'react-devtools-shared/src/backend/DevToolsNativeHost';125import {126  isError,127  rootSupportsProfiling,128  isErrorBoundary,129  getSecondaryEnvironmentName,130  areEqualRects,131} from './shared/DevToolsFiberInspection';132import {133  didFiberRender,134  getContextChanged,135  getChangedHooksIndices,136  getChangedKeys,137} from './shared/DevToolsFiberChangeDetection';138import {getInternalReactConstants} from './shared/DevToolsFiberInternalReactConstants';139import {140  ioExistsInSuspenseAncestor,141  getAwaitInSuspendedByFromIO,142  getVirtualEndTime,143} from './shared/DevToolsFiberSuspense';144145import {146  getStackByFiberInDevAndProd,147  getOwnerStackByFiberInDev,148  supportsOwnerStacks,149  supportsConsoleTasks,150} from './DevToolsFiberComponentStack';151152import {getStyleXData} from '../StyleX/utils';153import {createProfilingHooks} from '../profilingHooks';154155import type {GetTimelineData, ToggleProfilingStatus} from '../profilingHooks';156import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';157import type {158  ChangeDescription,159  CommitDataBackend,160  DevToolsHook,161  InspectedElement,162  InspectedElementPayload,163  InstanceAndStyle,164  HostInstance,165  PathFrame,166  PathMatch,167  ProfilingDataBackend,168  ProfilingDataForRootBackend,169  ReactRenderer,170  Rect,171  RendererInterface,172  SerializedElement,173  SerializedAsyncInfo,174  ProfilingSettings,175} from '../types';176import type {177  ComponentFilter,178  ActivitySliceFilter,179  ElementType,180  Plugins,181} from 'react-devtools-shared/src/frontend/types';182import type {ReactFunctionLocation} from 'shared/ReactTypes';183import type {184  FiberInstance,185  FilteredFiberInstance,186  VirtualInstance,187  DevToolsInstance,188  SuspenseNode,189} from './shared/DevToolsFiberTypes';190import {191  FIBER_INSTANCE,192  VIRTUAL_INSTANCE,193  FILTERED_FIBER_INSTANCE,194} from './shared/DevToolsFiberTypes';195import {getDispatcherRef} from '../shared/DevToolsReactDispatcher';196import {getSourceLocationByFiber} from './DevToolsFiberComponentStack';197import {formatOwnerStack} from '../shared/DevToolsOwnerStack';198199function createFiberInstance(fiber: Fiber): FiberInstance {200  return {201    kind: FIBER_INSTANCE,202    id: getUID(),203    parent: null,204    firstChild: null,205    nextSibling: null,206    source: null,207    logCount: 0,208    treeBaseDuration: 0,209    suspendedBy: null,210    suspenseNode: null,211    data: fiber,212  };213}214215// This is used to represent a filtered Fiber but still lets us find its host instance.216function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance {217  return {218    kind: FILTERED_FIBER_INSTANCE,219    id: 0,220    parent: null,221    firstChild: null,222    nextSibling: null,223    source: null,224    logCount: 0,225    treeBaseDuration: 0,226    suspendedBy: null,227    suspenseNode: null,228    data: fiber,229  } as any;230}231232function createVirtualInstance(233  debugEntry: ReactComponentInfo,234): VirtualInstance {235  return {236    kind: VIRTUAL_INSTANCE,237    id: getUID(),238    parent: null,239    firstChild: null,240    nextSibling: null,241    source: null,242    logCount: 0,243    treeBaseDuration: 0,244    suspendedBy: null,245    suspenseNode: null,246    data: debugEntry,247  };248}249250// Update flags need to be propagated up until the caller that put the corresponding251// node on the stack.252// If you push a new node, you need to handle ShouldResetChildren when you pop it.253// If you push a new Suspense node, you need to handle ShouldResetSuspenseChildren when you pop it.254type UpdateFlags = number;255const NoUpdate = /*                          */ 0b000;256const ShouldResetChildren = /*               */ 0b001;257const ShouldResetSuspenseChildren = /*       */ 0b010;258const ShouldResetParentSuspenseChildren = /* */ 0b100;259260function createSuspenseNode(261  instance: FiberInstance | FilteredFiberInstance,262): SuspenseNode {263  return (instance.suspenseNode = {264    instance: instance,265    parent: null,266    firstChild: null,267    nextSibling: null,268    rects: null,269    suspendedBy: new Map(),270    environments: new Map(),271    endTime: 0,272    hasUniqueSuspenders: false,273    hasUnknownSuspenders: false,274  });275}276277// All environment names we've seen so far. This lets us create a list of filters to apply.278// This should ideally include env of filtered Components too so that you can add those as279// filters at the same time as removing some other filter.280const knownEnvironmentNames: Set<string> = new Set();281282// Map of FiberRoot to their root FiberInstance.283const rootToFiberInstanceMap: Map<FiberRoot, FiberInstance> = new Map();284285// Map of id to FiberInstance or VirtualInstance.286// This Map is used to e.g. get the display name for a Fiber or schedule an update,287// operations that should be the same whether the current and work-in-progress Fiber is used.288const idToDevToolsInstanceMap: Map<289  FiberInstance['id'] | VirtualInstance['id'],290  FiberInstance | VirtualInstance,291> = new Map();292293let focusedActivityID: null | FiberInstance['id'] = null;294let focusedActivity: null | Fiber = null;295296const idToSuspenseNodeMap: Map<FiberInstance['id'], SuspenseNode> = new Map();297298// Map of canonical HostInstances to the nearest parent DevToolsInstance.299const publicInstanceToDevToolsInstanceMap: Map<HostInstance, DevToolsInstance> =300  new Map();301// Map of resource DOM nodes to all the nearest DevToolsInstances that depend on it.302const hostResourceToDevToolsInstanceMap: Map<303  HostInstance,304  Set<DevToolsInstance>,305> = new Map();306307function aquireHostInstance(308  nearestInstance: DevToolsInstance,309  hostInstance: HostInstance,310): void {311  const publicInstance = getPublicInstance(hostInstance);312  publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);313}314315function releaseHostInstance(316  nearestInstance: DevToolsInstance,317  hostInstance: HostInstance,318): void {319  const publicInstance = getPublicInstance(hostInstance);320  if (321    publicInstanceToDevToolsInstanceMap.get(publicInstance) === nearestInstance322  ) {323    publicInstanceToDevToolsInstanceMap.delete(publicInstance);324  }325}326327function aquireHostResource(328  nearestInstance: DevToolsInstance,329  resource: ?{instance?: HostInstance},330): void {331  const hostInstance = resource && resource.instance;332  if (hostInstance) {333    const publicInstance = getPublicInstance(hostInstance);334    let resourceInstances =335      hostResourceToDevToolsInstanceMap.get(publicInstance);336    if (resourceInstances === undefined) {337      resourceInstances = new Set();338      hostResourceToDevToolsInstanceMap.set(publicInstance, resourceInstances);339      // Store the first match in the main map for quick access when selecting DOM node.340      publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);341    }342    resourceInstances.add(nearestInstance);343  }344}345346function releaseHostResource(347  nearestInstance: DevToolsInstance,348  resource: ?{instance?: HostInstance},349): void {350  const hostInstance = resource && resource.instance;351  if (hostInstance) {352    const publicInstance = getPublicInstance(hostInstance);353    const resourceInstances =354      hostResourceToDevToolsInstanceMap.get(publicInstance);355    if (resourceInstances !== undefined) {356      resourceInstances.delete(nearestInstance);357      if (resourceInstances.size === 0) {358        hostResourceToDevToolsInstanceMap.delete(publicInstance);359        publicInstanceToDevToolsInstanceMap.delete(publicInstance);360      } else if (361        publicInstanceToDevToolsInstanceMap.get(publicInstance) ===362        nearestInstance363      ) {364        // This was the first one. Store the next first one in the main map for easy access.365        // eslint-disable-next-line no-for-of-loops/no-for-of-loops366        for (const firstInstance of resourceInstances) {367          publicInstanceToDevToolsInstanceMap.set(368            publicInstance,369            firstInstance,370          );371          break;372        }373      }374    }375  }376}377378export function attach(379  hook: DevToolsHook,380  rendererID: number,381  renderer: ReactRenderer,382  global: Object,383  shouldStartProfilingNow: boolean,384  profilingSettings: ProfilingSettings,385  componentFiltersOrComponentFiltersPromise:386    | Array<ComponentFilter>387    | Promise<Array<ComponentFilter>>,388): RendererInterface {389  // Newer versions of the reconciler package also specific reconciler version.390  // If that version number is present, use it.391  // Third party renderer versions may not match the reconciler version,392  // and the latter is what's important in terms of tags and symbols.393  const version = renderer.reconcilerVersion || renderer.version;394395  const {396    getDisplayNameForFiber,397    getTypeSymbol,398    ReactPriorityLevels,399    ReactTypeOfWork,400    StrictModeBits,401    SuspenseyImagesMode,402  } = getInternalReactConstants(version);403  const {404    ActivityComponent,405    ClassComponent,406    DehydratedSuspenseComponent,407    ForwardRef,408    Fragment,409    FunctionComponent,410    HostRoot,411    HostHoistable,412    HostSingleton,413    HostPortal,414    HostComponent,415    HostText,416    IncompleteClassComponent,417    IncompleteFunctionComponent,418    IndeterminateComponent,419    LegacyHiddenComponent,420    MemoComponent,421    OffscreenComponent,422    SimpleMemoComponent,423    SuspenseComponent,424    SuspenseListComponent,425    TracingMarkerComponent,426    Throw,427    ViewTransitionComponent,428  } = ReactTypeOfWork;429  const {430    ImmediatePriority,431    UserBlockingPriority,432    NormalPriority,433    LowPriority,434    IdlePriority,435    NoPriority,436  } = ReactPriorityLevels;437438  const {439    getLaneLabelMap,440    injectProfilingHooks,441    overrideHookState,442    overrideHookStateDeletePath,443    overrideHookStateRenamePath,444    overrideProps,445    overridePropsDeletePath,446    overridePropsRenamePath,447    scheduleRefresh,448    setErrorHandler,449    setSuspenseHandler,450    scheduleUpdate,451    scheduleRetry,452    getCurrentFiber,453  } = renderer;454  const supportsTogglingError =455    typeof setErrorHandler === 'function' &&456    typeof scheduleUpdate === 'function';457  const supportsTogglingSuspense =458    typeof setSuspenseHandler === 'function' &&459    typeof scheduleUpdate === 'function';460  const supportsPerformanceTracks = gte(version, '19.2.0');461462  if (typeof scheduleRefresh === 'function') {463    // When Fast Refresh updates a component, the frontend may need to purge cached information.464    // For example, ASTs cached for the component (for named hooks) may no longer be valid.465    // Send a signal to the frontend to purge this cached information.466    // The "fastRefreshScheduled" dispatched is global (not Fiber or even Renderer specific).467    // This is less effecient since it means the front-end will need to purge the entire cache,468    // but this is probably an okay trade off in order to reduce coupling between the DevTools and Fast Refresh.469    renderer.scheduleRefresh = (...args) => {470      try {471        hook.emit('fastRefreshScheduled');472      } finally {473        return scheduleRefresh(...args);474      }475    };476  }477478  let getTimelineData: null | GetTimelineData = null;479  let toggleProfilingStatus: null | ToggleProfilingStatus = null;480  if (typeof injectProfilingHooks === 'function') {481    const response = createProfilingHooks({482      getDisplayNameForFiber,483      getIsProfiling: () => isProfiling,484      getLaneLabelMap,485      currentDispatcherRef: getDispatcherRef(renderer),486      workTagMap: ReactTypeOfWork,487      reactVersion: version,488    });489490    // Pass the Profiling hooks to the reconciler for it to call during render.491    injectProfilingHooks(response.profilingHooks);492493    // Hang onto this toggle so we can notify the external methods of profiling status changes.494    getTimelineData = response.getTimelineData;495    toggleProfilingStatus = response.toggleProfilingStatus;496  }497498  type ComponentLogs = {499    errors: Map<string, number>,500    errorsCount: number,501    warnings: Map<string, number>,502    warningsCount: number,503  };504  // Tracks Errors/Warnings logs added to a Fiber. They are added before the commit and get505  // picked up a FiberInstance. This keeps it around as long as the Fiber is alive which506  // lets the Fiber get reparented/remounted and still observe the previous errors/warnings.507  // Unless we explicitly clear the logs from a Fiber.508  const fiberToComponentLogsMap: WeakMap<Fiber, ComponentLogs> = new WeakMap();509  // Tracks whether we've performed a commit since the last log. This is used to know510  // whether we received any new logs between the commit and post commit phases. I.e.511  // if any passive effects called console.warn / console.error.512  let needsToFlushComponentLogs = false;513514  function bruteForceFlushErrorsAndWarnings(root: FiberInstance) {515    // Refresh error/warning count for all mounted unfiltered Fibers.516    let hasChanges = false;517    // eslint-disable-next-line no-for-of-loops/no-for-of-loops518    for (const devtoolsInstance of idToDevToolsInstanceMap.values()) {519      if (devtoolsInstance.kind === FIBER_INSTANCE) {520        const fiber = devtoolsInstance.data;521        const componentLogsEntry = fiberToComponentLogsMap.get(fiber);522        const changed = recordConsoleLogs(devtoolsInstance, componentLogsEntry);523        if (changed) {524          hasChanges = true;525          updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);526        }527      } else {528        // Virtual Instances cannot log in passive effects and so never appear here.529      }530    }531    if (hasChanges) {532      flushPendingEvents(root);533    }534  }535536  function clearErrorsAndWarnings() {537    // Note, this only clears logs for Fibers that have instances. If they're filtered538    // and then mount, the logs are there. Ensuring we only clear what you've seen.539    // If we wanted to clear the whole set, we'd replace fiberToComponentLogsMap with a540    // new WeakMap. It's unclear whether we should clear componentInfoToComponentLogsMap541    // since it's shared by other renderers but presumably it would.542543    // eslint-disable-next-line no-for-of-loops/no-for-of-loops544    for (const devtoolsInstance of idToDevToolsInstanceMap.values()) {545      if (devtoolsInstance.kind === FIBER_INSTANCE) {546        const fiber = devtoolsInstance.data;547        fiberToComponentLogsMap.delete(fiber);548        if (fiber.alternate) {549          fiberToComponentLogsMap.delete(fiber.alternate);550        }551      } else {552        componentInfoToComponentLogsMap.delete(devtoolsInstance.data);553      }554      const changed = recordConsoleLogs(devtoolsInstance, undefined);555      if (changed) {556        updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);557      }558    }559    flushPendingEvents(null);560  }561562  function clearConsoleLogsHelper(instanceID: number, type: 'error' | 'warn') {563    const devtoolsInstance = idToDevToolsInstanceMap.get(instanceID);564    if (devtoolsInstance !== undefined) {565      let componentLogsEntry;566      if (devtoolsInstance.kind === FIBER_INSTANCE) {567        const fiber = devtoolsInstance.data;568        componentLogsEntry = fiberToComponentLogsMap.get(fiber);569570        if (componentLogsEntry === undefined && fiber.alternate !== null) {571          componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);572        }573      } else {574        const componentInfo = devtoolsInstance.data;575        componentLogsEntry = componentInfoToComponentLogsMap.get(componentInfo);576      }577      if (componentLogsEntry !== undefined) {578        if (type === 'error') {579          componentLogsEntry.errors.clear();580          componentLogsEntry.errorsCount = 0;581        } else {582          componentLogsEntry.warnings.clear();583          componentLogsEntry.warningsCount = 0;584        }585        const changed = recordConsoleLogs(devtoolsInstance, componentLogsEntry);586        if (changed) {587          flushPendingEvents(null);588          updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);589        }590      }591    }592  }593594  function clearErrorsForElementID(instanceID: number) {595    clearConsoleLogsHelper(instanceID, 'error');596  }597598  function clearWarningsForElementID(instanceID: number) {599    clearConsoleLogsHelper(instanceID, 'warn');600  }601602  function updateMostRecentlyInspectedElementIfNecessary(603    fiberID: number,604  ): void {605    if (606      mostRecentlyInspectedElement !== null &&607      mostRecentlyInspectedElement.id === fiberID608    ) {609      hasElementUpdatedSinceLastInspected = true;610    }611  }612613  function getComponentStack(614    topFrame: Error,615  ): null | {enableOwnerStacks: boolean, componentStack: string} {616    if (getCurrentFiber == null) {617      // Expected this to be part of the renderer. Ignore.618      return null;619    }620    const current = getCurrentFiber();621    if (current === null) {622      // Outside of our render scope.623      return null;624    }625626    if (supportsConsoleTasks(current)) {627      // This will be handled natively by console.createTask. No need for628      // DevTools to add it.629      return null;630    }631632    const dispatcherRef = getDispatcherRef(renderer);633    if (dispatcherRef === undefined) {634      return null;635    }636637    const enableOwnerStacks = supportsOwnerStacks(current);638    let componentStack = '';639    if (enableOwnerStacks) {640      // Prefix the owner stack with the current stack. I.e. what called641      // console.error. While this will also be part of the native stack,642      // it is hidden and not presented alongside this argument so we print643      // them all together.644      const topStackFrames = formatOwnerStack(topFrame);645      if (topStackFrames) {646        componentStack += '\n' + topStackFrames;647      }648      componentStack += getOwnerStackByFiberInDev(649        ReactTypeOfWork,650        current,651        dispatcherRef,652      );653    } else {654      componentStack = getStackByFiberInDevAndProd(655        ReactTypeOfWork,656        current,657        dispatcherRef,658      );659    }660    return {enableOwnerStacks, componentStack};661  }662663  // Called when an error or warning is logged during render, commit, or passive (including unmount functions).664  function onErrorOrWarning(665    type: 'error' | 'warn',666    args: $ReadOnlyArray<any>,667  ): void {668    if (getCurrentFiber == null) {669      // Expected this to be part of the renderer. Ignore.670      return;671    }672    const fiber = getCurrentFiber();673    if (fiber === null) {674      // Outside of our render scope.675      return;676    }677    if (type === 'error') {678      // if this is an error simulated by us to trigger error boundary, ignore679      if (680        forceErrorForFibers.get(fiber) === true ||681        (fiber.alternate !== null &&682          forceErrorForFibers.get(fiber.alternate) === true)683      ) {684        return;685      }686    }687688    // We can't really use this message as a unique key, since we can't distinguish689    // different objects in this implementation. We have to delegate displaying of the objects690    // to the environment, the browser console, for example, so this is why this should be kept691    // as an array of arguments, instead of the plain string.692    // [Warning: %o, {...}] and [Warning: %o, {...}] will be considered as the same message,693    // even if objects are different694    const message = formatConsoleArgumentsToSingleString(...args);695696    // Track the warning/error for later.697    let componentLogsEntry = fiberToComponentLogsMap.get(fiber);698    if (componentLogsEntry === undefined && fiber.alternate !== null) {699      componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);700      if (componentLogsEntry !== undefined) {701        // Use the same set for both Fibers.702        fiberToComponentLogsMap.set(fiber, componentLogsEntry);703      }704    }705    if (componentLogsEntry === undefined) {706      componentLogsEntry = {707        errors: new Map(),708        errorsCount: 0 as number,709        warnings: new Map(),710        warningsCount: 0 as number,711      };712      fiberToComponentLogsMap.set(fiber, componentLogsEntry);713    }714715    const messageMap =716      type === 'error'717        ? componentLogsEntry.errors718        : componentLogsEntry.warnings;719    const count = messageMap.get(message) || 0;720    messageMap.set(message, count + 1);721    if (type === 'error') {722      componentLogsEntry.errorsCount++;723    } else {724      componentLogsEntry.warningsCount++;725    }726727    // The changes will be flushed later when we commit.728729    // If the log happened in a passive effect, then this happens after we've730    // already committed the new tree so the change won't show up until we rerender731    // that component again. We need to visit a Component with passive effects in732    // handlePostCommitFiberRoot again to ensure that we flush the changes after passive.733    needsToFlushComponentLogs = true;734  }735736  function debug(737    name: string,738    instance: DevToolsInstance,739    parentInstance: null | DevToolsInstance,740    extraString: string = '',741  ): void {742    // $FlowFixMe[constant-condition]743    if (__DEBUG__) {744      const displayName =745        instance.kind === VIRTUAL_INSTANCE746          ? instance.data.name || 'null'747          : instance.data.tag +748            ':' +749            (getDisplayNameForFiber(instance.data) || 'null');750751      const maybeID =752        instance.kind === FILTERED_FIBER_INSTANCE ? '<no id>' : instance.id;753754      const parentDisplayName =755        parentInstance === null756          ? ''757          : parentInstance.kind === VIRTUAL_INSTANCE758            ? parentInstance.data.name || 'null'759            : parentInstance.data.tag +760              ':' +761              (getDisplayNameForFiber(parentInstance.data) || 'null');762763      const maybeParentID =764        parentInstance === null ||765        parentInstance.kind === FILTERED_FIBER_INSTANCE766          ? '<no id>'767          : parentInstance.id;768769      console.groupCollapsed(770        `[renderer] %c${name} %c${displayName} (${maybeID}) %c${771          parentInstance ? `${parentDisplayName} (${maybeParentID})` : ''772        } %c${extraString}`,773        'color: red; font-weight: bold;',774        'color: blue;',775        'color: purple;',776        'color: black;',777      );778      console.log(new Error().stack.split('\n').slice(1).join('\n'));779      console.groupEnd();780    }781  }782783  // eslint-disable-next-line no-unused-vars784  function debugTree(instance: DevToolsInstance, indent: number = 0) {785    // $FlowFixMe[constant-condition]786    if (__DEBUG__) {787      const name =788        (instance.kind !== VIRTUAL_INSTANCE789          ? getDisplayNameForFiber(instance.data)790          : instance.data.name) || '';791      console.log(792        '  '.repeat(indent) +793          '- ' +794          (instance.kind === FILTERED_FIBER_INSTANCE ? 0 : instance.id) +795          ' (' +796          name +797          ')',798        'parent',799        instance.parent === null800          ? ' '801          : instance.parent.kind === FILTERED_FIBER_INSTANCE802            ? 0803            : instance.parent.id,804        'next',805        instance.nextSibling === null ? ' ' : instance.nextSibling.id,806      );807      let child = instance.firstChild;808      while (child !== null) {809        debugTree(child, indent + 1);810        child = child.nextSibling;811      }812    }813  }814815  // Configurable Components tree filters.816  const hideElementsWithDisplayNames: Set<RegExp> = new Set();817  const hideElementsWithPaths: Set<RegExp> = new Set();818  const hideElementsWithTypes: Set<ElementType> = new Set();819  const hideElementsWithEnvs: Set<string> = new Set();820  let isInFocusedActivity: boolean = true;821822  // Highlight updates823  let traceUpdatesEnabled: boolean = false;824  const traceUpdatesForNodes: Set<HostInstance> = new Set();825826  function applyComponentFilters(827    componentFilters: Array<ComponentFilter>,828    nextActivitySlice: null | Fiber,829  ) {830    hideElementsWithTypes.clear();831    hideElementsWithDisplayNames.clear();832    hideElementsWithPaths.clear();833    hideElementsWithEnvs.clear();834    const previousFocusedActivityID = focusedActivityID;835    focusedActivityID = null;836    focusedActivity = null;837    // Consider everything in the slice by default838    isInFocusedActivity = true;839840    componentFilters.forEach(componentFilter => {841      if (!componentFilter.isEnabled) {842        return;843      }844845      switch (componentFilter.type) {846        case ComponentFilterDisplayName:847          if (componentFilter.isValid && componentFilter.value !== '') {848            hideElementsWithDisplayNames.add(849              new RegExp(componentFilter.value, 'i'),850            );851          }852          break;853        case ComponentFilterElementType:854          hideElementsWithTypes.add(componentFilter.value);855          break;856        case ComponentFilterLocation:857          if (componentFilter.isValid && componentFilter.value !== '') {858            hideElementsWithPaths.add(new RegExp(componentFilter.value, 'i'));859          }860          break;861        case ComponentFilterHOC:862          hideElementsWithDisplayNames.add(new RegExp('\\('));863          break;864        case ComponentFilterEnvironmentName:865          hideElementsWithEnvs.add(componentFilter.value);866          break;867        case ComponentFilterActivitySlice:868          if (869            nextActivitySlice !== null &&870            nextActivitySlice.tag === ActivityComponent871          ) {872            focusedActivity = nextActivitySlice;873            isInFocusedActivity = false;874            if (componentFilter.rendererID !== rendererID) {875              // We filtered an Activity from another renderer.876              // We need to restore the instance ID since we won't be mounting it877              // in this renderer.878              focusedActivityID = previousFocusedActivityID;879            }880          } else {881            // We're not filtering by activity slice after all.882            // Don't mark the filter as disabled here.883            // Otherwise updateComponentFilters() will think no enabled filter was changed.884          }885          break;886        default:887          console.warn(888            `Invalid component filter type "${componentFilter.type}"`,889          );890          break;891      }892    });893  }894895  if (Array.isArray(componentFiltersOrComponentFiltersPromise)) {896    applyComponentFilters(componentFiltersOrComponentFiltersPromise, null);897  } else {898    componentFiltersOrComponentFiltersPromise.then(componentFilters => {899      applyComponentFilters(componentFilters, null);900    });901  }902903  // If necessary, we can revisit optimizing this operation.904  // For example, we could add a new recursive unmount tree operation.905  // The unmount operations are already significantly smaller than mount operations though.906  // This is something to keep in mind for later.907  function updateComponentFilters(componentFilters: Array<ComponentFilter>) {908    if (isProfiling) {909      // Re-mounting a tree while profiling is in progress might break a lot of assumptions.910      // If necessary, we could support this- but it doesn't seem like a necessary use case.911      // Supporting change of filters while profiling would require a refactor912      // to flush after each root instead of at the end.913      throw Error('Cannot modify filter preferences while profiling');914    }915916    const previousForcedFallbacks =917      forceFallbackForFibers.size > 0 ? new Set(forceFallbackForFibers) : null;918    const previousForcedErrors =919      forceErrorForFibers.size > 0 ? new Map(forceErrorForFibers) : null;920921    // The ID will be based on the old tree. We need to find the Fiber based on922    // that ID before we unmount everything. We set the activity slice ID once923    // we mount it again.924    let nextFocusedActivity: null | Fiber = null;925    let focusedActivityFilter: null | ActivitySliceFilter = null;926    for (let i = 0; i < componentFilters.length; i++) {927      const filter = componentFilters[i];928      if (filter.type === ComponentFilterActivitySlice && filter.isEnabled) {929        focusedActivityFilter = filter;930        const instance = idToDevToolsInstanceMap.get(filter.activityID);931        if (instance !== undefined && instance.kind === FIBER_INSTANCE) {932          nextFocusedActivity = instance.data;933        }934      }935    }936937    // Recursively unmount all roots.938    hook.getFiberRoots(rendererID).forEach(root => {939      const rootInstance = rootToFiberInstanceMap.get(root);940      if (rootInstance === undefined) {941        throw new Error(942          'Expected the root instance to already exist when applying filters',943        );944      }945      currentRoot = rootInstance;946      unmountInstanceRecursively(rootInstance);947      rootToFiberInstanceMap.delete(root);948      currentRoot = null as any;949    });950951    if (952      nextFocusedActivity !== focusedActivity &&953      (focusedActivityFilter === null ||954        focusedActivityFilter.rendererID === rendererID)955    ) {956      // When we find the applied instance during mount we will send the actual ID.957      // Otherwise 0 will indicate that we unfocused the activity slice.958      pushOperation(TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE);959      pushOperation(0);960    }961    applyComponentFilters(componentFilters, nextFocusedActivity);962963    // Reset pseudo counters so that new path selections will be persisted.964    rootDisplayNameCounter.clear();965966    // We just cleared all the forced states. Schedule updates on the affected Fibers967    // so that we get their initial states again according to the new filters.968    if (typeof scheduleUpdate === 'function') {969      if (previousForcedFallbacks !== null) {970        // eslint-disable-next-line no-for-of-loops/no-for-of-loops971        for (const fiber of previousForcedFallbacks) {972          if (typeof scheduleRetry === 'function') {973            scheduleRetry(fiber);974          } else {975            scheduleUpdate(fiber);976          }977        }978      }979      if (980        previousForcedErrors !== null &&981        typeof setErrorHandler === 'function'982      ) {983        // Unlike for Suspense, disabling the forced error state requires setting984        // the status to false first. `shouldErrorFiberAccordingToMap` will clear985        // the Fibers later.986        setErrorHandler(shouldErrorFiberAccordingToMap);987        // eslint-disable-next-line no-for-of-loops/no-for-of-loops988        for (const [fiber, shouldError] of previousForcedErrors) {989          forceErrorForFibers.set(fiber, false);990          if (shouldError) {991            if (typeof scheduleRetry === 'function') {992              scheduleRetry(fiber);993            } else {994              scheduleUpdate(fiber);995            }996          }997        }998      }999    }10001001    // Recursively re-mount all roots with new filter criteria applied.1002    hook.getFiberRoots(rendererID).forEach(root => {1003      const current = root.current;1004      const newRoot = createFiberInstance(current);1005      rootToFiberInstanceMap.set(root, newRoot);1006      idToDevToolsInstanceMap.set(newRoot.id, newRoot);10071008      // Before the traversals, remember to start tracking1009      // our path in case we have selection to restore.1010      if (trackedPath !== null) {1011        mightBeOnTrackedPath = true;1012      }10131014      currentRoot = newRoot;1015      setRootPseudoKey(currentRoot.id, root.current);1016      mountFiberRecursively(root.current, false);1017      currentRoot = null as any;1018    });10191020    // We need to write back the new ID for the focused Fiber.1021    // Otherwise subsequent filter applications will try to focus based on the old ID.1022    // This is also relevant to filter across renderers.1023    if (focusedActivityFilter !== null && focusedActivityID !== null) {1024      focusedActivityFilter.activityID = focusedActivityID;1025    }10261027    // We're not profiling so it's safe to flush without a specific root.1028    flushPendingEvents(null);10291030    needsToFlushComponentLogs = false;1031  }10321033  function getEnvironmentNames(): Array<string> {1034    return Array.from(knownEnvironmentNames);1035  }10361037  function isFiberHydrated(fiber: Fiber): boolean {1038    if (OffscreenComponent === -1) {1039      throw new Error('not implemented for legacy suspense');1040    }1041    switch (fiber.tag) {1042      case HostRoot:1043        const rootState = fiber.memoizedState;1044        return !rootState.isDehydrated;1045      case SuspenseComponent:1046        const suspenseState = fiber.memoizedState;1047        return suspenseState === null || suspenseState.dehydrated === null;1048      default:1049        throw new Error('not implemented for work tag ' + fiber.tag);1050    }1051  }10521053  function shouldFilterVirtual(1054    data: ReactComponentInfo,1055    secondaryEnv: null | string,1056  ): boolean {1057    if (!isInFocusedActivity) {1058      return true;1059    }10601061    // For purposes of filtering Server Components are always Function Components.1062    // Environment will be used to filter Server vs Client.1063    // Technically they can be forwardRef and memo too but those filters will go away1064    // as those become just plain user space function components like any HoC.1065    if (hideElementsWithTypes.has(ElementTypeFunction)) {1066      return true;1067    }10681069    if (hideElementsWithDisplayNames.size > 0) {1070      const displayName = data.name;1071      if (displayName != null) {1072        // eslint-disable-next-line no-for-of-loops/no-for-of-loops1073        for (const displayNameRegExp of hideElementsWithDisplayNames) {1074          if (displayNameRegExp.test(displayName)) {1075            return true;1076          }1077        }1078      }1079    }10801081    if (1082      (data.env == null || hideElementsWithEnvs.has(data.env)) &&1083      (secondaryEnv === null || hideElementsWithEnvs.has(secondaryEnv))1084    ) {1085      // If a Component has two environments, you have to filter both for it not to appear.1086      return true;1087    }10881089    return false;1090  }10911092  // NOTICE Keep in sync with get*ForFiber methods1093  function shouldFilterFiber(fiber: Fiber): boolean {1094    const {tag, type, key} = fiber;10951096    // It is never valid to filter the root element.1097    if (tag !== HostRoot && !isInFocusedActivity) {1098      return true;1099    }11001101    switch (tag) {1102      case DehydratedSuspenseComponent:1103        // TODO: ideally we would show dehydrated Suspense immediately.1104        // However, it has some special behavior (like disconnecting1105        // an alternate and turning into real Suspense) which breaks DevTools.1106        // For now, ignore it, and only show it once it gets hydrated.1107        // https://github.com/bvaughn/react-devtools-experimental/issues/1971108        return true;1109      case HostPortal:1110      case HostText:1111      case LegacyHiddenComponent:1112      case OffscreenComponent:1113      case Throw:1114        return true;1115      case HostRoot:1116        // It is never valid to filter the root element.1117        return false;1118      case Fragment:1119        return key === null;1120      default:1121        const typeSymbol = getTypeSymbol(type);11221123        switch (typeSymbol) {1124          case CONCURRENT_MODE_NUMBER:1125          case CONCURRENT_MODE_SYMBOL_STRING:1126          case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:1127          case STRICT_MODE_NUMBER:1128          case STRICT_MODE_SYMBOL_STRING:1129            return true;1130          default:1131            break;1132        }1133    }11341135    const elementType = getElementTypeForFiber(fiber);1136    if (hideElementsWithTypes.has(elementType)) {1137      return true;1138    }11391140    if (hideElementsWithDisplayNames.size > 0) {1141      const displayName = getDisplayNameForFiber(fiber);1142      if (displayName != null) {1143        // eslint-disable-next-line no-for-of-loops/no-for-of-loops1144        for (const displayNameRegExp of hideElementsWithDisplayNames) {1145          if (displayNameRegExp.test(displayName)) {1146            return true;1147          }1148        }1149      }1150    }11511152    if (hideElementsWithEnvs.has('Client')) {1153      // If we're filtering out the Client environment we should filter out all1154      // "Client Components". Technically that also includes the built-ins but1155      // since that doesn't actually include any additional code loading it's1156      // useful to not filter out the built-ins. Those can be filtered separately.1157      // There's no other way to filter out just Function components on the Client.1158      // Therefore, this only filters Class and Function components.1159      switch (tag) {1160        case ClassComponent:1161        case IncompleteClassComponent:1162        case IncompleteFunctionComponent:1163        case FunctionComponent:1164        case IndeterminateComponent:1165        case ForwardRef:1166        case MemoComponent:1167        case SimpleMemoComponent:1168          return true;1169      }1170    }11711172    /* DISABLED: https://github.com/facebook/react/pull/284171173    if (hideElementsWithPaths.size > 0) {1174      const source = getSourceForFiber(fiber);11751176      if (source != null) {1177        const {fileName} = source;1178        // eslint-disable-next-line no-for-of-loops/no-for-of-loops1179        for (const pathRegExp of hideElementsWithPaths) {1180          if (pathRegExp.test(fileName)) {1181            return true;1182          }1183        }1184      }1185    }1186    */11871188    return false;1189  }11901191  // NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods1192  function getElementTypeForFiber(fiber: Fiber): ElementType {1193    const {type, tag} = fiber;11941195    switch (tag) {1196      case ActivityComponent:1197        return ElementTypeActivity;1198      case ClassComponent:1199      case IncompleteClassComponent:1200        return ElementTypeClass;1201      case IncompleteFunctionComponent:1202      case FunctionComponent:1203      case IndeterminateComponent:1204        return ElementTypeFunction;1205      case ForwardRef:1206        return ElementTypeForwardRef;1207      case HostRoot:1208        return ElementTypeRoot;1209      case HostComponent:1210      case HostHoistable:1211      case HostSingleton:1212        return ElementTypeHostComponent;1213      case HostPortal:1214      case HostText:1215      case Fragment:1216        return ElementTypeOtherOrUnknown;1217      case MemoComponent:1218      case SimpleMemoComponent:1219        return ElementTypeMemo;1220      case SuspenseComponent:1221        return ElementTypeSuspense;1222      case SuspenseListComponent:1223        return ElementTypeSuspenseList;1224      case TracingMarkerComponent:1225        return ElementTypeTracingMarker;1226      case ViewTransitionComponent:1227        return ElementTypeViewTransition;1228      default:1229        const typeSymbol = getTypeSymbol(type);12301231        switch (typeSymbol) {1232          case CONCURRENT_MODE_NUMBER:1233          case CONCURRENT_MODE_SYMBOL_STRING:1234          case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:1235            return ElementTypeOtherOrUnknown;1236          case PROVIDER_NUMBER:1237          case PROVIDER_SYMBOL_STRING:1238            return ElementTypeContext;1239          case CONTEXT_NUMBER:1240          case CONTEXT_SYMBOL_STRING:1241            return ElementTypeContext;1242          case STRICT_MODE_NUMBER:1243          case STRICT_MODE_SYMBOL_STRING:1244            return ElementTypeOtherOrUnknown;1245          case PROFILER_NUMBER:1246          case PROFILER_SYMBOL_STRING:1247            return ElementTypeProfiler;1248          default:1249            return ElementTypeOtherOrUnknown;1250        }1251    }1252  }12531254  // When a mount or update is in progress, this value tracks the root that is being operated on.1255  let currentRoot: FiberInstance = null as any;12561257  // Removes a Fiber (and its alternate) from the Maps used to track their id.1258  // This method should always be called when a Fiber is unmounting.1259  function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) {1260    if (forceErrorForFibers.size > 0) {1261      forceErrorForFibers.delete(fiber);1262      if (fiber.alternate) {1263        forceErrorForFibers.delete(fiber.alternate);1264      }1265      if (forceErrorForFibers.size === 0 && setErrorHandler != null) {1266        setErrorHandler(shouldErrorFiberAlwaysNull);1267      }1268    }12691270    if (forceFallbackForFibers.size > 0) {1271      forceFallbackForFibers.delete(fiber);1272      if (fiber.alternate) {1273        forceFallbackForFibers.delete(fiber.alternate);1274      }1275      if (forceFallbackForFibers.size === 0 && setSuspenseHandler != null) {1276        setSuspenseHandler(shouldSuspendFiberAlwaysFalse);1277      }1278    }12791280    // TODO: Consider using a WeakMap instead. The only thing where that doesn't work1281    // is React Native Paper which tracks tags but that support is eventually going away1282    // and can use the old findFiberByHostInstance strategy.12831284    if (fiber.tag === HostHoistable) {1285      releaseHostResource(nearestInstance, fiber.memoizedState);1286    } else if (1287      fiber.tag === HostComponent ||1288      fiber.tag === HostText ||1289      fiber.tag === HostSingleton1290    ) {1291      releaseHostInstance(nearestInstance, fiber.stateNode);1292    }12931294    // Recursively clean up any filtered Fibers below this one as well since1295    // we won't recordUnmount on those.1296    for (let child = fiber.child; child !== null; child = child.sibling) {1297      if (shouldFilterFiber(child)) {1298        untrackFiber(nearestInstance, child);1299      }1300    }1301  }13021303  function getChangeDescription(1304    prevFiber: Fiber | null,1305    nextFiber: Fiber,1306  ): ChangeDescription | null {1307    switch (nextFiber.tag) {1308      case ClassComponent:1309        if (prevFiber === null) {1310          return {1311            context: null,1312            didHooksChange: false,1313            isFirstMount: true,1314            props: null,1315            state: null,1316          };1317        } else {1318          const data: ChangeDescription = {1319            context: getContextChanged(prevFiber, nextFiber),1320            didHooksChange: false,1321            isFirstMount: false,1322            props: getChangedKeys(1323              prevFiber.memoizedProps,1324              nextFiber.memoizedProps,1325            ),1326            state: getChangedKeys(1327              prevFiber.memoizedState,1328              nextFiber.memoizedState,1329            ),1330          };1331          return data;1332        }1333      case IncompleteFunctionComponent:1334      case FunctionComponent:1335      case IndeterminateComponent:1336      case ForwardRef:1337      case MemoComponent:1338      case SimpleMemoComponent:1339        if (prevFiber === null) {1340          return {1341            context: null,1342            didHooksChange: false,1343            isFirstMount: true,1344            props: null,1345            state: null,1346          };1347        } else {1348          const prevHooks = inspectHooks(prevFiber);1349          const nextHooks = inspectHooks(nextFiber);1350          const indices = getChangedHooksIndices(prevHooks, nextHooks);1351          const data: ChangeDescription = {1352            context: getContextChanged(prevFiber, nextFiber),1353            didHooksChange: indices !== null && indices.length > 0,1354            isFirstMount: false,1355            props: getChangedKeys(1356              prevFiber.memoizedProps,1357              nextFiber.memoizedProps,1358            ),1359            state: null,1360            hooks: indices,1361          };1362          // Only traverse the hooks list once, depending on what info we're returning.1363          return data;1364        }1365      default:1366        return null;1367    }1368  }13691370  type OperationsArray = Array<number>;13711372  type StringTableEntry = {1373    encodedString: Array<number>,1374    id: number,1375  };13761377  const pendingOperations: OperationsArray = [];1378  const pendingRealUnmountedIDs: Array<FiberInstance['id']> = [];1379  const pendingRealUnmountedSuspenseIDs: Array<FiberInstance['id']> = [];1380  const pendingSuspenderChanges: Set<FiberInstance['id']> = new Set();1381  let pendingOperationsQueue: Array<OperationsArray> | null = [];1382  const pendingStringTable: Map<string, StringTableEntry> = new Map();1383  let pendingStringTableLength: number = 0;13841385  function pushOperation(op: number): void {1386    if (__DEV__) {1387      if (!Number.isInteger(op)) {1388        console.error(1389          'pushOperation() was called but the value is not an integer.',1390          op,1391        );1392      }1393    }1394    pendingOperations.push(op);1395  }13961397  function shouldBailoutWithPendingOperations() {1398    if (isProfiling) {1399      if (1400        currentCommitProfilingMetadata != null &&1401        currentCommitProfilingMetadata.durations.length > 01402      ) {1403        return false;1404      }1405    }14061407    return (1408      pendingOperations.length === 0 &&1409      pendingRealUnmountedIDs.length === 0 &&1410      pendingRealUnmountedSuspenseIDs.length === 0 &&1411      pendingSuspenderChanges.size === 01412    );1413  }14141415  function flushOrQueueOperations(operations: OperationsArray): void {1416    if (shouldBailoutWithPendingOperations()) {1417      return;1418    }14191420    if (pendingOperationsQueue !== null) {1421      pendingOperationsQueue.push(operations);1422    } else {1423      hook.emit('operations', operations);1424    }1425  }14261427  function recordConsoleLogs(1428    instance: FiberInstance | VirtualInstance,1429    componentLogsEntry: void | ComponentLogs,1430  ): boolean {1431    if (componentLogsEntry === undefined) {1432      if (instance.logCount === 0) {1433        // Nothing has changed.1434        return false;1435      }1436      // Reset to zero.1437      instance.logCount = 0;1438      pushOperation(TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS);1439      pushOperation(instance.id);1440      pushOperation(0);1441      pushOperation(0);1442      return true;1443    } else {1444      const totalCount =1445        componentLogsEntry.errorsCount + componentLogsEntry.warningsCount;1446      if (instance.logCount === totalCount) {1447        // Nothing has changed.1448        return false;1449      }1450      // Update counts.1451      instance.logCount = totalCount;1452      pushOperation(TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS);1453      pushOperation(instance.id);1454      pushOperation(componentLogsEntry.errorsCount);1455      pushOperation(componentLogsEntry.warningsCount);1456      return true;1457    }1458  }14591460  /**1461   * Allowed to flush pending events without a specific root when:1462   * - pending operations don't record tree mutations e.g. TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS1463   * - not profiling (the commit tree builder requires the root of the mutations)1464   */1465  function flushPendingEvents(root: FiberInstance | null): void {1466    if (shouldBailoutWithPendingOperations()) {1467      // If we aren't profiling, we can just bail out here.1468      // No use sending an empty update over the bridge.1469      //1470      // The Profiler stores metadata for each commit and reconstructs the app tree per commit using:1471      // (1) an initial tree snapshot and1472      // (2) the operations array for each commit1473      // Because of this, it's important that the operations and metadata arrays align,1474      // So it's important not to omit even empty operations while profiling is active.1475      return;1476    }14771478    const numUnmountIDs = pendingRealUnmountedIDs.length;1479    const numUnmountSuspenseIDs = pendingRealUnmountedSuspenseIDs.length;1480    const numSuspenderChanges = pendingSuspenderChanges.size;14811482    const operations = new Array<number>(1483      // Identify which renderer this update is coming from.1484      2 + // [rendererID, rootFiberID]1485        // How big is the string table?1486        1 + // [stringTableLength]1487        // Then goes the actual string table.1488        pendingStringTableLength +1489        // All unmounts of Suspense boundaries are batched in a single message.1490        // [TREE_OPERATION_REMOVE_SUSPENSE, removedSuspenseIDLength, ...ids]1491        (numUnmountSuspenseIDs > 0 ? 2 + numUnmountSuspenseIDs : 0) +1492        // All unmounts are batched in a single message.1493        // [TREE_OPERATION_REMOVE, removedIDLength, ...ids]1494        (numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +1495        // Regular operations1496        pendingOperations.length +1497        // All suspender changes are batched in a single message.1498        // [SUSPENSE_TREE_OPERATION_SUSPENDERS, suspenderChangesLength, ...[id, hasUniqueSuspenders, endTime, isSuspended]]1499        (numSuspenderChanges > 0 ? 2 + numSuspenderChanges * 4 : 0),1500    );15011502    // Identify which renderer this update is coming from.1503    // This enables roots to be mapped to renderers,1504    // Which in turn enables fiber props, states, and hooks to be inspected.1505    let i = 0;1506    operations[i++] = rendererID;1507    if (root === null) {1508      operations[i++] = -1;1509    } else {1510      operations[i++] = root.id;1511    }15121513    // Now fill in the string table.1514    // [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]1515    operations[i++] = pendingStringTableLength;1516    pendingStringTable.forEach((entry, stringKey) => {1517      const encodedString = entry.encodedString;15181519      // Don't use the string length.1520      // It won't work for multibyte characters (like emoji).1521      const length = encodedString.length;15221523      operations[i++] = length;1524      for (let j = 0; j < length; j++) {1525        operations[i + j] = encodedString[j];1526      }15271528      i += length;1529    });15301531    if (numUnmountSuspenseIDs > 0) {1532      // All unmounts of Suspense boundaries are batched in a single message.1533      operations[i++] = SUSPENSE_TREE_OPERATION_REMOVE;1534      // The first number is how many unmounted IDs we're gonna send.1535      operations[i++] = numUnmountSuspenseIDs;1536      // Fill in the real unmounts in the reverse order.1537      // They were inserted parents-first by React, but we want children-first.1538      // So we traverse our array backwards.1539      for (let j = 0; j < pendingRealUnmountedSuspenseIDs.length; j++) {1540        operations[i++] = pendingRealUnmountedSuspenseIDs[j];1541      }1542    }15431544    if (numUnmountIDs > 0) {1545      // All unmounts except roots are batched in a single message.1546      operations[i++] = TREE_OPERATION_REMOVE;1547      // The first number is how many unmounted IDs we're gonna send.1548      operations[i++] = numUnmountIDs;1549      // Fill in the real unmounts in the reverse order.1550      // They were inserted parents-first by React, but we want children-first.1551      // So we traverse our array backwards.1552      for (let j = 0; j < pendingRealUnmountedIDs.length; j++) {1553        operations[i++] = pendingRealUnmountedIDs[j];1554      }1555    }15561557    // Fill in pending operations.1558    for (let j = 0; j < pendingOperations.length; j++) {1559      operations[i + j] = pendingOperations[j];1560    }1561    i += pendingOperations.length;15621563    // Suspender changes might affect newly mounted nodes that we already recorded1564    // in pending operations.1565    if (numSuspenderChanges > 0) {1566      operations[i++] = SUSPENSE_TREE_OPERATION_SUSPENDERS;1567      operations[i++] = numSuspenderChanges;1568      pendingSuspenderChanges.forEach(fiberIdWithChanges => {1569        const suspense = idToSuspenseNodeMap.get(fiberIdWithChanges);1570        if (suspense === undefined) {1571          // Probably forgot to cleanup pendingSuspenderChanges when this node was removed.1572          throw new Error(1573            `Could not send suspender changes for "${fiberIdWithChanges}" since the Fiber no longer exists.`,1574          );1575        }1576        operations[i++] = fiberIdWithChanges;1577        operations[i++] = suspense.hasUniqueSuspenders ? 1 : 0;1578        operations[i++] = Math.round(suspense.endTime * 1000);1579        const instance = suspense.instance;1580        const isSuspended =1581          // TODO: Track if other SuspenseNode like SuspenseList rows are suspended.1582          (instance.kind === FIBER_INSTANCE ||1583            instance.kind === FILTERED_FIBER_INSTANCE) &&1584          instance.data.tag === SuspenseComponent &&1585          instance.data.memoizedState !== null;1586        operations[i++] = isSuspended ? 1 : 0;1587        operations[i++] = suspense.environments.size;1588        suspense.environments.forEach((count, env) => {1589          operations[i++] = getStringID(env);1590        });1591      });1592    }15931594    // Let the frontend know about tree operations.1595    flushOrQueueOperations(operations);15961597    // Reset all of the pending state now that we've told the frontend about it.1598    pendingOperations.length = 0;1599    pendingRealUnmountedIDs.length = 0;1600    pendingRealUnmountedSuspenseIDs.length = 0;1601    pendingSuspenderChanges.clear();1602    pendingStringTable.clear();1603    pendingStringTableLength = 0;1604  }16051606  function measureHostInstance(instance: HostInstance): null | Array<Rect> {1607    // Feature detect measurement capabilities of this environment.1608    // TODO: Consider making this capability injected by the ReactRenderer.1609    if (typeof instance !== 'object' || instance === null) {1610      return null;1611    }1612    if (1613      typeof instance.getClientRects === 'function' ||1614      instance.nodeType === 31615    ) {1616      // DOM1617      const doc = instance.ownerDocument;1618      if (instance === doc.documentElement) {1619        // This is the document element. The size of this element is not actually1620        // what determines the whole scrollable area of the screen. Because any1621        // thing that overflows the document will also contribute to the scrollable.1622        // This is unlike overflow: scroll which clips those.1623        // Therefore, we use the scrollable size for this rect instead.1624        return [1625          {1626            x: 0,1627            y: 0,1628            width: instance.scrollWidth,1629            height: instance.scrollHeight,1630          },1631        ];1632      }1633      const result: Array<Rect> = [];1634      const win = doc && doc.defaultView;1635      const scrollX = win ? win.scrollX : 0;1636      const scrollY = win ? win.scrollY : 0;1637      let rects;1638      if (instance.nodeType === 3) {1639        // Text nodes cannot be measured directly but we can measure a Range.1640        if (typeof doc.createRange !== 'function') {1641          return null;1642        }1643        const range = doc.createRange();1644        if (typeof range.getClientRects !== 'function') {1645          return null;1646        }1647        range.selectNodeContents(instance);1648        rects = range.getClientRects();1649      } else {1650        rects = instance.getClientRects();1651      }1652      for (let i = 0; i < rects.length; i++) {1653        const rect = rects[i];1654        result.push({1655          x: rect.x + scrollX,1656          y: rect.y + scrollY,1657          width: rect.width,1658          height: rect.height,1659        });1660      }1661      return result;1662    }1663    if (instance.canonical) {1664      // Native1665      const publicInstance = instance.canonical.publicInstance;1666      if (!publicInstance) {1667        // The publicInstance may not have been initialized yet if there was no ref on this node.1668        // We can't initialize it from any existing Hook but we could fallback to this async form:1669        // renderer.extraDevToolsConfig.getInspectorDataForInstance(instance).hierarchy[last].getInspectorData().measure(callback)1670        return null;1671      }1672      if (typeof publicInstance.getBoundingClientRect === 'function') {1673        // enableAccessToHostTreeInFabric / ReadOnlyElement1674        return [publicInstance.getBoundingClientRect()];1675      }1676      if (typeof publicInstance.unstable_getBoundingClientRect === 'function') {1677        // ReactFabricHostComponent1678        return [publicInstance.unstable_getBoundingClientRect()];1679      }1680    }1681    return null;1682  }16831684  function measureInstance(instance: DevToolsInstance): null | Array<Rect> {1685    // Synchronously return the client rects of the Host instances directly inside this Instance.1686    const hostInstances = findAllCurrentHostInstances(instance);1687    let result: null | Array<Rect> = null;1688    for (let i = 0; i < hostInstances.length; i++) {1689      const childResult = measureHostInstance(hostInstances[i]);1690      if (childResult !== null) {1691        if (result === null) {1692          result = childResult;1693        } else {1694          result = result.concat(childResult);1695        }1696      }1697    }1698    return result;1699  }17001701  function getStringID(string: string | null): number {1702    if (string === null) {1703      return 0;1704    }1705    const existingEntry = pendingStringTable.get(string);1706    if (existingEntry !== undefined) {1707      return existingEntry.id;1708    }17091710    const id = pendingStringTable.size + 1;1711    const encodedString = utfEncodeString(string);17121713    pendingStringTable.set(string, {1714      encodedString,1715      id,1716    });17171718    // The string table total length needs to account both for the string length,1719    // and for the array item that contains the length itself.1720    //1721    // Don't use string length for this table.1722    // It won't work for multibyte characters (like emoji).1723    pendingStringTableLength += encodedString.length + 1;17241725    return id;1726  }17271728  let isInDisconnectedSubtree = false;17291730  function recordMount(1731    fiber: Fiber,1732    parentInstance: DevToolsInstance | null,1733  ): FiberInstance {1734    const isRoot = fiber.tag === HostRoot;1735    let fiberInstance;1736    if (isRoot) {1737      const entry = rootToFiberInstanceMap.get(fiber.stateNode);1738      if (entry === undefined) {1739        throw new Error('The root should have been registered at this point');1740      }1741      fiberInstance = entry;1742    } else {1743      fiberInstance = createFiberInstance(fiber);1744    }1745    idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance);17461747    // $FlowFixMe[constant-condition]1748    if (__DEBUG__) {1749      debug('recordMount()', fiberInstance, parentInstance);1750    }17511752    recordReconnect(fiberInstance, parentInstance);1753    return fiberInstance;1754  }17551756  function recordReconnect(1757    fiberInstance: FiberInstance,1758    parentInstance: DevToolsInstance | null,1759  ): void {1760    if (isInDisconnectedSubtree) {1761      // We're disconnected. We'll reconnect a hidden mount after the parent reappears.1762      return;1763    }1764    const id = fiberInstance.id;1765    const fiber = fiberInstance.data;17661767    const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');17681769    const isRoot = fiber.tag === HostRoot;17701771    if (isRoot) {1772      const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');17731774      // Adding a new field here would require a bridge protocol version bump (a backwads breaking change).1775      // Instead let's re-purpose a pre-existing field to carry more information.1776      let profilingFlags = 0;1777      if (isProfilingSupported) {1778        profilingFlags = PROFILING_FLAG_BASIC_SUPPORT;1779        if (typeof injectProfilingHooks === 'function') {1780          profilingFlags |= PROFILING_FLAG_TIMELINE_SUPPORT;1781        }1782        if (supportsPerformanceTracks) {1783          profilingFlags |= PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT;1784        }1785      }17861787      // Set supportsStrictMode to false for production renderer builds1788      const isProductionBuildOfRenderer = renderer.bundleType === 0;17891790      pushOperation(TREE_OPERATION_ADD);1791      pushOperation(id);1792      pushOperation(ElementTypeRoot);1793      pushOperation((fiber.mode & StrictModeBits) !== 0 ? 1 : 0);1794      pushOperation(profilingFlags);1795      pushOperation(1796        !isProductionBuildOfRenderer && StrictModeBits !== 0 ? 1 : 0,1797      );1798      pushOperation(hasOwnerMetadata ? 1 : 0);17991800      if (isProfiling) {1801        if (displayNamesByRootID !== null) {1802          displayNamesByRootID.set(id, getDisplayNameForRoot(fiber));1803        }1804      }1805    } else {1806      const suspenseNode = fiberInstance.suspenseNode;1807      if (suspenseNode !== null && fiber.memoizedState === null) {1808        // We're reconnecting an unsuspended Suspense. Measure to see if anything changed.1809        const prevRects = suspenseNode.rects;1810        const nextRects = measureInstance(fiberInstance);1811        if (!areEqualRects(prevRects, nextRects)) {1812          suspenseNode.rects = nextRects;1813          recordSuspenseResize(suspenseNode);1814        }1815      }18161817      const {key} = fiber;1818      const displayName = getDisplayNameForFiber(fiber);1819      const elementType = getElementTypeForFiber(fiber);18201821      // Finding the owner instance might require traversing the whole parent path which1822      // doesn't have great big O notation. Ideally we'd lazily fetch the owner when we1823      // need it but we have some synchronous operations in the front end like Alt+Left1824      // which selects the owner immediately. Typically most owners are only a few parents1825      // away so maybe it's not so bad.1826      const debugOwner = getUnfilteredOwner(fiber);1827      const ownerInstance = findNearestOwnerInstance(1828        parentInstance,1829        debugOwner,1830      );1831      if (1832        ownerInstance !== null &&1833        debugOwner === fiber._debugOwner &&1834        fiber._debugStack != null &&1835        ownerInstance.source === null1836      ) {1837        // The new Fiber is directly owned by the ownerInstance. Therefore somewhere on1838        // the debugStack will be a stack frame inside the ownerInstance's source.1839        ownerInstance.source = fiber._debugStack;1840      }18411842      let unfilteredParent = parentInstance;1843      while (1844        unfilteredParent !== null &&1845        unfilteredParent.kind === FILTERED_FIBER_INSTANCE1846      ) {1847        unfilteredParent = unfilteredParent.parent;1848      }18491850      const ownerID = ownerInstance === null ? 0 : ownerInstance.id;1851      const parentID = unfilteredParent === null ? 0 : unfilteredParent.id;18521853      const displayNameStringID = getStringID(displayName);18541855      // This check is a guard to handle a React element that has been modified1856      // in such a way as to bypass the default stringification of the "key" property.1857      const keyString =1858        key === null1859          ? null1860          : key === REACT_OPTIMISTIC_KEY1861            ? 'React.optimisticKey'1862            : String(key);1863      const keyStringID = getStringID(keyString);18641865      const nameProp =1866        fiber.tag === SuspenseComponent1867          ? fiber.memoizedProps.name1868          : fiber.tag === ActivityComponent1869            ? fiber.memoizedProps.name1870            : null;1871      const namePropString = nameProp == null ? null : String(nameProp);1872      const namePropStringID = getStringID(namePropString);18731874      pushOperation(TREE_OPERATION_ADD);1875      pushOperation(id);1876      pushOperation(elementType);1877      pushOperation(parentID);1878      pushOperation(ownerID);1879      pushOperation(displayNameStringID);1880      pushOperation(keyStringID);1881      pushOperation(namePropStringID);18821883      // If this subtree has a new mode, let the frontend know.1884      if ((fiber.mode & StrictModeBits) !== 0) {1885        let parentFiber = null;1886        let parentFiberInstance = parentInstance;1887        while (parentFiberInstance !== null) {1888          if (parentFiberInstance.kind === FIBER_INSTANCE) {1889            parentFiber = parentFiberInstance.data;1890            break;1891          }1892          parentFiberInstance = parentFiberInstance.parent;1893        }1894        if (parentFiber === null || (parentFiber.mode & StrictModeBits) === 0) {1895          pushOperation(TREE_OPERATION_SET_SUBTREE_MODE);1896          pushOperation(id);1897          pushOperation(StrictMode);1898        }1899      }19001901      // If this is an Activity component, check if it's hidden.1902      if (fiber.tag === ActivityComponent) {1903        const offscreenChild = fiber.child;1904        if (1905          offscreenChild !== null &&1906          offscreenChild.tag === OffscreenComponent &&1907          offscreenChild.memoizedState !== null1908        ) {1909          pushOperation(TREE_OPERATION_SET_SUBTREE_MODE);1910          pushOperation(id);1911          pushOperation(ActivityHiddenMode);1912        }1913      }1914    }19151916    let componentLogsEntry = fiberToComponentLogsMap.get(fiber);1917    if (componentLogsEntry === undefined && fiber.alternate !== null) {1918      componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);1919    }1920    recordConsoleLogs(fiberInstance, componentLogsEntry);19211922    if (isProfilingSupported) {1923      recordProfilingDurations(fiberInstance, null);1924    }1925  }19261927  function recordVirtualMount(1928    instance: VirtualInstance,1929    parentInstance: DevToolsInstance | null,1930    secondaryEnv: null | string,1931  ): void {1932    const id = instance.id;19331934    idToDevToolsInstanceMap.set(id, instance);19351936    recordVirtualReconnect(instance, parentInstance, secondaryEnv);1937  }19381939  function recordVirtualReconnect(1940    instance: VirtualInstance,1941    parentInstance: DevToolsInstance | null,1942    secondaryEnv: null | string,1943  ): void {1944    if (isInDisconnectedSubtree) {1945      // We're disconnected. We'll reconnect a hidden mount after the parent reappears.1946      return;1947    }1948    const componentInfo = instance.data;19491950    const key =1951      typeof componentInfo.key === 'string' ? componentInfo.key : null;1952    const env = componentInfo.env;1953    let displayName = componentInfo.name || '';1954    if (typeof env === 'string') {1955      // We model environment as an HoC name for now.1956      if (secondaryEnv !== null) {1957        displayName = secondaryEnv + '(' + displayName + ')';1958      }1959      displayName = env + '(' + displayName + ')';1960    }1961    const elementType = ElementTypeVirtual;19621963    // Finding the owner instance might require traversing the whole parent path which1964    // doesn't have great big O notation. Ideally we'd lazily fetch the owner when we1965    // need it but we have some synchronous operations in the front end like Alt+Left1966    // which selects the owner immediately. Typically most owners are only a few parents1967    // away so maybe it's not so bad.1968    const debugOwner = getUnfilteredOwner(componentInfo);1969    const ownerInstance = findNearestOwnerInstance(parentInstance, debugOwner);1970    if (1971      ownerInstance !== null &&1972      debugOwner === componentInfo.owner &&1973      componentInfo.debugStack != null &&1974      ownerInstance.source === null1975    ) {1976      // The new Fiber is directly owned by the ownerInstance. Therefore somewhere on1977      // the debugStack will be a stack frame inside the ownerInstance's source.1978      ownerInstance.source = componentInfo.debugStack;1979    }19801981    let unfilteredParent = parentInstance;1982    while (1983      unfilteredParent !== null &&1984      unfilteredParent.kind === FILTERED_FIBER_INSTANCE1985    ) {1986      unfilteredParent = unfilteredParent.parent;1987    }19881989    const ownerID = ownerInstance === null ? 0 : ownerInstance.id;1990    const parentID = unfilteredParent === null ? 0 : unfilteredParent.id;19911992    const displayNameStringID = getStringID(displayName);19931994    // This check is a guard to handle a React element that has been modified1995    // in such a way as to bypass the default stringification of the "key" property.1996    const keyString = key === null ? null : String(key);1997    const keyStringID = getStringID(keyString);1998    const namePropStringID = getStringID(null);19992000    const id = instance.id;

Findings

✓ No findings reported for this file.

Get this view in your editor

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