From e5b9efb8e7ef09e67475c70a70faa1fd305794ef Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 31 Mar 2023 15:18:29 -0400 Subject: [PATCH] [Float] Suspend unstyled content for up to 1 minute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We almost never want to show content before its styles have loaded. But eventually we will give up and allow unstyled content. So this extends the timeout to a full minute. This somewhat arbitrary — big enough that you'd only reach it under extreme circumstances. Note that, like regular Suspense, the app is still interactive while we're waiting for content to load. Only the unstyled content is blocked from appearing, not updates in general. A new update will interupt it. We should out what the browser engines do during initial page load and consider aligning our behavior with that. It's supposed to be render blocking by default but there may be some cases where they, too, give up and FOUC. --- .../src/client/ReactDOMHostConfig.js | 9 ++++++++- .../src/__tests__/ReactDOMFloat-test.js | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js index 253b2da401cc8..8475755904946 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js @@ -3343,6 +3343,13 @@ export function waitForCommitToBeReady(): null | (Function => Function) { } function unsuspendAfterTimeout(state: SuspendedState) { + // We almost never want to show content before its styles have loaded. But + // eventually we will give up and allow unstyled content. So this number is + // somewhat arbitrary — big enough that you'd only reach it under + // extreme circumstances. + // TODO: Figure out what the browser engines do during initial page load and + // consider aligning our behavior with that. + const stylesheetTimeout = 60000; // one minute setTimeout(() => { if (state.stylesheets) { insertSuspendedStylesheets(state, state.stylesheets); @@ -3352,7 +3359,7 @@ function unsuspendAfterTimeout(state: SuspendedState) { state.unsuspend = null; unsuspend(); } - }, 500); + }, stylesheetTimeout); } function onUnsuspend(this: SuspendedState) { diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index d089b25ffad03..d362e1b94a4a9 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -3163,7 +3163,7 @@ body { ); }); - it('can unsuspend after a timeout even if some assets never load', async () => { + it('stylesheets block render, with a really long timeout', async () => { function App({children}) { return ( @@ -3191,7 +3191,22 @@ body { , ); - jest.advanceTimersByTime(1000); + // Advance time by 50 seconds. Even still, the transition is suspended. + jest.advanceTimersByTime(50000); + await waitForAll([]); + expect(getMeaningfulChildren(document)).toEqual( + + + + + + , + ); + + // Advance time by 10 seconds more. A full minute total has elapsed. At this + // point, something must have really gone wrong, so we time out and allow + // unstyled content to be displayed. + jest.advanceTimersByTime(10000); expect(getMeaningfulChildren(document)).toEqual(