From 951678a0ae5831b787bd1b3eb81bfd688fa218b5 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 7 Aug 2025 23:51:04 -0400 Subject: [PATCH 1/7] Add stack to SerializeElement This is the debug stack that makes up the owner stack. I.e. the stack of the JSX call. Exclude if it has no owner since we don't use the root in owner stacks. --- .../src/backend/fiber/renderer.js | 10 ++++++++++ .../src/backend/legacy/renderer.js | 1 + packages/react-devtools-shared/src/backend/types.js | 1 + packages/react-devtools-shared/src/frontend/types.js | 1 + 4 files changed, 13 insertions(+) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index b26da3530bf9b..3e95a371f855a 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -4819,6 +4819,11 @@ export function attach( id: instance.id, key: fiber.key, env: null, + stack: + fiber._debugOwner == null || + fiber._debugStack == null + ? null + : parseStackTrace(fiber._debugStack, 1), type: getElementTypeForFiber(fiber), }; } else { @@ -4828,6 +4833,11 @@ export function attach( id: instance.id, key: componentInfo.key == null ? null : componentInfo.key, env: componentInfo.env == null ? null : componentInfo.env, + stack: + componentInfo.owner == null || + componentInfo.debugStack == null + ? null + : parseStackTrace(componentInfo.debugStack, 1), type: ElementTypeVirtual, }; } diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 6153e08832a11..48696149e2121 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -796,6 +796,7 @@ export function attach( id: getID(owner), key: element.key, env: null, + stack: null, type: getElementType(owner), }); if (owner._currentElement) { diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 585654252da20..ba00b24828071 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -257,6 +257,7 @@ export type SerializedElement = { id: number, key: number | string | null, env: null | string, + stack: null | ReactStackTrace, type: ElementType, }; diff --git a/packages/react-devtools-shared/src/frontend/types.js b/packages/react-devtools-shared/src/frontend/types.js index e4a4c5400bfd5..ecb74a8a5e7be 100644 --- a/packages/react-devtools-shared/src/frontend/types.js +++ b/packages/react-devtools-shared/src/frontend/types.js @@ -209,6 +209,7 @@ export type SerializedElement = { id: number, key: number | string | null, env: null | string, + stack: null | ReactStackTrace, hocDisplayNames: Array | null, compiledWithForget: boolean, type: ElementType, From 8816b1c8ab329d95831ffac468d72ee91cb53043 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 7 Aug 2025 23:58:58 -0400 Subject: [PATCH 2/7] Add stacks to "rendered by" --- .../views/Components/InspectedElementView.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js index 95f7aee68da03..fd47b521d7b38 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -22,6 +22,7 @@ import InspectedElementSuspendedBy from './InspectedElementSuspendedBy'; import NativeStyleEditor from './NativeStyleEditor'; import {enableStyleXFeatures} from 'react-devtools-feature-flags'; import InspectedElementSourcePanel from './InspectedElementSourcePanel'; +import StackTraceView from './StackTraceView'; import OwnerView from './OwnerView'; import styles from './InspectedElementView.css'; @@ -170,18 +171,23 @@ export default function InspectedElementView({ {showOwnersList && owners?.map(owner => ( - + <> + + {owner.stack != null && owner.stack.length > 0 ? ( + + ) : null} + ))} {rootType !== null && ( From ba924c71d218cfa2d1365e9455ca359d88e86345 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 8 Aug 2025 00:05:38 -0400 Subject: [PATCH 3/7] Pass the stack of the inspected element itself --- .../src/backend/fiber/renderer.js | 16 ++++++++++++---- .../src/backend/legacy/renderer.js | 2 ++ .../react-devtools-shared/src/backend/types.js | 3 +++ packages/react-devtools-shared/src/backendAPI.js | 2 ++ .../react-devtools-shared/src/frontend/types.js | 3 +++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 3e95a371f855a..df848fa14fd95 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -4820,8 +4820,7 @@ export function attach( key: fiber.key, env: null, stack: - fiber._debugOwner == null || - fiber._debugStack == null + fiber._debugOwner == null || fiber._debugStack == null ? null : parseStackTrace(fiber._debugStack, 1), type: getElementTypeForFiber(fiber), @@ -4834,8 +4833,7 @@ export function attach( key: componentInfo.key == null ? null : componentInfo.key, env: componentInfo.env == null ? null : componentInfo.env, stack: - componentInfo.owner == null || - componentInfo.debugStack == null + componentInfo.owner == null || componentInfo.debugStack == null ? null : parseStackTrace(componentInfo.debugStack, 1), type: ElementTypeVirtual, @@ -5436,6 +5434,11 @@ export function attach( source, + stack: + fiber._debugOwner == null || fiber._debugStack == null + ? null + : parseStackTrace(fiber._debugStack, 1), + // Does the component have legacy context attached to it. hasLegacyContext, @@ -5536,6 +5539,11 @@ export function attach( source, + stack: + componentInfo.owner == null || componentInfo.debugStack == null + ? null + : parseStackTrace(componentInfo.debugStack, 1), + // Does the component have legacy context attached to it. hasLegacyContext: false, diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 48696149e2121..faceec35a12b5 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -838,6 +838,8 @@ export function attach( source: null, + stack: null, + // Only legacy context exists in legacy versions. hasLegacyContext: true, diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index ba00b24828071..55a1bc6532e22 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -309,6 +309,9 @@ export type InspectedElement = { source: ReactFunctionLocation | null, + // The location of the JSX creation. + stack: ReactStackTrace | null, + type: ElementType, // Meta information about the root this element belongs to. diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index a27e70c26d008..db22606377da1 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -257,6 +257,7 @@ export function convertInspectedElementBackendToFrontend( owners, env, source, + stack, context, hooks, plugins, @@ -295,6 +296,7 @@ export function convertInspectedElementBackendToFrontend( // Previous backend implementations (<= 6.1.5) have a different interface for Source. // This gates the source features for only compatible backends: >= 6.1.6 source: Array.isArray(source) ? source : null, + stack: stack, type, owners: owners === null diff --git a/packages/react-devtools-shared/src/frontend/types.js b/packages/react-devtools-shared/src/frontend/types.js index ecb74a8a5e7be..1cdc58f3fd0a8 100644 --- a/packages/react-devtools-shared/src/frontend/types.js +++ b/packages/react-devtools-shared/src/frontend/types.js @@ -273,6 +273,9 @@ export type InspectedElement = { // Location of component in source code. source: ReactFunctionLocation | null, + // The location of the JSX creation. + stack: ReactStackTrace | null, + type: ElementType, // Meta information about the root this element belongs to. From 7c294f67138b2597f4500ccffcd87f1fe66cbf52 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 8 Aug 2025 00:08:31 -0400 Subject: [PATCH 4/7] Show stack in "rendered by" view --- .../src/devtools/views/Components/InspectedElementView.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js index fd47b521d7b38..1318e96c30d0d 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -53,6 +53,7 @@ export default function InspectedElementView({ symbolicatedSourcePromise, }: Props): React.Node { const { + stack, owners, rendererPackageName, rendererVersion, @@ -69,8 +70,9 @@ export default function InspectedElementView({ ? `${rendererPackageName}@${rendererVersion}` : null; const showOwnersList = owners !== null && owners.length > 0; + const showStack = stack != null && stack.length > 0; const showRenderedBy = - showOwnersList || rendererLabel !== null || rootType !== null; + showStack || showOwnersList || rendererLabel !== null || rootType !== null; return ( @@ -169,6 +171,7 @@ export default function InspectedElementView({ data-testname="InspectedElementView-Owners">
rendered by
+ {showStack ? : null} {showOwnersList && owners?.map(owner => ( <> From 7623ef2540f9c2f24da53b1665fc3b9c1286a51f Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 8 Aug 2025 00:18:17 -0400 Subject: [PATCH 5/7] Fallback to using the JSX location for View Source button if no source location exists This is useful to jump to the creation of a built-in like
or . --- .../views/Components/InspectedElement.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index cc37953f4d271..7b19908cc8c4a 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -51,12 +51,19 @@ export default function InspectedElementWrapper(_: Props): React.Node { const fetchFileWithCaching = useContext(FetchFileWithCachingContext); + const source = + inspectedElement == null + ? null + : inspectedElement.source != null + ? inspectedElement.source + : inspectedElement.stack != null && inspectedElement.stack.length > 0 + ? inspectedElement.stack[0] + : null; + const symbolicatedSourcePromise: null | Promise = React.useMemo(() => { - if (inspectedElement == null) return null; if (fetchFileWithCaching == null) return Promise.resolve(null); - const {source} = inspectedElement; if (source == null) return Promise.resolve(null); const [, sourceURL, line, column] = source; @@ -66,7 +73,7 @@ export default function InspectedElementWrapper(_: Props): React.Node { line, column, ); - }, [inspectedElement]); + }, [source]); const element = inspectedElementID !== null @@ -223,13 +230,12 @@ export default function InspectedElementWrapper(_: Props): React.Node { {!alwaysOpenInEditor && !!editorURL && - inspectedElement != null && - inspectedElement.source != null && + source != null && symbolicatedSourcePromise != null && ( }> @@ -276,7 +282,7 @@ export default function InspectedElementWrapper(_: Props): React.Node { {!hideViewSourceAction && ( )} From ef8526c2971f7fc353ebe28577bbd0bc02d1caa7 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 8 Aug 2025 00:28:36 -0400 Subject: [PATCH 6/7] Update snapshot --- .../react-devtools-shared/src/__tests__/profilingCache-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index 795f37183a81f..d16062c69f488 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js @@ -949,6 +949,7 @@ describe('ProfilingCache', () => { "hocDisplayNames": null, "id": 1, "key": null, + "stack": null, "type": 11, }, ], From 113825eb2bfc2ab61c0f6ad40376df2e96282da4 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 8 Aug 2025 01:21:41 -0400 Subject: [PATCH 7/7] Update E2E test selectors --- .../__tests__/__e2e__/devtools-utils.js | 15 +++++++++++++-- .../src/devtools/views/Components/OwnerView.js | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-inline/__tests__/__e2e__/devtools-utils.js b/packages/react-devtools-inline/__tests__/__e2e__/devtools-utils.js index fe2bb3f6f222e..c39f63dc5bb4c 100644 --- a/packages/react-devtools-inline/__tests__/__e2e__/devtools-utils.js +++ b/packages/react-devtools-inline/__tests__/__e2e__/devtools-utils.js @@ -64,11 +64,22 @@ async function selectElement( createTestNameSelector('InspectedElementView-Owners'), ])[0]; + if (!ownersList) { + return false; + } + + const owners = findAllNodes(ownersList, [ + createTestNameSelector('OwnerView'), + ]); + return ( title && title.innerText.includes(titleText) && - ownersList && - ownersList.innerText.includes(ownersListText) + owners && + owners + .map(node => node.innerText) + .join('\n') + .includes(ownersListText) ); }, {titleText: displayName, ownersListText: waitForOwnersText} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnerView.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnerView.js index ac848484378bb..2b0f4b035a261 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnerView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnerView.js @@ -60,7 +60,8 @@ export default function OwnerView({ + title={displayName} + data-testname="OwnerView"> {'<' + displayName + '>'}