Skip to content

Commit 98539eb

Browse files
committed
Throttle reveals
1 parent 244ebc9 commit 98539eb

File tree

5 files changed

+41
-5
lines changed

5 files changed

+41
-5
lines changed

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export function clientRenderBoundary(
4747
}
4848
}
4949

50+
const FALLBACK_THROTTLE_MS = 300;
51+
5052
export function completeBoundary(suspenseBoundaryID, contentID) {
5153
const contentNodeOuter = document.getElementById(contentID);
5254
if (!contentNodeOuter) {
@@ -69,6 +71,7 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
6971
}
7072

7173
function revealCompletedBoundaries() {
74+
window['$RT'] = performance.now();
7275
const batch = window['$RB'];
7376
window['$RB'] = [];
7477
for (let i = 0; i < batch.length; i += 2) {
@@ -132,7 +135,18 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
132135
// Queue this boundary for the next batch
133136
window['$RB'].push(suspenseIdNodeOuter, contentNodeOuter);
134137

135-
revealCompletedBoundaries();
138+
if (window['$RB'].length === 2) {
139+
// This is the first time we've pushed to the batch. We need to schedule a callback
140+
// to flush the batch. This is delayed by the throttle heuristic.
141+
const globalMostRecentFallbackTime =
142+
typeof window['$RT'] !== 'number' ? 0 : window['$RT'];
143+
const msUntilTimeout =
144+
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - performance.now();
145+
// We always schedule the flush in a timer even if it's very low or negative to allow
146+
// for multiple completeBoundary calls that are already queued to have a chance to
147+
// make the batch.
148+
setTimeout(revealCompletedBoundaries, msUntilTimeout);
149+
}
136150
}
137151

138152
export function completeBoundaryWithStyles(

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ describe('ReactDOMFizzServer', () => {
209209
buffer = '';
210210

211211
if (!bufferedContent) {
212+
jest.runAllTimers();
212213
return;
213214
}
214215

@@ -317,6 +318,8 @@ describe('ReactDOMFizzServer', () => {
317318
div.innerHTML = bufferedContent;
318319
await insertNodesAndExecuteScripts(div, streamingContainer, CSPnonce);
319320
}
321+
// Let throttled boundaries reveal
322+
jest.runAllTimers();
320323
}
321324

322325
function resolveText(text) {

packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ describe('ReactDOMFizzStaticBrowser', () => {
3838
jest.resetModules();
3939
JSDOM = require('jsdom').JSDOM;
4040

41+
// We need the mocked version of setTimeout inside the document.
42+
window.setTimeout = setTimeout;
43+
4144
Scheduler = require('scheduler');
4245
patchMessageChannel(Scheduler);
4346
act = require('internal-test-utils').act;
@@ -133,13 +136,18 @@ describe('ReactDOMFizzStaticBrowser', () => {
133136
const temp = document.createElement('div');
134137
temp.innerHTML = result;
135138
await insertNodesAndExecuteScripts(temp, container, null);
139+
jest.runAllTimers();
136140
}
137141

138142
async function readIntoNewDocument(stream) {
139143
const content = await readContent(stream);
140-
const jsdom = new JSDOM(content, {
141-
runScripts: 'dangerously',
142-
});
144+
const jsdom = new JSDOM(
145+
// The Fizz runtime assumes requestAnimationFrame exists so we need to polyfill it.
146+
'<script>window.requestAnimationFrame = setTimeout;</script>' + content,
147+
{
148+
runScripts: 'dangerously',
149+
},
150+
);
143151
const originalWindow = global.window;
144152
const originalDocument = global.document;
145153
const originalNavigator = global.navigator;
@@ -167,6 +175,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
167175
const temp = document.createElement('div');
168176
temp.innerHTML = content;
169177
await insertNodesAndExecuteScripts(temp, document.body, null);
178+
jest.runAllTimers();
170179
}
171180

172181
it('should call prerender', async () => {
@@ -980,6 +989,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
980989
// Wait for the instruction microtasks to flush.
981990
await 0;
982991
await 0;
992+
jest.runAllTimers();
983993

984994
expect(getVisibleChildren(container)).toEqual([
985995
<link href="example.com" rel="preconnect" />,

packages/react-dom/src/__tests__/ReactDOMFloat-test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ describe('ReactDOMFloat', () => {
125125
buffer = '';
126126

127127
if (!bufferedContent) {
128+
jest.runAllTimers();
128129
return;
129130
}
130131

@@ -233,6 +234,9 @@ describe('ReactDOMFloat', () => {
233234
div.innerHTML = bufferedContent;
234235
await insertNodesAndExecuteScripts(div, streamingContainer, CSPnonce);
235236
}
237+
await 0;
238+
// Let throttled boundaries reveal
239+
jest.runAllTimers();
236240
}
237241

238242
function getMeaningfulChildren(element) {
@@ -3614,6 +3618,7 @@ body {
36143618
assertConsoleErrorDev([
36153619
"Hydration failed because the server rendered HTML didn't match the client.",
36163620
]);
3621+
jest.runAllTimers();
36173622

36183623
expect(getMeaningfulChildren(document)).toEqual(
36193624
<html>
@@ -5207,6 +5212,10 @@ body {
52075212
</html>,
52085213
);
52095214
loadStylesheets();
5215+
// Let the styles flush and then flush the boundaries
5216+
await 0;
5217+
await 0;
5218+
jest.runAllTimers();
52105219
assertLog([
52115220
'load stylesheet: shell preinit/shell',
52125221
'load stylesheet: shell/shell preinit',

0 commit comments

Comments
 (0)