Skip to content

Commit cd7e0ef

Browse files
committed
Limit suspensey image timeout to 50ms if we don't think we can download all images within 500ms
Since we're more conservative about starting to wait now, we now wait longer if we have started waiting so that we didn't wait for no good reason.
1 parent a4ba6e9 commit cd7e0ef

File tree

2 files changed

+39
-6
lines changed

2 files changed

+39
-6
lines changed

fixtures/view-transition/src/components/Page.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ function Component() {
5050
<p>
5151
<img
5252
src="https://react.dev/_next/image?url=%2Fimages%2Fteam%2Fsebmarkbage.jpg&w=3840&q=75"
53-
width="300"
53+
width="400"
54+
height="248"
5455
/>
5556
</p>
5657
</ViewTransition>

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
143143
export {default as rendererVersion} from 'shared/ReactVersion';
144144

145145
import noop from 'shared/noop';
146+
import estimateBandwidth from './estimateBandwidth';
146147

147148
export const rendererPackageName = 'react-dom';
148149
export const extraDevToolsConfig = null;
@@ -5907,6 +5908,7 @@ type SuspendedState = {
59075908
stylesheets: null | Map<StylesheetResource, HoistableRoot>,
59085909
count: number, // suspensey css and active view transitions
59095910
imgCount: number, // suspensey images
5911+
imgBytes: number, // number of bytes we estimate needing to download
59105912
waitingForImages: boolean, // false when we're no longer blocking on images
59115913
unsuspend: null | (() => void),
59125914
};
@@ -5917,6 +5919,7 @@ export function startSuspendingCommit(): void {
59175919
stylesheets: null,
59185920
count: 0,
59195921
imgCount: 0,
5922+
imgBytes: 0,
59205923
waitingForImages: true,
59215924
// We use a noop function when we begin suspending because if possible we want the
59225925
// waitfor step to finish synchronously. If it doesn't we'll return a function to
@@ -5926,10 +5929,6 @@ export function startSuspendingCommit(): void {
59265929
};
59275930
}
59285931

5929-
const SUSPENSEY_STYLESHEET_TIMEOUT = 60000;
5930-
5931-
const SUSPENSEY_IMAGE_TIMEOUT = 500;
5932-
59335932
export function suspendInstance(
59345933
instance: Instance,
59355934
type: Type,
@@ -5953,6 +5952,19 @@ export function suspendInstance(
59535952
// The loading should have already started at this point, so it should be enough to
59545953
// just call decode() which should also wait for the data to finish loading.
59555954
state.imgCount++;
5955+
// Estimate the byte size that we're about to download based on the width/height
5956+
// specified in the props. This is best practice to know ahead of time but if it's
5957+
// unspecified we'll fallback to a guess of 100x100 pixels.
5958+
if (!(instance: any).complete) {
5959+
const width: number = (instance: any).width || 100;
5960+
const height: number = (instance: any).height || 100;
5961+
const pixelRatio: number =
5962+
typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;
5963+
const AVERAGE_BYTE_PER_PIXEL = 0.25;
5964+
const pixelsToDownload =
5965+
width * height * pixelRatio * AVERAGE_BYTE_PER_PIXEL;
5966+
state.imgBytes += pixelsToDownload;
5967+
}
59565968
const ping = onUnsuspendImg.bind(state);
59575969
// $FlowFixMe[prop-missing]
59585970
instance.decode().then(ping, ping);
@@ -6070,6 +6082,14 @@ export function suspendOnActiveViewTransition(rootContainer: Container): void {
60706082
activeViewTransition.finished.then(ping, ping);
60716083
}
60726084

6085+
const SUSPENSEY_STYLESHEET_TIMEOUT = 60000;
6086+
6087+
const SUSPENSEY_IMAGE_TIMEOUT = 800;
6088+
6089+
const SUSPENSEY_IMAGE_TIME_ESTIMATE = 500;
6090+
6091+
let estimatedBytesWithinLimit: number = 0;
6092+
60736093
export function waitForCommitToBeReady(
60746094
timeoutOffset: number,
60756095
): null | ((() => void) => () => void) {
@@ -6109,6 +6129,18 @@ export function waitForCommitToBeReady(
61096129
}
61106130
}, SUSPENSEY_STYLESHEET_TIMEOUT + timeoutOffset);
61116131

6132+
if (state.imgBytes > 0 && estimatedBytesWithinLimit === 0) {
6133+
// Estimate how many bytes we can download in 500ms.
6134+
const mbps = estimateBandwidth();
6135+
estimatedBytesWithinLimit = mbps * 125 * SUSPENSEY_IMAGE_TIME_ESTIMATE;
6136+
}
6137+
// If we have more images to download than we expect to fit in the timeout, then
6138+
// don't wait for images longer than 50ms. The 50ms lets us still do decoding and
6139+
// hitting caches if it turns out that they're already in the HTTP cache.
6140+
const imgTimeout =
6141+
state.imgBytes > estimatedBytesWithinLimit
6142+
? 50
6143+
: SUSPENSEY_IMAGE_TIMEOUT;
61126144
const imgTimer = setTimeout(() => {
61136145
// We're no longer blocked on images. If CSS resolves after this we can commit.
61146146
state.waitingForImages = false;
@@ -6122,7 +6154,7 @@ export function waitForCommitToBeReady(
61226154
unsuspend();
61236155
}
61246156
}
6125-
}, SUSPENSEY_IMAGE_TIMEOUT + timeoutOffset);
6157+
}, imgTimeout + timeoutOffset);
61266158

61276159
state.unsuspend = commit;
61286160

0 commit comments

Comments
 (0)