Skip to content

Commit

Permalink
refactor isHostResourceType to not receive the context from reconcile…
Browse files Browse the repository at this point in the history
…r and not leak types (#25610)

type validateDOMNesting
move `isHostResourceType` to ReactDOMHostConfig
type `AncestorInfo`
refactor `resourceFormOnly` into `ancestorInfo.containerTagInScope`
provide hostContext from reconciler
  • Loading branch information
gnoff authored Nov 2, 2022
1 parent 8e69bc4 commit 4ea063b
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 172 deletions.
128 changes: 2 additions & 126 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,8 @@ import {
getResourcesFromRoot,
markNodeAsResource,
} from './ReactDOMComponentTree';
import {HTML_NAMESPACE} from '../shared/DOMNamespaces';
import {
getCurrentRootHostContainer,
getHostContext,
} from 'react-reconciler/src/ReactFiberHostContext';
import {getResourceFormOnly} from './validateDOMNesting';
import {getNamespace} from './ReactDOMHostConfig';
import {SVG_NAMESPACE} from '../shared/DOMNamespaces';
import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';

// The resource types we support. currently they match the form for the as argument.
// In the future this may need to change, especially when modules / scripts are supported
Expand Down Expand Up @@ -1426,124 +1420,6 @@ function insertResourceInstanceBefore(
}
}

export function isHostResourceType(type: string, props: Props): boolean {
let resourceFormOnly: boolean;
let namespace: string;
if (__DEV__) {
const hostContext = getHostContext();
resourceFormOnly = getResourceFormOnly(hostContext);
namespace = getNamespace(hostContext);
}
switch (type) {
case 'base':
case 'meta': {
return true;
}
case 'title': {
const hostContext = getHostContext();
return getNamespace(hostContext) !== SVG_NAMESPACE;
}
case 'link': {
const {onLoad, onError} = props;
if (onLoad || onError) {
if (__DEV__) {
if (resourceFormOnly) {
console.error(
'Cannot render a <link> with onLoad or onError listeners outside the main document.' +
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
' somewhere in the <body>.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>.' +
' Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor.',
);
}
}
return false;
}
switch (props.rel) {
case 'stylesheet': {
const {href, precedence, disabled} = props;
if (__DEV__) {
validateLinkPropsForStyleResource(props);
if (typeof precedence !== 'string') {
if (resourceFormOnly) {
console.error(
'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
' Consider adding precedence="default" or moving it into the root <head> tag.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
' Consider adding precedence="default" or moving it above the <svg> ancestor.',
);
}
}
}
return (
typeof href === 'string' &&
typeof precedence === 'string' &&
disabled == null
);
}
default: {
const {rel, href} = props;
return typeof href === 'string' && typeof rel === 'string';
}
}
}
case 'script': {
// We don't validate because it is valid to use async with onLoad/onError unlike combining
// precedence with these for style resources
const {src, async, onLoad, onError} = props;
if (__DEV__) {
if (async !== true) {
if (resourceFormOnly) {
console.error(
'Cannot render a sync or defer <script> outside the main document without knowing its order.' +
' Try adding async="" or moving it into the root <head> tag.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a sync or defer <script> as a descendent of an <svg> element.' +
' Try adding async="" or moving it above the ancestor <svg> element.',
);
}
} else if (onLoad || onError) {
if (resourceFormOnly) {
console.error(
'Cannot render a <script> with onLoad or onError listeners outside the main document.' +
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
' somewhere in the <body>.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element.' +
' Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element.',
);
}
}
}
return (async: any) && typeof src === 'string' && !onLoad && !onError;
}
case 'noscript':
case 'template':
case 'style': {
if (__DEV__) {
if (resourceFormOnly) {
console.error(
'Cannot render <%s> outside the main document. Try moving it into the root <head> tag.',
type,
);
}
}
return false;
}
}
return false;
}

// When passing user input into querySelector(All) the embedded string must not alter
// the semantics of the query. This escape function is safe to use when we know the
// provided value is going to be wrapped in double quotes as part of an attribute selector
Expand Down
157 changes: 137 additions & 20 deletions packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
ObserveVisibleRectsCallback,
} from 'react-reconciler/src/ReactTestSelectors';
import type {ReactScopeInstance} from 'shared/ReactTypes';
import type {AncestorInfoDev} from './validateDOMNesting';

