Skip to content

[pull] main from facebook:main #168

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

Merged
merged 1 commit into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 86 additions & 20 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -295,6 +309,8 @@ export type ResumableState = {
},
};

let currentlyFlushingRenderState: RenderState | null = null;

const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');

const startInlineScript = stringToPrecomputedChunk('<script');
Expand All @@ -307,6 +323,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
Expand Down Expand Up @@ -365,17 +383,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;

Expand Down Expand Up @@ -403,7 +436,7 @@ export function createRenderState(
src: externalRuntimeConfig,
async: true,
integrity: undefined,
nonce: nonce,
nonce: nonceScript,
});
} else {
externalRuntimeScript = {
Expand All @@ -414,7 +447,7 @@ export function createRenderState(
src: externalRuntimeConfig.src,
async: true,
integrity: externalRuntimeConfig.integrity,
nonce: nonce,
nonce: nonceScript,
});
}
}
Expand Down Expand Up @@ -459,6 +492,7 @@ export function createRenderState(
segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
boundaryPrefix: stringToPrecomputedChunk(idPrefix + 'B:'),
startInlineScript: inlineScriptWithNonce,
startInlineStyle: inlineStyleWithNonce,
preamble: createPreambleState(),

externalRuntimeScript: externalRuntimeScript,
Expand Down Expand Up @@ -500,7 +534,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,
Expand Down Expand Up @@ -539,10 +576,10 @@ export function createRenderState(
stringToChunk(escapeTextForBrowser(src)),
attributeEnd,
);
if (nonce) {
if (nonceScript) {
bootstrapChunks.push(
scriptNonce,
stringToChunk(escapeTextForBrowser(nonce)),
stringToChunk(escapeTextForBrowser(nonceScript)),
attributeEnd,
);
}
Expand Down Expand Up @@ -571,7 +608,7 @@ export function createRenderState(
const props: PreloadModuleProps = ({
rel: 'modulepreload',
fetchPriority: 'low',
nonce,
nonce: nonceScript,
}: any);
if (typeof scriptConfig === 'string') {
props.href = src = scriptConfig;
Expand All @@ -596,10 +633,10 @@ export function createRenderState(
stringToChunk(escapeTextForBrowser(src)),
attributeEnd,
);
if (nonce) {
if (nonceScript) {
bootstrapChunks.push(
scriptNonce,
stringToChunk(escapeTextForBrowser(nonce)),
stringToChunk(escapeTextForBrowser(nonceScript)),
attributeEnd,
);
}
Expand Down Expand Up @@ -627,7 +664,7 @@ export function createRenderState(

export function resumeRenderState(
resumableState: ResumableState,
nonce: string | void,
nonce: NonceOption | void,
): RenderState {
return createRenderState(
resumableState,
Expand Down Expand Up @@ -3046,6 +3083,7 @@ function pushStyle(
}
const precedence = props.precedence;
const href = props.href;
const nonce = props.nonce;

if (
formatContext.insertionMode === SVG_MODE ||
Expand Down Expand Up @@ -3091,15 +3129,33 @@ function pushStyle(
styleQueue = {
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 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.',
precedence,
nonce,
);
}
}
styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href)));
pushStyleContents(styleQueue.rules, props);
} else if (__DEV__) {
console.error(
'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.',
precedence,
nonce,
nonceStyle,
);
}
pushStyleContents(styleQueue.rules, props);
}
if (styleQueue) {
// We need to track whether this boundary should wait on this resource or not.
Expand Down Expand Up @@ -5148,7 +5204,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('">');
Expand Down Expand Up @@ -5176,6 +5232,10 @@ function flushStyleTagsLateForBoundary(
}
let i = 0;
if (hrefs.length) {
writeChunk(
this,
((currentlyFlushingRenderState: any): RenderState).startInlineStyle,
);
writeChunk(this, lateStyleTagResourceOpen1);
writeChunk(this, styleQueue.precedence);
writeChunk(this, lateStyleTagResourceOpen2);
Expand Down Expand Up @@ -5225,7 +5285,9 @@ export function writeHoistablesForBoundary(
destinationHasCapacity = true;

// Flush style tags for each precedence this boundary depends on
currentlyFlushingRenderState = renderState;
hoistableState.styles.forEach(flushStyleTagsLateForBoundary, destination);
currentlyFlushingRenderState = null;

// Determine if this boundary has stylesheets that need to be awaited upon completion
hoistableState.stylesheets.forEach(hasStylesToHoist);
Expand Down Expand Up @@ -5268,9 +5330,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('">');
Expand All @@ -5292,6 +5352,10 @@ 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,
((currentlyFlushingRenderState: any): RenderState).startInlineStyle,
);
writeChunk(this, styleTagResourceOpen1);
writeChunk(this, styleQueue.precedence);
let i = 0;
Expand Down Expand Up @@ -5466,7 +5530,9 @@ export function writePreambleStart(
renderState.highImagePreloads.clear();

// Flush unblocked stylesheets by precedence
currentlyFlushingRenderState = renderState;
renderState.styles.forEach(flushStylesInPreamble, destination);
currentlyFlushingRenderState = null;

const importMapChunks = renderState.importMapChunks;
for (i = 0; i < importMapChunks.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type RenderState = {
segmentPrefix: PrecomputedChunk,
boundaryPrefix: PrecomputedChunk,
startInlineScript: PrecomputedChunk,
startInlineStyle: PrecomputedChunk,
preamble: PreambleState,
externalRuntimeScript: null | any,
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
Expand Down Expand Up @@ -76,6 +77,10 @@ export type RenderState = {
scripts: Map<string, Resource>,
moduleScripts: Map<string, Resource>,
},
nonce: {
script: string | void,
style: string | void,
},
stylesToHoist: boolean,
// This is an extra field for the legacy renderer
generateStaticMarkup: boolean,
Expand All @@ -99,6 +104,7 @@ export function createRenderState(
segmentPrefix: renderState.segmentPrefix,
boundaryPrefix: renderState.boundaryPrefix,
startInlineScript: renderState.startInlineScript,
startInlineStyle: renderState.startInlineStyle,
preamble: renderState.preamble,
externalRuntimeScript: renderState.externalRuntimeScript,
bootstrapChunks: renderState.bootstrapChunks,
Expand All @@ -118,6 +124,7 @@ export function createRenderState(
scripts: renderState.scripts,
bulkPreloads: renderState.bulkPreloads,
preloads: renderState.preloads,
nonce: renderState.nonce,
stylesToHoist: renderState.stylesToHoist,

// This is an extra field for the legacy renderer
Expand Down
Loading
Loading