Files
react/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
Sebastian Markbåge 738aebdbac [DevTools] Add Badge to Owners and sometimes stack traces (#34106)
Stacked on #34101.

This adds a badge to owners if they are different from the currently
selected component's environment.

<img width="590" height="566" alt="Screenshot 2025-08-04 at 5 15 02 PM"
src="https://github.com/user-attachments/assets/e898254f-1b4c-498e-8713-978d90545340"
/>

We also add one to the end of stack traces if the stack trace has a
different environment than the owner which can happen when you call a
function (without rendering a component) into a third party environment
but the owner component was in the first party.

One awkward thing is that Suspense boundaries are always in the client
environment so their Server Components are always badged.
2025-08-07 10:39:08 -04:00

104 lines
2.6 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
*/
import * as React from 'react';
import {use, useContext} from 'react';
import useOpenResource from '../useOpenResource';
import ElementBadges from './ElementBadges';
import styles from './StackTraceView.css';
import type {
ReactStackTrace,
ReactCallSite,
ReactFunctionLocation,
} from 'shared/ReactTypes';
import FetchFileWithCachingContext from './FetchFileWithCachingContext';
import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
import formatLocationForDisplay from './formatLocationForDisplay';
type CallSiteViewProps = {
callSite: ReactCallSite,
environmentName: null | string,
};
export function CallSiteView({
callSite,
environmentName,
}: CallSiteViewProps): React.Node {
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
callSite;
const symbolicatedCallSite: null | ReactFunctionLocation =
fetchFileWithCaching !== null
? use(
symbolicateSourceWithCache(
fetchFileWithCaching,
virtualURL,
virtualLine,
virtualColumn,
),
)
: null;
const [linkIsEnabled, viewSource] = useOpenResource(
callSite,
symbolicatedCallSite,
);
const [functionName, url, line, column] =
symbolicatedCallSite !== null ? symbolicatedCallSite : callSite;
return (
<div className={styles.CallSite}>
{functionName || virtualFunctionName}
{' @ '}
<span
className={linkIsEnabled ? styles.Link : null}
onClick={viewSource}
title={url + ':' + line}>
{formatLocationForDisplay(url, line, column)}
</span>
<ElementBadges environmentName={environmentName} />
</div>
);
}
type Props = {
stack: ReactStackTrace,
environmentName: null | string,
};
export default function StackTraceView({
stack,
environmentName,
}: Props): React.Node {
return (
<div className={styles.StackTraceView}>
{stack.map((callSite, index) => (
<CallSiteView
key={index}
callSite={callSite}
environmentName={
// Badge last row
// TODO: If we start ignore listing the last row, we should badge the last
// non-ignored row.
index === stack.length - 1 ? environmentName : null
}
/>
))}
</div>
);
}