@@ -88,13 +88,29 @@ export type ScriptResource = {
8888 root : FloatRoot ,
8989} ;
9090
91- type HeadProps = {
91+ export type HeadResource = TitleResource | MetaResource ;
92+
93+ type TitleProps = {
94+ [ string ] : mixed ,
95+ } ;
96+ export type TitleResource = {
97+ type : 'title' ,
98+ props : TitleProps ,
99+
100+ count : number ,
101+ instance : ?Element ,
102+ root : Document ,
103+ } ;
104+
105+ type MetaProps = {
92106 [ string ] : mixed ,
93107} ;
94- export type HeadResource = {
95- type : 'head' ,
96- instanceType : string ,
97- props : HeadProps ,
108+ export type MetaResource = {
109+ type : 'meta' ,
110+ matcher : string ,
111+ property : ?string ,
112+ parentResource : ?MetaResource ,
113+ props : MetaProps ,
98114
99115 count : number ,
100116 instance : ?Element ,
@@ -109,6 +125,7 @@ export type RootResources = {
109125 styles : Map < string , StyleResource> ,
110126 scripts : Map < string , ScriptResource> ,
111127 head : Map < string , HeadResource> ,
128+ lastStructuredMeta : Map < string , MetaResource> ,
112129} ;
113130
114131// Brief on purpose due to insertion by script when streaming late boundaries
@@ -409,6 +426,84 @@ export function getResource(
409426 ) ;
410427 }
411428 switch ( type ) {
429+ case 'meta' : {
430+ let matcher, propertyString, parentResource ;
431+ const {
432+ charSet,
433+ content,
434+ httpEquiv,
435+ name,
436+ itemProp,
437+ property,
438+ } = pendingProps ;
439+ const headRoot : Document = getDocumentFromRoot ( resourceRoot ) ;
440+ const { head : headResources , lastStructuredMeta} = getResourcesFromRoot (
441+ headRoot ,
442+ ) ;
443+ if ( typeof charSet === 'string' ) {
444+ matcher = 'meta[charset]' ;
445+ } else if ( typeof content === 'string' ) {
446+ if ( typeof httpEquiv === 'string' ) {
447+ matcher = `meta[http-equiv="${ escapeSelectorAttributeValueInsideDoubleQuotes (
448+ httpEquiv ,
449+ ) } "][content="${ escapeSelectorAttributeValueInsideDoubleQuotes (
450+ content ,
451+ ) } "]`;
452+ } else if ( typeof property === 'string' ) {
453+ propertyString = property ;
454+ matcher = `meta[property="${ escapeSelectorAttributeValueInsideDoubleQuotes (
455+ property ,
456+ ) } "][content="${ escapeSelectorAttributeValueInsideDoubleQuotes (
457+ content ,
458+ ) } "]`;
459+
460+ const parentPropertyPath = property
461+ . split ( ':' )
462+ . slice ( 0 , - 1 )
463+ . join ( ':' ) ;
464+ parentResource = lastStructuredMeta . get ( parentPropertyPath ) ;
465+ if ( parentResource ) {
466+ // When using parentResource the matcher is not functional for locating
467+ // the instance in the DOM but it still serves as a unique key.
468+ matcher = parentResource . matcher + matcher ;
469+ }
470+ } else if ( typeof name === 'string' ) {
471+ matcher = `meta[name="${ escapeSelectorAttributeValueInsideDoubleQuotes (
472+ name ,
473+ ) } "][content="${ escapeSelectorAttributeValueInsideDoubleQuotes (
474+ content ,
475+ ) } "]`;
476+ } else if ( typeof itemProp === 'string' ) {
477+ matcher = `meta[itemprop="${ escapeSelectorAttributeValueInsideDoubleQuotes (
478+ itemProp ,
479+ ) } "][content="${ escapeSelectorAttributeValueInsideDoubleQuotes (
480+ content ,
481+ ) } "]`;
482+ }
483+ }
484+ if ( matcher ) {
485+ let resource = headResources . get ( matcher ) ;
486+ if ( ! resource ) {
487+ resource = {
488+ type : 'meta' ,
489+ matcher,
490+ property : propertyString ,
491+ parentResource,
492+ props : Object . assign ( { } , pendingProps ) ,
493+ count : 0 ,
494+ instance : null ,
495+ root : headRoot ,
496+ } ;
497+ headResources . set ( matcher , resource ) ;
498+ }
499+ if ( typeof resource . property === 'string' ) {
500+ // We cast because flow doesn't know that this resource must be a Meta resource
501+ lastStructuredMeta. set ( resource . property , ( resource : any ) ) ;
502+ }
503+ return resource ;
504+ }
505+ return null ;
506+ }
412507 case 'title ': {
413508 let child = pendingProps . children ;
414509 if ( Array . isArray ( child ) && child . length === 1 ) {
@@ -421,13 +516,14 @@ export function getResource(
421516 let resource = headResources . get ( key ) ;
422517 if ( ! resource ) {
423518 const titleProps = titlePropsFromRawProps ( child , pendingProps ) ;
424- resource = createHeadResource (
425- headResources ,
426- headRoot ,
427- 'title' ,
428- key ,
429- titleProps ,
430- ) ;
519+ resource = {
520+ type : 'title ',
521+ props : titleProps ,
522+ count : 0 ,
523+ instance : null ,
524+ root : headRoot ,
525+ } ;
526+ headResources . set ( key , resource) ;
431527 }
432528 return resource ;
433529 }
@@ -588,8 +684,8 @@ function preloadPropsFromRawProps(
588684function titlePropsFromRawProps (
589685 child : string | number ,
590686 rawProps : Props ,
591- ) : HeadProps {
592- const props : HeadProps = Object . assign ( { } , rawProps ) ;
687+ ) : TitleProps {
688+ const props : TitleProps = Object . assign ( { } , rawProps ) ;
593689 props . children = child ;
594690 return props ;
595691}
@@ -613,7 +709,8 @@ function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
613709
614710export function acquireResource ( resource : Resource ) : Instance {
615711 switch ( resource . type ) {
616- case 'head' : {
712+ case 'title' :
713+ case 'meta' : {
617714 return acquireHeadResource ( resource ) ;
618715 }
619716 case 'style' : {
@@ -635,7 +732,8 @@ export function acquireResource(resource: Resource): Instance {
635732
636733export function releaseResource ( resource : Resource ) : void {
637734 switch ( resource . type ) {
638- case 'head ': {
735+ case 'title ':
736+ case 'meta': {
639737 return releaseHeadResource ( resource ) ;
640738 }
641739 case 'style ': {
@@ -668,35 +766,6 @@ function createResourceInstance(
668766 return element ;
669767}
670768
671- function createHeadResource (
672- headResources : Map < string , HeadResource > ,
673- root : Document ,
674- instanceType : string ,
675- key : string ,
676- props : HeadProps ,
677- ) : HeadResource {
678- if ( __DEV__ ) {
679- if ( headResources . has ( key ) ) {
680- console . error (
681- 'createHeadResource was called when a head Resource matching the same key already exists. This is a bug in React.' ,
682- ) ;
683- }
684- }
685-
686- const resource : HeadResource = {
687- type : 'head' ,
688- instanceType,
689- props,
690-
691- count : 0 ,
692- instance : null ,
693- root,
694- } ;
695-
696- headResources . set ( key , resource ) ;
697- return resource ;
698- }
699-
700769function createStyleResource (
701770 styleResources : Map < string , StyleResource > ,
702771 root : FloatRoot ,
@@ -894,7 +963,7 @@ function createPreloadResource(
894963 ) ;
895964 if ( ! element ) {
896965 element = createResourceInstance ( 'link' , props , ownerDocument ) ;
897- appendResourceInstance ( element , ownerDocument ) ;
966+ insertResourceInstanceBefore ( ownerDocument , element , null ) ;
898967 } else {
899968 markNodeAsResource ( element ) ;
900969 }
@@ -911,8 +980,8 @@ function acquireHeadResource(resource: HeadResource): Instance {
911980 resource . count ++ ;
912981 let instance = resource . instance ;
913982 if ( ! instance ) {
914- const { props, root, instanceType } = resource ;
915- switch ( instanceType ) {
983+ const { props, root, type } = resource ;
984+ switch ( type ) {
916985 case 'title' : {
917986 const titles = root . querySelectorAll ( 'title' ) ;
918987 for ( let i = 0 ; i < titles . length ; i ++ ) {
@@ -922,18 +991,70 @@ function acquireHeadResource(resource: HeadResource): Instance {
922991 return instance ;
923992 }
924993 }
994+ instance = resource . instance = createResourceInstance (
995+ type ,
996+ props ,
997+ root ,
998+ ) ;
999+ insertResourceInstanceBefore ( root , instance , titles . item ( 0 ) ) ;
1000+ break ;
1001+ }
1002+ case 'meta' : {
1003+ let insertBefore = null ;
1004+
1005+ const metaResource : MetaResource = ( resource : any ) ;
1006+ const { matcher, property, parentResource} = metaResource ;
1007+
1008+ if ( parentResource && typeof property === 'string' ) {
1009+ // This resoruce is a structured meta type with a parent.
1010+ // Instead of using the matcher we just traverse forward
1011+ // siblings of the parent instance until we find a match
1012+ // or exhaust.
1013+ const parent = parentResource . instance ;
1014+ if ( parent ) {
1015+ let node = null ;
1016+ let nextNode = ( insertBefore = parent . nextSibling ) ;
1017+ while ( ( node = nextNode ) ) {
1018+ nextNode = node . nextSibling ;
1019+ if ( node . nodeName === 'META' ) {
1020+ const meta : Element = ( node : any ) ;
1021+ const propertyAttr = meta . getAttribute ( 'property' ) ;
1022+ if ( typeof propertyAttr !== 'string' ) {
1023+ continue ;
1024+ } else if (
1025+ propertyAttr === property &&
1026+ meta . getAttribute ( 'content' ) === props . content
1027+ ) {
1028+ resource . instance = meta ;
1029+ markNodeAsResource ( meta ) ;
1030+ return meta ;
1031+ } else if ( property . startsWith ( propertyAttr + ':' ) ) {
1032+ // This meta starts a new instance of a parent structure for this meta type
1033+ // We need to halt our search here because even if we find a later match it
1034+ // is for a different parent element
1035+ break ;
1036+ }
1037+ }
1038+ }
1039+ }
1040+ } else if ( ( instance = root . querySelector ( matcher ) ) ) {
1041+ resource . instance = instance ;
1042+ markNodeAsResource ( instance ) ;
1043+ return instance ;
1044+ }
1045+ instance = resource . instance = createResourceInstance (
1046+ type ,
1047+ props ,
1048+ root ,
1049+ ) ;
1050+ insertResourceInstanceBefore ( root , instance , insertBefore ) ;
1051+ break ;
1052+ }
1053+ default : {
1054+ throw new Error (
1055+ `acquireHeadResource encountered a resource type it did not expect: "${ type } ". This is a bug in React.` ,
1056+ ) ;
9251057 }
926- }
927- instance = resource . instance = createResourceInstance (
928- instanceType ,
929- props ,
930- root ,
931- ) ;
932-
933- if ( instanceType === 'title' ) {
934- prependResourceInstance ( instance , root ) ;
935- } else {
936- appendResourceInstance ( instance , root ) ;
9371058 }
9381059 }
9391060 return instance ;
@@ -1010,7 +1131,7 @@ function acquireScriptResource(resource: ScriptResource): Instance {
10101131 getDocumentFromRoot ( root ) ,
10111132 ) ;
10121133
1013- appendResourceInstance ( instance , getDocumentFromRoot ( root ) ) ;
1134+ insertResourceInstanceBefore ( getDocumentFromRoot ( root ) , instance , null ) ;
10141135 }
10151136 }
10161137 return instance ;
@@ -1113,45 +1234,22 @@ function insertStyleInstance(
11131234 }
11141235}
11151236
1116- function prependResourceInstance (
1117- instance : Instance ,
1237+ function insertResourceInstanceBefore (
11181238 ownerDocument : Document ,
1119- ) : void {
1120- if ( __DEV__ ) {
1121- if ( instance . tagName === 'LINK' && ( instance : any ) . rel === 'stylesheet' ) {
1122- console . error (
1123- 'prependResourceInstance was called with a stylesheet. Stylesheets must be' +
1124- ' inserted with insertStyleInstance instead. This is a bug in React.' ,
1125- ) ;
1126- }
1127- }
1128-
1129- const parent = ownerDocument . head ;
1130- if ( parent ) {
1131- parent . insertBefore ( instance , parent . firstChild ) ;
1132- } else {
1133- throw new Error (
1134- 'While attempting to insert a Resource, React expected the Document to contain' +
1135- ' a head element but it was not found.' ,
1136- ) ;
1137- }
1138- }
1139-
1140- function appendResourceInstance (
11411239 instance : Instance ,
1142- ownerDocument : Document ,
1240+ before : ? Node ,
11431241) : void {
11441242 if ( __DEV__ ) {
11451243 if ( instance . tagName === 'LINK' && ( instance : any ) . rel === 'stylesheet' ) {
11461244 console . error (
1147- 'appendResourceInstance was called with a stylesheet. Stylesheets must be' +
1245+ 'insertResourceInstanceBefore was called with a stylesheet. Stylesheets must be' +
11481246 ' inserted with insertStyleInstance instead. This is a bug in React.' ,
11491247 ) ;
11501248 }
11511249 }
1152- const parent = ownerDocument . head ;
1250+ const parent = ( before && before . parentNode ) || ownerDocument . head ;
11531251 if ( parent ) {
1154- parent . appendChild ( instance ) ;
1252+ parent . insertBefore ( instance , before ) ;
11551253 } else {
11561254 throw new Error (
11571255 'While attempting to insert a Resource, React expected the Document to contain' +
@@ -1162,6 +1260,7 @@ function appendResourceInstance(
11621260
11631261export function isHostResourceType ( type : string , props : Props ) : boolean {
11641262 switch ( type ) {
1263+ case 'meta':
11651264 case 'title ': {
11661265 return true ;
11671266 }
0 commit comments