Skip to content

Commit 738aebd

Browse files
authored
[DevTools] Add Badge to Owners and sometimes stack traces (facebook#34106)
Stacked on facebook#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.
1 parent 4c9c109 commit 738aebd

File tree

12 files changed

+90
-6
lines changed

12 files changed

+90
-6
lines changed

packages/react-devtools-shared/src/__tests__/profilingCache-test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,7 @@ describe('ProfilingCache', () => {
862862
{
863863
"compiledWithForget": false,
864864
"displayName": "render()",
865+
"env": null,
865866
"hocDisplayNames": null,
866867
"id": 1,
867868
"key": null,
@@ -903,6 +904,7 @@ describe('ProfilingCache', () => {
903904
{
904905
"compiledWithForget": false,
905906
"displayName": "createRoot()",
907+
"env": null,
906908
"hocDisplayNames": null,
907909
"id": 1,
908910
"key": null,
@@ -943,6 +945,7 @@ describe('ProfilingCache', () => {
943945
{
944946
"compiledWithForget": false,
945947
"displayName": "createRoot()",
948+
"env": null,
946949
"hocDisplayNames": null,
947950
"id": 1,
948951
"key": null,

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4818,6 +4818,7 @@ export function attach(
48184818
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
48194819
id: instance.id,
48204820
key: fiber.key,
4821+
env: null,
48214822
type: getElementTypeForFiber(fiber),
48224823
};
48234824
} else {
@@ -4826,6 +4827,7 @@ export function attach(
48264827
displayName: componentInfo.name || 'Anonymous',
48274828
id: instance.id,
48284829
key: componentInfo.key == null ? null : componentInfo.key,
4830+
env: componentInfo.env == null ? null : componentInfo.env,
48294831
type: ElementTypeVirtual,
48304832
};
48314833
}
@@ -5451,6 +5453,8 @@ export function attach(
54515453
// List of owners
54525454
owners,
54535455
5456+
env: null,
5457+
54545458
rootType,
54555459
rendererPackageName: renderer.rendererPackageName,
54565460
rendererVersion: renderer.version,
@@ -5554,6 +5558,8 @@ export function attach(
55545558
// List of owners
55555559
owners,
55565560
5561+
env: componentInfo.env == null ? null : componentInfo.env,
5562+
55575563
rootType,
55585564
rendererPackageName: renderer.rendererPackageName,
55595565
rendererVersion: renderer.version,

packages/react-devtools-shared/src/backend/legacy/renderer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ export function attach(
795795
displayName: getData(owner).displayName || 'Unknown',
796796
id: getID(owner),
797797
key: element.key,
798+
env: null,
798799
type: getElementType(owner),
799800
});
800801
if (owner._currentElement) {
@@ -857,6 +858,8 @@ export function attach(
857858
// List of owners
858859
owners,
859860

861+
env: null,
862+
860863
rootType: null,
861864
rendererPackageName: null,
862865
rendererVersion: null,

packages/react-devtools-shared/src/backend/types.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ export type SerializedElement = {
256256
displayName: string | null,
257257
id: number,
258258
key: number | string | null,
259+
env: null | string,
259260
type: ElementType,
260261
};
261262

@@ -301,6 +302,10 @@ export type InspectedElement = {
301302

302303
// List of owners
303304
owners: Array<SerializedElement> | null,
305+
306+
// Environment name that this component executed in or null for the client
307+
env: string | null,
308+
304309
source: ReactFunctionLocation | null,
305310

306311
type: ElementType,

packages/react-devtools-shared/src/backendAPI.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ export function convertInspectedElementBackendToFrontend(
255255
id,
256256
type,
257257
owners,
258+
env,
258259
source,
259260
context,
260261
hooks,
@@ -299,6 +300,7 @@ export function convertInspectedElementBackendToFrontend(
299300
owners === null
300301
? null
301302
: owners.map(backendToFrontendSerializedElementMapper),
303+
env,
302304
context: hydrateHelper(context),
303305
hooks: hydrateHelper(hooks),
304306
props: hydrateHelper(props),

packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ import styles from './ElementBadges.css';
1616

1717
type Props = {
1818
hocDisplayNames: Array<string> | null,
19+
environmentName: string | null,
1920
compiledWithForget: boolean,
2021
className?: string,
2122
};
2223

2324
export default function ElementBadges({
2425
compiledWithForget,
26+
environmentName,
2527
hocDisplayNames,
2628
className = '',
2729
}: Props): React.Node {
2830
if (
2931
!compiledWithForget &&
30-
(hocDisplayNames == null || hocDisplayNames.length === 0)
32+
(hocDisplayNames == null || hocDisplayNames.length === 0) &&
33+
environmentName == null
3134
) {
3235
return null;
3336
}
@@ -36,6 +39,8 @@ export default function ElementBadges({
3639
<div className={`${styles.Root} ${className}`}>
3740
{compiledWithForget && <ForgetBadge indexable={false} />}
3841

42+
{environmentName != null ? <Badge>{environmentName}</Badge> : null}
43+
3944
{hocDisplayNames != null && hocDisplayNames.length > 0 && (
4045
<Badge>{hocDisplayNames[0]}</Badge>
4146
)}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,28 @@ function SuspendedByRow({
150150
</Button>
151151
{isOpen && (
152152
<div className={styles.CollapsableContent}>
153-
{showIOStack && <StackTraceView stack={ioInfo.stack} />}
153+
{showIOStack && (
154+
<StackTraceView
155+
stack={ioInfo.stack}
156+
environmentName={
157+
ioOwner !== null && ioOwner.env === ioInfo.env
158+
? null
159+
: ioInfo.env
160+
}
161+
/>
162+
)}
154163
{(showIOStack || !showAwaitStack) &&
155164
ioOwner !== null &&
156165
ioOwner.id !== inspectedElement.id ? (
157166
<OwnerView
158167
key={ioOwner.id}
159168
displayName={ioOwner.displayName || 'Anonymous'}
169+
environmentName={
170+
ioOwner.env === inspectedElement.env &&
171+
ioOwner.env === ioInfo.env
172+
? null
173+
: ioOwner.env
174+
}
160175
hocDisplayNames={ioOwner.hocDisplayNames}
161176
compiledWithForget={ioOwner.compiledWithForget}
162177
id={ioOwner.id}
@@ -168,12 +183,25 @@ function SuspendedByRow({
168183
<>
169184
<div className={styles.SmallHeader}>awaited at:</div>
170185
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
171-
<StackTraceView stack={asyncInfo.stack} />
186+
<StackTraceView
187+
stack={asyncInfo.stack}
188+
environmentName={
189+
asyncOwner !== null && asyncOwner.env === asyncInfo.env
190+
? null
191+
: asyncInfo.env
192+
}
193+
/>
172194
)}
173195
{asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? (
174196
<OwnerView
175197
key={asyncOwner.id}
176198
displayName={asyncOwner.displayName || 'Anonymous'}
199+
environmentName={
200+
asyncOwner.env === inspectedElement.env &&
201+
asyncOwner.env === asyncInfo.env
202+
? null
203+
: asyncOwner.env
204+
}
177205
hocDisplayNames={asyncOwner.hocDisplayNames}
178206
compiledWithForget={asyncOwner.compiledWithForget}
179207
id={asyncOwner.id}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ export default function InspectedElementView({
174174
key={owner.id}
175175
displayName={owner.displayName || 'Anonymous'}
176176
hocDisplayNames={owner.hocDisplayNames}
177+
environmentName={
178+
inspectedElement.env === owner.env ? null : owner.env
179+
}
177180
compiledWithForget={owner.compiledWithForget}
178181
id={owner.id}
179182
isInStore={store.containsElement(owner.id)}

packages/react-devtools-shared/src/devtools/views/Components/OwnerView.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import styles from './OwnerView.css';
2020
type OwnerViewProps = {
2121
displayName: string,
2222
hocDisplayNames: Array<string> | null,
23+
environmentName: string | null,
2324
compiledWithForget: boolean,
2425
id: number,
2526
isInStore: boolean,
2627
};
2728

2829
export default function OwnerView({
2930
displayName,
31+
environmentName,
3032
hocDisplayNames,
3133
compiledWithForget,
3234
id,
@@ -65,6 +67,7 @@ export default function OwnerView({
6567
<ElementBadges
6668
hocDisplayNames={hocDisplayNames}
6769
compiledWithForget={compiledWithForget}
70+
environmentName={environmentName}
6871
/>
6972
</span>
7073
</Button>

packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ function ElementsDropdown({owners, selectOwner}: ElementsDropdownProps) {
220220

221221
<ElementBadges
222222
hocDisplayNames={owner.hocDisplayNames}
223+
environmentName={owner.env}
223224
compiledWithForget={owner.compiledWithForget}
224225
className={styles.BadgesBlock}
225226
/>
@@ -268,6 +269,7 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) {
268269

269270
<ElementBadges
270271
hocDisplayNames={hocDisplayNames}
272+
environmentName={owner.env}
271273
compiledWithForget={compiledWithForget}
272274
className={styles.BadgesBlock}
273275
/>

0 commit comments

Comments
 (0)