Skip to content

Commit a681a4f

Browse files
committed
[Fiber] Don't wait on Suspensey Images if we guess that we don't load them all in time anyway (#34481)
Stacked on #34478. In general we don't like to deal with timeouts in suspense world. We've had that in the past but in general it doesn't work well because if you have a timeout and then give up you made everything wait longer for no benefit at the end. That's why the recommendation is to remove a Suspense boundary if you expect it to be fast and add one if you expect it to be slow. You have to estimate as the developer. Suspensey images suffer from this same problem. We want to apply suspensey images to as much as possible so that it's the default to avoid flashing because if just a few images flash it's still almost as bad as all of them. However, we do know that it's also very common to use images and on a slow connection or many images, it's not worth it so we have the timeout to eventually give up. However, this means that in cases that are always slow or connections that are always slow, you're always punished for no reason. Suspensey images is mainly a polish feature to make high end experiences on high end connections better but we don't want to unnecessarily punish all slow connections in the process or things like lots of images below the viewport. This PR adds an estimate for whether or not we'll likely be able to load all the images within the timeout on a high end enough connection. If not, we'll still do a short suspend (unless we've already exceeded the wait time adjusted for #34478) to allow loading from cache if available. This estimate is based on two heuristics: 1) We compute an estimated bandwidth available on the current device in mbps. This is computed from performance entries that have loaded static resources already on the site. E.g. this can be other images, css, or scripts. We see how long they took. If we don't have any entries (or if they're all cross-origin in Safari) we fallback to `navigator.connection.downlink` in Chrome or a 5mbps default in Firefox/Safari. 2) To estimate how many bytes we'll have to download we use the width/height props of the img tag if available (or a 100 pixel default) times the device pixel ratio. We assume that a good img implementation downloads proper resolution image for the device and defines a width/height up front to avoid layout trash. Then we estimate that it takes about 0.25 bytes per pixel which is somewhat conservative estimate. This is somewhat conservative given that the image could've been preloaded and be better compressed. So it really only kicks in for high end connections that are known to load fast. In a follow up, we can add an additional wait for View Transitions that does the same estimate but only for the images that turn out to be in viewport. DiffTrain build for [ae22247](ae22247)
1 parent cf42336 commit a681a4f

34 files changed

+972
-190
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
e3f191803cbe53df360b32f6735b05bb969c55cf
1+
ae22247dce1449574cd324cae0d8aa0d4824a937
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
e3f191803cbe53df360b32f6735b05bb969c55cf
1+
ae22247dce1449574cd324cae0d8aa0d4824a937

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ __DEV__ &&
14191419
exports.useTransition = function () {
14201420
return resolveDispatcher().useTransition();
14211421
};
1422-
exports.version = "19.2.0-www-classic-e3f19180-20250915";
1422+
exports.version = "19.2.0-www-classic-ae22247d-20250915";
14231423
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14241424
"function" ===
14251425
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ __DEV__ &&
14191419
exports.useTransition = function () {
14201420
return resolveDispatcher().useTransition();
14211421
};
1422-
exports.version = "19.2.0-www-modern-e3f19180-20250915";
1422+
exports.version = "19.2.0-www-modern-ae22247d-20250915";
14231423
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14241424
"function" ===
14251425
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,4 +602,4 @@ exports.useSyncExternalStore = function (
602602
exports.useTransition = function () {
603603
return ReactSharedInternals.H.useTransition();
604604
};
605-
exports.version = "19.2.0-www-classic-e3f19180-20250915";
605+
exports.version = "19.2.0-www-classic-ae22247d-20250915";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,4 +602,4 @@ exports.useSyncExternalStore = function (
602602
exports.useTransition = function () {
603603
return ReactSharedInternals.H.useTransition();
604604
};
605-
exports.version = "19.2.0-www-modern-e3f19180-20250915";
605+
exports.version = "19.2.0-www-modern-ae22247d-20250915";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ exports.useSyncExternalStore = function (
606606
exports.useTransition = function () {
607607
return ReactSharedInternals.H.useTransition();
608608
};
609-
exports.version = "19.2.0-www-classic-e3f19180-20250915";
609+
exports.version = "19.2.0-www-classic-ae22247d-20250915";
610610
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
611611
"function" ===
612612
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ exports.useSyncExternalStore = function (
606606
exports.useTransition = function () {
607607
return ReactSharedInternals.H.useTransition();
608608
};
609-
exports.version = "19.2.0-www-modern-e3f19180-20250915";
609+
exports.version = "19.2.0-www-modern-ae22247d-20250915";
610610
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
611611
"function" ===
612612
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19761,10 +19761,10 @@ __DEV__ &&
1976119761
(function () {
1976219762
var internals = {
1976319763
bundleType: 1,
19764-
version: "19.2.0-www-classic-e3f19180-20250915",
19764+
version: "19.2.0-www-classic-ae22247d-20250915",
1976519765
rendererPackageName: "react-art",
1976619766
currentDispatcherRef: ReactSharedInternals,
19767-
reconcilerVersion: "19.2.0-www-classic-e3f19180-20250915"
19767+
reconcilerVersion: "19.2.0-www-classic-ae22247d-20250915"
1976819768
};
1976919769
internals.overrideHookState = overrideHookState;
1977019770
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19798,7 +19798,7 @@ __DEV__ &&
1979819798
exports.Shape = Shape;
1979919799
exports.Surface = Surface;
1980019800
exports.Text = Text;
19801-
exports.version = "19.2.0-www-classic-e3f19180-20250915";
19801+
exports.version = "19.2.0-www-classic-ae22247d-20250915";
1980219802
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1980319803
"function" ===
1980419804
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19532,10 +19532,10 @@ __DEV__ &&
1953219532
(function () {
1953319533
var internals = {
1953419534
bundleType: 1,
19535-
version: "19.2.0-www-modern-e3f19180-20250915",
19535+
version: "19.2.0-www-modern-ae22247d-20250915",
1953619536
rendererPackageName: "react-art",
1953719537
currentDispatcherRef: ReactSharedInternals,
19538-
reconcilerVersion: "19.2.0-www-modern-e3f19180-20250915"
19538+
reconcilerVersion: "19.2.0-www-modern-ae22247d-20250915"
1953919539
};
1954019540
internals.overrideHookState = overrideHookState;
1954119541
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19569,7 +19569,7 @@ __DEV__ &&
1956919569
exports.Shape = Shape;
1957019570
exports.Surface = Surface;
1957119571
exports.Text = Text;
19572-
exports.version = "19.2.0-www-modern-e3f19180-20250915";
19572+
exports.version = "19.2.0-www-modern-ae22247d-20250915";
1957319573
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1957419574
"function" ===
1957519575
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)