diff --git a/packages/react-devtools-shared/src/__tests__/utils-test.js b/packages/react-devtools-shared/src/__tests__/utils-test.js index 83b31903e06a8..dacbe0f46b9a6 100644 --- a/packages/react-devtools-shared/src/__tests__/utils-test.js +++ b/packages/react-devtools-shared/src/__tests__/utils-test.js @@ -401,7 +401,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.f = f; function f() { } //# sourceMappingURL=`; - const result = ['', 'http://test/a.mts', 1, 16]; + const result = ['', 'http://test/a.mts', 1, 17]; const fs = { 'http://test/a.mts': `export function f() {}`, 'http://test/a.mjs.map': `{"version":3,"file":"a.mjs","sourceRoot":"","sources":["a.mts"],"names":[],"mappings":";;AAAA,cAAsB;AAAtB,SAAgB,CAAC,KAAI,CAAC"}`, diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 2a41f6e08f096..189d504ad5125 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -59,6 +59,7 @@ import { import { extractLocationFromComponentStack, extractLocationFromOwnerStack, + parseStackTrace, } from 'react-devtools-shared/src/backend/utils/parseStackTrace'; import { cleanForBridge, @@ -2156,6 +2157,8 @@ export function attach( return id; } + let isInDisconnectedSubtree = false; + function recordMount( fiber: Fiber, parentInstance: DevToolsInstance | null, @@ -2173,14 +2176,29 @@ export function attach( } idToDevToolsInstanceMap.set(fiberInstance.id, fiberInstance); - const id = fiberInstance.id; - if (__DEBUG__) { debug('recordMount()', fiberInstance, parentInstance); } + recordReconnect(fiberInstance, parentInstance); + return fiberInstance; + } + + function recordReconnect( + fiberInstance: FiberInstance, + parentInstance: DevToolsInstance | null, + ): void { + if (isInDisconnectedSubtree) { + // We're disconnected. We'll reconnect a hidden mount after the parent reappears. + return; + } + const id = fiberInstance.id; + const fiber = fiberInstance.data; + const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration'); + const isRoot = fiber.tag === HostRoot; + if (isRoot) { const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner'); @@ -2292,7 +2310,6 @@ export function attach( if (isProfilingSupported) { recordProfilingDurations(fiberInstance, null); } - return fiberInstance; } function recordVirtualMount( @@ -2304,6 +2321,18 @@ export function attach( idToDevToolsInstanceMap.set(id, instance); + recordVirtualReconnect(instance, parentInstance, secondaryEnv); + } + + function recordVirtualReconnect( + instance: VirtualInstance, + parentInstance: DevToolsInstance | null, + secondaryEnv: null | string, + ): void { + if (isInDisconnectedSubtree) { + // We're disconnected. We'll reconnect a hidden mount after the parent reappears. + return; + } const componentInfo = instance.data; const key = @@ -2355,6 +2384,8 @@ export function attach( const keyString = key === null ? null : String(key); const keyStringID = getStringID(keyString); + const id = instance.id; + pushOperation(TREE_OPERATION_ADD); pushOperation(id); pushOperation(elementType); @@ -2369,14 +2400,27 @@ export function attach( } function recordUnmount(fiberInstance: FiberInstance): void { - const fiber = fiberInstance.data; if (__DEBUG__) { debug('recordUnmount()', fiberInstance, reconcilingParent); } + recordDisconnect(fiberInstance); + + idToDevToolsInstanceMap.delete(fiberInstance.id); + + untrackFiber(fiberInstance, fiberInstance.data); + } + + function recordDisconnect(fiberInstance: FiberInstance): void { + if (isInDisconnectedSubtree) { + // Already disconnected. + return; + } + const fiber = fiberInstance.data; + if (trackedPathMatchInstance === fiberInstance) { // We're in the process of trying to restore previous selection. - // If this fiber matched but is being unmounted, there's no use trying. + // If this fiber matched but is being hidden, there's no use trying. // Reset the state so we don't keep holding onto it. setTrackedPath(null); } @@ -2393,10 +2437,6 @@ export function attach( // and later arrange them in the correct order. pendingRealUnmountedIDs.push(id); } - - idToDevToolsInstanceMap.delete(fiberInstance.id); - - untrackFiber(fiberInstance, fiber); } // Running state of the remaining children from the previous version of this parent that @@ -2416,11 +2456,6 @@ export function attach( // the current parent here as well. let reconcilingParentSuspenseNode: null | SuspenseNode = null; - function isSuspenseInFallback(suspenseNode: SuspenseNode) { - const fiber = suspenseNode.instance.data; - return fiber.tag === SuspenseComponent && fiber.memoizedState !== null; - } - function ioExistsInSuspenseAncestor( suspenseNode: SuspenseNode, ioInfo: ReactIOInfo, @@ -2436,21 +2471,13 @@ export function attach( } function insertSuspendedBy(asyncInfo: ReactAsyncInfo): void { - let parentSuspenseNode = reconcilingParentSuspenseNode; - while ( - parentSuspenseNode !== null && - isSuspenseInFallback(parentSuspenseNode) - ) { - // If we have something that suspends inside the fallback tree of a Suspense boundary, then - // we bubble that up to the nearest parent Suspense boundary that isn't in fallback mode. - parentSuspenseNode = parentSuspenseNode.parent; - } - if (reconcilingParent === null || parentSuspenseNode === null) { + if (reconcilingParent === null || reconcilingParentSuspenseNode === null) { throw new Error( 'It should not be possible to have suspended data outside the root. ' + 'Even suspending at the first position is still a child of the root.', ); } + const parentSuspenseNode = reconcilingParentSuspenseNode; // Use the nearest unfiltered parent so that there's always some component that has // the entry on it even if you filter, or the root if all are filtered. let parentInstance = reconcilingParent; @@ -2694,10 +2721,31 @@ export function attach( } function unmountRemainingChildren() { - let child = remainingReconcilingChildren; - while (child !== null) { - unmountInstanceRecursively(child); - child = remainingReconcilingChildren; + if ( + reconcilingParent !== null && + (reconcilingParent.kind === FIBER_INSTANCE || + reconcilingParent.kind === FILTERED_FIBER_INSTANCE) && + reconcilingParent.data.tag === OffscreenComponent && + reconcilingParent.data.memoizedState !== null && + !isInDisconnectedSubtree + ) { + // This is a hidden offscreen, we need to execute this in the context of a disconnected subtree. + isInDisconnectedSubtree = true; + try { + let child = remainingReconcilingChildren; + while (child !== null) { + unmountInstanceRecursively(child); + child = remainingReconcilingChildren; + } + } finally { + isInDisconnectedSubtree = false; + } + } else { + let child = remainingReconcilingChildren; + while (child !== null) { + unmountInstanceRecursively(child); + child = remainingReconcilingChildren; + } } } @@ -2811,6 +2859,14 @@ export function attach( } function recordVirtualUnmount(instance: VirtualInstance) { + recordVirtualDisconnect(instance); + idToDevToolsInstanceMap.delete(instance.id); + } + + function recordVirtualDisconnect(instance: VirtualInstance) { + if (isInDisconnectedSubtree) { + return; + } if (trackedPathMatchInstance === instance) { // We're in the process of trying to restore previous selection. // If this fiber matched but is being unmounted, there's no use trying. @@ -2820,8 +2876,6 @@ export function attach( const id = instance.id; pendingRealUnmountedIDs.push(id); - - idToDevToolsInstanceMap.delete(instance.id); } function getSecondaryEnvironmentName( @@ -3030,10 +3084,12 @@ export function attach( previouslyReconciledSibling = null; remainingReconcilingChildren = null; } + let shouldPopSuspenseNode = false; if (newSuspenseNode !== null) { reconcilingParentSuspenseNode = newSuspenseNode; previouslyReconciledSiblingSuspenseNode = null; remainingReconcilingChildrenSuspenseNodes = null; + shouldPopSuspenseNode = true; } try { if (traceUpdatesEnabled) { @@ -3069,7 +3125,16 @@ export function attach( } if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) { - // If an Offscreen component is hidden, don't mount its children yet. + // If an Offscreen component is hidden, mount its children as disconnected. + const stashedDisconnected = isInDisconnectedSubtree; + isInDisconnectedSubtree = true; + try { + if (fiber.child !== null) { + mountChildrenRecursively(fiber.child, false); + } + } finally { + isInDisconnectedSubtree = stashedDisconnected; + } } else if (fiber.tag === SuspenseComponent && OffscreenComponent === -1) { // Legacy Suspense without the Offscreen wrapper. For the modern Suspense we just handle the // Offscreen wrapper itself specially. @@ -3102,6 +3167,44 @@ export function attach( ); } } + } else if ( + fiber.tag === SuspenseComponent && + OffscreenComponent !== -1 && + newInstance !== null && + newSuspenseNode !== null + ) { + // Modern Suspense path + const contentFiber = fiber.child; + if (contentFiber === null) { + throw new Error( + 'There should always be an Offscreen Fiber child in a Suspense boundary.', + ); + } + const fallbackFiber = contentFiber.sibling; + + // First update only the Offscreen boundary. I.e. the main content. + mountVirtualChildrenRecursively( + contentFiber, + fallbackFiber, + traceNearestHostComponentUpdate, + 0, // first level + ); + + // Next, we'll pop back out of the SuspenseNode that we added above and now we'll + // reconcile the fallback, reconciling anything by inserting into the parent SuspenseNode. + // Since the fallback conceptually blocks the parent. + reconcilingParentSuspenseNode = stashedSuspenseParent; + previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; + remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; + shouldPopSuspenseNode = false; + if (fallbackFiber !== null) { + mountVirtualChildrenRecursively( + fallbackFiber, + null, + traceNearestHostComponentUpdate, + 0, // first level + ); + } } else { if (fiber.child !== null) { mountChildrenRecursively( @@ -3116,7 +3219,7 @@ export function attach( previouslyReconciledSibling = stashedPrevious; remainingReconcilingChildren = stashedRemaining; } - if (newSuspenseNode !== null) { + if (shouldPopSuspenseNode) { reconcilingParentSuspenseNode = stashedSuspenseParent; previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; @@ -3305,7 +3408,12 @@ export function attach( let child: null | DevToolsInstance = parentInstance.firstChild; while (child !== null) { if (child.kind === FILTERED_FIBER_INSTANCE) { - addUnfilteredChildrenIDs(child, nextChildren); + const fiber = child.data; + if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) { + // The children of this Offscreen are hidden so they don't get added. + } else { + addUnfilteredChildrenIDs(child, nextChildren); + } } else { nextChildren.push(child.id); } @@ -3742,6 +3850,7 @@ export function attach( const stashedSuspenseParent = reconcilingParentSuspenseNode; const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode; const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes; + let shouldPopSuspenseNode = false; let previousSuspendedBy = null; if (fiberInstance !== null) { previousSuspendedBy = fiberInstance.suspendedBy; @@ -3771,6 +3880,7 @@ export function attach( previouslyReconciledSiblingSuspenseNode = null; remainingReconcilingChildrenSuspenseNodes = suspenseNode.firstChild; suspenseNode.firstChild = null; + shouldPopSuspenseNode = true; } } try { @@ -3888,25 +3998,93 @@ export function attach( ); shouldResetChildren = true; } - } else if (prevWasHidden && nextIsHidden) { - // We don't update any children while they're still hidden. + } else if (nextIsHidden) { + if (!prevWasHidden) { + // We're hiding the children. Disconnect them from the front end but keep state. + if (fiberInstance !== null && !isInDisconnectedSubtree) { + disconnectChildrenRecursively(remainingReconcilingChildren); + } + } + // Update children inside the hidden tree if they committed with a new updates. + const stashedDisconnected = isInDisconnectedSubtree; + isInDisconnectedSubtree = true; + try { + updateChildrenRecursively(nextFiber.child, prevFiber.child, false); + } finally { + isInDisconnectedSubtree = stashedDisconnected; + } } else if (prevWasHidden && !nextIsHidden) { // We're revealing the hidden children. We now need to update them to the latest state. - if (nextFiber.child !== null) { - mountChildrenRecursively( - nextFiber.child, - traceNearestHostComponentUpdate, + // We do this while still in the disconnected state and then we reconnect the new ones. + // This avoids reconnecting things that are about to be removed anyway. + const stashedDisconnected = isInDisconnectedSubtree; + isInDisconnectedSubtree = true; + try { + if (nextFiber.child !== null) { + updateChildrenRecursively(nextFiber.child, prevFiber.child, false); + } + // Ensure we unmount any remaining children inside the isInDisconnectedSubtree flag + // since they should not trigger real deletions. + unmountRemainingChildren(); + remainingReconcilingChildren = null; + } finally { + isInDisconnectedSubtree = stashedDisconnected; + } + if (fiberInstance !== null && !isInDisconnectedSubtree) { + reconnectChildrenRecursively(fiberInstance); + // Children may have reordered while they were hidden. + shouldResetChildren = true; + } + } else if ( + nextFiber.tag === SuspenseComponent && + OffscreenComponent !== -1 && + fiberInstance !== null && + fiberInstance.suspenseNode !== null + ) { + // Modern Suspense path + const prevContentFiber = prevFiber.child; + const nextContentFiber = nextFiber.child; + if (nextContentFiber === null || prevContentFiber === null) { + throw new Error( + 'There should always be an Offscreen Fiber child in a Suspense boundary.', ); + } + const prevFallbackFiber = prevContentFiber.sibling; + const nextFallbackFiber = nextContentFiber.sibling; + + // First update only the Offscreen boundary. I.e. the main content. + if ( + updateVirtualChildrenRecursively( + nextContentFiber, + nextFallbackFiber, + prevContentFiber, + traceNearestHostComponentUpdate, + 0, + ) + ) { shouldResetChildren = true; } - } else if (!prevWasHidden && nextIsHidden) { - // We're hiding the children. We really just unmount them for now. - updateChildrenRecursively( - null, - prevFiber.child, - traceNearestHostComponentUpdate, - ); - shouldResetChildren = true; + + // Next, we'll pop back out of the SuspenseNode that we added above and now we'll + // reconcile the fallback, reconciling anything by inserting into the parent SuspenseNode. + // Since the fallback conceptually blocks the parent. + reconcilingParentSuspenseNode = stashedSuspenseParent; + previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; + remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; + shouldPopSuspenseNode = false; + if (nextFallbackFiber !== null) { + if ( + updateVirtualChildrenRecursively( + nextFallbackFiber, + null, + prevFallbackFiber, + traceNearestHostComponentUpdate, + 0, + ) + ) { + shouldResetChildren = true; + } + } } else { // Common case: Primary -> Primary. // This is the same code path as for non-Suspense fibers. @@ -4000,7 +4178,7 @@ export function attach( reconcilingParent = stashedParent; previouslyReconciledSibling = stashedPrevious; remainingReconcilingChildren = stashedRemaining; - if (fiberInstance.suspenseNode !== null) { + if (shouldPopSuspenseNode) { reconcilingParentSuspenseNode = stashedSuspenseParent; previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; @@ -4009,6 +4187,51 @@ export function attach( } } + function disconnectChildrenRecursively(firstChild: null | DevToolsInstance) { + for (let child = firstChild; child !== null; child = child.nextSibling) { + if ( + (child.kind === FIBER_INSTANCE || + child.kind === FILTERED_FIBER_INSTANCE) && + child.data.tag === OffscreenComponent && + child.data.memoizedState !== null + ) { + // This instance's children are already disconnected. + } else { + disconnectChildrenRecursively(child.firstChild); + } + if (child.kind === FIBER_INSTANCE) { + recordDisconnect(child); + } else if (child.kind === VIRTUAL_INSTANCE) { + recordVirtualDisconnect(child); + } + } + } + + function reconnectChildrenRecursively(parentInstance: DevToolsInstance) { + for ( + let child = parentInstance.firstChild; + child !== null; + child = child.nextSibling + ) { + if (child.kind === FIBER_INSTANCE) { + recordReconnect(child, parentInstance); + } else if (child.kind === VIRTUAL_INSTANCE) { + const secondaryEnv = null; // TODO: We don't have this data anywhere. We could just stash it somewhere. + recordVirtualReconnect(child, parentInstance, secondaryEnv); + } + if ( + (child.kind === FIBER_INSTANCE || + child.kind === FILTERED_FIBER_INSTANCE) && + child.data.tag === OffscreenComponent && + child.data.memoizedState !== null + ) { + // This instance's children should remain disconnected. + } else { + reconnectChildrenRecursively(child); + } + } + } + function cleanup() { isProfiling = false; } @@ -4524,10 +4747,10 @@ export function attach( function getSuspendedByOfSuspenseNode( suspenseNode: SuspenseNode, - ): Array { + ): Array { // Collect all ReactAsyncInfo that was suspending this SuspenseNode but // isn't also in any parent set. - const result: Array = []; + const result: Array = []; if (!suspenseNode.hasUniqueSuspenders) { return result; } @@ -4552,7 +4775,8 @@ export function attach( ioInfo, ); if (asyncInfo !== null) { - result.push(asyncInfo); + const index = result.length; + result.push(serializeAsyncInfo(asyncInfo, index, firstInstance)); } } }); @@ -4569,10 +4793,63 @@ export function attach( parentInstance, ioInfo.owner, ); - const awaitOwnerInstance = findNearestOwnerInstance( - parentInstance, - asyncInfo.owner, - ); + let awaitStack = + asyncInfo.debugStack == null + ? null + : // While we have a ReactStackTrace on ioInfo.stack, that will point to the location on + // the server. We need a location that points to the virtual source on the client which + // we can then use to source map to the original location. + parseStackTrace(asyncInfo.debugStack, 1); + let awaitOwnerInstance: null | FiberInstance | VirtualInstance; + if ( + asyncInfo.owner == null && + (awaitStack === null || awaitStack.length === 0) + ) { + // We had no owner nor stack for the await. This can happen if you render it as a child + // or throw a Promise. Replace it with the parent as the await. + awaitStack = null; + awaitOwnerInstance = + parentInstance.kind === FILTERED_FIBER_INSTANCE ? null : parentInstance; + if ( + parentInstance.kind === FIBER_INSTANCE || + parentInstance.kind === FILTERED_FIBER_INSTANCE + ) { + const fiber = parentInstance.data; + switch (fiber.tag) { + case ClassComponent: + case FunctionComponent: + case IncompleteClassComponent: + case IncompleteFunctionComponent: + case IndeterminateComponent: + case MemoComponent: + case SimpleMemoComponent: + // If we awaited in the child position of a component, then the best stack would be the + // return callsite but we don't have that available so instead we skip. The callsite of + // the JSX would be misleading in this case. The same thing happens with throw-a-Promise. + break; + default: + // If we awaited by passing a Promise to a built-in element, then the JSX callsite is a + // good stack trace to use for the await. + if ( + fiber._debugOwner != null && + fiber._debugStack != null && + typeof fiber._debugStack !== 'string' + ) { + awaitStack = parseStackTrace(fiber._debugStack, 1); + awaitOwnerInstance = findNearestOwnerInstance( + parentInstance, + fiber._debugOwner, + ); + } + } + } + } else { + awaitOwnerInstance = findNearestOwnerInstance( + parentInstance, + asyncInfo.owner, + ); + } + const value: any = ioInfo.value; let resolvedValue = undefined; if ( @@ -4601,14 +4878,20 @@ export function attach( ioOwnerInstance === null ? null : instanceToSerializedElement(ioOwnerInstance), - stack: ioInfo.stack == null ? null : ioInfo.stack, + stack: + ioInfo.debugStack == null + ? null + : // While we have a ReactStackTrace on ioInfo.stack, that will point to the location on + // the server. We need a location that points to the virtual source on the client which + // we can then use to source map to the original location. + parseStackTrace(ioInfo.debugStack, 1), }, env: asyncInfo.env == null ? null : asyncInfo.env, owner: awaitOwnerInstance === null ? null : instanceToSerializedElement(awaitOwnerInstance), - stack: asyncInfo.stack == null ? null : asyncInfo.stack, + stack: awaitStack, }; } @@ -4914,8 +5197,11 @@ export function attach( // In this case, this becomes associated with the Client/Host Component where as normally // you'd expect these to be associated with the Server Component that awaited the data. // TODO: Prepend other suspense sources like css, images and use(). - fiberInstance.suspendedBy; - + fiberInstance.suspendedBy === null + ? [] + : fiberInstance.suspendedBy.map((info, index) => + serializeAsyncInfo(info, index, fiberInstance), + ); return { id: fiberInstance.id, @@ -4972,12 +5258,7 @@ export function attach( ? [] : Array.from(componentLogsEntry.warnings.entries()), - suspendedBy: - suspendedBy === null - ? [] - : suspendedBy.map((info, index) => - serializeAsyncInfo(info, index, fiberInstance), - ), + suspendedBy: suspendedBy, // List of owners owners, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css index ded305bbc66ca..0fb5107361c1c 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css @@ -123,3 +123,10 @@ .TimeBarSpanErrored { background-color: var(--color-timespan-background-errored); } + +.SmallHeader { + font-family: var(--font-family-monospace); + font-size: var(--font-size-monospace-normal); + padding-left: 1.25rem; + margin-top: 0.25rem; +} 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 c7d0b39df3b83..a1b76e49b6add 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js @@ -80,21 +80,13 @@ function SuspendedByRow({ maxTime, }: RowProps) { const [isOpen, setIsOpen] = useState(false); - const name = asyncInfo.awaited.name; - const description = asyncInfo.awaited.description; + const ioInfo = asyncInfo.awaited; + const name = ioInfo.name; + const description = ioInfo.description; const longName = description === '' ? name : name + ' (' + description + ')'; const shortDescription = getShortDescription(name, description); - let stack; - let owner; - if (asyncInfo.stack === null || asyncInfo.stack.length === 0) { - stack = asyncInfo.awaited.stack; - owner = asyncInfo.awaited.owner; - } else { - stack = asyncInfo.stack; - owner = asyncInfo.owner; - } - const start = asyncInfo.awaited.start; - const end = asyncInfo.awaited.end; + const start = ioInfo.start; + const end = ioInfo.end; const timeScale = 100 / (maxTime - minTime); let left = (start - minTime) * timeScale; let width = (end - start) * timeScale; @@ -106,7 +98,19 @@ function SuspendedByRow({ } } - const value: any = asyncInfo.awaited.value; + const ioOwner = ioInfo.owner; + const asyncOwner = asyncInfo.owner; + const showIOStack = ioInfo.stack !== null && ioInfo.stack.length !== 0; + // Only show the awaited stack if the I/O started in a different owner + // than where it was awaited. If it's started by the same component it's + // probably easy enough to infer and less noise in the common case. + const showAwaitStack = + !showIOStack || + (ioOwner === null + ? asyncOwner !== null + : asyncOwner === null || ioOwner.id !== asyncOwner.id); + + const value: any = ioInfo.value; const metaName = value !== null && typeof value === 'object' ? value[meta.name] : null; const isFulfilled = metaName === 'fulfilled Thenable'; @@ -146,20 +150,39 @@ function SuspendedByRow({ {isOpen && (
- {stack !== null && stack.length > 0 && ( - - )} - {owner !== null && owner.id !== inspectedElement.id ? ( + {showIOStack && } + {(showIOStack || !showAwaitStack) && + ioOwner !== null && + ioOwner.id !== inspectedElement.id ? ( ) : null} + {showAwaitStack ? ( + <> +
awaited at:
+ {asyncInfo.stack !== null && asyncInfo.stack.length > 0 && ( + + )} + {asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? ( + + ) : null} + + ) : null}
@@ -249,7 +284,7 @@ export default function InspectedElementSuspendedBy({
- {suspendedBy.map((asyncInfo, index) => ( + {sortedSuspendedBy.map((asyncInfo, index) => ( - {functionName} + {functionName || virtualFunctionName} {' @ '} , > = new Map(); -export async function symbolicateSourceWithCache( +export function symbolicateSourceWithCache( fetchFileWithCaching: FetchFileWithCaching, sourceURL: string, line: number, // 1-based @@ -82,12 +82,14 @@ export async function symbolicateSource( const { sourceURL: possiblyURL, line, - column, + column: columnZeroBased, } = consumer.originalPositionFor({ lineNumber, // 1-based columnNumber, // 1-based }); + const column = columnZeroBased + 1; + if (possiblyURL === null) { return null; }