@@ -135,6 +135,13 @@ const SentMarkShellTime /* */ = 0b001000000;
135
135
const NeedUpgradeToViewTransitions /* */ = 0b010000000 ;
136
136
const SentUpgradeToViewTransitions /* */ = 0b100000000 ;
137
137
138
+ type NonceOption =
139
+ | string
140
+ | {
141
+ script ?: string ,
142
+ style ?: string ,
143
+ } ;
144
+
138
145
// Per request, global state that is not contextual to the rendering subtree.
139
146
// This cannot be resumed and therefore should only contain things that are
140
147
// temporary working state or are never used in the prerender pass.
@@ -147,6 +154,8 @@ export type RenderState = {
147
154
// inline script streaming format, unused if using external runtime / data
148
155
startInlineScript : PrecomputedChunk ,
149
156
157
+ startInlineStyle : PrecomputedChunk ,
158
+
150
159
// the preamble must always flush before resuming, so all these chunks must
151
160
// be null or empty when resuming.
152
161
@@ -209,6 +218,11 @@ export type RenderState = {
209
218
moduleScripts : Map < string , Resource> ,
210
219
} ,
211
220
221
+ nonce : {
222
+ script : string | void ,
223
+ style : string | void ,
224
+ } ,
225
+
212
226
// Module-global-like reference for flushing/hoisting state of style resources
213
227
// We need to track whether the current request has flushed any style resources
214
228
// without sending an instruction to hoist them. we do that here
@@ -295,6 +309,8 @@ export type ResumableState = {
295
309
} ,
296
310
} ;
297
311
312
+ let currentlyFlushingRenderState : RenderState | null = null ;
313
+
298
314
const dataElementQuotedEnd = stringToPrecomputedChunk ( '"></template>' ) ;
299
315
300
316
const startInlineScript = stringToPrecomputedChunk ( '<script' ) ;
@@ -307,6 +323,8 @@ const scriptIntegirty = stringToPrecomputedChunk(' integrity="');
307
323
const scriptCrossOrigin = stringToPrecomputedChunk ( ' crossorigin="' ) ;
308
324
const endAsyncScript = stringToPrecomputedChunk ( ' async=""></script>' ) ;
309
325
326
+ const startInlineStyle = stringToPrecomputedChunk ( '<style' ) ;
327
+
310
328
/**
311
329
* This escaping function is designed to work with with inline scripts where the entire
312
330
* contents are escaped. Because we know we are escaping the entire script we can avoid for instance
@@ -365,17 +383,32 @@ if (__DEV__) {
365
383
// is set, the server will send instructions via data attributes (instead of inline scripts)
366
384
export function createRenderState (
367
385
resumableState : ResumableState ,
368
- nonce : string | void ,
386
+ nonce :
387
+ | string
388
+ | {
389
+ script ?: string ,
390
+ style ?: string ,
391
+ }
392
+ | void ,
369
393
externalRuntimeConfig : string | BootstrapScriptDescriptor | void ,
370
394
importMap : ImportMap | void ,
371
395
onHeaders : void | ( ( headers : HeadersDescriptor ) => void ) ,
372
396
maxHeadersLength : void | number ,
373
397
) : RenderState {
398
+ const nonceScript = typeof nonce === 'string' ? nonce : nonce && nonce . script ;
374
399
const inlineScriptWithNonce =
375
- nonce === undefined
400
+ nonceScript === undefined
376
401
? startInlineScript
377
402
: 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 ) + '"' ,
379
412
) ;
380
413
const idPrefix = resumableState . idPrefix ;
381
414
@@ -403,7 +436,7 @@ export function createRenderState(
403
436
src : externalRuntimeConfig ,
404
437
async : true ,
405
438
integrity : undefined ,
406
- nonce : nonce ,
439
+ nonce : nonceScript ,
407
440
} ) ;
408
441
} else {
409
442
externalRuntimeScript = {
@@ -414,7 +447,7 @@ export function createRenderState(
414
447
src : externalRuntimeConfig . src ,
415
448
async : true ,
416
449
integrity : externalRuntimeConfig . integrity ,
417
- nonce : nonce ,
450
+ nonce : nonceScript ,
418
451
} ) ;
419
452
}
420
453
}
@@ -459,6 +492,7 @@ export function createRenderState(
459
492
segmentPrefix : stringToPrecomputedChunk ( idPrefix + 'S:' ) ,
460
493
boundaryPrefix : stringToPrecomputedChunk ( idPrefix + 'B:' ) ,
461
494
startInlineScript : inlineScriptWithNonce ,
495
+ startInlineStyle : inlineStyleWithNonce ,
462
496
preamble : createPreambleState ( ) ,
463
497
464
498
externalRuntimeScript : externalRuntimeScript ,
@@ -500,7 +534,10 @@ export function createRenderState(
500
534
moduleScripts : new Map ( ) ,
501
535
} ,
502
536
503
- nonce,
537
+ nonce : {
538
+ script : nonceScript ,
539
+ style : nonceStyle ,
540
+ } ,
504
541
// like a module global for currently rendering boundary
505
542
hoistableState : null ,
506
543
stylesToHoist : false ,
@@ -539,10 +576,10 @@ export function createRenderState(
539
576
stringToChunk ( escapeTextForBrowser ( src ) ) ,
540
577
attributeEnd ,
541
578
) ;
542
- if ( nonce ) {
579
+ if ( nonceScript ) {
543
580
bootstrapChunks . push (
544
581
scriptNonce ,
545
- stringToChunk ( escapeTextForBrowser ( nonce ) ) ,
582
+ stringToChunk ( escapeTextForBrowser ( nonceScript ) ) ,
546
583
attributeEnd ,
547
584
) ;
548
585
}
@@ -571,7 +608,7 @@ export function createRenderState(
571
608
const props : PreloadModuleProps = ( {
572
609
rel : 'modulepreload' ,
573
610
fetchPriority : 'low' ,
574
- nonce,
611
+ nonce : nonceScript ,
575
612
} : any ) ;
576
613
if ( typeof scriptConfig === 'string' ) {
577
614
props . href = src = scriptConfig ;
@@ -596,10 +633,10 @@ export function createRenderState(
596
633
stringToChunk ( escapeTextForBrowser ( src ) ) ,
597
634
attributeEnd ,
598
635
) ;
599
- if ( nonce ) {
636
+ if ( nonceScript ) {
600
637
bootstrapChunks . push (
601
638
scriptNonce ,
602
- stringToChunk ( escapeTextForBrowser ( nonce ) ) ,
639
+ stringToChunk ( escapeTextForBrowser ( nonceScript ) ) ,
603
640
attributeEnd ,
604
641
) ;
605
642
}
@@ -627,7 +664,7 @@ export function createRenderState(
627
664
628
665
export function resumeRenderState (
629
666
resumableState : ResumableState ,
630
- nonce : string | void ,
667
+ nonce : NonceOption | void ,
631
668
) : RenderState {
632
669
return createRenderState (
633
670
resumableState ,
@@ -3046,6 +3083,7 @@ function pushStyle(
3046
3083
}
3047
3084
const precedence = props . precedence ;
3048
3085
const href = props . href ;
3086
+ const nonce = props . nonce ;
3049
3087
3050
3088
if (
3051
3089
formatContext . insertionMode === SVG_MODE ||
@@ -3091,15 +3129,33 @@ function pushStyle(
3091
3129
styleQueue = {
3092
3130
precedence : stringToChunk ( escapeTextForBrowser ( precedence ) ) ,
3093
3131
rules : ( [ ] : Array < Chunk | PrecomputedChunk > ) ,
3094
- hrefs : [ stringToChunk ( escapeTextForBrowser ( href ) ) ] ,
3132
+ hrefs : ( [ ] : Array < Chunk | PrecomputedChunk > ) ,
3095
3133
sheets : ( new Map ( ) : Map < string , StylesheetResource > ) ,
3096
3134
} ;
3097
3135
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
+ }
3100
3149
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
+ ) ;
3101
3158
}
3102
- pushStyleContents ( styleQueue . rules , props ) ;
3103
3159
}
3104
3160
if ( styleQueue ) {
3105
3161
// We need to track whether this boundary should wait on this resource or not.
@@ -5148,7 +5204,7 @@ function escapeJSObjectForInstructionScripts(input: Object): string {
5148
5204
}
5149
5205
5150
5206
const lateStyleTagResourceOpen1 = stringToPrecomputedChunk (
5151
- '<style media="not all" data-precedence="' ,
5207
+ ' media="not all" data-precedence="' ,
5152
5208
) ;
5153
5209
const lateStyleTagResourceOpen2 = stringToPrecomputedChunk ( '" data-href="' ) ;
5154
5210
const lateStyleTagResourceOpen3 = stringToPrecomputedChunk ( '">' ) ;
@@ -5176,6 +5232,10 @@ function flushStyleTagsLateForBoundary(
5176
5232
}
5177
5233
let i = 0 ;
5178
5234
if ( hrefs . length ) {
5235
+ writeChunk (
5236
+ this ,
5237
+ ( ( currentlyFlushingRenderState : any ) : RenderState ) . startInlineStyle ,
5238
+ ) ;
5179
5239
writeChunk ( this , lateStyleTagResourceOpen1 ) ;
5180
5240
writeChunk ( this , styleQueue . precedence ) ;
5181
5241
writeChunk ( this , lateStyleTagResourceOpen2 ) ;
@@ -5225,7 +5285,9 @@ export function writeHoistablesForBoundary(
5225
5285
destinationHasCapacity = true ;
5226
5286
5227
5287
// Flush style tags for each precedence this boundary depends on
5288
+ currentlyFlushingRenderState = renderState ;
5228
5289
hoistableState . styles . forEach ( flushStyleTagsLateForBoundary , destination ) ;
5290
+ currentlyFlushingRenderState = null ;
5229
5291
5230
5292
// Determine if this boundary has stylesheets that need to be awaited upon completion
5231
5293
hoistableState . stylesheets . forEach ( hasStylesToHoist ) ;
@@ -5268,9 +5330,7 @@ function flushStyleInPreamble(
5268
5330
stylesheet . state = PREAMBLE ;
5269
5331
}
5270
5332
5271
- const styleTagResourceOpen1 = stringToPrecomputedChunk (
5272
- '<style data-precedence="' ,
5273
- ) ;
5333
+ const styleTagResourceOpen1 = stringToPrecomputedChunk ( ' data-precedence="' ) ;
5274
5334
const styleTagResourceOpen2 = stringToPrecomputedChunk ( '" data-href="' ) ;
5275
5335
const spaceSeparator = stringToPrecomputedChunk ( ' ' ) ;
5276
5336
const styleTagResourceOpen3 = stringToPrecomputedChunk ( '">' ) ;
@@ -5292,6 +5352,10 @@ function flushStylesInPreamble(
5292
5352
// order so even if there are no rules for style tags at this precedence we emit an empty style
5293
5353
// tag with the data-precedence attribute
5294
5354
if ( ! hasStylesheets || hrefs . length ) {
5355
+ writeChunk (
5356
+ this ,
5357
+ ( ( currentlyFlushingRenderState : any ) : RenderState ) . startInlineStyle ,
5358
+ ) ;
5295
5359
writeChunk ( this , styleTagResourceOpen1 ) ;
5296
5360
writeChunk ( this , styleQueue . precedence ) ;
5297
5361
let i = 0 ;
@@ -5466,7 +5530,9 @@ export function writePreambleStart(
5466
5530
renderState . highImagePreloads . clear ( ) ;
5467
5531
5468
5532
// Flush unblocked stylesheets by precedence
5533
+ currentlyFlushingRenderState = renderState ;
5469
5534
renderState . styles . forEach ( flushStylesInPreamble , destination ) ;
5535
+ currentlyFlushingRenderState = null ;
5470
5536
5471
5537
const importMapChunks = renderState . importMapChunks ;
5472
5538
for ( i = 0 ; i < importMapChunks . length ; i ++ ) {
0 commit comments