{compiledWithForget &&
}
+ {environmentName != null ?
{environmentName} : null}
+
{hocDisplayNames != null && hocDisplayNames.length > 0 && (
{hocDisplayNames[0]}
)}
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
index a1b76e49b6a..c24dd881e98 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
@@ -150,13 +150,28 @@ function SuspendedByRow({
{isOpen && (
- {showIOStack &&
}
+ {showIOStack && (
+
+ )}
{(showIOStack || !showAwaitStack) &&
ioOwner !== null &&
ioOwner.id !== inspectedElement.id ? (
awaited at:
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
-
+
)}
{asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? (
| null,
+ environmentName: string | null,
compiledWithForget: boolean,
id: number,
isInStore: boolean,
@@ -27,6 +28,7 @@ type OwnerViewProps = {
export default function OwnerView({
displayName,
+ environmentName,
hocDisplayNames,
compiledWithForget,
id,
@@ -65,6 +67,7 @@ export default function OwnerView({
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js
index 0fa5c0910bb..09bdb96af01 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js
@@ -220,6 +220,7 @@ function ElementsDropdown({owners, selectOwner}: ElementsDropdownProps) {
@@ -268,6 +269,7 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) {
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
index 4352ad6a827..fdbdba702da 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
@@ -12,6 +12,8 @@ import {use, useContext} from 'react';
import useOpenResource from '../useOpenResource';
+import ElementBadges from './ElementBadges';
+
import styles from './StackTraceView.css';
import type {
@@ -28,9 +30,13 @@ import formatLocationForDisplay from './formatLocationForDisplay';
type CallSiteViewProps = {
callSite: ReactCallSite,
+ environmentName: null | string,
};
-export function CallSiteView({callSite}: CallSiteViewProps): React.Node {
+export function CallSiteView({
+ callSite,
+ environmentName,
+}: CallSiteViewProps): React.Node {
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
@@ -64,19 +70,33 @@ export function CallSiteView({callSite}: CallSiteViewProps): React.Node {
title={url + ':' + line}>
{formatLocationForDisplay(url, line, column)}
+
);
}
type Props = {
stack: ReactStackTrace,
+ environmentName: null | string,
};
-export default function StackTraceView({stack}: Props): React.Node {
+export default function StackTraceView({
+ stack,
+ environmentName,
+}: Props): React.Node {
return (
{stack.map((callSite, index) => (
-
+
))}
);
diff --git a/packages/react-devtools-shared/src/frontend/types.js b/packages/react-devtools-shared/src/frontend/types.js
index 622205dd672..e4a4c5400bf 100644
--- a/packages/react-devtools-shared/src/frontend/types.js
+++ b/packages/react-devtools-shared/src/frontend/types.js
@@ -208,6 +208,7 @@ export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
+ env: null | string,
hocDisplayNames: Array
| null,
compiledWithForget: boolean,
type: ElementType,
@@ -265,6 +266,9 @@ export type InspectedElement = {
// List of owners
owners: Array | null,
+ // Environment name that this component executed in or null for the client
+ env: string | null,
+
// Location of component in source code.
source: ReactFunctionLocation | null,
From 3958d5d84b3d3e6ae5c1caef9c8cf0dc58e862e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Thu, 7 Aug 2025 10:55:01 -0400
Subject: [PATCH 4/4] [Flight] Copy the name field of a serialized function
debug value (#34085)
This ensures that if the name is set manually after the declaration,
then we get that name when we log the value. For example Node.js
`Response` is declared as `_Response` and then later assigned a new
name.
We should probably really serialize all static enumerable properties but
"name" is non-enumerable so it's still a special case.
---
.../react-client/src/ReactFlightClient.js | 94 +++++++++++++------
.../src/__tests__/ReactFlight-test.js | 3 +
.../react-server/src/ReactFlightServer.js | 13 ++-
3 files changed, 78 insertions(+), 32 deletions(-)
diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js
index fe88fe0357b..c4d47bfecc5 100644
--- a/packages/react-client/src/ReactFlightClient.js
+++ b/packages/react-client/src/ReactFlightClient.js
@@ -1969,6 +1969,44 @@ function createModel(response: Response, model: any): any {
return model;
}
+const mightHaveStaticConstructor = /\bclass\b.*\bstatic\b/;
+
+function getInferredFunctionApproximate(code: string): () => void {
+ let slicedCode;
+ if (code.startsWith('Object.defineProperty(')) {
+ slicedCode = code.slice('Object.defineProperty('.length);
+ } else if (code.startsWith('(')) {
+ slicedCode = code.slice(1);
+ } else {
+ slicedCode = code;
+ }
+ if (slicedCode.startsWith('async function')) {
+ const idx = slicedCode.indexOf('(', 14);
+ if (idx !== -1) {
+ const name = slicedCode.slice(14, idx).trim();
+ // eslint-disable-next-line no-eval
+ return (0, eval)('({' + JSON.stringify(name) + ':async function(){}})')[
+ name
+ ];
+ }
+ } else if (slicedCode.startsWith('function')) {
+ const idx = slicedCode.indexOf('(', 8);
+ if (idx !== -1) {
+ const name = slicedCode.slice(8, idx).trim();
+ // eslint-disable-next-line no-eval
+ return (0, eval)('({' + JSON.stringify(name) + ':function(){}})')[name];
+ }
+ } else if (slicedCode.startsWith('class')) {
+ const idx = slicedCode.indexOf('{', 5);
+ if (idx !== -1) {
+ const name = slicedCode.slice(5, idx).trim();
+ // eslint-disable-next-line no-eval
+ return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[name];
+ }
+ }
+ return function () {};
+}
+
function parseModelString(
response: Response,
parentObject: Object,
@@ -2158,41 +2196,37 @@ function parseModelString(
// This should not compile to eval() because then it has local scope access.
const code = value.slice(2);
try {
- // eslint-disable-next-line no-eval
- return (0, eval)(code);
+ // If this might be a class constructor with a static initializer or
+ // static constructor then don't eval it. It might cause unexpected
+ // side-effects. Instead, fallback to parsing out the function type
+ // and name.
+ if (!mightHaveStaticConstructor.test(code)) {
+ // eslint-disable-next-line no-eval
+ return (0, eval)(code);
+ }
} catch (x) {
- // We currently use this to express functions so we fail parsing it,
- // let's just return a blank function as a place holder.
- if (code.startsWith('(async function')) {
- const idx = code.indexOf('(', 15);
- if (idx !== -1) {
- const name = code.slice(15, idx).trim();
- // eslint-disable-next-line no-eval
- return (0, eval)(
- '({' + JSON.stringify(name) + ':async function(){}})',
- )[name];
- }
- } else if (code.startsWith('(function')) {
- const idx = code.indexOf('(', 9);
- if (idx !== -1) {
- const name = code.slice(9, idx).trim();
- // eslint-disable-next-line no-eval
- return (0, eval)(
- '({' + JSON.stringify(name) + ':function(){}})',
- )[name];
- }
- } else if (code.startsWith('(class')) {
- const idx = code.indexOf('{', 6);
+ // Fallthrough to fallback case.
+ }
+ // We currently use this to express functions so we fail parsing it,
+ // let's just return a blank function as a place holder.
+ let fn;
+ try {
+ fn = getInferredFunctionApproximate(code);
+ if (code.startsWith('Object.defineProperty(')) {
+ const DESCRIPTOR = ',"name",{value:"';
+ const idx = code.lastIndexOf(DESCRIPTOR);
if (idx !== -1) {
- const name = code.slice(6, idx).trim();
- // eslint-disable-next-line no-eval
- return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[
- name
- ];
+ const name = JSON.parse(
+ code.slice(idx + DESCRIPTOR.length - 1, code.length - 2),
+ );
+ // $FlowFixMe[cannot-write]
+ Object.defineProperty(fn, 'name', {value: name});
}
}
- return function () {};
+ } catch (_) {
+ fn = function () {};
}
+ return fn;
}
// Fallthrough
}
diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js
index 150997c1c37..9a60c3bd66b 100644
--- a/packages/react-client/src/__tests__/ReactFlight-test.js
+++ b/packages/react-client/src/__tests__/ReactFlight-test.js
@@ -3239,6 +3239,8 @@ describe('ReactFlight', () => {
}
Object.defineProperty(MyClass.prototype, 'y', {enumerable: true});
+ Object.defineProperty(MyClass, 'name', {value: 'MyClassName'});
+
function ServerComponent() {
console.log('hi', {
prop: 123,
@@ -3341,6 +3343,7 @@ describe('ReactFlight', () => {
const instance = mockConsoleLog.mock.calls[0][1].instance;
expect(typeof Class).toBe('function');
expect(Class.prototype.constructor).toBe(Class);
+ expect(Class.name).toBe('MyClassName');
expect(instance instanceof Class).toBe(true);
expect(Object.getPrototypeOf(instance)).toBe(Class.prototype);
expect(instance.x).toBe(1);
diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index 1a5cee7ba72..19a49dcd32b 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -4848,9 +4848,18 @@ function renderDebugModel(
return existingReference;
}
+ // $FlowFixMe[method-unbinding]
+ const functionBody: string = Function.prototype.toString.call(value);
+
+ const name = value.name;
const serializedValue = serializeEval(
- // $FlowFixMe[method-unbinding]
- '(' + Function.prototype.toString.call(value) + ')',
+ typeof name === 'string'
+ ? 'Object.defineProperty(' +
+ functionBody +
+ ',"name",{value:' +
+ JSON.stringify(name) +
+ '})'
+ : '(' + functionBody + ')',
);
request.pendingDebugChunks++;
const id = request.nextChunkId++;