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/__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, }, ], diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index b26da3530bf9b..df848fa14fd95 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -4819,6 +4819,10 @@ 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 +4832,10 @@ 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, }; } @@ -5426,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, @@ -5526,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 6153e08832a11..faceec35a12b5 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) { @@ -837,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 585654252da20..55a1bc6532e22 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, }; @@ -308,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/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 && ( )} 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..1318e96c30d0d 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'; @@ -52,6 +53,7 @@ export default function InspectedElementView({ symbolicatedSourcePromise, }: Props): React.Node { const { + stack, owners, rendererPackageName, rendererVersion, @@ -68,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 ( @@ -168,20 +171,26 @@ export default function InspectedElementView({ data-testname="InspectedElementView-Owners">
rendered by
+ {showStack ? : null} {showOwnersList && owners?.map(owner => ( - + <> + + {owner.stack != null && owner.stack.length > 0 ? ( + + ) : null} + ))} {rootType !== null && ( 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 + '>'} diff --git a/packages/react-devtools-shared/src/frontend/types.js b/packages/react-devtools-shared/src/frontend/types.js index e4a4c5400bfd5..1cdc58f3fd0a8 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, @@ -272,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.