@@ -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,16 @@ 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
+ break ;
385
+ }
366
386
return tag === 'head' || tag === 'body' || tag === 'frameset' ;
367
387
case 'frameset' :
368
388
return tag === 'frame' ;
369
389
case '#document' :
390
+ if ( implicitRootScope ) {
391
+ break ;
392
+ }
370
393
return tag === 'html' ;
371
394
}
372
395
@@ -393,14 +416,11 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
393
416
case 'rt' :
394
417
return impliedEndTags . indexOf ( parentTag ) === - 1 ;
395
418
396
- case 'body' :
397
419
case 'caption' :
398
420
case 'col' :
399
421
case 'colgroup' :
400
422
case 'frameset' :
401
423
case 'frame' :
402
- case 'head' :
403
- case 'html' :
404
424
case 'tbody' :
405
425
case 'td' :
406
426
case 'tfoot' :
@@ -412,6 +432,21 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
412
432
// so we allow it only if we don't know what the parent is, as all other
413
433
// cases are invalid.
414
434
return parentTag == null ;
435
+ case 'head' :
436
+ return ( implicitRootScope && parentTag !== 'head' ) || parentTag === null ;
437
+ case 'html' :
438
+ return (
439
+ ( implicitRootScope &&
440
+ parentTag !== 'body' &&
441
+ parentTag !== 'html' &&
442
+ parentTag !== 'head' ) ||
443
+ parentTag === null
444
+ ) ;
445
+ case 'body' :
446
+ return (
447
+ ( implicitRootScope && parentTag !== 'body' && parentTag !== 'head' ) ||
448
+ parentTag === null
449
+ ) ;
415
450
}
416
451
417
452
return true ;
@@ -513,7 +548,11 @@ function validateDOMNesting(
513
548
const parentInfo = ancestorInfo . current ;
514
549
const parentTag = parentInfo && parentInfo . tag ;
515
550
516
- const invalidParent = isTagValidWithParent ( childTag , parentTag )
551
+ const invalidParent = isTagValidWithParent (
552
+ childTag ,
553
+ parentTag ,
554
+ ancestorInfo . implicitRootScope ,
555
+ )
517
556
? null
518
557
: parentInfo ;
519
558
const invalidAncestor = invalidParent
@@ -594,9 +633,13 @@ function validateDOMNesting(
594
633
return true ;
595
634
}
596
635
597
- function validateTextNesting ( childText : string , parentTag : string ) : boolean {
636
+ function validateTextNesting (
637
+ childText : string ,
638
+ parentTag : string ,
639
+ implicitRootScope : boolean ,
640
+ ) : boolean {
598
641
if ( __DEV__ ) {
599
- if ( isTagValidWithParent ( '#text' , parentTag ) ) {
642
+ if ( implicitRootScope || isTagValidWithParent ( '#text' , parentTag ) ) {
600
643
return true ;
601
644
}
602
645
0 commit comments