Skip to content

Commit 14094f8

Browse files
authored
Allow nonce to be used on hoistable styles (facebook#32461)
fixes facebook#32449 This is my first time touching this code. There are multiple systems in place here and I wouldn't be surprised to learn that this has to be handled in some other areas too. I have found some other style-related code areas but I had no time yet to double-check them. cc @gnoff
1 parent 5717f19 commit 14094f8

11 files changed

+398
-30
lines changed

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ const SentMarkShellTime /* */ = 0b001000000;
135135
const NeedUpgradeToViewTransitions /* */ = 0b010000000;
136136
const 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+
298314
const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');
299315

300316
const startInlineScript = stringToPrecomputedChunk('<script');
@@ -307,6 +323,8 @@ const scriptIntegirty = stringToPrecomputedChunk(' integrity="');
307323
const scriptCrossOrigin = stringToPrecomputedChunk(' crossorigin="');
308324
const 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)
366384
export 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

628665
export 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

51505206
const lateStyleTagResourceOpen1 = stringToPrecomputedChunk(
5151-
'<style media="not all" data-precedence="',
5207+
' media="not all" data-precedence="',
51525208
);
51535209
const lateStyleTagResourceOpen2 = stringToPrecomputedChunk('" data-href="');
51545210
const 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="');
52745334
const styleTagResourceOpen2 = stringToPrecomputedChunk('" data-href="');
52755335
const spaceSeparator = stringToPrecomputedChunk(' ');
52765336
const 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++) {

packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type RenderState = {
4747
segmentPrefix: PrecomputedChunk,
4848
boundaryPrefix: PrecomputedChunk,
4949
startInlineScript: PrecomputedChunk,
50+
startInlineStyle: PrecomputedChunk,
5051
preamble: PreambleState,
5152
externalRuntimeScript: null | any,
5253
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
@@ -76,6 +77,10 @@ export type RenderState = {
7677
scripts: Map<string, Resource>,
7778
moduleScripts: Map<string, Resource>,
7879
},
80+
nonce: {
81+
script: string | void,
82+
style: string | void,
83+
},
7984
stylesToHoist: boolean,
8085
// This is an extra field for the legacy renderer
8186
generateStaticMarkup: boolean,
@@ -99,6 +104,7 @@ export function createRenderState(
99104
segmentPrefix: renderState.segmentPrefix,
100105
boundaryPrefix: renderState.boundaryPrefix,
101106
startInlineScript: renderState.startInlineScript,
107+
startInlineStyle: renderState.startInlineStyle,
102108
preamble: renderState.preamble,
103109
externalRuntimeScript: renderState.externalRuntimeScript,
104110
bootstrapChunks: renderState.bootstrapChunks,
@@ -118,6 +124,7 @@ export function createRenderState(
118124
scripts: renderState.scripts,
119125
bulkPreloads: renderState.bulkPreloads,
120126
preloads: renderState.preloads,
127+
nonce: renderState.nonce,
121128
stylesToHoist: renderState.stylesToHoist,
122129

123130
// This is an extra field for the legacy renderer

0 commit comments

Comments
 (0)