Skip to content

Commit 09def59

Browse files
authored
[Float] handle noscript context for Resources (#25559)
stacked on #25569 On the client noscript already never renders children so no resources will be extracted from this context. On the server we now track if we are in a noscript context and turn off Resource semantics in this scope
1 parent 1720405 commit 09def59

File tree

4 files changed

+287
-20
lines changed

4 files changed

+287
-20
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,10 @@ export function resourcesFromLink(props: Props): boolean {
863863
}
864864
}
865865
if (props.onLoad || props.onError) {
866+
// When a link has these props we can't treat it is a Resource but if we rendered it on the
867+
// server it would look like a Resource in the rendered html (the onLoad/onError aren't emitted)
868+
// Instead we expect the client to insert them rather than hydrate them which also guarantees
869+
// that the onLoad and onError won't fire before the event handlers are attached
866870
return true;
867871
}
868872

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

+108-20
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,18 @@ type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
274274
export type FormatContext = {
275275
insertionMode: InsertionMode, // root/svg/html/mathml/table
276276
selectedValue: null | string | Array<string>, // the selected value(s) inside a <select>, or null outside <select>
277+
noscriptTagInScope: boolean,
277278
};
278279

279280
function createFormatContext(
280281
insertionMode: InsertionMode,
281282
selectedValue: null | string,
283+
noscriptTagInScope: boolean,
282284
): FormatContext {
283285
return {
284286
insertionMode,
285287
selectedValue,
288+
noscriptTagInScope,
286289
};
287290
}
288291

@@ -293,7 +296,7 @@ export function createRootFormatContext(namespaceURI?: string): FormatContext {
293296
: namespaceURI === 'http://www.w3.org/1998/Math/MathML'
294297
? MATHML_MODE
295298
: ROOT_HTML_MODE;
296-
return createFormatContext(insertionMode, null);
299+
return createFormatContext(insertionMode, null, false);
297300
}
298301

