Skip to content

Commit 9341775

Browse files
authored
fix transposed escape functions (#25534)
escapeTextForBrowser accepts any type so flow did not identify that we were escaping a Chunk rather than a string. It's tricky because we sometimes want to be able to escape non strings. I've also updated the types for `Chunk` and `escapeTextForBrowser` so that we should be able to catch this statically in the future. The reason this did not show up in tests is almost all of our tests of float (the areas affected by transpositions) are tested using the Node runtime where a chunk type is a string. It may be wise to run these tests in every runtime in the future or at least make sure there is broad representation of resources in each specific runtime test suite.
1 parent d1ced9f commit 9341775

File tree

7 files changed

+31
-11
lines changed

7 files changed

+31
-11
lines changed

packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export interface Destination {
1212
destroy(error: Error): mixed;
1313
}
1414

15-
export type PrecomputedChunk = string;
16-
export type Chunk = string;
15+
export opaque type PrecomputedChunk = string;
16+
export opaque type Chunk = string;
1717

1818
export function scheduleWork(callback: () => void) {
1919
callback();

packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2385,7 +2385,7 @@ export function writeInitialResources(
23852385
} else {
23862386
target.push(
23872387
precedencePlaceholderStart,
2388-
escapeTextForBrowser(stringToChunk(precedence)),
2388+
stringToChunk(escapeTextForBrowser(precedence)),
23892389
precedencePlaceholderEnd,
23902390
);
23912391
}
@@ -2417,7 +2417,7 @@ export function writeInitialResources(
24172417
case 'title': {
24182418
pushStartTitleImpl(target, r.props, responseState);
24192419
if (typeof r.props.children === 'string') {
2420-
target.push(escapeTextForBrowser(stringToChunk(r.props.children)));
2420+
target.push(stringToChunk(escapeTextForBrowser(r.props.children)));
24212421
}
24222422
pushEndInstance(target, target, 'title', r.props);
24232423
break;
@@ -2518,7 +2518,7 @@ export function writeImmediateResources(
25182518
case 'title': {
25192519
pushStartTitleImpl(target, r.props, responseState);
25202520
if (typeof r.props.children === 'string') {
2521-
target.push(escapeTextForBrowser(stringToChunk(r.props.children)));
2521+
target.push(stringToChunk(escapeTextForBrowser(r.props.children)));
25222522
}
25232523
pushEndInstance(target, target, 'title', r.props);
25242524
break;

packages/react-dom-bindings/src/server/escapeTextForBrowser.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2929
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3030
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31+
*
32+
* @flow
3133
*/
3234

3335
// code copied and modified from escape-html
@@ -103,12 +105,12 @@ function escapeHtml(string) {
103105
* @param {*} text Text value to escape.
104106
* @return {string} An escaped string.
105107
*/
106-
function escapeTextForBrowser(text) {
108+
function escapeTextForBrowser(text: string | number | boolean): string {
107109
if (typeof text === 'boolean' || typeof text === 'number') {
108110
// this shortcircuit helps perf for types that we know will never have
109111
// special characters, especially given that this function is used often
110112
// for numeric dom ids.
111-
return '' + text;
113+
return '' + (text: any);
112114
}
113115
return escapeHtml(text);
114116
}

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

+18
Original file line numberDiff line numberDiff line change
@@ -484,4 +484,22 @@ describe('ReactDOMFizzServerBrowser', () => {
484484

485485
expect(errors).toEqual(['uh oh', 'uh oh']);
486486
});
487+
488+
// https://github.com/facebook/react/pull/25534/files - fix transposed escape functions
489+
// @gate enableFloat
490+
it('should encode title properly', async () => {
491+
const stream = await ReactDOMFizzServer.renderToReadableStream(
492+
<html>
493+
<head>
494+
<title>foo</title>
495+
</head>
496+
<body>bar</body>
497+
</html>,
498+
);
499+
500+
const result = await readResult(stream);
501+
expect(result).toEqual(
502+
'<!DOCTYPE html><html><head><title>foo</title></title></head><body>bar</body></html>',
503+
);
504+
});
487505
});

packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export type Destination = {
1414
error: mixed,
1515
};
1616

17-
export type PrecomputedChunk = string;
18-
export type Chunk = string;
17+
export opaque type PrecomputedChunk = string;
18+
export opaque type Chunk = string;
1919

2020
export function scheduleWork(callback: () => void) {
2121
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.

packages/react-server/src/ReactServerStreamConfigBrowser.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
export type Destination = ReadableStreamController;
1111

1212
export type PrecomputedChunk = Uint8Array;
13-
export type Chunk = Uint8Array;
13+
export opaque type Chunk = Uint8Array;
1414

1515
export function scheduleWork(callback: () => void) {
1616
callback();

packages/react-server/src/ReactServerStreamConfigNode.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface MightBeFlushable {
1717
export type Destination = Writable & MightBeFlushable;
1818

1919
export type PrecomputedChunk = Uint8Array;
20-
export type Chunk = string;
20+
export opaque type Chunk = string;
2121

2222
export function scheduleWork(callback: () => void) {
2323
setImmediate(callback);

0 commit comments

Comments
 (0)