From 42b1b33a244f91d00650d82105871d37cc120e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Sun, 17 Aug 2025 16:17:11 -0400 Subject: [PATCH] [DevTools] Add byteSize field to ReactIOInfo and show this in the tooltip (#34221) This is intended to be used by various client side resources where the transfer size is interesting to know how it'll perform in various network conditions. Not intended to be added by the server. For now it's only added internally by DevTools itself on img/css but I'll add it from Flight Client too in a follow up. This now shows this as the "transfer size" which is the encoded body size + headers/overhead. Where as the "fileSize" that I add to images is the decoded body size, like what you'd see on disk. This is what Chrome shows so it's less confusing if you compare Network tab and this view. --- .../src/backend/fiber/renderer.js | 28 ++++++++++++++----- .../src/backend/types.js | 1 + .../react-devtools-shared/src/backendAPI.js | 1 + .../Components/InspectedElementSuspendedBy.js | 21 +++++++++++++- .../src/frontend/types.js | 1 + packages/shared/ReactTypes.js | 1 + 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index fc054d817d46b..a49cf25a1d1f4 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3343,6 +3343,7 @@ export function attach( } let start = -1; let end = -1; + let byteSize = 0; // $FlowFixMe[method-unbinding] if (typeof performance.getEntriesByType === 'function') { // We may be able to collect the start and end time of this resource from Performance Observer. @@ -3352,6 +3353,8 @@ export function attach( if (resourceEntry.name === href) { start = resourceEntry.startTime; end = start + resourceEntry.duration; + // $FlowFixMe[prop-missing] + byteSize = (resourceEntry.transferSize: any) || 0; } } } @@ -3367,6 +3370,10 @@ export function attach( // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. owner: fiber, // Allow linking to the if it's not filtered. }; + if (byteSize > 0) { + // $FlowFixMe[cannot-write] + ioInfo.byteSize = byteSize; + } const asyncInfo: ReactAsyncInfo = { awaited: ioInfo, // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. @@ -3431,6 +3438,7 @@ export function attach( } let start = -1; let end = -1; + let byteSize = 0; let fileSize = 0; // $FlowFixMe[method-unbinding] if (typeof performance.getEntriesByType === 'function') { @@ -3442,7 +3450,9 @@ export function attach( start = resourceEntry.startTime; end = start + resourceEntry.duration; // $FlowFixMe[prop-missing] - fileSize = (resourceEntry.encodedBodySize: any) || 0; + fileSize = (resourceEntry.decodedBodySize: any) || 0; + // $FlowFixMe[prop-missing] + byteSize = (resourceEntry.transferSize: any) || 0; } } } @@ -3476,6 +3486,10 @@ export function attach( // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. owner: fiber, // Allow linking to the if it's not filtered. }; + if (byteSize > 0) { + // $FlowFixMe[cannot-write] + ioInfo.byteSize = byteSize; + } const asyncInfo: ReactAsyncInfo = { awaited: ioInfo, // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file. @@ -4704,16 +4718,15 @@ export function attach( trackDebugInfoFromLazyType(nextFiber); trackDebugInfoFromUsedThenables(nextFiber); - if ( - nextFiber.tag === HostHoistable && - prevFiber.memoizedState !== nextFiber.memoizedState - ) { + if (nextFiber.tag === HostHoistable) { const nearestInstance = reconcilingParent; if (nearestInstance === null) { throw new Error('Did not expect a host hoistable to be the root'); } - releaseHostResource(nearestInstance, prevFiber.memoizedState); - aquireHostResource(nearestInstance, nextFiber.memoizedState); + if (prevFiber.memoizedState !== nextFiber.memoizedState) { + releaseHostResource(nearestInstance, prevFiber.memoizedState); + aquireHostResource(nearestInstance, nextFiber.memoizedState); + } trackDebugInfoFromHostResource(nearestInstance, nextFiber); } else if ( nextFiber.tag === HostComponent || @@ -5948,6 +5961,7 @@ export function attach( description: getIODescription(resolvedValue), start: ioInfo.start, end: ioInfo.end, + byteSize: ioInfo.byteSize == null ? null : ioInfo.byteSize, value: ioInfo.value == null ? null : ioInfo.value, env: ioInfo.env == null ? null : ioInfo.env, owner: diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index ffbacea01aaba..12b082aeb2e55 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -239,6 +239,7 @@ export type SerializedIOInfo = { description: string, start: number, end: number, + byteSize: null | number, value: null | Promise, env: null | string, owner: null | SerializedElement, diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index f8fa4da372549..f6c11fb10d66a 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -221,6 +221,7 @@ function backendToFrontendSerializedAsyncInfo( description: ioInfo.description, start: ioInfo.start, end: ioInfo.end, + byteSize: ioInfo.byteSize, value: ioInfo.value, env: ioInfo.env, owner: 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 451b53b4ac597..95c4fe817d528 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js @@ -76,6 +76,19 @@ function getShortDescription(name: string, description: string): string { return ''; } +function formatBytes(bytes: number) { + if (bytes < 1_000) { + return bytes + ' bytes'; + } + if (bytes < 1_000_000) { + return (bytes / 1_000).toFixed(1) + ' kB'; + } + if (bytes < 1_000_000_000) { + return (bytes / 1_000_000).toFixed(1) + ' mB'; + } + return (bytes / 1_000_000_000).toFixed(1) + ' gB'; +} + function SuspendedByRow({ bridge, element, @@ -145,7 +158,13 @@ function SuspendedByRow({