import {
precacheFiberNode,
Expand Down Expand Up @@ -47,13 +48,13 @@ import {
} from './ReactDOMComponent';
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
import setTextContent from './setTextContent';
import {validateDOMNesting, updatedAncestorInfo} from './validateDOMNesting';
import {validateDOMNesting, updatedAncestorInfoDev} from './validateDOMNesting';
import {
isEnabled as ReactBrowserEventEmitterIsEnabled,
setEnabled as ReactBrowserEventEmitterSetEnabled,
getEventPriority,
} from '../events/ReactDOMEventListener';
import {getChildNamespace} from '../shared/DOMNamespaces';
import {getChildNamespace, SVG_NAMESPACE} from '../shared/DOMNamespaces';
import {
ELEMENT_NODE,
TEXT_NODE,
Expand Down Expand Up @@ -89,8 +90,8 @@ import {
prepareToRenderResources,
cleanupAfterRenderResources,
clearRootResources,
isHostResourceType,
} from './ReactDOMFloatClient';
import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';

export type Type = string;
export type Props = {
Expand All @@ -107,6 +108,9 @@ export type Props = {
top?: null | number,
...
};
type RawProps = {
[string]: mixed,
};
export type EventTargetChildElement = {
type: string,
props: null | {
Expand Down Expand Up @@ -136,8 +140,7 @@ export type HydratableInstance = Instance | TextInstance | SuspenseInstance;
export type PublicInstance = Element | Text;
type HostContextDev = {
namespace: string,
ancestorInfo: mixed,
...
ancestorInfo: AncestorInfoDev,
};
type HostContextProd = string;
export type HostContext = HostContextDev | HostContextProd;
Expand Down Expand Up @@ -193,7 +196,7 @@ export function getRootHostContext(
}
if (__DEV__) {
const validatedTag = type.toLowerCase();
const ancestorInfo = updatedAncestorInfo(null, validatedTag);
const ancestorInfo = updatedAncestorInfoDev(null, validatedTag);
return {namespace, ancestorInfo};
}
return namespace;
Expand All @@ -206,7 +209,7 @@ export function getChildHostContext(
if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
const ancestorInfo = updatedAncestorInfo(
const ancestorInfo = updatedAncestorInfoDev(
parentHostContextDev.ancestorInfo,
type,
);
Expand All @@ -220,16 +223,6 @@ export function getPublicInstance(instance: Instance): Instance {
return instance;
}

export function getNamespace(hostContext: HostContext): string {
if (__DEV__) {
const hostContextDev: HostContextDev = (hostContext: any);
return hostContextDev.namespace;
} else {
const hostContextProd: HostContextProd = (hostContext: any);
return hostContextProd;
}
}

export function prepareForCommit(containerInfo: Container): Object | null {
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
selectionInformation = getSelectionInformation();
Expand Down Expand Up @@ -287,7 +280,7 @@ export function createInstance(
typeof props.children === 'number'
) {
const string = '' + props.children;
const ownAncestorInfo = updatedAncestorInfo(
const ownAncestorInfo = updatedAncestorInfoDev(
hostContextDev.ancestorInfo,
type,
);
Expand Down Expand Up @@ -350,7 +343,7 @@ export function prepareUpdate(
typeof newProps.children === 'number')
) {
const string = '' + newProps.children;
const ownAncestorInfo = updatedAncestorInfo(
const ownAncestorInfo = updatedAncestorInfoDev(
hostContextDev.ancestorInfo,
type,
);
Expand Down Expand Up @@ -1573,7 +1566,131 @@ export function requestPostPaintCallback(callback: (time: number) => void) {

export const supportsResources = true;

export {isHostResourceType};
export function isHostResourceType(
type: string,
props: RawProps,
hostContext: HostContext,
): boolean {
let outsideHostContainerContext: boolean;
let namespace: string;
if (__DEV__) {
const hostContextDev: HostContextDev = (hostContext: any);
// We can only render resources when we are not within the host container context
outsideHostContainerContext = !hostContextDev.ancestorInfo
.containerTagInScope;
namespace = hostContextDev.namespace;
} else {
const hostContextProd: HostContextProd = (hostContext: any);
namespace = hostContextProd;
}
switch (type) {
case 'base':
case 'meta': {
return true;
}
case 'title': {
return namespace !== SVG_NAMESPACE;
}
case 'link': {
const {onLoad, onError} = props;
if (onLoad || onError) {
if (__DEV__) {
if (outsideHostContainerContext) {
console.error(
'Cannot render a <link> with onLoad or onError listeners outside the main document.' +
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
' somewhere in the <body>.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>.' +
' Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor.',
);
}
}
return false;
}
switch (props.rel) {
case 'stylesheet': {
const {href, precedence, disabled} = props;
if (__DEV__) {
validateLinkPropsForStyleResource(props);
if (typeof precedence !== 'string') {
if (outsideHostContainerContext) {
console.error(
'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
' Consider adding precedence="default" or moving it into the root <head> tag.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
' Consider adding precedence="default" or moving it above the <svg> ancestor.',
);
}
}
}
return (
typeof href === 'string' &&
typeof precedence === 'string' &&
disabled == null
);
}
default: {
const {rel, href} = props;
return typeof href === 'string' && typeof rel === 'string';
}
}
}
case 'script': {
// We don't validate because it is valid to use async with onLoad/onError unlike combining
// precedence with these for style resources
const {src, async, onLoad, onError} = props;
if (__DEV__) {
if (async !== true) {
if (outsideHostContainerContext) {
console.error(
'Cannot render a sync or defer <script> outside the main document without knowing its order.' +
' Try adding async="" or moving it into the root <head> tag.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a sync or defer <script> as a descendent of an <svg> element.' +
' Try adding async="" or moving it above the ancestor <svg> element.',
);
}
} else if (onLoad || onError) {
if (outsideHostContainerContext) {
console.error(
'Cannot render a <script> with onLoad or onError listeners outside the main document.' +
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
' somewhere in the <body>.',
);
} else if (namespace === SVG_NAMESPACE) {
console.error(
'Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element.' +
' Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element.',
);
}
}
}
return (async: any) && typeof src === 'string' && !onLoad && !onError;
}
case 'noscript':
case 'template':
case 'style': {
if (__DEV__) {
if (outsideHostContainerContext) {
console.error(
'Cannot render <%s> outside the main document. Try moving it into the root <head> tag.',
type,
);
}
}
return false;
}
}
return false;
}

export function prepareRendererToRender(rootContainer: Container) {
if (enableFloat) {
Expand Down
Loading

0 comments on commit 4ea063b

Please sign in to comment.