@@ -13,7 +13,7 @@ import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js';
1313const { Dispatcher} = ReactDOMSharedInternals ;
1414import { DOCUMENT_NODE } from '../shared/HTMLNodeType' ;
1515import {
16- validateUnmatchedLinkResourceProps ,
16+ warnOnMissingHrefAndRel ,
1717 validatePreloadResourceDifference ,
1818 validateURLKeyedUpdatedProps ,
1919 validateStyleResourceDifference ,
@@ -54,7 +54,7 @@ type StyleProps = {
5454 'data-precedence' : string ,
5555 [ string ] : mixed ,
5656} ;
57- export type StyleResource = {
57+ type StyleResource = {
5858 type : 'style' ,
5959
6060 // Ref count for resource
@@ -79,7 +79,7 @@ type ScriptProps = {
7979 src : string ,
8080 [ string ] : mixed ,
8181} ;
82- export type ScriptResource = {
82+ type ScriptResource = {
8383 type : 'script' ,
8484 src : string ,
8585 props : ScriptProps ,
@@ -88,12 +88,10 @@ export type ScriptResource = {
8888 root : FloatRoot ,
8989} ;
9090
91- export type HeadResource = TitleResource | MetaResource ;
92-
9391type TitleProps = {
9492 [ string ] : mixed ,
9593} ;
96- export type TitleResource = {
94+ type TitleResource = {
9795 type : 'title' ,
9896 props : TitleProps ,
9997
@@ -105,7 +103,7 @@ export type TitleResource = {
105103type MetaProps = {
106104 [ string ] : mixed ,
107105} ;
108- export type MetaResource = {
106+ type MetaResource = {
109107 type : 'meta' ,
110108 matcher : string ,
111109 property : ?string ,
@@ -117,8 +115,23 @@ export type MetaResource = {
117115 root : Document ,
118116} ;
119117
118+ type LinkProps = {
119+ href : string ,
120+ rel : string ,
121+ [ string ] : mixed ,
122+ } ;
123+ type LinkResource = {
124+ type : 'link' ,
125+ props : LinkProps ,
126+
127+ count : number ,
128+ instance : ?Element ,
129+ root : Document ,
130+ } ;
131+
120132type Props = { [ string ] : mixed } ;
121133
134+ type HeadResource = TitleResource | MetaResource | LinkResource ;
122135type Resource = StyleResource | ScriptResource | PreloadResource | HeadResource ;
123136
124137export type RootResources = {
@@ -617,8 +630,30 @@ export function getResource(
617630 return null ;
618631 }
619632 default : {
633+ const { href , sizes , media } = pendingProps ;
634+ if ( typeof rel === 'string' && typeof href === 'string' ) {
635+ const sizeKey =
636+ '::sizes :' + ( typeof sizes === 'string' ? sizes : '' ) ;
637+ const mediaKey =
638+ '::media:' + ( typeof media === 'string' ? media : '' ) ;
639+ const key = 'rel:' + rel + '::href:' + href + sizeKey + mediaKey ;
640+ const headRoot = getDocumentFromRoot ( resourceRoot ) ;
641+ const headResources = getResourcesFromRoot ( headRoot ) . head ;
642+ let resource = headResources . get ( key ) ;
643+ if ( ! resource ) {
644+ resource = {
645+ type : 'link' ,
646+ props : Object . assign ( { } , pendingProps ) ,
647+ count : 0 ,
648+ instance : null ,
649+ root : headRoot ,
650+ } ;
651+ headResources . set ( key , resource ) ;
652+ }
653+ return resource ;
654+ }
620655 if ( __DEV__ ) {
621- validateUnmatchedLinkResourceProps ( pendingProps , currentProps ) ;
656+ warnOnMissingHrefAndRel ( pendingProps , currentProps ) ;
622657 }
623658 return null ;
624659 }
@@ -710,6 +745,7 @@ function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
710745export function acquireResource ( resource : Resource ) : Instance {
711746 switch ( resource . type ) {
712747 case 'title' :
748+ case 'link' :
713749 case 'meta' : {
714750 return acquireHeadResource ( resource ) ;
715751 }
@@ -732,6 +768,7 @@ export function acquireResource(resource: Resource): Instance {
732768
733769export function releaseResource ( resource : Resource ) : void {
734770 switch ( resource . type ) {
771+ case 'link ':
735772 case 'title ':
736773 case 'meta': {
737774 return releaseHeadResource ( resource ) ;
@@ -1050,6 +1087,41 @@ function acquireHeadResource(resource: HeadResource): Instance {
10501087 insertResourceInstanceBefore ( root , instance , insertBefore ) ;
10511088 break ;
10521089 }
1090+ case 'link' : {
1091+ const linkProps : LinkProps = ( props : any ) ;
1092+ const limitedEscapedRel = escapeSelectorAttributeValueInsideDoubleQuotes (
1093+ linkProps . rel ,
1094+ ) ;
1095+ const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes (
1096+ linkProps . href ,
1097+ ) ;
1098+ let selector = `link[rel="${ limitedEscapedRel } "][href="${ limitedEscapedHref } "]` ;
1099+ if ( typeof linkProps . sizes === 'string' ) {
1100+ const limitedEscapedSizes = escapeSelectorAttributeValueInsideDoubleQuotes (
1101+ linkProps . sizes ,
1102+ ) ;
1103+ selector += `[sizes="${ limitedEscapedSizes } "]` ;
1104+ }
1105+ if ( typeof linkProps . media === 'string' ) {
1106+ const limitedEscapedMedia = escapeSelectorAttributeValueInsideDoubleQuotes (
1107+ linkProps . media ,
1108+ ) ;
1109+ selector += `[media="${ limitedEscapedMedia } "]` ;
1110+ }
1111+ const existingEl = root . querySelector ( selector ) ;
1112+ if ( existingEl ) {
1113+ instance = resource . instance = existingEl ;
1114+ markNodeAsResource ( instance ) ;
1115+ return instance ;
1116+ }
1117+ instance = resource . instance = createResourceInstance (
1118+ type ,
1119+ props ,
1120+ root ,
1121+ ) ;
1122+ insertResourceInstanceBefore ( root , instance , null ) ;
1123+ return instance ;
1124+ }
10531125 default : {
10541126 throw new Error (
10551127 `acquireHeadResource encountered a resource type it did not expect: "${ type } ". This is a bug in React.` ,
@@ -1265,26 +1337,27 @@ export function isHostResourceType(type: string, props: Props): boolean {
12651337 return true ;
12661338 }
12671339 case 'link ': {
1340+ const { onLoad , onError } = props ;
1341+ if ( onLoad || onError ) {
1342+ return false ;
1343+ }
12681344 switch ( props . rel ) {
12691345 case 'stylesheet ': {
12701346 if ( __DEV__ ) {
12711347 validateLinkPropsForStyleResource ( props ) ;
12721348 }
1273- const { href , precedence , onLoad , onError , disabled } = props ;
1349+ const { href , precedence , disabled } = props ;
12741350 return (
12751351 typeof href === 'string' &&
12761352 typeof precedence === 'string' &&
1277- ! onLoad &&
1278- ! onError &&
12791353 disabled == null
12801354 ) ;
12811355 }
1282- case ' preload ' : {
1283- const { href , onLoad , onError } = props ;
1284- return ! onLoad && ! onError && typeof href === 'string' ;
1356+ default : {
1357+ const { rel , href } = props ;
1358+ return typeof href === 'string' && typeof rel === 'string' ;
12851359 }
12861360 }
1287- return false ;
12881361 }
12891362 case 'script' : {
12901363 // We don't validate because it is valid to use async with onLoad/onError unlike combining
0 commit comments