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 b26da3530bf..3e95a371f85 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 6153e08832a..48696149e21 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 585654252da..ba00b248280 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 e4a4c5400bf..ecb74a8a5e7 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 95f7aee68da..fd47b521d7b 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 3e95a371f85..df848fa14fd 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 48696149e21..faceec35a12 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 ba00b248280..55a1bc6532e 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 a27e70c26d0..db22606377d 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 ecb74a8a5e7..1cdc58f3fd0 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 fd47b521d7b..1318e96c30d 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 cc37953f4d2..7b19908cc8c 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 795f37183a8..d16062c69f4 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 fe2bb3f6f22..c39f63dc5bb 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 ac848484378..2b0f4b035a2 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 + '>'}