Skip to content

Commit 538ac7a

Browse files
authored
[Flight] Fix debug info leaking to outer handler (facebook#34081)
The `waitForReference` call for debug info can trigger inside a different object's initializingHandler. In that case, we can get confused by which one is the root object. We have this special case to detect if the initializing handler's object is `null` and we have an empty string key, then we should replace the root object's value with the resolved value. https://github.com/facebook/react/blob/52612a7cbdd8e1fee9599478247f78725869ebad/packages/react-client/src/ReactFlightClient.js#L1374 However, if the initializing handler actually should have the value `null` then we might get confused by this and replace it with the resolved value from a debug object. This fixes it by just using a non-empty string as the key for the waitForReference on debug value since we're not going to use it anyway. It used to be impossible to get into this state since a `null` value at the root couldn't have any reference inside itself but now the debug info for a `null` value can have outstanding references. However, a better fix might be using a placeholder marker object instead of null or better yet ensuring that we know which root we're initializing in the debug model.
1 parent 52612a7 commit 538ac7a

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ function initializeDebugChunk(
879879
waitForReference(
880880
debugChunk,
881881
{}, // noop, since we'll have already added an entry to debug info
882-
'', // noop
882+
'debug', // noop, but we need it to not be empty string since that indicates the root object
883883
response,
884884
initializeDebugInfo,
885885
[''], // path

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,4 +1915,57 @@ describe('ReactFlightDOMEdge', () => {
19151915
expect(ownerStack).toBeNull();
19161916
}
19171917
});
1918+
1919+
it('can pass an async import that resolves later as a prop to a null component', async () => {
1920+
let resolveClientComponentChunk;
1921+
const client = clientExports(
1922+
{
1923+
foo: 'bar',
1924+
},
1925+
'42',
1926+
'/test.js',
1927+
new Promise(resolve => (resolveClientComponentChunk = resolve)),
1928+
);
1929+
1930+
function ServerComponent(props) {
1931+
return null;
1932+
}
1933+
1934+
function App() {
1935+
return (
1936+
<div>
1937+
<ServerComponent client={client} />
1938+
</div>
1939+
);
1940+
}
1941+
1942+
const stream = await serverAct(() =>
1943+
passThrough(
1944+
ReactServerDOMServer.renderToReadableStream(<App />, webpackMap),
1945+
),
1946+
);
1947+
1948+
// Parsing the root blocks because the module hasn't loaded yet
1949+
const response = ReactServerDOMClient.createFromReadableStream(stream, {
1950+
serverConsumerManifest: {
1951+
moduleMap: null,
1952+
moduleLoading: null,
1953+
},
1954+
});
1955+
1956+
function ClientRoot() {
1957+
return use(response);
1958+
}
1959+
1960+
// Initialize to be blocked.
1961+
response.then(() => {});
1962+
// Unblock.
1963+
resolveClientComponentChunk();
1964+
1965+
const ssrStream = await serverAct(() =>
1966+
ReactDOMServer.renderToReadableStream(<ClientRoot />),
1967+
);
1968+
const result = await readResult(ssrStream);
1969+
expect(result).toEqual('<div></div>');
1970+
});
19181971
});

0 commit comments

Comments
 (0)