-
Notifications
You must be signed in to change notification settings - Fork 50.2k
Allow nonce to be used on hoistable styles
#32461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
cf5d7dd
9c044b7
947e2f1
57b4c9e
cbf18c5
574374a
7906af5
3c152a9
9967076
a3259a0
6936609
a89a50d
63e0282
44d19b1
0c0eaf8
3a6a817
7ede4ed
aaebd2f
68d592a
acc9f05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2712,6 +2712,7 @@ function pushStyle( | |
| } | ||
| const precedence = props.precedence; | ||
| const href = props.href; | ||
| const nonce = props.nonce; | ||
|
|
||
| if ( | ||
| insertionMode === SVG_MODE || | ||
|
|
@@ -2759,9 +2760,23 @@ 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 (!('nonce' in styleQueue)) { | ||
| // `styleQueue` could have been created by `preinit` where `nonce` is not required | ||
| styleQueue.nonce = nonce && stringToChunk(escapeTextForBrowser(nonce)); | ||
| } | ||
|
||
| 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 && stringToChunk(escapeTextForBrowser(nonce)), | ||
Andarist marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| styleQueue.nonce, | ||
| ); | ||
| } | ||
| } | ||
| // We have seen this precedence before and need to track this href | ||
| styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); | ||
| } | ||
|
|
@@ -4684,8 +4699,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 | ||
|
|
@@ -4701,6 +4717,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( | ||
|
|
@@ -4712,13 +4729,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]); | ||
| } | ||
|
|
@@ -4805,9 +4826,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>'); | ||
|
|
||
|
|
@@ -4822,22 +4844,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]); | ||
| } | ||
|
|
@@ -5534,6 +5561,7 @@ export type StyleQueue = { | |
| rules: Array<Chunk | PrecomputedChunk>, | ||
| hrefs: Array<Chunk | PrecomputedChunk>, | ||
| sheets: Map<string, StylesheetResource>, | ||
| nonce?: ?Chunk, | ||
Andarist marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| export function createHoistableState(): HoistableState { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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').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', | ||
| ]); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nonce should be known early even if using preinitStyle. we can assume whatever nonce was used at styleQueue creation time is sufficient for all other style rules and don't need this lazy nonce check.
We can separately add a dev only warning which indicates when you are passing incompatible nonces to style tags. You would do this by having a dev only extra property like
It wouldn't be part of the type and you'd have to type cast to any when you read it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nonceisn't a part of thePreinitStyleOptions:react/packages/react-dom/src/shared/ReactDOMTypes.js
Lines 67 to 77 in 2980f27
According to the research I've done before opening this,
noncehas no effect on external stylesheets. They are controlled bystyle-srcdirective. That's the reason I concluded I have to make it work conditionally like this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gnoff friendly 🏓
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this context the nonce for style tag would be the nonce you are configuring for
style-srcCPS directive. It can be but doesn't have to be the same nonce value used forscript-src. but from the perspective of preinitStyle this is irrelevant since this just maps the argument to the<style nonce=...>attribute. So you should just add it as an optional option property for PreinitStyleOptions. It's already part of the public typescript type because we merge all the option types for the public APIThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically in a correctly configured program you will always pass a nonce to preinitStyle or never. So we can infer that the option passed in on the first invocation actually applies to all invocations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I then assume that it has to be passed to
preinitStylefor inline<style/>s to work properly (as that will require it to be known upfront)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do that though? Styles texts are merged together. This is how it currently works:
But now if the second
styleelement would have a differentnoncewe really shouldn't merge it with the first style (the one with the "correct" nonce).