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

refactor isHostResourceType to not receive the context from reconciler and not leak types #25610

Merged
merged 7 commits into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
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 {AncestorInfoDevType} 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: AncestorInfoDevType,
};
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