@@ -71,6 +71,7 @@ export type AncestorInfoDev = {
71
71
72
72
// <head> or <body>
73
73
containerTagInScope : ?Info ,
74
+ implicitRootScope : boolean ,
74
75
} ;
75
76
76
77
// This validation code was written based on the HTML5 parsing spec:
@@ -219,10 +220,11 @@ const emptyAncestorInfoDev: AncestorInfoDev = {
219
220
dlItemTagAutoclosing : null ,
220
221
221
222
containerTagInScope : null ,
223
+ implicitRootScope : false ,
222
224
} ;
223
225
224
226
function updatedAncestorInfoDev (
225
- oldInfo : ? AncestorInfoDev ,
227
+ oldInfo : null | AncestorInfoDev ,
226
228
tag : string ,
227
229
) : AncestorInfoDev {
228
230
if ( __DEV__ ) {
@@ -238,14 +240,14 @@ function updatedAncestorInfoDev(
238
240
ancestorInfo . pTagInButtonScope = null ;
239
241
}
240
242
241
- // See rules for 'li', 'dd', 'dt' start tags in
242
- // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
243
243
if (
244
244
specialTags . indexOf ( tag ) !== - 1 &&
245
245
tag !== 'address' &&
246
246
tag !== 'div' &&
247
247
tag !== 'p'
248
248
) {
249
+ // See rules for 'li', 'dd', 'dt' start tags in
250
+ // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
249
251
ancestorInfo . listItemTagAutoclosing = null ;
250
252
ancestorInfo . dlItemTagAutoclosing = null ;
251
253
}
@@ -279,6 +281,17 @@ function updatedAncestorInfoDev(
279
281
ancestorInfo . containerTagInScope = info ;
280
282
}
281
283
284
+ if (
285
+ oldInfo === null &&
286
+ ( tag === '#document' || tag === 'html' || tag === 'body' )
287
+ ) {
288
+ // While <head> is also a singleton we don't want to support semantics where
289
+ // you can escape the head by rendering a body singleton so we treat it like a normal scope
290
+ ancestorInfo . implicitRootScope = true ;
291
+ } else if ( ancestorInfo . implicitRootScope === true ) {
292
+ ancestorInfo . implicitRootScope = false ;
293
+ }
294
+
282
295
return ancestorInfo ;
283
296
} else {
284
297
return ( null : any ) ;
@@ -288,7 +301,11 @@ function updatedAncestorInfoDev(
288
301
/**
289
302
* Returns whether
290
303
*/
291
- function isTagValidWithParent ( tag : string , parentTag : ?string ) : boolean {
304
+ function isTagValidWithParent (
305
+ tag : string ,
306
+ parentTag : ?string ,
307
+ implicitRootScope : boolean ,
308
+ ) : boolean {
292
309
// First, let's check if we're in an unusual parsing mode...
293
310
switch ( parentTag ) {
294
311
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
@@ -363,10 +380,22 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
363
380
) ;
364
381
// https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
365
382
case 'html' :
383
+ if ( implicitRootScope ) {
384
+ // When our parent tag is html and we're in the root scope we will actually
385
+ // insert most tags into the body so we need to fall through to validating
386
+ // the specific tag with "in body" parsing mode below
387
+ break ;
388
+ }
366
389
return tag === 'head' || tag === 'body' || tag === 'frameset' ;
367
390
case 'frameset' :
368
391
return tag === 'frame' ;
369
392
case '#document' :
393
+ if ( implicitRootScope ) {
394
+ // When our parent is the Document and we're in the root scope we will actually
395
+ // insert most tags into the body so we need to fall through to validating
396
+ // the specific tag with "in body" parsing mode below
397
+ break ;
398
+ }
370
399
return tag === 'html' ;
371
400
}
372
401
@@ -393,14 +422,11 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
393
422
case 'rt' :
394
423
return impliedEndTags . indexOf ( parentTag ) === - 1 ;
395
424
396
- case 'body' :
397
425
case 'caption' :
398
426
case 'col' :
399
427
case 'colgroup' :
400
428
case 'frameset' :
401
429
case 'frame' :
402
- case 'head' :
403
- case 'html' :
404
430
case 'tbody' :
405
431
case 'td' :
406
432
case 'tfoot' :
@@ -412,6 +438,24 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
412
438
// so we allow it only if we don't know what the parent is, as all other
413
439
// cases are invalid.
414
440
return parentTag == null ;
441
+ case 'head' :
442
+ // We support rendering <head> in the root when the container is
443
+ // #document, <html>, or <body>.
444
+ return implicitRootScope || parentTag === null ;
445
+ case 'html' :
446
+ // We support rendering <html> in the root when the container is
447
+ // #document
448
+ return (
449
+ ( implicitRootScope && parentTag === '#document' ) || parentTag === null
450
+ ) ;
451
+ case 'body' :
452
+ // We support rendering <body> in the root when the container is
453
+ // #document or <html>
454
+ return (
455
+ ( implicitRootScope &&
456
+ ( parentTag === '#document' || parentTag === 'html' ) ) ||
457
+ parentTag === null
458
+ ) ;
415
459
}
416
460
417
461
return true ;
@@ -513,7 +557,11 @@ function validateDOMNesting(
513
557
const parentInfo = ancestorInfo . current ;
514
558
const parentTag = parentInfo && parentInfo . tag ;
515
559
516
- const invalidParent = isTagValidWithParent ( childTag , parentTag )
560
+ const invalidParent = isTagValidWithParent (
561
+ childTag ,
562
+ parentTag ,
563
+ ancestorInfo . implicitRootScope ,
564
+ )
517
565
? null
518
566
: parentInfo ;
519
567
const invalidAncestor = invalidParent
@@ -594,9 +642,13 @@ function validateDOMNesting(
594
642
return true ;
595
643
}
596
644
597
- function validateTextNesting ( childText : string , parentTag : string ) : boolean {
645
+ function validateTextNesting (
646
+ childText : string ,
647
+ parentTag : string ,
648
+ implicitRootScope : boolean ,
649
+ ) : boolean {
598
650
if ( __DEV__ ) {
599
- if ( isTagValidWithParent ( '#text' , parentTag ) ) {
651
+ if ( implicitRootScope || isTagValidWithParent ( '#text' , parentTag , false ) ) {
600
652
return true ;
601
653
}
602
654
0 commit comments