299302
export function getChildFormatContext(
@@ -302,38 +305,77 @@ export function getChildFormatContext(
302305
props: Object,
303306
): FormatContext {
304307
switch (type) {
308+
case 'noscript':
309+
return createFormatContext(HTML_MODE, null, true);
305310
case 'select':
306311
return createFormatContext(
307312
HTML_MODE,
308313
props.value != null ? props.value : props.defaultValue,
314+
parentContext.noscriptTagInScope,
309315
);
310316
case 'svg':
311-
return createFormatContext(SVG_MODE, null);
317+
return createFormatContext(
318+
SVG_MODE,
319+
null,
320+
parentContext.noscriptTagInScope,
321+
);
312322
case 'math':
313-
return createFormatContext(MATHML_MODE, null);
323+
return createFormatContext(
324+
MATHML_MODE,
325+
null,
326+
parentContext.noscriptTagInScope,
327+
);
314328
case 'foreignObject':
315-
return createFormatContext(HTML_MODE, null);
329+
return createFormatContext(
330+
HTML_MODE,
331+
null,
332+
parentContext.noscriptTagInScope,
333+
);
316334
// Table parents are special in that their children can only be created at all if they're
317335
// wrapped in a table parent. So we need to encode that we're entering this mode.
318336
case 'table':
319-
return createFormatContext(HTML_TABLE_MODE, null);
337+
return createFormatContext(
338+
HTML_TABLE_MODE,
339+
null,
340+
parentContext.noscriptTagInScope,
341+
);
320342
case 'thead':
321343
case 'tbody':
322344
case 'tfoot':
323-
return createFormatContext(HTML_TABLE_BODY_MODE, null);
345+
return createFormatContext(
346+
HTML_TABLE_BODY_MODE,
347+
null,
348+
parentContext.noscriptTagInScope,
349+
);
324350
case 'colgroup':
325-
return createFormatContext(HTML_COLGROUP_MODE, null);
351+
return createFormatContext(
352+
HTML_COLGROUP_MODE,
353+
null,
354+
parentContext.noscriptTagInScope,
355+
);
326356
case 'tr':
327-
return createFormatContext(HTML_TABLE_ROW_MODE, null);
357+
return createFormatContext(
358+
HTML_TABLE_ROW_MODE,
359+
null,
360+
parentContext.noscriptTagInScope,
361+
);
328362
}
329363
if (parentContext.insertionMode >= HTML_TABLE_MODE) {
330364
// Whatever tag this was, it wasn't a table parent or other special parent, so we must have
331365
// entered plain HTML again.
332-
return createFormatContext(HTML_MODE, null);
366+
return createFormatContext(
367+
HTML_MODE,
368+
null,
369+
parentContext.noscriptTagInScope,
370+
);
333371
}
334372
if (parentContext.insertionMode === ROOT_HTML_MODE) {
335373
// We've emitted the root and is now in plain HTML mode.
336-
return createFormatContext(HTML_MODE, null);
374+
return createFormatContext(
375+
HTML_MODE,
376+
null,
377+
parentContext.noscriptTagInScope,
378+
);
337379
}
338380
return parentContext;
339381
}
@@ -1155,8 +1197,13 @@ function pushBase(
11551197
props: Object,
11561198
responseState: ResponseState,
11571199
textEmbedded: boolean,
1200+
noscriptTagInScope: boolean,
11581201
): ReactNodeList {
1159-
if (enableFloat && resourcesFromElement('base', props)) {
1202+
if (
1203+
enableFloat &&
1204+
!noscriptTagInScope &&
1205+
resourcesFromElement('base', props)
1206+
) {
11601207
if (textEmbedded) {
11611208
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
11621209
// to be safe and assume text will follow by inserting a textSeparator
@@ -1175,8 +1222,13 @@ function pushMeta(
11751222
props: Object,
11761223
responseState: ResponseState,
11771224
textEmbedded: boolean,
1225+
noscriptTagInScope: boolean,
11781226
): ReactNodeList {
1179-
if (enableFloat && resourcesFromElement('meta', props)) {
1227+
if (
1228+
enableFloat &&
1229+
!noscriptTagInScope &&
1230+
resourcesFromElement('meta', props)
1231+
) {
11801232
if (textEmbedded) {
11811233
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
11821234
// to be safe and assume text will follow by inserting a textSeparator
@@ -1195,8 +1247,9 @@ function pushLink(
11951247
props: Object,
11961248
responseState: ResponseState,
11971249
textEmbedded: boolean,
1250+
noscriptTagInScope: boolean,
11981251
): ReactNodeList {
1199-
if (enableFloat && resourcesFromLink(props)) {
1252+
if (enableFloat && !noscriptTagInScope && resourcesFromLink(props)) {
12001253
if (textEmbedded) {
12011254
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
12021255
// to be safe and assume text will follow by inserting a textSeparator
@@ -1318,6 +1371,7 @@ function pushTitle(
13181371
target: Array<Chunk | PrecomputedChunk>,
13191372
props: Object,
13201373
responseState: ResponseState,
1374+
noscriptTagInScope: boolean,
13211375
): ReactNodeList {
13221376
if (__DEV__) {
13231377
const children = props.children;
@@ -1359,7 +1413,11 @@ function pushTitle(
13591413
}
13601414
}
13611415

1362-
if (enableFloat && resourcesFromElement('title', props)) {
1416+
if (
1417+
enableFloat &&
1418+
!noscriptTagInScope &&
1419+
resourcesFromElement('title', props)
1420+
) {
13631421
// We have converted this link exclusively to a resource and no longer
13641422
// need to emit it
13651423
return null;
@@ -1520,8 +1578,9 @@ function pushScript(
15201578
props: Object,
15211579
responseState: ResponseState,
15221580
textEmbedded: boolean,
1581+
noscriptTagInScope: boolean,
15231582
): null {
1524-
if (enableFloat && resourcesFromScript(props)) {
1583+
if (enableFloat && !noscriptTagInScope && resourcesFromScript(props)) {
15251584
if (textEmbedded) {
15261585
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
15271586
// to be safe and assume text will follow by inserting a textSeparator
@@ -1863,18 +1922,47 @@ export function pushStartInstance(
18631922
return pushStartMenuItem(target, props, responseState);
18641923
case 'title':
18651924
return enableFloat
1866-
? pushTitle(target, props, responseState)
1925+
? pushTitle(
1926+
target,
1927+
props,
1928+
responseState,
1929+
formatContext.noscriptTagInScope,
1930+
)
18671931
: pushStartTitle(target, props, responseState);
18681932
case 'link':
1869-
return pushLink(target, props, responseState, textEmbedded);
1933+
return pushLink(
1934+
target,
1935+
props,
1936+
responseState,
1937+
textEmbedded,
1938+
formatContext.noscriptTagInScope,
1939+
);
18701940
case 'script':
18711941
return enableFloat
1872-
? pushScript(target, props, responseState, textEmbedded)
1942+
? pushScript(
1943+
target,
1944+
props,
1945+
responseState,
1946+
textEmbedded,
1947+
formatContext.noscriptTagInScope,
1948+
)
18731949
: pushStartGenericElement(target, props, type, responseState);
18741950
case 'meta':
1875-
return pushMeta(target, props, responseState, textEmbedded);
1951+
return pushMeta(
1952+
target,
1953+
props,
1954+
responseState,
1955+
textEmbedded,
1956+
formatContext.noscriptTagInScope,
1957+
);
18761958
case 'base':
1877-
return pushBase(target, props, responseState, textEmbedded);
1959+
return pushBase(
1960+
target,
1961+
props,
1962+
responseState,
1963+
textEmbedded,
1964+
formatContext.noscriptTagInScope,
1965+
);
18781966
// Newline eating tags
18791967
case 'listing':
18801968
case 'pre': {

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

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function createRootFormatContext(): FormatContext {
7272
return {
7373
insertionMode: HTML_MODE, // We skip the root mode because we don't want to emit the DOCTYPE in legacy mode.
7474
selectedValue: null,
75+
noscriptTagInScope: false,
7576
};
7677
}
7778

0 commit comments

Comments
 (0)