@@ -71,6 +71,7 @@ export type AncestorInfoDev = {
7171
7272 // <head> or <body>
7373 containerTagInScope : ?Info ,
74+ implicitRootScope : boolean ,
7475} ;
7576
7677// This validation code was written based on the HTML5 parsing spec:
@@ -219,10 +220,11 @@ const emptyAncestorInfoDev: AncestorInfoDev = {
219220 dlItemTagAutoclosing : null ,
220221
221222 containerTagInScope : null ,
223+ implicitRootScope : false ,
222224} ;
223225
224226function updatedAncestorInfoDev (
225- oldInfo : ? AncestorInfoDev ,
227+ oldInfo : null | AncestorInfoDev ,
226228 tag : string ,
227229) : AncestorInfoDev {
228230 if ( __DEV__ ) {
@@ -238,14 +240,14 @@ function updatedAncestorInfoDev(
238240 ancestorInfo . pTagInButtonScope = null ;
239241 }
240242
241- // See rules for 'li', 'dd', 'dt' start tags in
242- // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
243243 if (
244244 specialTags . indexOf ( tag ) !== - 1 &&
245245 tag !== 'address' &&
246246 tag !== 'div' &&
247247 tag !== 'p'
248248 ) {
249+ // See rules for 'li', 'dd', 'dt' start tags in
250+ // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
249251 ancestorInfo . listItemTagAutoclosing = null ;
250252 ancestorInfo . dlItemTagAutoclosing = null ;
251253 }
@@ -279,6 +281,17 @@ function updatedAncestorInfoDev(
279281 ancestorInfo . containerTagInScope = info ;
280282 }
281283
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+
282295 return ancestorInfo ;
283296 } else {
284297 return ( null : any ) ;
@@ -288,7 +301,11 @@ function updatedAncestorInfoDev(
288301/**
289302 * Returns whether
290303 */
291- function isTagValidWithParent ( tag : string , parentTag : ?string ) : boolean {
304+ function isTagValidWithParent (
305+ tag : string ,
306+ parentTag : ?string ,
307+ implicitRootScope : boolean ,
308+ ) : boolean {
292309 // First, let's check if we're in an unusual parsing mode...
293310 switch ( parentTag ) {
294311 // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
@@ -363,10 +380,22 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
363380 ) ;
364381 // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
365382 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+ }
366389 return tag === 'head' || tag === 'body' || tag === 'frameset' ;
367390 case 'frameset' :
368391 return tag === 'frame' ;
369392 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+ }
370399 return tag === 'html' ;
371400 }
372401
@@ -393,14 +422,11 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
393422 case 'rt' :
394423 return impliedEndTags . indexOf ( parentTag ) === - 1 ;
395424
396- case 'body' :
397425 case 'caption' :
398426 case 'col' :
399427 case 'colgroup' :
400428 case 'frameset' :
401429 case 'frame' :
402- case 'head' :
403- case 'html' :
404430 case 'tbody' :
405431 case 'td' :
406432 case 'tfoot' :
@@ -412,6 +438,24 @@ function isTagValidWithParent(tag: string, parentTag: ?string): boolean {
412438 // so we allow it only if we don't know what the parent is, as all other
413439 // cases are invalid.
414440 return parentTag == null ;
441+ case 'head' :
442+ // We support rendering <head> in the root when the container is
443+ // #document, <htm>, 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+ ) ;
415459 }
416460
417461 return true ;
@@ -513,7 +557,11 @@ function validateDOMNesting(
513557 const parentInfo = ancestorInfo . current ;
514558 const parentTag = parentInfo && parentInfo . tag ;
515559
516- const invalidParent = isTagValidWithParent ( childTag , parentTag )
560+ const invalidParent = isTagValidWithParent (
561+ childTag ,
562+ parentTag ,
563+ ancestorInfo . implicitRootScope ,
564+ )
517565 ? null
518566 : parentInfo ;
519567 const invalidAncestor = invalidParent
@@ -594,9 +642,13 @@ function validateDOMNesting(
594642 return true ;
595643}
596644
597- function validateTextNesting ( childText : string , parentTag : string ) : boolean {
645+ function validateTextNesting (
646+ childText : string ,
647+ parentTag : string ,
648+ implicitRootScope : boolean ,
649+ ) : boolean {
598650 if ( __DEV__ ) {
599- if ( isTagValidWithParent ( '#text' , parentTag ) ) {
651+ if ( implicitRootScope || isTagValidWithParent ( '#text' , parentTag , false ) ) {
600652 return true ;
601653 }
602654
0 commit comments