-
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 17 commits
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 |
|---|---|---|
|
|
@@ -135,6 +135,13 @@ const SentMarkShellTime /* */ = 0b001000000; | |
| const NeedUpgradeToViewTransitions /* */ = 0b010000000; | ||
| const SentUpgradeToViewTransitions /* */ = 0b100000000; | ||
|
|
||
| type NonceOption = | ||
| | string | ||
| | { | ||
| script?: string, | ||
| style?: string, | ||
| }; | ||
|
|
||
| // Per request, global state that is not contextual to the rendering subtree. | ||
| // This cannot be resumed and therefore should only contain things that are | ||
| // temporary working state or are never used in the prerender pass. | ||
|
|
@@ -147,6 +154,8 @@ export type RenderState = { | |
| // inline script streaming format, unused if using external runtime / data | ||
| startInlineScript: PrecomputedChunk, | ||
|
|
||
| startInlineStyle: PrecomputedChunk, | ||
|
|
||
| // the preamble must always flush before resuming, so all these chunks must | ||
| // be null or empty when resuming. | ||
|
|
||
|
|
@@ -209,6 +218,11 @@ export type RenderState = { | |
| moduleScripts: Map<string, Resource>, | ||
| }, | ||
|
|
||
| nonce: { | ||
| script: string | void, | ||
| style: string | void, | ||
| }, | ||
|
|
||
| // Module-global-like reference for flushing/hoisting state of style resources | ||
| // We need to track whether the current request has flushed any style resources | ||
| // without sending an instruction to hoist them. we do that here | ||
|
|
@@ -307,6 +321,8 @@ const scriptIntegirty = stringToPrecomputedChunk(' integrity="'); | |
| const scriptCrossOrigin = stringToPrecomputedChunk(' crossorigin="'); | ||
| const endAsyncScript = stringToPrecomputedChunk(' async=""></script>'); | ||
|
|
||
| const startInlineStyle = stringToPrecomputedChunk('<style'); | ||
|
|
||
| /** | ||
| * This escaping function is designed to work with with inline scripts where the entire | ||
| * contents are escaped. Because we know we are escaping the entire script we can avoid for instance | ||
|
|
@@ -365,17 +381,32 @@ if (__DEV__) { | |
| // is set, the server will send instructions via data attributes (instead of inline scripts) | ||
| export function createRenderState( | ||
| resumableState: ResumableState, | ||
| nonce: string | void, | ||
| nonce: | ||
| | string | ||
| | { | ||
| script?: string, | ||
| style?: string, | ||
| } | ||
| | void, | ||
| externalRuntimeConfig: string | BootstrapScriptDescriptor | void, | ||
| importMap: ImportMap | void, | ||
| onHeaders: void | ((headers: HeadersDescriptor) => void), | ||
| maxHeadersLength: void | number, | ||
| ): RenderState { | ||
| const nonceScript = typeof nonce === 'string' ? nonce : nonce && nonce.script; | ||
| const inlineScriptWithNonce = | ||
| nonce === undefined | ||
| nonceScript === undefined | ||
| ? startInlineScript | ||
| : stringToPrecomputedChunk( | ||
| '<script nonce="' + escapeTextForBrowser(nonce) + '"', | ||
| '<script nonce="' + escapeTextForBrowser(nonceScript) + '"', | ||
| ); | ||
| const nonceStyle = | ||
| typeof nonce === 'string' ? undefined : nonce && nonce.style; | ||
| const inlineStyleWithNonce = | ||
| nonceStyle === undefined | ||
| ? startInlineStyle | ||
| : stringToPrecomputedChunk( | ||
| '<style nonce="' + escapeTextForBrowser(nonceStyle) + '"', | ||
| ); | ||
| const idPrefix = resumableState.idPrefix; | ||
|
|
||
|
|
@@ -403,7 +434,7 @@ export function createRenderState( | |
| src: externalRuntimeConfig, | ||
| async: true, | ||
| integrity: undefined, | ||
| nonce: nonce, | ||
| nonce: nonceScript, | ||
| }); | ||
| } else { | ||
| externalRuntimeScript = { | ||
|
|
@@ -414,7 +445,7 @@ export function createRenderState( | |
| src: externalRuntimeConfig.src, | ||
| async: true, | ||
| integrity: externalRuntimeConfig.integrity, | ||
| nonce: nonce, | ||
| nonce: nonceScript, | ||
| }); | ||
| } | ||
| } | ||
|
|
@@ -459,6 +490,7 @@ export function createRenderState( | |
| segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'), | ||
| boundaryPrefix: stringToPrecomputedChunk(idPrefix + 'B:'), | ||
| startInlineScript: inlineScriptWithNonce, | ||
| startInlineStyle: inlineStyleWithNonce, | ||
| preamble: createPreambleState(), | ||
|
|
||
| externalRuntimeScript: externalRuntimeScript, | ||
|
|
@@ -500,7 +532,10 @@ export function createRenderState( | |
| moduleScripts: new Map(), | ||
| }, | ||
|
|
||
| nonce, | ||
| nonce: { | ||
| script: nonceScript, | ||
| style: nonceStyle, | ||
| }, | ||
| // like a module global for currently rendering boundary | ||
| hoistableState: null, | ||
| stylesToHoist: false, | ||
|
|
@@ -539,10 +574,10 @@ export function createRenderState( | |
| stringToChunk(escapeTextForBrowser(src)), | ||
| attributeEnd, | ||
| ); | ||
| if (nonce) { | ||
| if (nonceScript) { | ||
| bootstrapChunks.push( | ||
| scriptNonce, | ||
| stringToChunk(escapeTextForBrowser(nonce)), | ||
| stringToChunk(escapeTextForBrowser(nonceScript)), | ||
| attributeEnd, | ||
| ); | ||
| } | ||
|
|
@@ -571,7 +606,7 @@ export function createRenderState( | |
| const props: PreloadModuleProps = ({ | ||
| rel: 'modulepreload', | ||
| fetchPriority: 'low', | ||
| nonce, | ||
| nonce: nonceScript, | ||
| }: any); | ||
| if (typeof scriptConfig === 'string') { | ||
| props.href = src = scriptConfig; | ||
|
|
@@ -596,10 +631,10 @@ export function createRenderState( | |
| stringToChunk(escapeTextForBrowser(src)), | ||
| attributeEnd, | ||
| ); | ||
| if (nonce) { | ||
| if (nonceScript) { | ||
| bootstrapChunks.push( | ||
| scriptNonce, | ||
| stringToChunk(escapeTextForBrowser(nonce)), | ||
| stringToChunk(escapeTextForBrowser(nonceScript)), | ||
| attributeEnd, | ||
| ); | ||
| } | ||
|
|
@@ -627,7 +662,7 @@ export function createRenderState( | |
|
|
||
| export function resumeRenderState( | ||
| resumableState: ResumableState, | ||
| nonce: string | void, | ||
| nonce: NonceOption | void, | ||
| ): RenderState { | ||
| return createRenderState( | ||
| resumableState, | ||
|
|
@@ -2882,6 +2917,7 @@ function pushLink( | |
| // to create a StyleQueue | ||
| if (!styleQueue) { | ||
| styleQueue = { | ||
| start: renderState.startInlineStyle, | ||
| precedence: stringToChunk(escapeTextForBrowser(precedence)), | ||
| rules: ([]: Array<Chunk | PrecomputedChunk>), | ||
| hrefs: ([]: Array<Chunk | PrecomputedChunk>), | ||
|
|
@@ -3046,6 +3082,7 @@ function pushStyle( | |
| } | ||
| const precedence = props.precedence; | ||
| const href = props.href; | ||
| const nonce = props.nonce; | ||
|
|
||
| if ( | ||
| formatContext.insertionMode === SVG_MODE || | ||
|
|
@@ -3089,17 +3126,34 @@ function pushStyle( | |
| // This is the first time we've encountered this precedence we need | ||
| // to create a StyleQueue. | ||
| styleQueue = { | ||
| start: renderState.startInlineStyle, | ||
| precedence: stringToChunk(escapeTextForBrowser(precedence)), | ||
| rules: ([]: Array<Chunk | PrecomputedChunk>), | ||
| hrefs: [stringToChunk(escapeTextForBrowser(href))], | ||
| hrefs: ([]: Array<Chunk | PrecomputedChunk>), | ||
| sheets: (new Map(): Map<string, StylesheetResource>), | ||
| }; | ||
| renderState.styles.set(precedence, styleQueue); | ||
| } else { | ||
| // We have seen this precedence before and need to track this href | ||
| } | ||
|
|
||
| const nonceStyle = renderState.nonce.style; | ||
| if (!nonceStyle || nonceStyle === nonce) { | ||
| if (__DEV__) { | ||
| if (!nonceStyle && nonce) { | ||
| console.error( | ||
| 'React encountered a hoistable style tag with "%s" nonce. The nonce was not passed to the render call though.', | ||
| nonce, | ||
| ); | ||
| } | ||
| } | ||
| styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); | ||
| pushStyleContents(styleQueue.rules, props); | ||
| } else if (__DEV__) { | ||
| console.error( | ||
| 'React encountered a hoistable style tag with "%s" nonce. It doesn\'t match the "%s" nonce passed to the render call. They have to be the same.', | ||
Andarist marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| nonce, | ||
| nonceStyle, | ||
| ); | ||
| } | ||
| pushStyleContents(styleQueue.rules, props); | ||
| } | ||
| if (styleQueue) { | ||
| // We need to track whether this boundary should wait on this resource or not. | ||
|
|
@@ -5148,7 +5202,7 @@ function escapeJSObjectForInstructionScripts(input: Object): string { | |
| } | ||
|
|
||
| const lateStyleTagResourceOpen1 = stringToPrecomputedChunk( | ||
| '<style media="not all" data-precedence="', | ||
| ' media="not all" data-precedence="', | ||
| ); | ||
| const lateStyleTagResourceOpen2 = stringToPrecomputedChunk('" data-href="'); | ||
| const lateStyleTagResourceOpen3 = stringToPrecomputedChunk('">'); | ||
|
|
@@ -5176,6 +5230,7 @@ function flushStyleTagsLateForBoundary( | |
| } | ||
| let i = 0; | ||
| if (hrefs.length) { | ||
| writeChunk(this, styleQueue.start); | ||
|
||
| writeChunk(this, lateStyleTagResourceOpen1); | ||
| writeChunk(this, styleQueue.precedence); | ||
| writeChunk(this, lateStyleTagResourceOpen2); | ||
|
|
@@ -5268,9 +5323,7 @@ function flushStyleInPreamble( | |
| stylesheet.state = PREAMBLE; | ||
| } | ||
|
|
||
| const styleTagResourceOpen1 = stringToPrecomputedChunk( | ||
| '<style data-precedence="', | ||
| ); | ||
| const styleTagResourceOpen1 = stringToPrecomputedChunk(' data-precedence="'); | ||
| const styleTagResourceOpen2 = stringToPrecomputedChunk('" data-href="'); | ||
| const spaceSeparator = stringToPrecomputedChunk(' '); | ||
| const styleTagResourceOpen3 = stringToPrecomputedChunk('">'); | ||
|
|
@@ -5292,6 +5345,7 @@ function flushStylesInPreamble( | |
| // 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, styleQueue.start); | ||
| writeChunk(this, styleTagResourceOpen1); | ||
| writeChunk(this, styleQueue.precedence); | ||
| let i = 0; | ||
|
|
@@ -6057,6 +6111,7 @@ export type HoistableState = { | |
| }; | ||
|
|
||
| export type StyleQueue = { | ||
| start: PrecomputedChunk, | ||
| precedence: Chunk | PrecomputedChunk, | ||
| rules: Array<Chunk | PrecomputedChunk>, | ||
| hrefs: Array<Chunk | PrecomputedChunk>, | ||
|
|
@@ -6508,6 +6563,7 @@ function preinitStyle( | |
| // to create a StyleQueue | ||
| if (!styleQueue) { | ||
| styleQueue = { | ||
| start: renderState.startInlineStyle, | ||
| precedence: stringToChunk(escapeTextForBrowser(precedence)), | ||
| rules: ([]: Array<Chunk | PrecomputedChunk>), | ||
| hrefs: ([]: Array<Chunk | PrecomputedChunk>), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.