Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Float] support all links as Resources #25515

Merged
merged 2 commits into from
Oct 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 88 additions & 15 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js';
const {Dispatcher} = ReactDOMSharedInternals;
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
import {
validateUnmatchedLinkResourceProps,
warnOnMissingHrefAndRel,
validatePreloadResourceDifference,
validateURLKeyedUpdatedProps,
validateStyleResourceDifference,
Expand Down Expand Up @@ -54,7 +54,7 @@ type StyleProps = {
'data-precedence': string,
[string]: mixed,
};
export type StyleResource = {
type StyleResource = {
type: 'style',

// Ref count for resource
Expand All @@ -79,7 +79,7 @@ type ScriptProps = {
src: string,
[string]: mixed,
};
export type ScriptResource = {
type ScriptResource = {
type: 'script',
src: string,
props: ScriptProps,
Expand All @@ -88,12 +88,10 @@ export type ScriptResource = {
root: FloatRoot,
};

export type HeadResource = TitleResource | MetaResource;

type TitleProps = {
[string]: mixed,
};
export type TitleResource = {
type TitleResource = {
type: 'title',
props: TitleProps,

Expand All @@ -105,7 +103,7 @@ export type TitleResource = {
type MetaProps = {
[string]: mixed,
};
export type MetaResource = {
type MetaResource = {
type: 'meta',
matcher: string,
property: ?string,
Expand All @@ -117,8 +115,23 @@ export type MetaResource = {
root: Document,
};

type LinkProps = {
href: string,
rel: string,
[string]: mixed,
};
type LinkResource = {
type: 'link',
props: LinkProps,

count: number,
instance: ?Element,
root: Document,
};

type Props = {[string]: mixed};

type HeadResource = TitleResource | MetaResource | LinkResource;
type Resource = StyleResource | ScriptResource | PreloadResource | HeadResource;

export type RootResources = {
Expand Down Expand Up @@ -617,8 +630,30 @@ export function getResource(
return null;
}
default: {
const {href, sizes, media} = pendingProps;
if (typeof rel === 'string' && typeof href === 'string') {
const sizeKey =
'::sizes:' + (typeof sizes === 'string' ? sizes : '');
const mediaKey =
'::media:' + (typeof media === 'string' ? media : '');
const key = 'rel:' + rel + '::href:' + href + sizeKey + mediaKey;
const headRoot = getDocumentFromRoot(resourceRoot);
const headResources = getResourcesFromRoot(headRoot).head;
let resource = headResources.get(key);
if (!resource) {
resource = {
type: 'link',
props: Object.assign({}, pendingProps),
count: 0,
instance: null,
root: headRoot,
};
headResources.set(key, resource);
}
return resource;
}
if (__DEV__) {
validateUnmatchedLinkResourceProps(pendingProps, currentProps);
warnOnMissingHrefAndRel(pendingProps, currentProps);
}
return null;
}
Expand Down Expand Up @@ -710,6 +745,7 @@ function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
export function acquireResource(resource: Resource): Instance {
switch (resource.type) {
case 'title':
case 'link':
case 'meta': {
return acquireHeadResource(resource);
}
Expand All @@ -732,6 +768,7 @@ export function acquireResource(resource: Resource): Instance {

export function releaseResource(resource: Resource): void {
switch (resource.type) {
case 'link':
case 'title':
case 'meta': {
return releaseHeadResource(resource);
Expand Down Expand Up @@ -1050,6 +1087,41 @@ function acquireHeadResource(resource: HeadResource): Instance {
insertResourceInstanceBefore(root, instance, insertBefore);
break;
}
case 'link': {
const linkProps: LinkProps = (props: any);
const limitedEscapedRel = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.rel,
);
const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.href,
);
let selector = `link[rel="${limitedEscapedRel}"][href="${limitedEscapedHref}"]`;
if (typeof linkProps.sizes === 'string') {
const limitedEscapedSizes = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.sizes,
);
selector += `[sizes="${limitedEscapedSizes}"]`;
}
if (typeof linkProps.media === 'string') {
const limitedEscapedMedia = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.media,
);
selector += `[media="${limitedEscapedMedia}"]`;
}
const existingEl = root.querySelector(selector);
if (existingEl) {
instance = resource.instance = existingEl;
markNodeAsResource(instance);
return instance;
}
instance = resource.instance = createResourceInstance(
type,
props,
root,
);
insertResourceInstanceBefore(root, instance, null);
return instance;
}
default: {
throw new Error(
`acquireHeadResource encountered a resource type it did not expect: "${type}". This is a bug in React.`,
Expand Down Expand Up @@ -1265,26 +1337,27 @@ export function isHostResourceType(type: string, props: Props): boolean {
return true;
}
case 'link': {
const {onLoad, onError} = props;
if (onLoad || onError) {
return false;
}
switch (props.rel) {
case 'stylesheet': {
if (__DEV__) {
validateLinkPropsForStyleResource(props);
}
const {href, precedence, onLoad, onError, disabled} = props;
const {href, precedence, disabled} = props;
return (
typeof href === 'string' &&
typeof precedence === 'string' &&
!onLoad &&
!onError &&
disabled == null
);
}
case 'preload': {
const {href, onLoad, onError} = props;
return !onLoad && !onError && typeof href === 'string';
default: {
const {rel, href} = props;
return typeof href === 'string' && typeof rel === 'string';
}
}
return false;
}
case 'script': {
// We don't validate because it is valid to use async with onLoad/onError unlike combining
Expand Down
50 changes: 46 additions & 4 deletions packages/react-dom-bindings/src/server/ReactDOMFloatServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,20 @@ type MetaResource = {
flushed: boolean,
};

type LinkProps = {
href: string,
rel: string,
[string]: mixed,
};
type LinkResource = {
type: 'link',
props: LinkProps,

flushed: boolean,
};

export type Resource = PreloadResource | StyleResource | ScriptResource;
export type HeadResource = TitleResource | MetaResource;
export type HeadResource = TitleResource | MetaResource | LinkResource;

export type Resources = {
// Request local cache
Expand All @@ -101,6 +113,7 @@ export type Resources = {

// Flushing queues for Resource dependencies
charset: null | MetaResource,
preconnects: Set<LinkResource>,
fontPreloads: Set<PreloadResource>,
// usedImagePreloads: Set<PreloadResource>,
precedences: Map<string, Set<StyleResource>>,
Expand Down Expand Up @@ -131,6 +144,7 @@ export function createResources(): Resources {

// cleared on flush
charset: null,
preconnects: new Set(),
fontPreloads: new Set(),
// usedImagePreloads: new Set(),
precedences: new Map(),
Expand Down Expand Up @@ -697,10 +711,11 @@ export function resourcesFromLink(props: Props): boolean {
const resources = currentResources;

const {rel, href} = props;
if (!href || typeof href !== 'string') {
if (!href || typeof href !== 'string' || !rel || typeof rel !== 'string') {
return false;
}

let key = '';
switch (rel) {
case 'stylesheet': {
const {onLoad, onError, precedence, disabled} = props;
Expand Down Expand Up @@ -813,10 +828,37 @@ export function resourcesFromLink(props: Props): boolean {
return true;
}
}
return false;
break;
}
}
return false;
if (props.onLoad || props.onError) {
return false;
}

const sizes = typeof props.sizes === 'string' ? props.sizes : '';
const media = typeof props.media === 'string' ? props.media : '';
key =
'rel:' + rel + '::href:' + href + '::sizes:' + sizes + '::media:' + media;
let resource = resources.headsMap.get(key);
if (!resource) {
resource = {
type: 'link',
props: Object.assign({}, props),
flushed: false,
};
resources.headsMap.set(key, resource);
switch (rel) {
case 'preconnect':
case 'dns-prefetch': {
resources.preconnects.add(resource);
break;
}
default: {
resources.headResources.add(resource);
}
}
}
return true;
}

// Construct a resource from link props.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,7 @@ export function writeInitialResources(

const {
charset,
preconnects,
fontPreloads,
precedences,
usedStylePreloads,
Expand All @@ -2356,6 +2357,13 @@ export function writeInitialResources(
resources.charset = null;
}

preconnects.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
r.flushed = true;
});
preconnects.clear();

fontPreloads.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
Expand Down Expand Up @@ -2418,6 +2426,10 @@ export function writeInitialResources(
pushSelfClosing(target, r.props, 'meta', responseState);
break;
}
case 'link': {
pushLinkImpl(target, r.props, responseState);
break;
}
}
r.flushed = true;
});
Expand Down Expand Up @@ -2450,6 +2462,7 @@ export function writeImmediateResources(

const {
charset,
preconnects,
fontPreloads,
usedStylePreloads,
scripts,
Expand All @@ -2465,6 +2478,13 @@ export function writeImmediateResources(
resources.charset = null;
}

preconnects.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
r.flushed = true;
});
preconnects.clear();

fontPreloads.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
Expand Down Expand Up @@ -2507,6 +2527,10 @@ export function writeImmediateResources(
pushSelfClosing(target, r.props, 'meta', responseState);
break;
}
case 'link': {
pushLinkImpl(target, r.props, responseState);
break;
}
}
r.flushed = true;
});
Expand Down
Loading