Skip to content

Commit ccbb26c

Browse files
committed
Adds a test demonstrating preload credential transfer across prerender/resume
1 parent 5a1f45d commit ccbb26c

File tree

2 files changed

+278
-2
lines changed

2 files changed

+278
-2
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
import {
13+
getVisibleChildren,
14+
insertNodesAndExecuteScripts,
15+
} from '../test-utils/FizzTestUtils';
16+
17+
// Polyfills for test environment
18+
global.ReadableStream =
19+
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
20+
global.TextEncoder = require('util').TextEncoder;
21+
22+
let React;
23+
let ReactDOM;
24+
let ReactDOMFizzServer;
25+
let ReactDOMFizzStatic;
26+
let Suspense;
27+
let container;
28+
29+
describe('ReactDOMFizzStaticFloat', () => {
30+
beforeEach(() => {
31+
jest.resetModules();
32+
React = require('react');
33+
ReactDOM = require('react-dom');
34+
ReactDOMFizzServer = require('react-dom/server.browser');
35+
if (__EXPERIMENTAL__) {
36+
ReactDOMFizzStatic = require('react-dom/static.browser');
37+
}
38+
Suspense = React.Suspense;
39+
container = document.createElement('div');
40+
document.body.appendChild(container);
41+
});
42+
43+
afterEach(() => {
44+
document.body.removeChild(container);
45+
});
46+
47+
async function readIntoContainer(stream) {
48+
const reader = stream.getReader();
49+
let result = '';
50+
while (true) {
51+
const {done, value} = await reader.read();
52+
if (done) {
53+
break;
54+
}
55+
result += Buffer.from(value).toString('utf8');
56+
}
57+
const temp = document.createElement('div');
58+
temp.innerHTML = result;
59+
await insertNodesAndExecuteScripts(temp, container, null);
60+
}
61+
62+
// @gate experimental
63+
it('should transfer connection credentials across prerender and resume for stylesheets, scripts, and moduleScripts', async () => {
64+
let prerendering = true;
65+
function Postpone() {
66+
if (prerendering) {
67+
React.unstable_postpone();
68+
}
69+
return (
70+
<>
71+
<link rel="stylesheet" href="style creds" precedence="default" />
72+
<script async={true} src="script creds" data-meaningful="" />
73+
<script
74+
type="module"
75+
async={true}
76+
src="module creds"
77+
data-meaningful=""
78+
/>
79+
<link rel="stylesheet" href="style anon" precedence="default" />
80+
<script async={true} src="script anon" data-meaningful="" />
81+
<script
82+
type="module"
83+
async={true}
84+
src="module default"
85+
data-meaningful=""
86+
/>
87+
</>
88+
);
89+
}
90+
91+
function App() {
92+
ReactDOM.preload('style creds', {
93+
as: 'style',
94+
crossOrigin: 'use-credentials',
95+
});
96+
ReactDOM.preload('script creds', {
97+
as: 'script',
98+
crossOrigin: 'use-credentials',
99+
integrity: 'script-hash',
100+
});
101+
ReactDOM.preloadModule('module creds', {
102+
crossOrigin: 'use-credentials',
103+
integrity: 'module-hash',
104+
});
105+
ReactDOM.preload('style anon', {
106+
as: 'style',
107+
crossOrigin: 'anonymous',
108+
});
109+
ReactDOM.preload('script anon', {
110+
as: 'script',
111+
crossOrigin: 'foobar',
112+
});
113+
ReactDOM.preloadModule('module default', {
114+
integrity: 'module-hash',
115+
});
116+
return (
117+
<div>
118+
<Suspense fallback="Loading...">
119+
<Postpone />
120+
</Suspense>
121+
</div>
122+
);
123+
}
124+
125+
jest.mock('script creds', () => {}, {
126+
virtual: true,
127+
});
128+
jest.mock('module creds', () => {}, {
129+
virtual: true,
130+
});
131+
jest.mock('script anon', () => {}, {
132+
virtual: true,
133+
});
134+
jest.mock('module default', () => {}, {
135+
virtual: true,
136+
});
137+
138+
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
139+
expect(prerendered.postponed).not.toBe(null);
140+
141+
await readIntoContainer(prerendered.prelude);
142+
143+
expect(getVisibleChildren(container)).toEqual([
144+
<link
145+
rel="preload"
146+
as="style"
147+
href="style creds"
148+
crossorigin="use-credentials"
149+
/>,
150+
<link
151+
rel="preload"
152+
as="script"
153+
href="script creds"
154+
crossorigin="use-credentials"
155+
integrity="script-hash"
156+
/>,
157+
<link
158+
rel="modulepreload"
159+
href="module creds"
160+
crossorigin="use-credentials"
161+
integrity="module-hash"
162+
/>,
163+
<link rel="preload" as="style" href="style anon" crossorigin="" />,
164+
<link rel="preload" as="script" href="script anon" crossorigin="" />,
165+
<link
166+
rel="modulepreload"
167+
href="module default"
168+
integrity="module-hash"
169+
/>,
170+
<div>Loading...</div>,
171+
]);
172+
173+
prerendering = false;
174+
const content = await ReactDOMFizzServer.resume(
175+
<App />,
176+
JSON.parse(JSON.stringify(prerendered.postponed)),
177+
);
178+
179+
await readIntoContainer(content);
180+
181+
// Dispatch load event to injected stylesheet
182+
const linkCreds = document.querySelector(
183+
'link[rel="stylesheet"][href="style creds"]',
184+
);
185+
const linkAnon = document.querySelector(
186+
'link[rel="stylesheet"][href="style anon"]',
187+
);
188+
const event = document.createEvent('Events');
189+
event.initEvent('load', true, true);
190+
linkCreds.dispatchEvent(event);
191+
linkAnon.dispatchEvent(event);
192+
193+
// Wait for the instruction microtasks to flush.
194+
await 0;
195+
await 0;
196+
197+
expect(getVisibleChildren(document)).toEqual(
198+
<html>
199+
<head>
200+
<link
201+
rel="stylesheet"
202+
data-precedence="default"
203+
href="style creds"
204+
crossorigin="use-credentials"
205+
/>
206+
<link
207+
rel="stylesheet"
208+
data-precedence="default"
209+
href="style anon"
210+
crossorigin=""
211+
/>
212+
</head>
213+
<body>
214+
<div>
215+
<link
216+
rel="preload"
217+
as="style"
218+
href="style creds"
219+
crossorigin="use-credentials"
220+
/>
221+
<link
222+
rel="preload"
223+
as="script"
224+
href="script creds"
225+
crossorigin="use-credentials"
226+
integrity="script-hash"
227+
/>
228+
<link
229+
rel="modulepreload"
230+
href="module creds"
231+
crossorigin="use-credentials"
232+
integrity="module-hash"
233+
/>
234+
<link rel="preload" as="style" href="style anon" crossorigin="" />
235+
<link rel="preload" as="script" href="script anon" crossorigin="" />
236+
<link
237+
rel="modulepreload"
238+
href="module default"
239+
integrity="module-hash"
240+
/>
241+
<div />
242+
<script
243+
async=""
244+
src="script creds"
245+
crossorigin="use-credentials"
246+
integrity="script-hash"
247+
data-meaningful=""
248+
/>
249+
<script
250+
type="module"
251+
async=""
252+
src="module creds"
253+
crossorigin="use-credentials"
254+
integrity="module-hash"
255+
data-meaningful=""
256+
/>
257+
<script
258+
async=""
259+
src="script anon"
260+
crossorigin=""
261+
data-meaningful=""
262+
/>
263+
<script
264+
type="module"
265+
async=""
266+
src="module default"
267+
integrity="module-hash"
268+
data-meaningful=""
269+
/>
270+
</div>
271+
</body>
272+
</html>,
273+
);
274+
});
275+
});

packages/react-dom/src/test-utils/FizzTestUtils.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ async function executeScript(script: Element) {
9393
'You must set the current document to the global document to use script src in tests',
9494
);
9595
}
96+
9697
try {
9798
// $FlowFixMe
9899
require(scriptSrc);
@@ -177,8 +178,8 @@ function getVisibleChildren(element: Element): React$Node {
177178
while (node) {
178179
if (node.nodeType === 1) {
179180
if (
180-
node.tagName !== 'SCRIPT' &&
181-
node.tagName !== 'script' &&
181+
((node.tagName !== 'SCRIPT' && node.tagName !== 'script') ||
182+
node.hasAttribute('data-meaningful')) &&
182183
node.tagName !== 'TEMPLATE' &&
183184
node.tagName !== 'template' &&
184185
!node.hasAttribute('hidden') &&

0 commit comments

Comments
 (0)