Skip to content

Commit

Permalink
Allow nonce to be used on hoistable styles
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Feb 23, 2025
1 parent 9b042f9 commit 805f507
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 8 deletions.
41 changes: 33 additions & 8 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -2712,6 +2712,7 @@ function pushStyle(
}
const precedence = props.precedence;
const href = props.href;
const nonce = props.nonce;

if (
insertionMode === SVG_MODE ||
Expand Down Expand Up @@ -2759,9 +2760,19 @@ function pushStyle(
rules: ([]: Array<Chunk | PrecomputedChunk>),
hrefs: [stringToChunk(escapeTextForBrowser(href))],
sheets: (new Map(): Map<string, StylesheetResource>),
nonce: nonce && stringToChunk(escapeTextForBrowser(nonce)),
};
renderState.styles.set(precedence, styleQueue);
} else {
if (__DEV__) {
if (nonce !== styleQueue.nonce) {
console.error(
'React encountered a hoistable style tag with "%s" nonce. It doesn\'t match the previously encountered nonce "%s". They have to be the same',
nonce,
styleQueue.nonce,
);
}
}
// We have seen this precedence before and need to track this href
styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href)));
}
Expand Down Expand Up @@ -4684,8 +4695,9 @@ function escapeJSObjectForInstructionScripts(input: Object): string {
const lateStyleTagResourceOpen1 = stringToPrecomputedChunk(
'<style media="not all" data-precedence="',
);
const lateStyleTagResourceOpen2 = stringToPrecomputedChunk('" data-href="');
const lateStyleTagResourceOpen3 = stringToPrecomputedChunk('">');
const lateStyleTagResourceOpen2 = stringToPrecomputedChunk('" nonce="');
const lateStyleTagResourceOpen3 = stringToPrecomputedChunk('" data-href="');
const lateStyleTagResourceOpen4 = stringToPrecomputedChunk('">');
const lateStyleTagTemplateClose = stringToPrecomputedChunk('</style>');

// Tracks whether the boundary currently flushing is flushign style tags or has any
Expand All @@ -4701,6 +4713,7 @@ function flushStyleTagsLateForBoundary(
) {
const rules = styleQueue.rules;
const hrefs = styleQueue.hrefs;
const nonce = styleQueue.nonce;
if (__DEV__) {
if (rules.length > 0 && hrefs.length === 0) {
console.error(
Expand All @@ -4712,13 +4725,17 @@ function flushStyleTagsLateForBoundary(
if (hrefs.length) {
writeChunk(this, lateStyleTagResourceOpen1);
writeChunk(this, styleQueue.precedence);
writeChunk(this, lateStyleTagResourceOpen2);
if (nonce) {
writeChunk(this, lateStyleTagResourceOpen2);
writeChunk(this, nonce);
}
writeChunk(this, lateStyleTagResourceOpen3);
for (; i < hrefs.length - 1; i++) {
writeChunk(this, hrefs[i]);
writeChunk(this, spaceSeparator);
}
writeChunk(this, hrefs[i]);
writeChunk(this, lateStyleTagResourceOpen3);
writeChunk(this, lateStyleTagResourceOpen4);
for (i = 0; i < rules.length; i++) {
writeChunk(this, rules[i]);
}
Expand Down Expand Up @@ -4805,9 +4822,10 @@ function flushStyleInPreamble(
const styleTagResourceOpen1 = stringToPrecomputedChunk(
'<style data-precedence="',
);
const styleTagResourceOpen2 = stringToPrecomputedChunk('" data-href="');
const styleTagResourceOpen2 = stringToPrecomputedChunk('" nonce="');
const styleTagResourceOpen3 = stringToPrecomputedChunk('" data-href="');
const spaceSeparator = stringToPrecomputedChunk(' ');
const styleTagResourceOpen3 = stringToPrecomputedChunk('">');
const styleTagResourceOpen4 = stringToPrecomputedChunk('">');

const styleTagResourceClose = stringToPrecomputedChunk('</style>');

Expand All @@ -4822,22 +4840,27 @@ function flushStylesInPreamble(

const rules = styleQueue.rules;
const hrefs = styleQueue.hrefs;
const nonce = styleQueue.nonce;
// If we don't emit any stylesheets at this precedence we still need to maintain the precedence
// order so even if there are no rules for style tags at this precedence we emit an empty style
// tag with the data-precedence attribute
if (!hasStylesheets || hrefs.length) {
writeChunk(this, styleTagResourceOpen1);
writeChunk(this, styleQueue.precedence);
if (nonce) {
writeChunk(this, styleTagResourceOpen2);
writeChunk(this, nonce);
}
let i = 0;
if (hrefs.length) {
writeChunk(this, styleTagResourceOpen2);
writeChunk(this, styleTagResourceOpen3);
for (; i < hrefs.length - 1; i++) {
writeChunk(this, hrefs[i]);
writeChunk(this, spaceSeparator);
}
writeChunk(this, hrefs[i]);
}
writeChunk(this, styleTagResourceOpen3);
writeChunk(this, styleTagResourceOpen4);
for (i = 0; i < rules.length; i++) {
writeChunk(this, rules[i]);
}
Expand Down Expand Up @@ -5534,6 +5557,7 @@ export type StyleQueue = {
rules: Array<Chunk | PrecomputedChunk>,
hrefs: Array<Chunk | PrecomputedChunk>,
sheets: Map<string, StylesheetResource>,
nonce: ?string,
};

export function createHoistableState(): HoistableState {
Expand Down Expand Up @@ -5985,6 +6009,7 @@ function preinitStyle(
rules: ([]: Array<Chunk | PrecomputedChunk>),
hrefs: ([]: Array<Chunk | PrecomputedChunk>),
sheets: (new Map(): Map<string, StylesheetResource>),
nonce: options.nonce && stringToChunk(escapeTextForBrowser(options.nonce)),
};
renderState.styles.set(precedence, styleQueue);
}
Expand Down
45 changes: 45 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10331,4 +10331,49 @@ describe('ReactDOMFizzServer', () => {
</html>,
);
});

it('can render styles with nonce', async () => {
CSPnonce = 'R4nd0m';
await act(() => {
const {pipe} = renderToPipeableStream(
<>
<style
href="foo"
precedence="default"
nonce={CSPnonce}>{`.foo { color: hotpink; }`}</style>
<style
href="bar"
precedence="default"
nonce={CSPnonce}>{`.bar { background-color: blue; }`}</style>
</>,
);
pipe(writable);
});
expect(document.querySelector('style').getAttribute('nonce')).toBe(
CSPnonce,
);
});

// @gate __DEV__
it('warns when it encounters a mismatched nonce on a style', async () => {
CSPnonce = 'R4nd0m';
await act(() => {
const {pipe} = renderToPipeableStream(
<>
<style
href="foo"
precedence="default"
nonce={CSPnonce}>{`.foo { color: hotpink; }`}</style>
<style
href="bar"
precedence="default"
nonce={`${CSPnonce}${CSPnonce}`}>{`.bar { background-color: blue; }`}</style>
</>,
);
pipe(writable);
});
assertConsoleErrorDev([
'React encountered a hoistable style tag with "R4nd0mR4nd0m" nonce. It doesn\'t match the previously encountered nonce "R4nd0m". They have to be the same',
]);
});
});
79 changes: 79 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFloat-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8435,6 +8435,85 @@ background-color: green;
: '\n in body (at **)' + '\n in html (at **)'),
]);
});

it('can emit styles with nonce', async () => {
const nonce = 'R4nD0m';
const fooCss = '.foo { color: hotpink; }';
const barCss = '.bar { background-color: blue; }';
const bazCss = '.baz { border: 1px solid black; }';
await act(() => {
renderToPipeableStream(
<html>
<body>
<Suspense>
<BlockedOn value="first">
<div>first</div>
<style href="foo" precedence="default" nonce={nonce}>
{fooCss}
</style>
<style href="bar" precedence="default" nonce={nonce}>
{barCss}
</style>
<BlockedOn value="second">
<div>second</div>
<style href="baz" precedence="default" nonce={nonce}>
{bazCss}
</style>
</BlockedOn>
</BlockedOn>
</Suspense>
</body>
</html>,
).pipe(writable);
});

expect(getMeaningfulChildren(document)).toEqual(
<html>
<head />
<body />
</html>,
);

await act(() => {
resolveText('first');
});

expect(getMeaningfulChildren(document)).toEqual(
<html>
<head />
<body>
<style
data-href="foo bar"
data-precedence="default"
media="not all"
nonce={nonce}>
{`${fooCss}${barCss}`}
</style>
</body>
</html>,
);

await act(() => {
resolveText('second');
});

expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<style data-href="foo bar" data-precedence="default" nonce={nonce}>
{`${fooCss}${barCss}`}
</style>
<style data-href="baz" data-precedence="default" nonce={nonce}>
{bazCss}
</style>
</head>
<body>
<div>first</div>
<div>second</div>
</body>
</html>,
);
});
});

describe('Script Resources', () => {
Expand Down

0 comments on commit 805f507

Please sign in to comment.