Files
react/packages/react-devtools-shared/src/backend/fiber/DevToolsFiberComponentStack.js
Sebastian Markbåge c4595ca4c8 Add ViewTransitionComponent to Stacks and DevTools (#32034)
Just adding the name so it shows up.

(Note that no experimental ones are added to the list of filters atm.
Including SuspenseList etc.)
2025-01-09 11:33:34 -05:00

226 lines
7.3 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// This is a DevTools fork of ReactFiberComponentStack.
// This fork enables DevTools to use the same "native" component stack format,
// while still maintaining support for multiple renderer versions
// (which use different values for ReactTypeOfWork).
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {CurrentDispatcherRef, WorkTagMap} from '../types';
import type {ReactComponentInfo} from 'shared/ReactTypes';
import {
describeBuiltInComponentFrame,
describeFunctionComponentFrame,
describeClassComponentFrame,
describeDebugInfoFrame,
} from '../shared/DevToolsComponentStackFrame';
import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
export function describeFiber(
workTagMap: WorkTagMap,
workInProgress: Fiber,
currentDispatcherRef: CurrentDispatcherRef,
): string {
const {
HostHoistable,
HostSingleton,
HostComponent,
LazyComponent,
SuspenseComponent,
SuspenseListComponent,
FunctionComponent,
IndeterminateComponent,
SimpleMemoComponent,
ForwardRef,
ClassComponent,
ViewTransitionComponent,
} = workTagMap;
switch (workInProgress.tag) {
case HostHoistable:
case HostSingleton:
case HostComponent:
return describeBuiltInComponentFrame(workInProgress.type);
case LazyComponent:
// TODO: When we support Thenables as component types we should rename this.
return describeBuiltInComponentFrame('Lazy');
case SuspenseComponent:
return describeBuiltInComponentFrame('Suspense');
case SuspenseListComponent:
return describeBuiltInComponentFrame('SuspenseList');
case ViewTransitionComponent:
return describeBuiltInComponentFrame('ViewTransition');
case FunctionComponent:
case IndeterminateComponent:
case SimpleMemoComponent:
return describeFunctionComponentFrame(
workInProgress.type,
currentDispatcherRef,
);
case ForwardRef:
return describeFunctionComponentFrame(
workInProgress.type.render,
currentDispatcherRef,
);
case ClassComponent:
return describeClassComponentFrame(
workInProgress.type,
currentDispatcherRef,
);
default:
return '';
}
}
export function getStackByFiberInDevAndProd(
workTagMap: WorkTagMap,
workInProgress: Fiber,
currentDispatcherRef: CurrentDispatcherRef,
): string {
try {
let info = '';
let node: Fiber = workInProgress;
do {
info += describeFiber(workTagMap, node, currentDispatcherRef);
// Add any Server Component stack frames in reverse order.
const debugInfo = node._debugInfo;
if (debugInfo) {
for (let i = debugInfo.length - 1; i >= 0; i--) {
const entry = debugInfo[i];
if (typeof entry.name === 'string') {
info += describeDebugInfoFrame(entry.name, entry.env);
}
}
}
// $FlowFixMe[incompatible-type] we bail out when we get a null
node = node.return;
} while (node);
return info;
} catch (x) {
return '\nError generating stack: ' + x.message + '\n' + x.stack;
}
}
export function getSourceLocationByFiber(
workTagMap: WorkTagMap,
fiber: Fiber,
currentDispatcherRef: CurrentDispatcherRef,
): null | string {
// This is like getStackByFiberInDevAndProd but just the first stack frame.
try {
const info = describeFiber(workTagMap, fiber, currentDispatcherRef);
if (info !== '') {
return info.slice(1); // skip the leading newline
}
} catch (x) {
console.error(x);
}
return null;
}
export function supportsConsoleTasks(fiber: Fiber): boolean {
// If this Fiber supports native console.createTask then we are already running
// inside a native async stack trace if it's active - meaning the DevTools is open.
// Ideally we'd detect if this task was created while the DevTools was open or not.
return !!fiber._debugTask;
}
export function supportsOwnerStacks(fiber: Fiber): boolean {
// If this Fiber supports owner stacks then it'll have the _debugStack field.
// It might be null but that still means we should use the owner stack logic.
return fiber._debugStack !== undefined;
}
export function getOwnerStackByFiberInDev(
workTagMap: WorkTagMap,
workInProgress: Fiber,
currentDispatcherRef: CurrentDispatcherRef,
): string {
const {
HostHoistable,
HostSingleton,
HostText,
HostComponent,
SuspenseComponent,
SuspenseListComponent,
ViewTransitionComponent,
} = workTagMap;
try {
let info = '';
if (workInProgress.tag === HostText) {
// Text nodes never have an owner/stack because they're not created through JSX.
// We use the parent since text nodes are always created through a host parent.
workInProgress = (workInProgress.return: any);
}
// The owner stack of the current fiber will be where it was created, i.e. inside its owner.
// There's no actual name of the currently executing component. Instead, that is available
// on the regular stack that's currently executing. However, for built-ins there is no such
// named stack frame and it would be ignored as being internal anyway. Therefore we add
// add one extra frame just to describe the "current" built-in component by name.
switch (workInProgress.tag) {
case HostHoistable:
case HostSingleton:
case HostComponent:
info += describeBuiltInComponentFrame(workInProgress.type);
break;
case SuspenseComponent:
info += describeBuiltInComponentFrame('Suspense');
break;
case SuspenseListComponent:
info += describeBuiltInComponentFrame('SuspenseList');
break;
case ViewTransitionComponent:
info += describeBuiltInComponentFrame('ViewTransition');
break;
}
let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
while (owner) {
if (typeof owner.tag === 'number') {
const fiber: Fiber = (owner: any);
owner = fiber._debugOwner;
let debugStack = fiber._debugStack;
// If we don't actually print the stack if there is no owner of this JSX element.
// In a real app it's typically not useful since the root app is always controlled
// by the framework. These also tend to have noisy stacks because they're not rooted
// in a React render but in some imperative bootstrapping code. It could be useful
// if the element was created in module scope. E.g. hoisted. We could add a a single
// stack frame for context for example but it doesn't say much if that's a wrapper.
if (owner && debugStack) {
if (typeof debugStack !== 'string') {
debugStack = formatOwnerStack(debugStack);
}
if (debugStack !== '') {
info += '\n' + debugStack;
}
}
} else if (owner.debugStack != null) {
// Server Component
const ownerStack: Error = owner.debugStack;
owner = owner.owner;
if (owner && ownerStack) {
info += '\n' + formatOwnerStack(ownerStack);
}
} else {
break;
}
}
return info;
} catch (x) {
return '\nError generating stack: ' + x.message + '\n' + x.stack;
}
}