import type {Instance, Container} from './ReactDOMHostConfig';
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js';
const {Dispatcher} = ReactDOMSharedInternals;
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
import {
warnOnMissingHrefAndRel,
validatePreloadResourceDifference,
validateURLKeyedUpdatedProps,
validateStyleResourceDifference,
validateScriptResourceDifference,
validateLinkPropsForStyleResource,
validateLinkPropsForPreloadResource,
validatePreloadArguments,
validatePreinitArguments,
} from '../shared/ReactDOMResourceValidation';
import {createElement, setInitialProperties} from './ReactDOMComponent';
import {
getResourcesFromRoot,
markNodeAsResource,
} from './ReactDOMComponentTree';
import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
type ResourceType = 'style' | 'font' | 'script';
type PreloadProps = {
rel: 'preload',
href: string,
[string]: mixed,
};
type PreloadResource = {
type: 'preload',
href: string,
ownerDocument: Document,
props: PreloadProps,
instance: Element,
};
type StyleProps = {
rel: 'stylesheet',
href: string,
'data-precedence': string,
[string]: mixed,
};
type StyleResource = {
type: 'style',
count: number,
href: string,
precedence: string,
props: StyleProps,
hint: ?PreloadResource,
preloaded: boolean,
loaded: boolean,
error: mixed,
instance: ?Element,
root: FloatRoot,
};
type ScriptProps = {
src: string,
[string]: mixed,
};
type ScriptResource = {
type: 'script',
src: string,
props: ScriptProps,
instance: ?Element,
root: FloatRoot,
};
type TitleProps = {
[string]: mixed,
};
type TitleResource = {
type: 'title',
props: TitleProps,
count: number,
instance: ?Element,
root: Document,
};
type MetaProps = {
[string]: mixed,
};
type MetaResource = {
type: 'meta',
matcher: string,
property: ?string,
parentResource: ?MetaResource,
props: MetaProps,
count: number,
instance: ?Element,
root: Document,
};
type LinkProps = {
href: string,
rel: string,
[string]: mixed,
};
type LinkResource = {
type: 'link',
props: LinkProps,
count: number,
instance: ?Element,
root: Document,
};
type BaseResource = {
type: 'base',
matcher: string,
props: Props,
count: number,
instance: ?Element,
root: Document,
};
type Props = {[string]: mixed};
type HeadResource = TitleResource | MetaResource | LinkResource | BaseResource;
type Resource = StyleResource | ScriptResource | PreloadResource | HeadResource;
export type RootResources = {
styles: Map<string, StyleResource>,
scripts: Map<string, ScriptResource>,
head: Map<string, HeadResource>,
lastStructuredMeta: Map<string, MetaResource>,
};
type StyleResourceLoadingState = Promise<mixed> & {s?: 'l' | 'e'};
let lastCurrentDocument: ?Document = null;
let previousDispatcher = null;
export function prepareToRenderResources(rootContainer: Container) {
const rootNode = getRootNode(rootContainer);
lastCurrentDocument = getDocumentFromRoot(rootNode);
previousDispatcher = Dispatcher.current;
Dispatcher.current = ReactDOMClientDispatcher;
}
export function cleanupAfterRenderResources() {
Dispatcher.current = previousDispatcher;
previousDispatcher = null;
}
export const ReactDOMClientDispatcher = {preload, preinit};
export type FloatRoot = Document | ShadowRoot;
const preloadResources: Map<string, PreloadResource> = new Map();
function getRootNode(container: Container): FloatRoot {
return typeof container.getRootNode === 'function'
?
container.getRootNode()
: container.ownerDocument;
}
function getCurrentResourceRoot(): null | FloatRoot {
const currentContainer = getCurrentRootHostContainer();
return currentContainer ? getRootNode(currentContainer) : null;
}
function resetInstance(resource: ScriptResource | HeadResource) {
resource.instance = undefined;
}
export function clearRootResources(rootContainer: Container): void {
const rootNode = getRootNode(rootContainer);
const resources = getResourcesFromRoot(rootNode);
resources.scripts.forEach(resetInstance);
resources.head.forEach(resetInstance);
}
function getDocumentForPreloads(): ?Document {
const root = getCurrentResourceRoot();
if (root) {
return root.ownerDocument || root;
} else {
try {
return lastCurrentDocument || window.document;
} catch (error) {
return null;
}
}
}
function getDocumentFromRoot(root: FloatRoot): Document {
return root.ownerDocument || root;
}
type PreloadAs = ResourceType;
type PreloadOptions = {as: PreloadAs, crossOrigin?: string, integrity?: string};
function preload(href: string, options: PreloadOptions) {
if (__DEV__) {
validatePreloadArguments(href, options);
}
const ownerDocument = getDocumentForPreloads();
if (
typeof href === 'string' &&
href &&
typeof options === 'object' &&
options !== null &&
ownerDocument
) {
const as = options.as;
const resource = preloadResources.get(href);
if (resource) {
if (__DEV__) {
const originallyImplicit =
(resource: any)._dev_implicit_construction === true;
const latestProps = preloadPropsFromPreloadOptions(href, as, options);
validatePreloadResourceDifference(
resource.props,
originallyImplicit,
latestProps,
false,
);
}
} else {
const resourceProps = preloadPropsFromPreloadOptions(href, as, options);
createPreloadResource(ownerDocument, href, resourceProps);
}
}
}
function preloadPropsFromPreloadOptions(
href: string,
as: ResourceType,
options: PreloadOptions,
): PreloadProps {
return {
href,
rel: 'preload',
as,
crossOrigin: as === 'font' ? '' : options.crossOrigin,
integrity: options.integrity,
};
}
type PreinitAs = 'style' | 'script';
type PreinitOptions = {
as: PreinitAs,
precedence?: string,
crossOrigin?: string,
integrity?: string,
};
function preinit(href: string, options: PreinitOptions) {
if (__DEV__) {
validatePreinitArguments(href, options);
}
if (
typeof href === 'string' &&
href &&
typeof options === 'object' &&
options !== null
) {
const resourceRoot = getCurrentResourceRoot();
const as = options.as;
if (!resourceRoot) {
const preloadDocument = getDocumentForPreloads();
if (preloadDocument) {
const preloadResource = preloadResources.get(href);
if (!preloadResource) {
const preloadProps = preloadPropsFromPreinitOptions(
href,
as,
options,
);
createPreloadResource(preloadDocument, href, preloadProps);
}
}
return;
}
switch (as) {
case 'style': {
const styleResources = getResourcesFromRoot(resourceRoot).styles;
const precedence = options.precedence || 'default';
let resource = styleResources.get(href);
if (resource) {
if (__DEV__) {
const latestProps = stylePropsFromPreinitOptions(
href,
precedence,
options,
);
validateStyleResourceDifference(resource.props, latestProps);
}
} else {
const resourceProps = stylePropsFromPreinitOptions(
href,
precedence,
options,
);
resource = createStyleResource(
styleResources,
resourceRoot,
href,
precedence,
resourceProps,
);
}
acquireResource(resource);
return;
}
case 'script': {
const src = href;
const scriptResources = getResourcesFromRoot(resourceRoot).scripts;
let resource = scriptResources.get(src);
if (resource) {
if (__DEV__) {
const latestProps = scriptPropsFromPreinitOptions(src, options);
validateScriptResourceDifference(resource.props, latestProps);
}
} else {
const resourceProps = scriptPropsFromPreinitOptions(src, options);
resource = createScriptResource(
scriptResources,
resourceRoot,
src,
resourceProps,
);
}
acquireResource(resource);
return;
}
}
}
}
function preloadPropsFromPreinitOptions(
href: string,
as: ResourceType,
options: PreinitOptions,
): PreloadProps {
return {
href,
rel: 'preload',
as,
crossOrigin: as === 'font' ? '' : options.crossOrigin,
integrity: options.integrity,
};
}
function stylePropsFromPreinitOptions(
href: string,
precedence: string,
options: PreinitOptions,
): StyleProps {
return {
rel: 'stylesheet',
href,
'data-precedence': precedence,
crossOrigin: options.crossOrigin,
};
}
function scriptPropsFromPreinitOptions(
src: string,
options: PreinitOptions,
): ScriptProps {
return {
src,
async: true,
crossOrigin: options.crossOrigin,
integrity: options.integrity,
};
}
type StyleQualifyingProps = {
rel: 'stylesheet',
href: string,
precedence: string,
[string]: mixed,
};
type PreloadQualifyingProps = {
rel: 'preload',
href: string,
[string]: mixed,
};
type ScriptQualifyingProps = {
src: string,
async: true,
[string]: mixed,
};
function getTitleKey(child: string | number): string {
return 'title:' + child;
}
export function getResource(
type: string,
pendingProps: Props,
currentProps: null | Props,
): null | Resource {
const resourceRoot = getCurrentResourceRoot();
if (!resourceRoot) {
throw new Error(
'"resourceRoot" was expected to exist. This is a bug in React.',
);
}
switch (type) {
case 'base': {
const headRoot: Document = getDocumentFromRoot(resourceRoot);
const headResources = getResourcesFromRoot(headRoot).head;
const {target, href} = pendingProps;
let matcher = 'base';
matcher +=
typeof href === 'string'
? `[href="${escapeSelectorAttributeValueInsideDoubleQuotes(href)}"]`
: ':not([href])';
matcher +=
typeof target === 'string'
? `[target="${escapeSelectorAttributeValueInsideDoubleQuotes(
target,
)}"]`
: ':not([target])';
let resource = headResources.get(matcher);
if (!resource) {
resource = {
type: 'base',
matcher,
props: Object.assign({}, pendingProps),
count: 0,
instance: null,
root: headRoot,
};
headResources.set(matcher, resource);
}
return resource;
}
case 'meta': {
let matcher, propertyString, parentResource;
const {
charSet,
content,
httpEquiv,
name,
itemProp,
property,
} = pendingProps;
const headRoot: Document = getDocumentFromRoot(resourceRoot);
const {head: headResources, lastStructuredMeta} = getResourcesFromRoot(
headRoot,
);
if (typeof charSet === 'string') {
matcher = 'meta[charset]';
} else if (typeof content === 'string') {
if (typeof httpEquiv === 'string') {
matcher = `meta[http-equiv="${escapeSelectorAttributeValueInsideDoubleQuotes(
httpEquiv,
)}"][content="${escapeSelectorAttributeValueInsideDoubleQuotes(
content,
)}"]`;
} else if (typeof property === 'string') {
propertyString = property;
matcher = `meta[property="${escapeSelectorAttributeValueInsideDoubleQuotes(
property,
)}"][content="${escapeSelectorAttributeValueInsideDoubleQuotes(
content,
)}"]`;
const parentPropertyPath = property
.split(':')
.slice(0, -1)
.join(':');
parentResource = lastStructuredMeta.get(parentPropertyPath);
if (parentResource) {
matcher = parentResource.matcher + matcher;
}
} else if (typeof name === 'string') {
matcher = `meta[name="${escapeSelectorAttributeValueInsideDoubleQuotes(
name,
)}"][content="${escapeSelectorAttributeValueInsideDoubleQuotes(
content,
)}"]`;
} else if (typeof itemProp === 'string') {
matcher = `meta[itemprop="${escapeSelectorAttributeValueInsideDoubleQuotes(
itemProp,
)}"][content="${escapeSelectorAttributeValueInsideDoubleQuotes(
content,
)}"]`;
}
}
if (matcher) {
let resource = headResources.get(matcher);
if (!resource) {
resource = {
type: 'meta',
matcher,
property: propertyString,
parentResource,
props: Object.assign({}, pendingProps),
count: 0,
instance: null,
root: headRoot,
};
headResources.set(matcher, resource);
}
if (typeof resource.property === 'string') {
lastStructuredMeta.set(resource.property, (resource: any));
}
return resource;
}
return null;
}
case 'title': {
const children = pendingProps.children;
let child;
if (Array.isArray(children)) {
child = children.length === 1 ? children[0] : null;
} else {
child = children;
}
if (
typeof child !== 'function' &&
typeof child !== 'symbol' &&
child !== null &&
child !== undefined
) {
const childString = '' + (child: any);
const headRoot: Document = getDocumentFromRoot(resourceRoot);
const headResources = getResourcesFromRoot(headRoot).head;
const key = getTitleKey(childString);
let resource = headResources.get(key);
if (!resource) {
const titleProps = titlePropsFromRawProps(childString, pendingProps);
resource = {
type: 'title',
props: titleProps,
count: 0,
instance: null,
root: headRoot,
};
headResources.set(key, resource);
}
return resource;
}
return null;
}
case 'link': {
const {rel} = pendingProps;
switch (rel) {
case 'stylesheet': {
const styleResources = getResourcesFromRoot(resourceRoot).styles;
let didWarn;
if (__DEV__) {
if (currentProps) {
didWarn = validateURLKeyedUpdatedProps(
pendingProps,
currentProps,
'style',
'href',
);
}
if (!didWarn) {
didWarn = validateLinkPropsForStyleResource(pendingProps);
}
}
const {precedence, href} = pendingProps;
if (typeof href === 'string' && typeof precedence === 'string') {
const styleRawProps: StyleQualifyingProps = (pendingProps: any);
let resource = styleResources.get(href);
if (resource) {
if (__DEV__) {
if (!didWarn) {
const latestProps = stylePropsFromRawProps(styleRawProps);
if ((resource: any)._dev_preload_props) {
adoptPreloadPropsForStyle(
latestProps,
(resource: any)._dev_preload_props,
);
}
validateStyleResourceDifference(resource.props, latestProps);
}
}
} else {
const resourceProps = stylePropsFromRawProps(styleRawProps);
resource = createStyleResource(
styleResources,
resourceRoot,
href,
precedence,
resourceProps,
);
immediatelyPreloadStyleResource(resource);
}
return resource;
}
return null;
}
case 'preload': {
if (__DEV__) {
validateLinkPropsForPreloadResource(pendingProps);
}
const {href} = pendingProps;
if (typeof href === 'string') {
const preloadRawProps: PreloadQualifyingProps = (pendingProps: any);
let resource = preloadResources.get(href);
if (resource) {
if (__DEV__) {
const originallyImplicit =
(resource: any)._dev_implicit_construction === true;
const latestProps = preloadPropsFromRawProps(preloadRawProps);
validatePreloadResourceDifference(
resource.props,
originallyImplicit,
latestProps,
false,
);
}
} else {
const resourceProps = preloadPropsFromRawProps(preloadRawProps);
resource = createPreloadResource(
getDocumentFromRoot(resourceRoot),
href,
resourceProps,
);
}
return resource;
}
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__) {
warnOnMissingHrefAndRel(pendingProps, currentProps);
}
return null;
}
}
}
case 'script': {
const scriptResources = getResourcesFromRoot(resourceRoot).scripts;
let didWarn;
if (__DEV__) {
if (currentProps) {
didWarn = validateURLKeyedUpdatedProps(
pendingProps,
currentProps,
'script',
'src',
);
}
}
const {src, async} = pendingProps;
if (async && typeof src === 'string') {
const scriptRawProps: ScriptQualifyingProps = (pendingProps: any);
let resource = scriptResources.get(src);
if (resource) {
if (__DEV__) {
if (!didWarn) {
const latestProps = scriptPropsFromRawProps(scriptRawProps);
if ((resource: any)._dev_preload_props) {
adoptPreloadPropsForScript(
latestProps,
(resource: any)._dev_preload_props,
);
}
validateScriptResourceDifference(resource.props, latestProps);
}
}
} else {
const resourceProps = scriptPropsFromRawProps(scriptRawProps);
resource = createScriptResource(
scriptResources,
resourceRoot,
src,
resourceProps,
);
}
return resource;
}
return null;
}
default: {
throw new Error(
`getResource encountered a resource type it did not expect: "${type}". this is a bug in React.`,
);
}
}
}
function preloadPropsFromRawProps(
rawBorrowedProps: PreloadQualifyingProps,
): PreloadProps {
return Object.assign({}, rawBorrowedProps);
}
function titlePropsFromRawProps(
child: string | number,
rawProps: Props,
): TitleProps {
const props: TitleProps = Object.assign({}, rawProps);
props.children = child;
return props;
}
function stylePropsFromRawProps(rawProps: StyleQualifyingProps): StyleProps {
const props: StyleProps = Object.assign({}, rawProps);
props['data-precedence'] = rawProps.precedence;
props.precedence = null;
return props;
}
function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
const props: ScriptProps = Object.assign({}, rawProps);
return props;
}
export function acquireResource(resource: Resource): Instance {
switch (resource.type) {
case 'base':
case 'title':
case 'link':
case 'meta': {
return acquireHeadResource(resource);
}
case 'style': {
return acquireStyleResource(resource);
}
case 'script': {
return acquireScriptResource(resource);
}
case 'preload': {
return resource.instance;
}
default: {
throw new Error(
`acquireResource encountered a resource type it did not expect: "${resource.type}". this is a bug in React.`,
);
}
}
}
export function releaseResource(resource: Resource): void {
switch (resource.type) {
case 'link':
case 'title':
case 'meta': {
return releaseHeadResource(resource);
}
case 'style': {
resource.count--;
return;
}
}
}
function releaseHeadResource(resource: HeadResource): void {
if (--resource.count === 0) {
const instance: Instance = (resource.instance: any);
const parent = instance.parentNode;
if (parent) {
parent.removeChild(instance);
}
resource.instance = null;
}
}
function createResourceInstance(
type: string,
props: Object,
ownerDocument: Document,
): Instance {
const element = createElement(type, props, ownerDocument, HTML_NAMESPACE);
setInitialProperties(element, type, props);
markNodeAsResource(element);
return element;
}
function createStyleResource(
styleResources: Map<string, StyleResource>,
root: FloatRoot,
href: string,
precedence: string,
props: StyleProps,
): StyleResource {
if (__DEV__) {
if (styleResources.has(href)) {
console.error(
'createStyleResource was called when a style Resource matching the same href already exists. This is a bug in React.',
);
}
}
const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
href,
);
const existingEl = root.querySelector(
`link[rel="stylesheet"][href="${limitedEscapedHref}"]`,
);
const resource = {
type: 'style',
count: 0,
href,
precedence,
props,
hint: null,
preloaded: false,
loaded: false,
error: false,
root,
instance: null,
};
styleResources.set(href, resource);
if (existingEl) {
const loadingState: ?StyleResourceLoadingState = (existingEl: any)._p;
if (loadingState) {
switch (loadingState.s) {
case 'l': {
resource.loaded = true;
break;
}
case 'e': {
resource.error = true;
break;
}
default: {
attachLoadListeners(existingEl, resource);
}
}
} else {
resource.loaded = true;
}
} else {
const hint = preloadResources.get(href);
if (hint) {
resource.hint = hint;
const preloadProps = hint.props;
adoptPreloadPropsForStyle(resource.props, hint.props);
if (__DEV__) {
(resource: any)._dev_preload_props = preloadProps;
}
}
}
return resource;
}
function adoptPreloadPropsForStyle(
styleProps: StyleProps,
preloadProps: PreloadProps,
): void {
if (styleProps.crossOrigin == null)
styleProps.crossOrigin = preloadProps.crossOrigin;
if (styleProps.referrerPolicy == null)
styleProps.referrerPolicy = preloadProps.referrerPolicy;
if (styleProps.title == null) styleProps.title = preloadProps.title;
}
function immediatelyPreloadStyleResource(resource: StyleResource) {
if (resource.loaded === false && resource.hint === null) {
const {href, props} = resource;
const preloadProps = preloadPropsFromStyleProps(props);
resource.hint = createPreloadResource(
getDocumentFromRoot(resource.root),
href,
preloadProps,
);
}
}
function preloadPropsFromStyleProps(props: StyleProps): PreloadProps {
return {
rel: 'preload',
as: 'style',
href: props.href,
crossOrigin: props.crossOrigin,
integrity: props.integrity,
media: props.media,
hrefLang: props.hrefLang,
referrerPolicy: props.referrerPolicy,
};
}
function createScriptResource(
scriptResources: Map<string, ScriptResource>,
root: FloatRoot,
src: string,
props: ScriptProps,
): ScriptResource {
if (__DEV__) {
if (scriptResources.has(src)) {
console.error(
'createScriptResource was called when a script Resource matching the same src already exists. This is a bug in React.',
);
}
}
const limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(src);
const existingEl = root.querySelector(
`script[async][src="${limitedEscapedSrc}"]`,
);
const resource = {
type: 'script',
src,
props,
root,
instance: existingEl || null,
};
scriptResources.set(src, resource);
if (!existingEl) {
const hint = preloadResources.get(src);
if (hint) {
const preloadProps = hint.props;
adoptPreloadPropsForScript(props, hint.props);
if (__DEV__) {
(resource: any)._dev_preload_props = preloadProps;
}
}
} else {
markNodeAsResource(existingEl);
}
return resource;
}
function adoptPreloadPropsForScript(
scriptProps: ScriptProps,
preloadProps: PreloadProps,
): void {
if (scriptProps.crossOrigin == null)
scriptProps.crossOrigin = preloadProps.crossOrigin;
if (scriptProps.referrerPolicy == null)
scriptProps.referrerPolicy = preloadProps.referrerPolicy;
if (scriptProps.integrity == null)
scriptProps.referrerPolicy = preloadProps.integrity;
}
function createPreloadResource(
ownerDocument: Document,
href: string,
props: PreloadProps,
): PreloadResource {
const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
href,
);
let element: null | Instance | HTMLElement = ownerDocument.querySelector(
`link[rel="preload"][href="${limitedEscapedHref}"]`,
);
if (!element) {
element = createResourceInstance('link', props, ownerDocument);
insertResourceInstanceBefore(ownerDocument, element, null);
} else {
markNodeAsResource(element);
}
return {
type: 'preload',
href: href,
ownerDocument,
props,
instance: element,
};
}
function acquireHeadResource(resource: HeadResource): Instance {
resource.count++;
let instance = resource.instance;
if (!instance) {
const {props, root, type} = resource;
switch (type) {
case 'title': {
const titles = root.querySelectorAll('title');
for (let i = 0; i < titles.length; i++) {
if (titles[i].textContent === props.children) {
instance = resource.instance = titles[i];
markNodeAsResource(instance);
return instance;
}
}
instance = resource.instance = createResourceInstance(
type,
props,
root,
);
const firstTitle = titles[0];
insertResourceInstanceBefore(
root,
instance,
firstTitle && firstTitle.namespaceURI !== SVG_NAMESPACE
? firstTitle
: null,
);
break;
}
case 'meta': {
let insertBefore = null;
const metaResource: MetaResource = (resource: any);
const {matcher, property, parentResource} = metaResource;
if (parentResource && typeof property === 'string') {
const parent = parentResource.instance;
if (parent) {
let node = null;
let nextNode = (insertBefore = parent.nextSibling);
while ((node = nextNode)) {
nextNode = node.nextSibling;
if (node.nodeName === 'META') {
const meta: Element = (node: any);
const propertyAttr = meta.getAttribute('property');
if (typeof propertyAttr !== 'string') {
continue;
} else if (
propertyAttr === property &&
meta.getAttribute('content') === props.content
) {
resource.instance = meta;
markNodeAsResource(meta);
return meta;
} else if (property.startsWith(propertyAttr + ':')) {
break;
}
}
}
}
} else if ((instance = root.querySelector(matcher))) {
resource.instance = instance;
markNodeAsResource(instance);
return instance;
}
instance = resource.instance = createResourceInstance(
type,
props,
root,
);
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;
}
case 'base': {
const baseResource: BaseResource = (resource: any);
const {matcher} = baseResource;
const base = root.querySelector(matcher);
if (base) {
instance = resource.instance = base;
markNodeAsResource(instance);
} else {
instance = resource.instance = createResourceInstance(
type,
props,
root,
);
insertResourceInstanceBefore(
root,
instance,
root.querySelector('base'),
);
}
return instance;
}
default: {
throw new Error(
`acquireHeadResource encountered a resource type it did not expect: "${type}". This is a bug in React.`,
);
}
}
}
return instance;
}
function acquireStyleResource(resource: StyleResource): Instance {
let instance = resource.instance;
if (!instance) {
const {props, root, precedence} = resource;
const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
props.href,
);
const existingEl = root.querySelector(
`link[rel="stylesheet"][data-precedence][href="${limitedEscapedHref}"]`,
);
if (existingEl) {
instance = resource.instance = existingEl;
markNodeAsResource(instance);
resource.preloaded = true;
const loadingState: ?StyleResourceLoadingState = (existingEl: any)._p;
if (loadingState) {
switch (loadingState.s) {
case 'l': {
resource.loaded = true;
resource.error = false;
break;
}
case 'e': {
resource.error = true;
break;
}
default: {
attachLoadListeners(existingEl, resource);
}
}
} else {
resource.loaded = true;
}
} else {
instance = resource.instance = createResourceInstance(
'link',
resource.props,
getDocumentFromRoot(root),
);
attachLoadListeners(instance, resource);
insertStyleInstance(instance, precedence, root);
}
}
resource.count++;
return instance;
}
function acquireScriptResource(resource: ScriptResource): Instance {
let instance = resource.instance;
if (!instance) {
const {props, root} = resource;
const limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(
props.src,
);
const existingEl = root.querySelector(
`script[async][src="${limitedEscapedSrc}"]`,
);
if (existingEl) {
instance = resource.instance = existingEl;
markNodeAsResource(instance);
} else {
instance = resource.instance = createResourceInstance(
'script',
resource.props,
getDocumentFromRoot(root),
);
insertResourceInstanceBefore(getDocumentFromRoot(root), instance, null);
}
}
return instance;
}
function attachLoadListeners(instance: Instance, resource: StyleResource) {
const listeners: {
[string]: () => mixed,
} = {};
listeners.load = onResourceLoad.bind(
null,
instance,
resource,
listeners,
loadAndErrorEventListenerOptions,
);
listeners.error = onResourceError.bind(
null,
instance,
resource,
listeners,
loadAndErrorEventListenerOptions,
);
instance.addEventListener(
'load',
listeners.load,
loadAndErrorEventListenerOptions,
);
instance.addEventListener(
'error',
listeners.error,
loadAndErrorEventListenerOptions,
);
}
const loadAndErrorEventListenerOptions = {
passive: true,
};
function onResourceLoad(
instance: Instance,
resource: StyleResource,
listeners: {[string]: () => mixed},
listenerOptions: typeof loadAndErrorEventListenerOptions,
) {
resource.loaded = true;
resource.error = false;
for (const event in listeners) {
instance.removeEventListener(event, listeners[event], listenerOptions);
}
}
function onResourceError(
instance: Instance,
resource: StyleResource,
listeners: {[string]: () => mixed},
listenerOptions: typeof loadAndErrorEventListenerOptions,
) {
resource.loaded = false;
resource.error = true;
for (const event in listeners) {
instance.removeEventListener(event, listeners[event], listenerOptions);
}
}
function insertStyleInstance(
instance: Instance,
precedence: string,
root: FloatRoot,
): void {
const nodes = root.querySelectorAll(
'link[rel="stylesheet"][data-precedence]',
);
const last = nodes.length ? nodes[nodes.length - 1] : null;
let prior = last;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const nodePrecedence = node.dataset.precedence;
if (nodePrecedence === precedence) {
prior = node;
} else if (prior !== last) {
break;
}
}
if (prior) {
((prior.parentNode: any): Node).insertBefore(instance, prior.nextSibling);
} else {
const parent =
root.nodeType === DOCUMENT_NODE ? ((root: any): Document).head : root;
if (parent) {
parent.insertBefore(instance, parent.firstChild);
} else {
throw new Error(
'While attempting to insert a Resource, React expected the Document to contain' +
' a head element but it was not found.',
);
}
}
}
function insertResourceInstanceBefore(
ownerDocument: Document,
instance: Instance,
before: ?Node,
): void {
if (__DEV__) {
if (instance.tagName === 'LINK' && (instance: any).rel === 'stylesheet') {
console.error(
'insertResourceInstanceBefore was called with a stylesheet. Stylesheets must be' +
' inserted with insertStyleInstance instead. This is a bug in React.',
);
}
}
const parent = (before && before.parentNode) || ownerDocument.head;
if (parent) {
parent.insertBefore(instance, before);
} else {
throw new Error(
'While attempting to insert a Resource, React expected the Document to contain' +
' a head element but it was not found.',
);
}
}
const escapeSelectorAttributeValueInsideDoubleQuotesRegex = /[\n\"\\]/g;
function escapeSelectorAttributeValueInsideDoubleQuotes(value: string): string {
return value.replace(
escapeSelectorAttributeValueInsideDoubleQuotesRegex,
ch => '\\' + ch.charCodeAt(0).toString(16),
);
}