@@ -135,6 +135,13 @@ const SentMarkShellTime /* */ = 0b001000000;
135135const NeedUpgradeToViewTransitions /* */ = 0b010000000 ;
136136const SentUpgradeToViewTransitions /* */ = 0b100000000 ;
137137
138+ type NonceOption =
139+ | string
140+ | {
141+ script ?: string ,
142+ style ?: string ,
143+ } ;
144+
138145// Per request, global state that is not contextual to the rendering subtree.
139146// This cannot be resumed and therefore should only contain things that are
140147// temporary working state or are never used in the prerender pass.
@@ -147,6 +154,8 @@ export type RenderState = {
147154 // inline script streaming format, unused if using external runtime / data
148155 startInlineScript : PrecomputedChunk ,
149156
157+ startInlineStyle : PrecomputedChunk ,
158+
150159 // the preamble must always flush before resuming, so all these chunks must
151160 // be null or empty when resuming.
152161
@@ -209,6 +218,11 @@ export type RenderState = {
209218 moduleScripts : Map < string , Resource> ,
210219 } ,
211220
221+ nonce : {
222+ script : string | void ,
223+ style : string | void ,
224+ } ,
225+
212226 // Module-global-like reference for flushing/hoisting state of style resources
213227 // We need to track whether the current request has flushed any style resources
214228 // without sending an instruction to hoist them. we do that here
@@ -295,6 +309,8 @@ export type ResumableState = {
295309 } ,
296310} ;
297311
312+ let currentlyFlushingRenderState : RenderState | null = null ;
313+
298314const dataElementQuotedEnd = stringToPrecomputedChunk ( '"></template>' ) ;
299315
300316const startInlineScript = stringToPrecomputedChunk ( '<script' ) ;
@@ -307,6 +323,8 @@ const scriptIntegirty = stringToPrecomputedChunk(' integrity="');
307323const scriptCrossOrigin = stringToPrecomputedChunk ( ' crossorigin="' ) ;
308324const endAsyncScript = stringToPrecomputedChunk ( ' async=""></script>' ) ;
309325
326+ const startInlineStyle = stringToPrecomputedChunk ( '<style' ) ;
327+
310328/**
311329 * This escaping function is designed to work with with inline scripts where the entire
312330 * contents are escaped. Because we know we are escaping the entire script we can avoid for instance
@@ -365,17 +383,32 @@ if (__DEV__) {
365383// is set, the server will send instructions via data attributes (instead of inline scripts)
366384export function createRenderState (
367385 resumableState : ResumableState ,
368- nonce : string | void ,
386+ nonce :
387+ | string
388+ | {
389+ script ?: string ,
390+ style ?: string ,
391+ }
392+ | void ,
369393 externalRuntimeConfig : string | BootstrapScriptDescriptor | void ,
370394 importMap : ImportMap | void ,
371395 onHeaders : void | ( ( headers : HeadersDescriptor ) => void ) ,
372396 maxHeadersLength : void | number ,
373397) : RenderState {
398+ const nonceScript = typeof nonce === 'string' ? nonce : nonce && nonce . script ;
374399 const inlineScriptWithNonce =
375- nonce === undefined
400+ nonceScript === undefined
376401 ? startInlineScript
377402 : stringToPrecomputedChunk (
378- '<script nonce="' + escapeTextForBrowser ( nonce ) + '"' ,
403+ '<script nonce="' + escapeTextForBrowser ( nonceScript ) + '"' ,
404+ ) ;
405+ const nonceStyle =
406+ typeof nonce === 'string' ? undefined : nonce && nonce . style ;
407+ const inlineStyleWithNonce =
408+ nonceStyle === undefined
409+ ? startInlineStyle
410+ : stringToPrecomputedChunk (
411+ '<style nonce="' + escapeTextForBrowser ( nonceStyle ) + '"' ,
379412 ) ;
380413 const idPrefix = resumableState . idPrefix ;
381414
@@ -403,7 +436,7 @@ export function createRenderState(
403436 src : externalRuntimeConfig ,
404437 async : true ,
405438 integrity : undefined ,
406- nonce : nonce ,
439+ nonce : nonceScript ,
407440 } ) ;
408441 } else {
409442 externalRuntimeScript = {
@@ -414,7 +447,7 @@ export function createRenderState(
414447 src : externalRuntimeConfig . src ,
415448 async : true ,
416449 integrity : externalRuntimeConfig . integrity ,
417- nonce : nonce ,
450+ nonce : nonceScript ,
418451 } ) ;
419452 }
420453 }
@@ -459,6 +492,7 @@ export function createRenderState(
459492 segmentPrefix : stringToPrecomputedChunk ( idPrefix + 'S:' ) ,
460493 boundaryPrefix : stringToPrecomputedChunk ( idPrefix + 'B:' ) ,
461494 startInlineScript : inlineScriptWithNonce ,
495+ startInlineStyle : inlineStyleWithNonce ,
462496 preamble : createPreambleState ( ) ,
463497
464498 externalRuntimeScript : externalRuntimeScript ,
@@ -500,7 +534,10 @@ export function createRenderState(
500534 moduleScripts : new Map ( ) ,
501535 } ,
502536
503- nonce,
537+ nonce : {
538+ script : nonceScript ,
539+ style : nonceStyle ,
540+ } ,
504541 // like a module global for currently rendering boundary
505542 hoistableState : null ,
506543 stylesToHoist : false ,
@@ -539,10 +576,10 @@ export function createRenderState(
539576 stringToChunk ( escapeTextForBrowser ( src ) ) ,
540577 attributeEnd ,
541578 ) ;
542- if ( nonce ) {
579+ if ( nonceScript ) {
543580 bootstrapChunks . push (
544581 scriptNonce ,
545- stringToChunk ( escapeTextForBrowser ( nonce ) ) ,
582+ stringToChunk ( escapeTextForBrowser ( nonceScript ) ) ,
546583 attributeEnd ,
547584 ) ;
548585 }
@@ -571,7 +608,7 @@ export function createRenderState(
571608 const props : PreloadModuleProps = ( {
572609 rel : 'modulepreload' ,
573610 fetchPriority : 'low' ,
574- nonce,
611+ nonce : nonceScript ,
575612 } : any ) ;
576613 if ( typeof scriptConfig === 'string' ) {
577614 props . href = src = scriptConfig ;
@@ -596,10 +633,10 @@ export function createRenderState(
596633 stringToChunk ( escapeTextForBrowser ( src ) ) ,
597634 attributeEnd ,
598635 ) ;
599- if ( nonce ) {
636+ if ( nonceScript ) {
600637 bootstrapChunks . push (
601638 scriptNonce ,
602- stringToChunk ( escapeTextForBrowser ( nonce ) ) ,
639+ stringToChunk ( escapeTextForBrowser ( nonceScript ) ) ,
603640 attributeEnd ,
604641 ) ;
605642 }
@@ -627,7 +664,7 @@ export function createRenderState(
627664
628665export function resumeRenderState (
629666 resumableState : ResumableState ,
630- nonce : string | void ,
667+ nonce : NonceOption | void ,
631668) : RenderState {
632669 return createRenderState (
633670 resumableState ,
@@ -3046,6 +3083,7 @@ function pushStyle(
30463083 }
30473084 const precedence = props . precedence ;
30483085 const href = props . href ;
3086+ const nonce = props . nonce ;
30493087
30503088 if (
30513089 formatContext . insertionMode === SVG_MODE ||
@@ -3091,15 +3129,33 @@ function pushStyle(
30913129 styleQueue = {
30923130 precedence : stringToChunk ( escapeTextForBrowser ( precedence ) ) ,
30933131 rules : ( [ ] : Array < Chunk | PrecomputedChunk > ) ,
3094- hrefs : [ stringToChunk ( escapeTextForBrowser ( href ) ) ] ,
3132+ hrefs : ( [ ] : Array < Chunk | PrecomputedChunk > ) ,
30953133 sheets : ( new Map ( ) : Map < string , StylesheetResource > ) ,
30963134 } ;
30973135 renderState . styles . set ( precedence , styleQueue ) ;
3098- } else {
3099- // We have seen this precedence before and need to track this href
3136+ }
3137+
3138+ const nonceStyle = renderState . nonce . style ;
3139+ if ( ! nonceStyle || nonceStyle === nonce ) {
3140+ if ( __DEV__ ) {
3141+ if ( ! nonceStyle && nonce ) {
3142+ console . error (
3143+ 'React encountered a style tag with `precedence` "%s" and `nonce` "%s". When React manages style rules using `precedence` it will only include a nonce attributes if you also provide the same style nonce value as a render option.' ,
3144+ precedence ,
3145+ nonce ,
3146+ ) ;
3147+ }
3148+ }
31003149 styleQueue . hrefs . push ( stringToChunk ( escapeTextForBrowser ( href ) ) ) ;
3150+ pushStyleContents ( styleQueue . rules , props ) ;
3151+ } else if ( __DEV__ ) {
3152+ console . error (
3153+ 'React encountered a style tag with `precedence` "%s" and `nonce` "%s". When React manages style rules using `precedence` it will only include rules if the nonce matches the style nonce "%s" that was included with this render.' ,
3154+ precedence ,
3155+ nonce ,
3156+ nonceStyle ,
3157+ ) ;
31013158 }
3102- pushStyleContents ( styleQueue . rules , props ) ;
31033159 }
31043160 if ( styleQueue ) {
31053161 // We need to track whether this boundary should wait on this resource or not.
@@ -5148,7 +5204,7 @@ function escapeJSObjectForInstructionScripts(input: Object): string {
51485204}
51495205
51505206const lateStyleTagResourceOpen1 = stringToPrecomputedChunk (
5151- '<style media="not all" data-precedence="' ,
5207+ ' media="not all" data-precedence="' ,
51525208) ;
51535209const lateStyleTagResourceOpen2 = stringToPrecomputedChunk ( '" data-href="' ) ;
51545210const lateStyleTagResourceOpen3 = stringToPrecomputedChunk ( '">' ) ;
@@ -5176,6 +5232,10 @@ function flushStyleTagsLateForBoundary(
51765232 }
51775233 let i = 0 ;
51785234 if ( hrefs . length ) {
5235+ writeChunk (
5236+ this ,
5237+ ( ( currentlyFlushingRenderState : any ) : RenderState ) . startInlineStyle ,
5238+ ) ;
51795239 writeChunk ( this , lateStyleTagResourceOpen1 ) ;
51805240 writeChunk ( this , styleQueue . precedence ) ;
51815241 writeChunk ( this , lateStyleTagResourceOpen2 ) ;
@@ -5225,7 +5285,9 @@ export function writeHoistablesForBoundary(
52255285 destinationHasCapacity = true ;
52265286
52275287 // Flush style tags for each precedence this boundary depends on
5288+ currentlyFlushingRenderState = renderState ;
52285289 hoistableState . styles . forEach ( flushStyleTagsLateForBoundary , destination ) ;
5290+ currentlyFlushingRenderState = null ;
52295291
52305292 // Determine if this boundary has stylesheets that need to be awaited upon completion
52315293 hoistableState . stylesheets . forEach ( hasStylesToHoist ) ;
@@ -5268,9 +5330,7 @@ function flushStyleInPreamble(
52685330 stylesheet . state = PREAMBLE ;
52695331}
52705332
5271- const styleTagResourceOpen1 = stringToPrecomputedChunk (
5272- '<style data-precedence="' ,
5273- ) ;
5333+ const styleTagResourceOpen1 = stringToPrecomputedChunk ( ' data-precedence="' ) ;
52745334const styleTagResourceOpen2 = stringToPrecomputedChunk ( '" data-href="' ) ;
52755335const spaceSeparator = stringToPrecomputedChunk ( ' ' ) ;
52765336const styleTagResourceOpen3 = stringToPrecomputedChunk ( '">' ) ;
@@ -5292,6 +5352,10 @@ function flushStylesInPreamble(
52925352 // order so even if there are no rules for style tags at this precedence we emit an empty style
52935353 // tag with the data-precedence attribute
52945354 if ( ! hasStylesheets || hrefs . length ) {
5355+ writeChunk (
5356+ this ,
5357+ ( ( currentlyFlushingRenderState : any ) : RenderState ) . startInlineStyle ,
5358+ ) ;
52955359 writeChunk ( this , styleTagResourceOpen1 ) ;
52965360 writeChunk ( this , styleQueue . precedence ) ;
52975361 let i = 0 ;
@@ -5466,7 +5530,9 @@ export function writePreambleStart(
54665530 renderState . highImagePreloads . clear ( ) ;
54675531
54685532 // Flush unblocked stylesheets by precedence
5533+ currentlyFlushingRenderState = renderState ;
54695534 renderState . styles . forEach ( flushStylesInPreamble , destination ) ;
5535+ currentlyFlushingRenderState = null ;
54705536
54715537 const importMapChunks = renderState . importMapChunks ;
54725538 for ( i = 0 ; i < importMapChunks . length ; i ++ ) {
0 commit comments