packages/react-devtools-shared/src/backend/fiber/renderer.js JAVASCRIPT 8,083 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 8,083.
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_PERFORMANCE_TRACKS_SUPPORT,79  TREE_OPERATION_ADD,80  TREE_OPERATION_REMOVE,81  TREE_OPERATION_REORDER_CHILDREN,82  TREE_OPERATION_SET_SUBTREE_MODE,83  TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,84  TREE_OPERATION_UPDATE_TREE_BASE_DURATION,85  TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE,86  SUSPENSE_TREE_OPERATION_ADD,87  SUSPENSE_TREE_OPERATION_REMOVE,88  SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,89  SUSPENSE_TREE_OPERATION_RESIZE,90  SUSPENSE_TREE_OPERATION_SUSPENDERS,91  UNKNOWN_SUSPENDERS_NONE,92  UNKNOWN_SUSPENDERS_REASON_PRODUCTION,93  UNKNOWN_SUSPENDERS_REASON_OLD_VERSION,94  UNKNOWN_SUSPENDERS_REASON_THROWN_PROMISE,95} from '../../constants';96import {inspectHooksOfFiber} from 'react-debug-tools';97import {98  CONCURRENT_MODE_NUMBER,99  CONCURRENT_MODE_SYMBOL_STRING,100  DEPRECATED_ASYNC_MODE_SYMBOL_STRING,101  PROVIDER_NUMBER,102  PROVIDER_SYMBOL_STRING,103  CONTEXT_NUMBER,104  CONTEXT_SYMBOL_STRING,105  CONSUMER_SYMBOL_STRING,106  STRICT_MODE_NUMBER,107  STRICT_MODE_SYMBOL_STRING,108  PROFILER_NUMBER,109  PROFILER_SYMBOL_STRING,110  LAZY_SYMBOL_STRING,111  REACT_OPTIMISTIC_KEY,112} from '../shared/ReactSymbols';113import {enableStyleXFeatures} from 'react-devtools-feature-flags';114115import {componentInfoToComponentLogsMap} from '../shared/DevToolsServerComponentLogs';116117import {getIODescription} from 'shared/ReactIODescription';118119import {120  getPublicInstance,121  getNativeTag,122  getCurrentTime,123} from 'react-devtools-shared/src/backend/DevToolsNativeHost';124import {125  isError,126  rootSupportsProfiling,127  isErrorBoundary,128  getSecondaryEnvironmentName,129  areEqualRects,130} from './shared/DevToolsFiberInspection';131import {132  didFiberRender,133  getContextChanged,134  getChangedHooksIndices,135  getChangedKeys,136} from './shared/DevToolsFiberChangeDetection';137import {getInternalReactConstants} from './shared/DevToolsFiberInternalReactConstants';138import {139  ioExistsInSuspenseAncestor,140  getAwaitInSuspendedByFromIO,141  getVirtualEndTime,142} from './shared/DevToolsFiberSuspense';143144import {145  getStackByFiberInDevAndProd,146  getOwnerStackByFiberInDev,147  supportsOwnerStacks,148  supportsConsoleTasks,149} from './DevToolsFiberComponentStack';150151import {getStyleXData} from '../StyleX/utils';152153import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';154import type {155  ChangeDescription,156  CommitDataBackend,157  DevToolsHook,158  InspectedElement,159  InspectedElementPayload,160  InstanceAndStyle,161  HostInstance,162  PathFrame,163  PathMatch,164  ProfilingDataBackend,165  ProfilingDataForRootBackend,166  ReactRenderer,167  Rect,168  RendererInterface,169  SerializedElement,170  SerializedAsyncInfo,171  ProfilingSettings,172} from '../types';173import type {174  ComponentFilter,175  ActivitySliceFilter,176  ElementType,177  Plugins,178} from 'react-devtools-shared/src/frontend/types';179import type {ReactFunctionLocation} from 'shared/ReactTypes';180import type {181  FiberInstance,182  FilteredFiberInstance,183  VirtualInstance,184  DevToolsInstance,185  SuspenseNode,186} from './shared/DevToolsFiberTypes';187import {188  FIBER_INSTANCE,189  VIRTUAL_INSTANCE,190  FILTERED_FIBER_INSTANCE,191} from './shared/DevToolsFiberTypes';192import {getDispatcherRef} from '../shared/DevToolsReactDispatcher';193import {getSourceLocationByFiber} from './DevToolsFiberComponentStack';194import {formatOwnerStack} from '../shared/DevToolsOwnerStack';195196function createFiberInstance(fiber: Fiber): FiberInstance {197  return {198    kind: FIBER_INSTANCE,199    id: getUID(),200    parent: null,201    firstChild: null,202    nextSibling: null,203    source: null,204    logCount: 0,205    treeBaseDuration: 0,206    suspendedBy: null,207    suspenseNode: null,208    data: fiber,209  };210}211212// This is used to represent a filtered Fiber but still lets us find its host instance.213function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance {214  return {215    kind: FILTERED_FIBER_INSTANCE,216    id: 0,217    parent: null,218    firstChild: null,219    nextSibling: null,220    source: null,221    logCount: 0,222    treeBaseDuration: 0,223    suspendedBy: null,224    suspenseNode: null,225    data: fiber,226  } as any;227}228229function createVirtualInstance(230  debugEntry: ReactComponentInfo,231): VirtualInstance {232  return {233    kind: VIRTUAL_INSTANCE,234    id: getUID(),235    parent: null,236    firstChild: null,237    nextSibling: null,238    source: null,239    logCount: 0,240    treeBaseDuration: 0,241    suspendedBy: null,242    suspenseNode: null,243    data: debugEntry,244  };245}246247// Update flags need to be propagated up until the caller that put the corresponding248// node on the stack.249// If you push a new node, you need to handle ShouldResetChildren when you pop it.250// If you push a new Suspense node, you need to handle ShouldResetSuspenseChildren when you pop it.251type UpdateFlags = number;252const NoUpdate = /*                          */ 0b000;253const ShouldResetChildren = /*               */ 0b001;254const ShouldResetSuspenseChildren = /*       */ 0b010;255const ShouldResetParentSuspenseChildren = /* */ 0b100;256257function createSuspenseNode(258  instance: FiberInstance | FilteredFiberInstance,259): SuspenseNode {260  return (instance.suspenseNode = {261    instance: instance,262    parent: null,263    firstChild: null,264    nextSibling: null,265    rects: null,266    suspendedBy: new Map(),267    environments: new Map(),268    endTime: 0,269    hasUniqueSuspenders: false,270    hasUnknownSuspenders: false,271  });272}273274// All environment names we've seen so far. This lets us create a list of filters to apply.275// This should ideally include env of filtered Components too so that you can add those as276// filters at the same time as removing some other filter.277const knownEnvironmentNames: Set<string> = new Set();278279// Map of FiberRoot to their root FiberInstance.280const rootToFiberInstanceMap: Map<FiberRoot, FiberInstance> = new Map();281282// Map of id to FiberInstance or VirtualInstance.283// This Map is used to e.g. get the display name for a Fiber or schedule an update,284// operations that should be the same whether the current and work-in-progress Fiber is used.285const idToDevToolsInstanceMap: Map<286  FiberInstance['id'] | VirtualInstance['id'],287  FiberInstance | VirtualInstance,288> = new Map();289290let focusedActivityID: null | FiberInstance['id'] = null;291let focusedActivity: null | Fiber = null;292293const idToSuspenseNodeMap: Map<FiberInstance['id'], SuspenseNode> = new Map();294295// Map of canonical HostInstances to the nearest parent DevToolsInstance.296const publicInstanceToDevToolsInstanceMap: Map<HostInstance, DevToolsInstance> =297  new Map();298// Map of resource DOM nodes to all the nearest DevToolsInstances that depend on it.299const hostResourceToDevToolsInstanceMap: Map<300  HostInstance,301  Set<DevToolsInstance>,302> = new Map();303304function aquireHostInstance(305  nearestInstance: DevToolsInstance,306  hostInstance: HostInstance,307): void {308  const publicInstance = getPublicInstance(hostInstance);309  publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);310}311312function releaseHostInstance(313  nearestInstance: DevToolsInstance,314  hostInstance: HostInstance,315): void {316  const publicInstance = getPublicInstance(hostInstance);317  if (318    publicInstanceToDevToolsInstanceMap.get(publicInstance) === nearestInstance319  ) {320    publicInstanceToDevToolsInstanceMap.delete(publicInstance);321  }322}323324function aquireHostResource(325  nearestInstance: DevToolsInstance,326  resource: ?{instance?: HostInstance},327): void {328  const hostInstance = resource && resource.instance;329  if (hostInstance) {330    const publicInstance = getPublicInstance(hostInstance);331    let resourceInstances =332      hostResourceToDevToolsInstanceMap.get(publicInstance);333    if (resourceInstances === undefined) {334      resourceInstances = new Set();335      hostResourceToDevToolsInstanceMap.set(publicInstance, resourceInstances);336      // Store the first match in the main map for quick access when selecting DOM node.337      publicInstanceToDevToolsInstanceMap.set(publicInstance, nearestInstance);338    }339    resourceInstances.add(nearestInstance);340  }341}342343function releaseHostResource(344  nearestInstance: DevToolsInstance,345  resource: ?{instance?: HostInstance},346): void {347  const hostInstance = resource && resource.instance;348  if (hostInstance) {349    const publicInstance = getPublicInstance(hostInstance);350    const resourceInstances =351      hostResourceToDevToolsInstanceMap.get(publicInstance);352    if (resourceInstances !== undefined) {353      resourceInstances.delete(nearestInstance);354      if (resourceInstances.size === 0) {355        hostResourceToDevToolsInstanceMap.delete(publicInstance);356        publicInstanceToDevToolsInstanceMap.delete(publicInstance);357      } else if (358        publicInstanceToDevToolsInstanceMap.get(publicInstance) ===359        nearestInstance360      ) {361        // This was the first one. Store the next first one in the main map for easy access.362        // eslint-disable-next-line no-for-of-loops/no-for-of-loops363        for (const firstInstance of resourceInstances) {364          publicInstanceToDevToolsInstanceMap.set(365            publicInstance,366            firstInstance,367          );368          break;369        }370      }371    }372  }373}374375export function attach(376  hook: DevToolsHook,377  rendererID: number,378  renderer: ReactRenderer,379  global: Object,380  shouldStartProfilingNow: boolean,381  profilingSettings: ProfilingSettings,382  componentFiltersOrComponentFiltersPromise:383    | Array<ComponentFilter>384    | Promise<Array<ComponentFilter>>,385): RendererInterface {386  // Newer versions of the reconciler package also specific reconciler version.387  // If that version number is present, use it.388  // Third party renderer versions may not match the reconciler version,389  // and the latter is what's important in terms of tags and symbols.390  const version = renderer.reconcilerVersion || renderer.version;391392  const {393    getDisplayNameForFiber,394    getTypeSymbol,395    ReactPriorityLevels,396    ReactTypeOfWork,397    StrictModeBits,398    SuspenseyImagesMode,399  } = getInternalReactConstants(version);400  const {401    ActivityComponent,402    ClassComponent,403    DehydratedSuspenseComponent,404    ForwardRef,405    Fragment,406    FunctionComponent,407    HostRoot,408    HostHoistable,409    HostSingleton,410    HostPortal,411    HostComponent,412    HostText,413    IncompleteClassComponent,414    IncompleteFunctionComponent,415    IndeterminateComponent,416    LegacyHiddenComponent,417    MemoComponent,418    OffscreenComponent,419    SimpleMemoComponent,420    SuspenseComponent,421    SuspenseListComponent,422    TracingMarkerComponent,423    Throw,424    ViewTransitionComponent,425  } = ReactTypeOfWork;426  const {427    ImmediatePriority,428    UserBlockingPriority,429    NormalPriority,430    LowPriority,431    IdlePriority,432    NoPriority,433  } = ReactPriorityLevels;434435  const {436    overrideHookState,437    overrideHookStateDeletePath,438    overrideHookStateRenamePath,439    overrideProps,440    overridePropsDeletePath,441    overridePropsRenamePath,442    scheduleRefresh,443    setErrorHandler,444    setSuspenseHandler,445    scheduleUpdate,446    scheduleRetry,447    getCurrentFiber,448  } = renderer;449  const supportsTogglingError =450    typeof setErrorHandler === 'function' &&451    typeof scheduleUpdate === 'function';452  const supportsTogglingSuspense =453    typeof setSuspenseHandler === 'function' &&454    typeof scheduleUpdate === 'function';455  const supportsPerformanceTracks = gte(version, '19.2.0');456457  if (typeof scheduleRefresh === 'function') {458    // When Fast Refresh updates a component, the frontend may need to purge cached information.459    // For example, ASTs cached for the component (for named hooks) may no longer be valid.460    // Send a signal to the frontend to purge this cached information.461    // The "fastRefreshScheduled" dispatched is global (not Fiber or even Renderer specific).462    // This is less effecient since it means the front-end will need to purge the entire cache,463    // but this is probably an okay trade off in order to reduce coupling between the DevTools and Fast Refresh.464    renderer.scheduleRefresh = (...args) => {465      try {466        hook.emit('fastRefreshScheduled');467      } finally {468        return scheduleRefresh(...args);469      }470    };471  }472473  type ComponentLogs = {474    errors: Map<string, number>,475    errorsCount: number,476    warnings: Map<string, number>,477    warningsCount: number,478  };479  // Tracks Errors/Warnings logs added to a Fiber. They are added before the commit and get480  // picked up a FiberInstance. This keeps it around as long as the Fiber is alive which481  // lets the Fiber get reparented/remounted and still observe the previous errors/warnings.482  // Unless we explicitly clear the logs from a Fiber.483  const fiberToComponentLogsMap: WeakMap<Fiber, ComponentLogs> = new WeakMap();484  // Tracks whether we've performed a commit since the last log. This is used to know485  // whether we received any new logs between the commit and post commit phases. I.e.486  // if any passive effects called console.warn / console.error.487  let needsToFlushComponentLogs = false;488489  function bruteForceFlushErrorsAndWarnings(root: FiberInstance) {490    // Refresh error/warning count for all mounted unfiltered Fibers.491    let hasChanges = false;492    // eslint-disable-next-line no-for-of-loops/no-for-of-loops493    for (const devtoolsInstance of idToDevToolsInstanceMap.values()) {494      if (devtoolsInstance.kind === FIBER_INSTANCE) {495        const fiber = devtoolsInstance.data;496        const componentLogsEntry = fiberToComponentLogsMap.get(fiber);497        const changed = recordConsoleLogs(devtoolsInstance, componentLogsEntry);498        if (changed) {499          hasChanges = true;500          updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);501        }502      } else {503        // Virtual Instances cannot log in passive effects and so never appear here.504      }505    }506    if (hasChanges) {507      flushPendingEvents(root);508    }509  }510511  function clearErrorsAndWarnings() {512    // Note, this only clears logs for Fibers that have instances. If they're filtered513    // and then mount, the logs are there. Ensuring we only clear what you've seen.514    // If we wanted to clear the whole set, we'd replace fiberToComponentLogsMap with a515    // new WeakMap. It's unclear whether we should clear componentInfoToComponentLogsMap516    // since it's shared by other renderers but presumably it would.517518    // eslint-disable-next-line no-for-of-loops/no-for-of-loops519    for (const devtoolsInstance of idToDevToolsInstanceMap.values()) {520      if (devtoolsInstance.kind === FIBER_INSTANCE) {521        const fiber = devtoolsInstance.data;522        fiberToComponentLogsMap.delete(fiber);523        if (fiber.alternate) {524          fiberToComponentLogsMap.delete(fiber.alternate);525        }526      } else {527        componentInfoToComponentLogsMap.delete(devtoolsInstance.data);528      }529      const changed = recordConsoleLogs(devtoolsInstance, undefined);530      if (changed) {531        updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);532      }533    }534    flushPendingEvents(null);535  }536537  function clearConsoleLogsHelper(instanceID: number, type: 'error' | 'warn') {538    const devtoolsInstance = idToDevToolsInstanceMap.get(instanceID);539    if (devtoolsInstance !== undefined) {540      let componentLogsEntry;541      if (devtoolsInstance.kind === FIBER_INSTANCE) {542        const fiber = devtoolsInstance.data;543        componentLogsEntry = fiberToComponentLogsMap.get(fiber);544545        if (componentLogsEntry === undefined && fiber.alternate !== null) {546          componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);547        }548      } else {549        const componentInfo = devtoolsInstance.data;550        componentLogsEntry = componentInfoToComponentLogsMap.get(componentInfo);551      }552      if (componentLogsEntry !== undefined) {553        if (type === 'error') {554          componentLogsEntry.errors.clear();555          componentLogsEntry.errorsCount = 0;556        } else {557          componentLogsEntry.warnings.clear();558          componentLogsEntry.warningsCount = 0;559        }560        const changed = recordConsoleLogs(devtoolsInstance, componentLogsEntry);561        if (changed) {562          flushPendingEvents(null);563          updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);564        }565      }566    }567  }568569  function clearErrorsForElementID(instanceID: number) {570    clearConsoleLogsHelper(instanceID, 'error');571  }572573  function clearWarningsForElementID(instanceID: number) {574    clearConsoleLogsHelper(instanceID, 'warn');575  }576577  function updateMostRecentlyInspectedElementIfNecessary(578    fiberID: number,579  ): void {580    if (581      mostRecentlyInspectedElement !== null &&582      mostRecentlyInspectedElement.id === fiberID583    ) {584      hasElementUpdatedSinceLastInspected = true;585    }586  }587588  function getComponentStack(589    topFrame: Error,590  ): null | {enableOwnerStacks: boolean, componentStack: string} {591    if (getCurrentFiber == null) {592      // Expected this to be part of the renderer. Ignore.593      return null;594    }595    const current = getCurrentFiber();596    if (current === null) {597      // Outside of our render scope.598      return null;599    }600601    if (supportsConsoleTasks(current)) {602      // This will be handled natively by console.createTask. No need for603      // DevTools to add it.604      return null;605    }606607    const dispatcherRef = getDispatcherRef(renderer);608    if (dispatcherRef === undefined) {609      return null;610    }611612    const enableOwnerStacks = supportsOwnerStacks(current);613    let componentStack = '';614    if (enableOwnerStacks) {615      // Prefix the owner stack with the current stack. I.e. what called616      // console.error. While this will also be part of the native stack,617      // it is hidden and not presented alongside this argument so we print618      // them all together.619      const topStackFrames = formatOwnerStack(topFrame);620      if (topStackFrames) {621        componentStack += '\n' + topStackFrames;622      }623      componentStack += getOwnerStackByFiberInDev(624        ReactTypeOfWork,625        current,626        dispatcherRef,627      );628    } else {629      componentStack = getStackByFiberInDevAndProd(630        ReactTypeOfWork,631        current,632        dispatcherRef,633      );634    }635    return {enableOwnerStacks, componentStack};636  }637638  // Called when an error or warning is logged during render, commit, or passive (including unmount functions).639  function onErrorOrWarning(640    type: 'error' | 'warn',641    args: $ReadOnlyArray<any>,642  ): void {643    if (getCurrentFiber == null) {644      // Expected this to be part of the renderer. Ignore.645      return;646    }647    const fiber = getCurrentFiber();648    if (fiber === null) {649      // Outside of our render scope.650      return;651    }652    if (type === 'error') {653      // if this is an error simulated by us to trigger error boundary, ignore654      if (655        forceErrorForFibers.get(fiber) === true ||656        (fiber.alternate !== null &&657          forceErrorForFibers.get(fiber.alternate) === true)658      ) {659        return;660      }661    }662663    // We can't really use this message as a unique key, since we can't distinguish664    // different objects in this implementation. We have to delegate displaying of the objects665    // to the environment, the browser console, for example, so this is why this should be kept666    // as an array of arguments, instead of the plain string.667    // [Warning: %o, {...}] and [Warning: %o, {...}] will be considered as the same message,668    // even if objects are different669    const message = formatConsoleArgumentsToSingleString(...args);670671    // Track the warning/error for later.672    let componentLogsEntry = fiberToComponentLogsMap.get(fiber);673    if (componentLogsEntry === undefined && fiber.alternate !== null) {674      componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);675      if (componentLogsEntry !== undefined) {676        // Use the same set for both Fibers.677        fiberToComponentLogsMap.set(fiber, componentLogsEntry);678      }679    }680    if (componentLogsEntry === undefined) {681      componentLogsEntry = {682        errors: new Map(),683        errorsCount: 0 as number,684        warnings: new Map(),685        warningsCount: 0 as number,686      };687      fiberToComponentLogsMap.set(fiber, componentLogsEntry);688    }689690    const messageMap =691      type === 'error'692        ? componentLogsEntry.errors693        : componentLogsEntry.warnings;694    const count = messageMap.get(message) || 0;695    messageMap.set(message, count + 1);696    if (type === 'error') {697      componentLogsEntry.errorsCount++;698    } else {699      componentLogsEntry.warningsCount++;700    }701702    // The changes will be flushed later when we commit.703704    // If the log happened in a passive effect, then this happens after we've705    // already committed the new tree so the change won't show up until we rerender706    // that component again. We need to visit a Component with passive effects in707    // handlePostCommitFiberRoot again to ensure that we flush the changes after passive.708    needsToFlushComponentLogs = true;709  }710711  function debug(712    name: string,713    instance: DevToolsInstance,714    parentInstance: null | DevToolsInstance,715    extraString: string = '',716  ): void {717    // $FlowFixMe[constant-condition]718    if (__DEBUG__) {719      const displayName =720        instance.kind === VIRTUAL_INSTANCE721          ? instance.data.name || 'null'722          : instance.data.tag +723            ':' +724            (getDisplayNameForFiber(instance.data) || 'null');725726      const maybeID =727        instance.kind === FILTERED_FIBER_INSTANCE ? '<no id>' : instance.id;728729      const parentDisplayName =730        parentInstance === null731          ? ''732          : parentInstance.kind === VIRTUAL_INSTANCE733            ? parentInstance.data.name || 'null'734            : parentInstance.data.tag +735              ':' +736              (getDisplayNameForFiber(parentInstance.data) || 'null');737738      const maybeParentID =739        parentInstance === null ||740        parentInstance.kind === FILTERED_FIBER_INSTANCE741          ? '<no id>'742          : parentInstance.id;743744      console.groupCollapsed(745        `[renderer] %c${name} %c${displayName} (${maybeID}) %c${746          parentInstance ? `${parentDisplayName} (${maybeParentID})` : ''747        } %c${extraString}`,748        'color: red; font-weight: bold;',749        'color: blue;',750        'color: purple;',751        'color: black;',752      );753      console.log(new Error().stack.split('\n').slice(1).join('\n'));754      console.groupEnd();755    }756  }757758  // eslint-disable-next-line no-unused-vars759  function debugTree(instance: DevToolsInstance, indent: number = 0) {760    // $FlowFixMe[constant-condition]761    if (__DEBUG__) {762      const name =763        (instance.kind !== VIRTUAL_INSTANCE764          ? getDisplayNameForFiber(instance.data)765          : instance.data.name) || '';766      console.log(767        '  '.repeat(indent) +768          '- ' +769          (instance.kind === FILTERED_FIBER_INSTANCE ? 0 : instance.id) +770          ' (' +771          name +772          ')',773        'parent',774        instance.parent === null775          ? ' '776          : instance.parent.kind === FILTERED_FIBER_INSTANCE777            ? 0778            : instance.parent.id,779        'next',780        instance.nextSibling === null ? ' ' : instance.nextSibling.id,781      );782      let child = instance.firstChild;783      while (child !== null) {784        debugTree(child, indent + 1);785        child = child.nextSibling;786      }787    }788  }789790  // Configurable Components tree filters.791  const hideElementsWithDisplayNames: Set<RegExp> = new Set();792  const hideElementsWithPaths: Set<RegExp> = new Set();793  const hideElementsWithTypes: Set<ElementType> = new Set();794  const hideElementsWithEnvs: Set<string> = new Set();795  let isInFocusedActivity: boolean = true;796797  // Highlight updates798  let traceUpdatesEnabled: boolean = false;799  const traceUpdatesForNodes: Set<HostInstance> = new Set();800801  function applyComponentFilters(802    componentFilters: Array<ComponentFilter>,803    nextActivitySlice: null | Fiber,804  ) {805    hideElementsWithTypes.clear();806    hideElementsWithDisplayNames.clear();807    hideElementsWithPaths.clear();808    hideElementsWithEnvs.clear();809    const previousFocusedActivityID = focusedActivityID;810    focusedActivityID = null;811    focusedActivity = null;812    // Consider everything in the slice by default813    isInFocusedActivity = true;814815    componentFilters.forEach(componentFilter => {816      if (!componentFilter.isEnabled) {817        return;818      }819820      switch (componentFilter.type) {821        case ComponentFilterDisplayName:822          if (componentFilter.isValid && componentFilter.value !== '') {823            hideElementsWithDisplayNames.add(824              new RegExp(componentFilter.value, 'i'),825            );826          }827          break;828        case ComponentFilterElementType:829          hideElementsWithTypes.add(componentFilter.value);830          break;831        case ComponentFilterLocation:832          if (componentFilter.isValid && componentFilter.value !== '') {833            hideElementsWithPaths.add(new RegExp(componentFilter.value, 'i'));834          }835          break;836        case ComponentFilterHOC:837          hideElementsWithDisplayNames.add(new RegExp('\\('));838          break;839        case ComponentFilterEnvironmentName:840          hideElementsWithEnvs.add(componentFilter.value);841          break;842        case ComponentFilterActivitySlice:843          if (844            nextActivitySlice !== null &&845            nextActivitySlice.tag === ActivityComponent846          ) {847            focusedActivity = nextActivitySlice;848            isInFocusedActivity = false;849            if (componentFilter.rendererID !== rendererID) {850              // We filtered an Activity from another renderer.851              // We need to restore the instance ID since we won't be mounting it852              // in this renderer.853              focusedActivityID = previousFocusedActivityID;854            }855          } else {856            // We're not filtering by activity slice after all.857            // Don't mark the filter as disabled here.858            // Otherwise updateComponentFilters() will think no enabled filter was changed.859          }860          break;861        default:862          console.warn(863            `Invalid component filter type "${componentFilter.type}"`,864          );865          break;866      }867    });868  }869870  if (Array.isArray(componentFiltersOrComponentFiltersPromise)) {871    applyComponentFilters(componentFiltersOrComponentFiltersPromise, null);872  } else {873    componentFiltersOrComponentFiltersPromise.then(componentFilters => {874      applyComponentFilters(componentFilters, null);875    });876  }877878  // If necessary, we can revisit optimizing this operation.879  // For example, we could add a new recursive unmount tree operation.880  // The unmount operations are already significantly smaller than mount operations though.881  // This is something to keep in mind for later.882  function updateComponentFilters(componentFilters: Array<ComponentFilter>) {883    if (isProfiling) {884      // Re-mounting a tree while profiling is in progress might break a lot of assumptions.885      // If necessary, we could support this- but it doesn't seem like a necessary use case.886      // Supporting change of filters while profiling would require a refactor887      // to flush after each root instead of at the end.888      throw Error('Cannot modify filter preferences while profiling');889    }890891    const previousForcedFallbacks =892      forceFallbackForFibers.size > 0 ? new Set(forceFallbackForFibers) : null;893    const previousForcedErrors =894      forceErrorForFibers.size > 0 ? new Map(forceErrorForFibers) : null;895896    // The ID will be based on the old tree. We need to find the Fiber based on897    // that ID before we unmount everything. We set the activity slice ID once898    // we mount it again.899    let nextFocusedActivity: null | Fiber = null;900    let focusedActivityFilter: null | ActivitySliceFilter = null;901    for (let i = 0; i < componentFilters.length; i++) {902      const filter = componentFilters[i];903      if (filter.type === ComponentFilterActivitySlice && filter.isEnabled) {904        focusedActivityFilter = filter;905        const instance = idToDevToolsInstanceMap.get(filter.activityID);906        if (instance !== undefined && instance.kind === FIBER_INSTANCE) {907          nextFocusedActivity = instance.data;908        }909      }910    }911912    // Recursively unmount all roots.913    hook.getFiberRoots(rendererID).forEach(root => {914      const rootInstance = rootToFiberInstanceMap.get(root);915      if (rootInstance === undefined) {916        throw new Error(917          'Expected the root instance to already exist when applying filters',918        );919      }920      currentRoot = rootInstance;921      unmountInstanceRecursively(rootInstance);922      rootToFiberInstanceMap.delete(root);923      currentRoot = null as any;924    });925926    if (927      nextFocusedActivity !== focusedActivity &&928      (focusedActivityFilter === null ||929        focusedActivityFilter.rendererID === rendererID)930    ) {931      // When we find the applied instance during mount we will send the actual ID.932      // Otherwise 0 will indicate that we unfocused the activity slice.933      pushOperation(TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE);934      pushOperation(0);935    }936    applyComponentFilters(componentFilters, nextFocusedActivity);937938    // Reset pseudo counters so that new path selections will be persisted.939    rootDisplayNameCounter.clear();940941    // We just cleared all the forced states. Schedule updates on the affected Fibers942    // so that we get their initial states again according to the new filters.943    if (typeof scheduleUpdate === 'function') {944      if (previousForcedFallbacks !== null) {945        // eslint-disable-next-line no-for-of-loops/no-for-of-loops946        for (const fiber of previousForcedFallbacks) {947          if (typeof scheduleRetry === 'function') {948            scheduleRetry(fiber);949          } else {950            scheduleUpdate(fiber);951          }952        }953      }954      if (955        previousForcedErrors !== null &&956        typeof setErrorHandler === 'function'957      ) {958        // Unlike for Suspense, disabling the forced error state requires setting959        // the status to false first. `shouldErrorFiberAccordingToMap` will clear960        // the Fibers later.961        setErrorHandler(shouldErrorFiberAccordingToMap);962        // eslint-disable-next-line no-for-of-loops/no-for-of-loops963        for (const [fiber, shouldError] of previousForcedErrors) {964          forceErrorForFibers.set(fiber, false);965          if (shouldError) {966            if (typeof scheduleRetry === 'function') {967              scheduleRetry(fiber);968            } else {969              scheduleUpdate(fiber);970            }971          }972        }973      }974    }975976    // Recursively re-mount all roots with new filter criteria applied.977    hook.getFiberRoots(rendererID).forEach(root => {978      const current = root.current;979      const newRoot = createFiberInstance(current);980      rootToFiberInstanceMap.set(root, newRoot);981      idToDevToolsInstanceMap.set(newRoot.id, newRoot);982983      // Before the traversals, remember to start tracking984      // our path in case we have selection to restore.985      if (trackedPath !== null) {986        mightBeOnTrackedPath = true;987      }988989      currentRoot = newRoot;990      setRootPseudoKey(currentRoot.id, root.current);991      mountFiberRecursively(root.current, false);992      currentRoot = null as any;993    });994995    // We need to write back the new ID for the focused Fiber.996    // Otherwise subsequent filter applications will try to focus based on the old ID.997    // This is also relevant to filter across renderers.998    if (focusedActivityFilter !== null && focusedActivityID !== null) {999      focusedActivityFilter.activityID = focusedActivityID;1000    }10011002    // We're not profiling so it's safe to flush without a specific root.1003    flushPendingEvents(null);10041005    needsToFlushComponentLogs = false;1006  }10071008  function getEnvironmentNames(): Array<string> {1009    return Array.from(knownEnvironmentNames);1010  }10111012  function isFiberHydrated(fiber: Fiber): boolean {1013    if (OffscreenComponent === -1) {1014      throw new Error('not implemented for legacy suspense');1015    }1016    switch (fiber.tag) {1017      case HostRoot:1018        const rootState = fiber.memoizedState;1019        return !rootState.isDehydrated;1020      case SuspenseComponent:1021        const suspenseState = fiber.memoizedState;1022        return suspenseState === null || suspenseState.dehydrated === null;1023      default:1024        throw new Error('not implemented for work tag ' + fiber.tag);1025    }1026  }10271028  function shouldFilterVirtual(1029    data: ReactComponentInfo,1030    secondaryEnv: null | string,1031  ): boolean {1032    if (!isInFocusedActivity) {1033      return true;1034    }10351036    // For purposes of filtering Server Components are always Function Components.1037    // Environment will be used to filter Server vs Client.1038    // Technically they can be forwardRef and memo too but those filters will go away1039    // as those become just plain user space function components like any HoC.1040    if (hideElementsWithTypes.has(ElementTypeFunction)) {1041      return true;1042    }10431044    if (hideElementsWithDisplayNames.size > 0) {1045      const displayName = data.name;1046      if (displayName != null) {1047        // eslint-disable-next-line no-for-of-loops/no-for-of-loops1048        for (const displayNameRegExp of hideElementsWithDisplayNames) {1049          if (displayNameRegExp.test(displayName)) {1050            return true;1051          }1052        }1053      }1054    }10551056    if (1057      (data.env == null || hideElementsWithEnvs.has(data.env)) &&1058      (secondaryEnv === null || hideElementsWithEnvs.has(secondaryEnv))1059    ) {1060      // If a Component has two environments, you have to filter both for it not to appear.1061      return true;1062    }10631064    return false;1065  }10661067  // NOTICE Keep in sync with get*ForFiber methods1068  function shouldFilterFiber(fiber: Fiber): boolean {1069    const {tag, type, key} = fiber;10701071    // It is never valid to filter the root element.1072    if (tag !== HostRoot && !isInFocusedActivity) {1073      return true;1074    }10751076    switch (tag) {1077      case DehydratedSuspenseComponent:1078        // TODO: ideally we would show dehydrated Suspense immediately.1079        // However, it has some special behavior (like disconnecting1080        // an alternate and turning into real Suspense) which breaks DevTools.1081        // For now, ignore it, and only show it once it gets hydrated.1082        // https://github.com/bvaughn/react-devtools-experimental/issues/1971083        return true;1084      case HostPortal:1085      case HostText:1086      case LegacyHiddenComponent:1087      case OffscreenComponent:1088      case Throw:1089        return true;1090      case HostRoot:1091        // It is never valid to filter the root element.1092        return false;1093      case Fragment:1094        return key === null;1095      default:1096        const typeSymbol = getTypeSymbol(type);10971098        switch (typeSymbol) {1099          case CONCURRENT_MODE_NUMBER:1100          case CONCURRENT_MODE_SYMBOL_STRING:1101          case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:1102          case STRICT_MODE_NUMBER:1103          case STRICT_MODE_SYMBOL_STRING:1104            return true;1105          default:1106            break;1107        }1108    }11091110    const elementType = getElementTypeForFiber(fiber);1111    if (hideElementsWithTypes.has(elementType)) {1112      return true;1113    }11141115    if (hideElementsWithDisplayNames.size > 0) {1116      const displayName = getDisplayNameForFiber(fiber);1117      if (displayName != null) {1118        // eslint-disable-next-line no-for-of-loops/no-for-of-loops1119        for (const displayNameRegExp of hideElementsWithDisplayNames) {1120          if (displayNameRegExp.test(displayName)) {1121            return true;1122          }1123        }1124      }1125    }11261127    if (hideElementsWithEnvs.has('Client')) {1128      // If we're filtering out the Client environment we should filter out all1129      // "Client Components". Technically that also includes the built-ins but1130      // since that doesn't actually include any additional code loading it's1131      // useful to not filter out the built-ins. Those can be filtered separately.1132      // There's no other way to filter out just Function components on the Client.1133      // Therefore, this only filters Class and Function components.1134      switch (tag) {1135        case ClassComponent:1136        case IncompleteClassComponent:1137        case IncompleteFunctionComponent:1138        case FunctionComponent:1139        case IndeterminateComponent:1140        case ForwardRef:1141        case MemoComponent:1142        case SimpleMemoComponent:1143          return true;1144      }1145    }11461147    /* DISABLED: https://github.com/react/react/pull/284171148    if (hideElementsWithPaths.size > 0) {1149      const source = getSourceForFiber(fiber);11501151      if (source != null) {1152        const {fileName} = source;1153        // eslint-disable-next-line no-for-of-loops/no-for-of-loops1154        for (const pathRegExp of hideElementsWithPaths) {1155          if (pathRegExp.test(fileName)) {1156            return true;1157          }1158        }1159      }1160    }1161    */11621163    return false;1164  }11651166  // NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods1167  function getElementTypeForFiber(fiber: Fiber): ElementType {1168    const {type, tag} = fiber;11691170    switch (tag) {1171      case ActivityComponent:1172        return ElementTypeActivity;1173      case ClassComponent:1174      case IncompleteClassComponent:1175        return ElementTypeClass;1176      case IncompleteFunctionComponent:1177      case FunctionComponent:1178      case IndeterminateComponent:1179        return ElementTypeFunction;1180      case ForwardRef:1181        return ElementTypeForwardRef;1182      case HostRoot:1183        return ElementTypeRoot;1184      case HostComponent:1185      case HostHoistable:1186      case HostSingleton:1187        return ElementTypeHostComponent;1188      case HostPortal:1189      case HostText:1190      case Fragment:1191        return ElementTypeOtherOrUnknown;1192      case MemoComponent:1193      case SimpleMemoComponent:1194        return ElementTypeMemo;1195      case SuspenseComponent:1196        return ElementTypeSuspense;1197      case SuspenseListComponent:1198        return ElementTypeSuspenseList;1199      case TracingMarkerComponent:1200        return ElementTypeTracingMarker;1201      case ViewTransitionComponent:1202        return ElementTypeViewTransition;1203      default:1204        const typeSymbol = getTypeSymbol(type);12051206        switch (typeSymbol) {1207          case CONCURRENT_MODE_NUMBER:1208          case CONCURRENT_MODE_SYMBOL_STRING:1209          case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:1210            return ElementTypeOtherOrUnknown;1211          case PROVIDER_NUMBER:1212          case PROVIDER_SYMBOL_STRING:1213            return ElementTypeContext;1214          case CONTEXT_NUMBER:1215          case CONTEXT_SYMBOL_STRING:1216            return ElementTypeContext;1217          case STRICT_MODE_NUMBER:1218          case STRICT_MODE_SYMBOL_STRING:1219            return ElementTypeOtherOrUnknown;1220          case PROFILER_NUMBER:1221          case PROFILER_SYMBOL_STRING:1222            return ElementTypeProfiler;1223          default:1224            return ElementTypeOtherOrUnknown;1225        }1226    }1227  }12281229  // When a mount or update is in progress, this value tracks the root that is being operated on.1230  let currentRoot: FiberInstance = null as any;12311232  // Removes a Fiber (and its alternate) from the Maps used to track their id.1233  // This method should always be called when a Fiber is unmounting.1234  function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) {1235    if (forceErrorForFibers.size > 0) {1236      forceErrorForFibers.delete(fiber);1237      if (fiber.alternate) {1238        forceErrorForFibers.delete(fiber.alternate);1239      }1240      if (forceErrorForFibers.size === 0 && setErrorHandler != null) {1241        setErrorHandler(shouldErrorFiberAlwaysNull);1242      }1243    }12441245    if (forceFallbackForFibers.size > 0) {1246      forceFallbackForFibers.delete(fiber);1247      if (fiber.alternate) {1248        forceFallbackForFibers.delete(fiber.alternate);1249      }1250      if (forceFallbackForFibers.size === 0 && setSuspenseHandler != null) {1251        setSuspenseHandler(shouldSuspendFiberAlwaysFalse);1252      }1253    }12541255    // TODO: Consider using a WeakMap instead. The only thing where that doesn't work1256    // is React Native Paper which tracks tags but that support is eventually going away1257    // and can use the old findFiberByHostInstance strategy.12581259    if (fiber.tag === HostHoistable) {1260      releaseHostResource(nearestInstance, fiber.memoizedState);1261    } else if (1262      fiber.tag === HostComponent ||1263      fiber.tag === HostText ||1264      fiber.tag === HostSingleton1265    ) {1266      releaseHostInstance(nearestInstance, fiber.stateNode);1267    }12681269    // Recursively clean up any filtered Fibers below this one as well since1270    // we won't recordUnmount on those.1271    for (let child = fiber.child; child !== null; child = child.sibling) {1272      if (shouldFilterFiber(child)) {1273        untrackFiber(nearestInstance, child);1274      }1275    }1276  }12771278  function getChangeDescription(1279    prevFiber: Fiber | null,1280    nextFiber: Fiber,1281  ): ChangeDescription | null {1282    switch (nextFiber.tag) {1283      case ClassComponent:1284        if (prevFiber === null) {1285          return {1286            context: null,1287            didHooksChange: false,1288            isFirstMount: true,1289            props: null,1290            state: null,1291          };1292        } else {1293          const data: ChangeDescription = {1294            context: getContextChanged(prevFiber, nextFiber),1295            didHooksChange: false,1296            isFirstMount: false,1297            props: getChangedKeys(1298              prevFiber.memoizedProps,1299              nextFiber.memoizedProps,1300            ),1301            state: getChangedKeys(1302              prevFiber.memoizedState,1303              nextFiber.memoizedState,1304            ),1305          };1306          return data;1307        }1308      case IncompleteFunctionComponent:1309      case FunctionComponent:1310      case IndeterminateComponent:1311      case ForwardRef:1312      case MemoComponent:1313      case SimpleMemoComponent:1314        if (prevFiber === null) {1315          return {1316            context: null,1317            didHooksChange: false,1318            isFirstMount: true,1319            props: null,1320            state: null,1321          };1322        } else {1323          const prevHooks = inspectHooks(prevFiber);1324          const nextHooks = inspectHooks(nextFiber);1325          const indices = getChangedHooksIndices(prevHooks, nextHooks);1326          const data: ChangeDescription = {1327            context: getContextChanged(prevFiber, nextFiber),1328            didHooksChange: indices !== null && indices.length > 0,1329            isFirstMount: false,1330            props: getChangedKeys(1331              prevFiber.memoizedProps,1332              nextFiber.memoizedProps,1333            ),1334            state: null,1335            hooks: indices,1336          };1337          // Only traverse the hooks list once, depending on what info we're returning.1338          return data;1339        }1340      default:1341        return null;1342    }1343  }13441345  type OperationsArray = Array<number>;13461347  type StringTableEntry = {1348    encodedString: Array<number>,1349    id: number,1350  };13511352  const pendingOperations: OperationsArray = [];1353  const pendingRealUnmountedIDs: Array<FiberInstance['id']> = [];1354  const pendingRealUnmountedSuspenseIDs: Array<FiberInstance['id']> = [];1355  const pendingSuspenderChanges: Set<FiberInstance['id']> = new Set();1356  let pendingOperationsQueue: Array<OperationsArray> | null = [];1357  const pendingStringTable: Map<string, StringTableEntry> = new Map();1358  let pendingStringTableLength: number = 0;13591360  function pushOperation(op: number): void {1361    if (__DEV__) {1362      if (!Number.isInteger(op)) {1363        console.error(1364          'pushOperation() was called but the value is not an integer.',1365          op,1366        );1367      }1368    }1369    pendingOperations.push(op);1370  }13711372  function shouldBailoutWithPendingOperations() {1373    if (isProfiling) {1374      if (1375        currentCommitProfilingMetadata != null &&1376        currentCommitProfilingMetadata.durations.length > 01377      ) {1378        return false;1379      }1380    }13811382    return (1383      pendingOperations.length === 0 &&1384      pendingRealUnmountedIDs.length === 0 &&1385      pendingRealUnmountedSuspenseIDs.length === 0 &&1386      pendingSuspenderChanges.size === 01387    );1388  }13891390  function flushOrQueueOperations(operations: OperationsArray): void {1391    if (shouldBailoutWithPendingOperations()) {1392      return;1393    }13941395    if (pendingOperationsQueue !== null) {1396      pendingOperationsQueue.push(operations);1397    } else {1398      hook.emit('operations', operations);1399    }1400  }14011402  function recordConsoleLogs(1403    instance: FiberInstance | VirtualInstance,1404    componentLogsEntry: void | ComponentLogs,1405  ): boolean {1406    if (componentLogsEntry === undefined) {1407      if (instance.logCount === 0) {1408        // Nothing has changed.1409        return false;1410      }1411      // Reset to zero.1412      instance.logCount = 0;1413      pushOperation(TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS);1414      pushOperation(instance.id);1415      pushOperation(0);1416      pushOperation(0);1417      return true;1418    } else {1419      const totalCount =1420        componentLogsEntry.errorsCount + componentLogsEntry.warningsCount;1421      if (instance.logCount === totalCount) {1422        // Nothing has changed.1423        return false;1424      }1425      // Update counts.1426      instance.logCount = totalCount;1427      pushOperation(TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS);1428      pushOperation(instance.id);1429      pushOperation(componentLogsEntry.errorsCount);1430      pushOperation(componentLogsEntry.warningsCount);1431      return true;1432    }1433  }14341435  /**1436   * Allowed to flush pending events without a specific root when:1437   * - pending operations don't record tree mutations e.g. TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS1438   * - not profiling (the commit tree builder requires the root of the mutations)1439   */1440  function flushPendingEvents(root: FiberInstance | null): void {1441    if (shouldBailoutWithPendingOperations()) {1442      // If we aren't profiling, we can just bail out here.1443      // No use sending an empty update over the bridge.1444      //1445      // The Profiler stores metadata for each commit and reconstructs the app tree per commit using:1446      // (1) an initial tree snapshot and1447      // (2) the operations array for each commit1448      // Because of this, it's important that the operations and metadata arrays align,1449      // So it's important not to omit even empty operations while profiling is active.1450      return;1451    }14521453    const numUnmountIDs = pendingRealUnmountedIDs.length;1454    const numUnmountSuspenseIDs = pendingRealUnmountedSuspenseIDs.length;1455    const numSuspenderChanges = pendingSuspenderChanges.size;14561457    const operations = new Array<number>(1458      // Identify which renderer this update is coming from.1459      2 + // [rendererID, rootFiberID]1460        // How big is the string table?1461        1 + // [stringTableLength]1462        // Then goes the actual string table.1463        pendingStringTableLength +1464        // All unmounts of Suspense boundaries are batched in a single message.1465        // [TREE_OPERATION_REMOVE_SUSPENSE, removedSuspenseIDLength, ...ids]1466        (numUnmountSuspenseIDs > 0 ? 2 + numUnmountSuspenseIDs : 0) +1467        // All unmounts are batched in a single message.1468        // [TREE_OPERATION_REMOVE, removedIDLength, ...ids]1469        (numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +1470        // Regular operations1471        pendingOperations.length +1472        // All suspender changes are batched in a single message.1473        // [SUSPENSE_TREE_OPERATION_SUSPENDERS, suspenderChangesLength, ...[id, hasUniqueSuspenders, endTime, isSuspended]]1474        (numSuspenderChanges > 0 ? 2 + numSuspenderChanges * 4 : 0),1475    );14761477    // Identify which renderer this update is coming from.1478    // This enables roots to be mapped to renderers,1479    // Which in turn enables fiber props, states, and hooks to be inspected.1480    let i = 0;1481    operations[i++] = rendererID;1482    if (root === null) {1483      operations[i++] = -1;1484    } else {1485      operations[i++] = root.id;1486    }14871488    // Now fill in the string table.1489    // [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]1490    operations[i++] = pendingStringTableLength;1491    pendingStringTable.forEach((entry, stringKey) => {1492      const encodedString = entry.encodedString;14931494      // Don't use the string length.1495      // It won't work for multibyte characters (like emoji).1496      const length = encodedString.length;14971498      operations[i++] = length;1499      for (let j = 0; j < length; j++) {1500        operations[i + j] = encodedString[j];1501      }15021503      i += length;1504    });15051506    if (numUnmountSuspenseIDs > 0) {1507      // All unmounts of Suspense boundaries are batched in a single message.1508      operations[i++] = SUSPENSE_TREE_OPERATION_REMOVE;1509      // The first number is how many unmounted IDs we're gonna send.1510      operations[i++] = numUnmountSuspenseIDs;1511      // Fill in the real unmounts in the reverse order.1512      // They were inserted parents-first by React, but we want children-first.1513      // So we traverse our array backwards.1514      for (let j = 0; j < pendingRealUnmountedSuspenseIDs.length; j++) {1515        operations[i++] = pendingRealUnmountedSuspenseIDs[j];1516      }1517    }15181519    if (numUnmountIDs > 0) {1520      // All unmounts except roots are batched in a single message.1521      operations[i++] = TREE_OPERATION_REMOVE;1522      // The first number is how many unmounted IDs we're gonna send.1523      operations[i++] = numUnmountIDs;1524      // Fill in the real unmounts in the reverse order.1525      // They were inserted parents-first by React, but we want children-first.1526      // So we traverse our array backwards.1527      for (let j = 0; j < pendingRealUnmountedIDs.length; j++) {1528        operations[i++] = pendingRealUnmountedIDs[j];1529      }1530    }15311532    // Fill in pending operations.1533    for (let j = 0; j < pendingOperations.length; j++) {1534      operations[i + j] = pendingOperations[j];1535    }1536    i += pendingOperations.length;15371538    // Suspender changes might affect newly mounted nodes that we already recorded1539    // in pending operations.1540    if (numSuspenderChanges > 0) {1541      operations[i++] = SUSPENSE_TREE_OPERATION_SUSPENDERS;1542      operations[i++] = numSuspenderChanges;1543      pendingSuspenderChanges.forEach(fiberIdWithChanges => {1544        const suspense = idToSuspenseNodeMap.get(fiberIdWithChanges);1545        if (suspense === undefined) {1546          // Probably forgot to cleanup pendingSuspenderChanges when this node was removed.1547          throw new Error(1548            `Could not send suspender changes for "${fiberIdWithChanges}" since the Fiber no longer exists.`,1549          );1550        }1551        operations[i++] = fiberIdWithChanges;1552        operations[i++] = suspense.hasUniqueSuspenders ? 1 : 0;1553        operations[i++] = Math.round(suspense.endTime * 1000);1554        const instance = suspense.instance;1555        const isSuspended =1556          // TODO: Track if other SuspenseNode like SuspenseList rows are suspended.1557          (instance.kind === FIBER_INSTANCE ||1558            instance.kind === FILTERED_FIBER_INSTANCE) &&1559          instance.data.tag === SuspenseComponent &&1560          instance.data.memoizedState !== null;1561        operations[i++] = isSuspended ? 1 : 0;1562        operations[i++] = suspense.environments.size;1563        suspense.environments.forEach((count, env) => {1564          operations[i++] = getStringID(env);1565        });1566      });1567    }15681569    // Let the frontend know about tree operations.1570    flushOrQueueOperations(operations);15711572    // Reset all of the pending state now that we've told the frontend about it.1573    pendingOperations.length = 0;1574    pendingRealUnmountedIDs.length = 0;1575    pendingRealUnmountedSuspenseIDs.length = 0;1576    pendingSuspenderChanges.clear();1577    pendingStringTable.clear();1578    pendingStringTableLength = 0;1579  }15801581  function measureHostInstance(instance: HostInstance): null | Array<Rect> {1582    // Feature detect measurement capabilities of this environment.1583    // TODO: Consider making this capability injected by the ReactRenderer.1584    if (typeof instance !== 'object' || instance === null) {1585      return null;1586    }1587    if (1588      typeof instance.getClientRects === 'function' ||1589      instance.nodeType === 31590    ) {1591      // DOM1592      const doc = instance.ownerDocument;1593      if (instance === doc.documentElement) {1594        // This is the document element. The size of this element is not actually1595        // what determines the whole scrollable area of the screen. Because any1596        // thing that overflows the document will also contribute to the scrollable.1597        // This is unlike overflow: scroll which clips those.1598        // Therefore, we use the scrollable size for this rect instead.1599        return [1600          {1601            x: 0,1602            y: 0,1603            width: instance.scrollWidth,1604            height: instance.scrollHeight,1605          },1606        ];1607      }1608      const result: Array<Rect> = [];1609      const win = doc && doc.defaultView;1610      const scrollX = win ? win.scrollX : 0;1611      const scrollY = win ? win.scrollY : 0;1612      let rects;1613      if (instance.nodeType === 3) {1614        // Text nodes cannot be measured directly but we can measure a Range.1615        if (typeof doc.createRange !== 'function') {1616          return null;1617        }1618        const range = doc.createRange();1619        if (typeof range.getClientRects !== 'function') {1620          return null;1621        }1622        range.selectNodeContents(instance);1623        rects = range.getClientRects();1624      } else {1625        rects = instance.getClientRects();1626      }1627      for (let i = 0; i < rects.length; i++) {1628        const rect = rects[i];1629        result.push({1630          x: rect.x + scrollX,1631          y: rect.y + scrollY,1632          width: rect.width,1633          height: rect.height,1634        });1635      }1636      return result;1637    }1638    if (instance.canonical) {1639      // Native1640      const publicInstance = instance.canonical.publicInstance;1641      if (!publicInstance) {1642        // The publicInstance may not have been initialized yet if there was no ref on this node.1643        // We can't initialize it from any existing Hook but we could fallback to this async form:1644        // renderer.extraDevToolsConfig.getInspectorDataForInstance(instance).hierarchy[last].getInspectorData().measure(callback)1645        return null;1646      }1647      if (typeof publicInstance.getBoundingClientRect === 'function') {1648        // enableAccessToHostTreeInFabric / ReadOnlyElement1649        return [publicInstance.getBoundingClientRect()];1650      }1651      if (typeof publicInstance.unstable_getBoundingClientRect === 'function') {1652        // ReactFabricHostComponent1653        return [publicInstance.unstable_getBoundingClientRect()];1654      }1655    }1656    return null;1657  }16581659  function measureInstance(instance: DevToolsInstance): null | Array<Rect> {1660    // Synchronously return the client rects of the Host instances directly inside this Instance.1661    const hostInstances = findAllCurrentHostInstances(instance);1662    let result: null | Array<Rect> = null;1663    for (let i = 0; i < hostInstances.length; i++) {1664      const childResult = measureHostInstance(hostInstances[i]);1665      if (childResult !== null) {1666        if (result === null) {1667          result = childResult;1668        } else {1669          result = result.concat(childResult);1670        }1671      }1672    }1673    return result;1674  }16751676  function getStringID(string: string | null): number {1677    if (string === null) {1678      return 0;1679    }1680    const existingEntry = pendingStringTable.get(string);1681    if (existingEntry !== undefined) {1682      return existingEntry.id;1683    }16841685    const id = pendingStringTable.size + 1;1686    const encodedString = utfEncodeString(string);16871688    pendingStringTable.set(string, {1689      encodedString,1690      id,1691    });16921693    // The string table total length needs to account both for the string length,1694    // and for the array item that contains the length itself.1695    //1696    // Don't use string length for this table.1697    // It won't work for multibyte characters (like emoji).1698    pendingStringTableLength += encodedString.length + 1;16991700    return id;1701  }17021703  let isInDisconnectedSubtree = false;17041705  function recordMount(1706    fiber: Fiber,1707    parentInstance: DevToolsInstance | null,1708  ): FiberInstance {1709    const isRoot = fiber.tag === HostRoot;1710    let fiberInstance;1711    if (isRoot) {1712      const entry = rootToFiberInstanceMap.get(fiber.stateNode);1713      if (entry === undefined) {1714        throw new Error('The root should have been registered at this point');1715      }1716      fiberInstance = entry;1717    } else {1718      fiberInstance = createFiberInstance(fiber);1719    }1720    idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance);17211722    // $FlowFixMe[constant-condition]1723    if (__DEBUG__) {1724      debug('recordMount()', fiberInstance, parentInstance);1725    }17261727    recordReconnect(fiberInstance, parentInstance);1728    return fiberInstance;1729  }17301731  function recordReconnect(1732    fiberInstance: FiberInstance,1733    parentInstance: DevToolsInstance | null,1734  ): void {1735    if (isInDisconnectedSubtree) {1736      // We're disconnected. We'll reconnect a hidden mount after the parent reappears.1737      return;1738    }1739    const id = fiberInstance.id;1740    const fiber = fiberInstance.data;17411742    const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');17431744    const isRoot = fiber.tag === HostRoot;17451746    if (isRoot) {1747      const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');17481749      // Adding a new field here would require a bridge protocol version bump (a backwads breaking change).1750      // Instead let's re-purpose a pre-existing field to carry more information.1751      let profilingFlags = 0;1752      if (isProfilingSupported) {1753        profilingFlags = PROFILING_FLAG_BASIC_SUPPORT;1754        if (supportsPerformanceTracks) {1755          profilingFlags |= PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT;1756        }1757      }17581759      // Set supportsStrictMode to false for production renderer builds1760      const isProductionBuildOfRenderer = renderer.bundleType === 0;17611762      pushOperation(TREE_OPERATION_ADD);1763      pushOperation(id);1764      pushOperation(ElementTypeRoot);1765      pushOperation((fiber.mode & StrictModeBits) !== 0 ? 1 : 0);1766      pushOperation(profilingFlags);1767      pushOperation(1768        !isProductionBuildOfRenderer && StrictModeBits !== 0 ? 1 : 0,1769      );1770      pushOperation(hasOwnerMetadata ? 1 : 0);17711772      if (isProfiling) {1773        if (displayNamesByRootID !== null) {1774          displayNamesByRootID.set(id, getDisplayNameForRoot(fiber));1775        }1776      }1777    } else {1778      const suspenseNode = fiberInstance.suspenseNode;1779      if (suspenseNode !== null && fiber.memoizedState === null) {1780        // We're reconnecting an unsuspended Suspense. Measure to see if anything changed.1781        const prevRects = suspenseNode.rects;1782        const nextRects = measureInstance(fiberInstance);1783        if (!areEqualRects(prevRects, nextRects)) {1784          suspenseNode.rects = nextRects;1785          recordSuspenseResize(suspenseNode);1786        }1787      }17881789      const {key} = fiber;1790      const displayName = getDisplayNameForFiber(fiber);1791      const elementType = getElementTypeForFiber(fiber);17921793      // Finding the owner instance might require traversing the whole parent path which1794      // doesn't have great big O notation. Ideally we'd lazily fetch the owner when we1795      // need it but we have some synchronous operations in the front end like Alt+Left1796      // which selects the owner immediately. Typically most owners are only a few parents1797      // away so maybe it's not so bad.1798      const debugOwner = getUnfilteredOwner(fiber);1799      const ownerInstance = findNearestOwnerInstance(1800        parentInstance,1801        debugOwner,1802      );1803      if (1804        ownerInstance !== null &&1805        debugOwner === fiber._debugOwner &&1806        fiber._debugStack != null &&1807        ownerInstance.source === null1808      ) {1809        // The new Fiber is directly owned by the ownerInstance. Therefore somewhere on1810        // the debugStack will be a stack frame inside the ownerInstance's source.1811        ownerInstance.source = fiber._debugStack;1812      }18131814      let unfilteredParent = parentInstance;1815      while (1816        unfilteredParent !== null &&1817        unfilteredParent.kind === FILTERED_FIBER_INSTANCE1818      ) {1819        unfilteredParent = unfilteredParent.parent;1820      }18211822      const ownerID = ownerInstance === null ? 0 : ownerInstance.id;1823      const parentID = unfilteredParent === null ? 0 : unfilteredParent.id;18241825      const displayNameStringID = getStringID(displayName);18261827      // This check is a guard to handle a React element that has been modified1828      // in such a way as to bypass the default stringification of the "key" property.1829      const keyString =1830        key === null1831          ? null1832          : key === REACT_OPTIMISTIC_KEY1833            ? 'React.optimisticKey'1834            : String(key);1835      const keyStringID = getStringID(keyString);18361837      const nameProp =1838        fiber.tag === SuspenseComponent1839          ? fiber.memoizedProps.name1840          : fiber.tag === ActivityComponent1841            ? fiber.memoizedProps.name1842            : null;1843      const namePropString = nameProp == null ? null : String(nameProp);1844      const namePropStringID = getStringID(namePropString);18451846      pushOperation(TREE_OPERATION_ADD);1847      pushOperation(id);1848      pushOperation(elementType);1849      pushOperation(parentID);1850      pushOperation(ownerID);1851      pushOperation(displayNameStringID);1852      pushOperation(keyStringID);1853      pushOperation(namePropStringID);18541855      // If this subtree has a new mode, let the frontend know.1856      if ((fiber.mode & StrictModeBits) !== 0) {1857        let parentFiber = null;1858        let parentFiberInstance = parentInstance;1859        while (parentFiberInstance !== null) {1860          if (parentFiberInstance.kind === FIBER_INSTANCE) {1861            parentFiber = parentFiberInstance.data;1862            break;1863          }1864          parentFiberInstance = parentFiberInstance.parent;1865        }1866        if (parentFiber === null || (parentFiber.mode & StrictModeBits) === 0) {1867          pushOperation(TREE_OPERATION_SET_SUBTREE_MODE);1868          pushOperation(id);1869          pushOperation(StrictMode);1870        }1871      }18721873      // If this is an Activity component, check if it's hidden.1874      if (fiber.tag === ActivityComponent) {1875        const offscreenChild = fiber.child;1876        if (1877          offscreenChild !== null &&1878          offscreenChild.tag === OffscreenComponent &&1879          offscreenChild.memoizedState !== null1880        ) {1881          pushOperation(TREE_OPERATION_SET_SUBTREE_MODE);1882          pushOperation(id);1883          pushOperation(ActivityHiddenMode);1884        }1885      }1886    }18871888    let componentLogsEntry = fiberToComponentLogsMap.get(fiber);1889    if (componentLogsEntry === undefined && fiber.alternate !== null) {1890      componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);1891    }1892    recordConsoleLogs(fiberInstance, componentLogsEntry);18931894    if (isProfilingSupported) {1895      recordProfilingDurations(fiberInstance, null);1896    }1897  }18981899  function recordVirtualMount(1900    instance: VirtualInstance,1901    parentInstance: DevToolsInstance | null,1902    secondaryEnv: null | string,1903  ): void {1904    const id = instance.id;19051906    idToDevToolsInstanceMap.set(id, instance);19071908    recordVirtualReconnect(instance, parentInstance, secondaryEnv);1909  }19101911  function recordVirtualReconnect(1912    instance: VirtualInstance,1913    parentInstance: DevToolsInstance | null,1914    secondaryEnv: null | string,1915  ): void {1916    if (isInDisconnectedSubtree) {1917      // We're disconnected. We'll reconnect a hidden mount after the parent reappears.1918      return;1919    }1920    const componentInfo = instance.data;19211922    const key =1923      typeof componentInfo.key === 'string' ? componentInfo.key : null;1924    const env = componentInfo.env;1925    let displayName = componentInfo.name || '';1926    if (typeof env === 'string') {1927      // We model environment as an HoC name for now.1928      if (secondaryEnv !== null) {1929        displayName = secondaryEnv + '(' + displayName + ')';1930      }1931      displayName = env + '(' + displayName + ')';1932    }1933    const elementType = ElementTypeVirtual;19341935    // Finding the owner instance might require traversing the whole parent path which1936    // doesn't have great big O notation. Ideally we'd lazily fetch the owner when we1937    // need it but we have some synchronous operations in the front end like Alt+Left1938    // which selects the owner immediately. Typically most owners are only a few parents1939    // away so maybe it's not so bad.1940    const debugOwner = getUnfilteredOwner(componentInfo);1941    const ownerInstance = findNearestOwnerInstance(parentInstance, debugOwner);1942    if (1943      ownerInstance !== null &&1944      debugOwner === componentInfo.owner &&1945      componentInfo.debugStack != null &&1946      ownerInstance.source === null1947    ) {1948      // The new Fiber is directly owned by the ownerInstance. Therefore somewhere on1949      // the debugStack will be a stack frame inside the ownerInstance's source.1950      ownerInstance.source = componentInfo.debugStack;1951    }19521953    let unfilteredParent = parentInstance;1954    while (1955      unfilteredParent !== null &&1956      unfilteredParent.kind === FILTERED_FIBER_INSTANCE1957    ) {1958      unfilteredParent = unfilteredParent.parent;1959    }19601961    const ownerID = ownerInstance === null ? 0 : ownerInstance.id;1962    const parentID = unfilteredParent === null ? 0 : unfilteredParent.id;19631964    const displayNameStringID = getStringID(displayName);19651966    // This check is a guard to handle a React element that has been modified1967    // in such a way as to bypass the default stringification of the "key" property.1968    const keyString = key === null ? null : String(key);1969    const keyStringID = getStringID(keyString);1970    const namePropStringID = getStringID(null);19711972    const id = instance.id;19731974    pushOperation(TREE_OPERATION_ADD);1975    pushOperation(id);1976    pushOperation(elementType);1977    pushOperation(parentID);1978    pushOperation(ownerID);1979    pushOperation(displayNameStringID);1980    pushOperation(keyStringID);1981    pushOperation(namePropStringID);19821983    const componentLogsEntry =1984      componentInfoToComponentLogsMap.get(componentInfo);1985    recordConsoleLogs(instance, componentLogsEntry);1986  }19871988  function recordSuspenseMount(1989    suspenseInstance: SuspenseNode,1990    parentSuspenseInstance: SuspenseNode | null,1991  ): void {1992    const fiberInstance = suspenseInstance.instance;1993    if (fiberInstance.kind === FILTERED_FIBER_INSTANCE) {1994      throw new Error('Cannot record a mount for a filtered Fiber instance.');1995    }1996    const fiberID = fiberInstance.id;19971998    let unfilteredParent = parentSuspenseInstance;1999    while (2000      unfilteredParent !== null &&

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.

Generated by searchcode.com · powered by code_get_file + code_get_findings