Skip to content

Commit 4ea063b

Browse files
authored
refactor isHostResourceType to not receive the context from reconciler and not leak types (#25610)
type validateDOMNesting move `isHostResourceType` to ReactDOMHostConfig type `AncestorInfo` refactor `resourceFormOnly` into `ancestorInfo.containerTagInScope` provide hostContext from reconciler
1 parent 8e69bc4 commit 4ea063b

File tree

5 files changed

+206
-172
lines changed

5 files changed

+206
-172
lines changed

packages/react-dom-bindings/src/client/ReactDOMFloatClient.js

+2-126
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,8 @@ import {
2828
getResourcesFromRoot,
2929
markNodeAsResource,
3030
} from './ReactDOMComponentTree';
31-
import {HTML_NAMESPACE} from '../shared/DOMNamespaces';
32-
import {
33-
getCurrentRootHostContainer,
34-
getHostContext,
35-
} from 'react-reconciler/src/ReactFiberHostContext';
36-
import {getResourceFormOnly} from './validateDOMNesting';
37-
import {getNamespace} from './ReactDOMHostConfig';
38-
import {SVG_NAMESPACE} from '../shared/DOMNamespaces';
31+
import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
32+
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
3933

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

1429-
export function isHostResourceType(type: string, props: Props): boolean {
1430-
let resourceFormOnly: boolean;
1431-
let namespace: string;
1432-
if (__DEV__) {
1433-
const hostContext = getHostContext();
1434-
resourceFormOnly = getResourceFormOnly(hostContext);
1435-
namespace = getNamespace(hostContext);
1436-
}
1437-
switch (type) {
1438-
case 'base':
1439-
case 'meta': {
1440-
return true;
1441-
}
1442-
case 'title': {
1443-
const hostContext = getHostContext();
1444-
return getNamespace(hostContext) !== SVG_NAMESPACE;
1445-
}
1446-
case 'link': {
1447-
const {onLoad, onError} = props;
1448-
if (onLoad || onError) {
1449-
if (__DEV__) {
1450-
if (resourceFormOnly) {
1451-
console.error(
1452-
'Cannot render a <link> with onLoad or onError listeners outside the main document.' +
1453-
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1454-
' somewhere in the <body>.',
1455-
);
1456-
} else if (namespace === SVG_NAMESPACE) {
1457-
console.error(
1458-
'Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>.' +
1459-
' Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor.',
1460-
);
1461-
}
1462-
}
1463-
return false;
1464-
}
1465-
switch (props.rel) {
1466-
case 'stylesheet': {
1467-
const {href, precedence, disabled} = props;
1468-
if (__DEV__) {
1469-
validateLinkPropsForStyleResource(props);
1470-
if (typeof precedence !== 'string') {
1471-
if (resourceFormOnly) {
1472-
console.error(
1473-
'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
1474-
' Consider adding precedence="default" or moving it into the root <head> tag.',
1475-
);
1476-
} else if (namespace === SVG_NAMESPACE) {
1477-
console.error(
1478-
'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
1479-
' Consider adding precedence="default" or moving it above the <svg> ancestor.',
1480-
);
1481-
}
1482-
}
1483-
}
1484-
return (
1485-
typeof href === 'string' &&
1486-
typeof precedence === 'string' &&
1487-
disabled == null
1488-
);
1489-
}
1490-
default: {
1491-
const {rel, href} = props;
1492-
return typeof href === 'string' && typeof rel === 'string';
1493-
}
1494-
}
1495-
}
1496-
case 'script': {
1497-
// We don't validate because it is valid to use async with onLoad/onError unlike combining
1498-
// precedence with these for style resources
1499-
const {src, async, onLoad, onError} = props;
1500-
if (__DEV__) {
1501-
if (async !== true) {
1502-
if (resourceFormOnly) {
1503-
console.error(
1504-
'Cannot render a sync or defer <script> outside the main document without knowing its order.' +
1505-
' Try adding async="" or moving it into the root <head> tag.',
1506-
);
1507-
} else if (namespace === SVG_NAMESPACE) {
1508-
console.error(
1509-
'Cannot render a sync or defer <script> as a descendent of an <svg> element.' +
1510-
' Try adding async="" or moving it above the ancestor <svg> element.',
1511-
);
1512-
}
1513-
} else if (onLoad || onError) {
1514-
if (resourceFormOnly) {
1515-
console.error(
1516-
'Cannot render a <script> with onLoad or onError listeners outside the main document.' +
1517-
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1518-
' somewhere in the <body>.',
1519-
);
1520-
} else if (namespace === SVG_NAMESPACE) {
1521-
console.error(
1522-
'Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element.' +
1523-
' Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element.',
1524-
);
1525-
}
1526-
}
1527-
}
1528-
return (async: any) && typeof src === 'string' && !onLoad && !onError;
1529-
}
1530-
case 'noscript':
1531-
case 'template':
1532-
case 'style': {
1533-
if (__DEV__) {
1534-
if (resourceFormOnly) {
1535-
console.error(
1536-
'Cannot render <%s> outside the main document. Try moving it into the root <head> tag.',
1537-
type,
1538-
);
1539-
}
1540-
}
1541-
return false;
1542-
}
1543-
}
1544-
return false;
1545-
}
1546-
15471423
// When passing user input into querySelector(All) the embedded string must not alter
15481424
// the semantics of the query. This escape function is safe to use when we know the
15491425
// provided value is going to be wrapped in double quotes as part of an attribute selector

packages/react-dom-bindings/src/client/ReactDOMHostConfig.js

+137-20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
ObserveVisibleRectsCallback,
1717
} from 'react-reconciler/src/ReactTestSelectors';
1818
import type {ReactScopeInstance} from 'shared/ReactTypes';
19+
import type {AncestorInfoDev} from './validateDOMNesting';
1920

2021
import {
2122
precacheFiberNode,
@@ -47,13 +48,13 @@ import {
4748
} from './ReactDOMComponent';
4849
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
4950
import setTextContent from './setTextContent';
50-
import {validateDOMNesting, updatedAncestorInfo} from './validateDOMNesting';
51+
import {validateDOMNesting, updatedAncestorInfoDev} from './validateDOMNesting';
5152
import {
5253
isEnabled as ReactBrowserEventEmitterIsEnabled,
5354
setEnabled as ReactBrowserEventEmitterSetEnabled,
5455
getEventPriority,
5556
} from '../events/ReactDOMEventListener';
56-
import {getChildNamespace} from '../shared/DOMNamespaces';
57+
import {getChildNamespace, SVG_NAMESPACE} from '../shared/DOMNamespaces';
5758
import {
5859
ELEMENT_NODE,
5960
TEXT_NODE,
@@ -89,8 +90,8 @@ import {
8990
prepareToRenderResources,
9091
cleanupAfterRenderResources,
9192
clearRootResources,
92-
isHostResourceType,
9393
} from './ReactDOMFloatClient';
94+
import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';
9495

9596
export type Type = string;
9697
export type Props = {
@@ -107,6 +108,9 @@ export type Props = {
107108
top?: null | number,
108109
...
109110
};
111+
type RawProps = {
112+
[string]: mixed,
113+
};
110114
export type EventTargetChildElement = {
111115
type: string,
112116
props: null | {
@@ -136,8 +140,7 @@ export type HydratableInstance = Instance | TextInstance | SuspenseInstance;
136140
export type PublicInstance = Element | Text;
137141
type HostContextDev = {
138142
namespace: string,
139-
ancestorInfo: mixed,
140-
...
143+
ancestorInfo: AncestorInfoDev,
141144
};
142145
type HostContextProd = string;
143146
export type HostContext = HostContextDev | HostContextProd;
@@ -193,7 +196,7 @@ export function getRootHostContext(
193196
}
194197
if (__DEV__) {
195198
const validatedTag = type.toLowerCase();
196-
const ancestorInfo = updatedAncestorInfo(null, validatedTag);
199+
const ancestorInfo = updatedAncestorInfoDev(null, validatedTag);
197200
return {namespace, ancestorInfo};
198201
}
199202
return namespace;
@@ -206,7 +209,7 @@ export function getChildHostContext(
206209
if (__DEV__) {
207210
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
208211
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
209-
const ancestorInfo = updatedAncestorInfo(
212+
const ancestorInfo = updatedAncestorInfoDev(
210213
parentHostContextDev.ancestorInfo,
211214
type,
212215
);
@@ -220,16 +223,6 @@ export function getPublicInstance(instance: Instance): Instance {
220223
return instance;
221224
}
222225

223-
export function getNamespace(hostContext: HostContext): string {
224-
if (__DEV__) {
225-
const hostContextDev: HostContextDev = (hostContext: any);
226-
return hostContextDev.namespace;
227-
} else {
228-
const hostContextProd: HostContextProd = (hostContext: any);
229-
return hostContextProd;
230-
}
231-
}
232-
233226
export function prepareForCommit(containerInfo: Container): Object | null {
234227
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
235228
selectionInformation = getSelectionInformation();
@@ -287,7 +280,7 @@ export function createInstance(
287280
typeof props.children === 'number'
288281
) {
289282
const string = '' + props.children;
290-
const ownAncestorInfo = updatedAncestorInfo(
283+
const ownAncestorInfo = updatedAncestorInfoDev(
291284
hostContextDev.ancestorInfo,
292285
type,
293286
);
@@ -350,7 +343,7 @@ export function prepareUpdate(
350343
typeof newProps.children === 'number')
351344
) {
352345
const string = '' + newProps.children;
353-
const ownAncestorInfo = updatedAncestorInfo(
346+
const ownAncestorInfo = updatedAncestorInfoDev(
354347
hostContextDev.ancestorInfo,
355348
type,
356349
);
@@ -1573,7 +1566,131 @@ export function requestPostPaintCallback(callback: (time: number) => void) {
15731566

15741567
export const supportsResources = true;
15751568

1576-
export {isHostResourceType};
1569+
export function isHostResourceType(
1570+
type: string,
1571+
props: RawProps,
1572+
hostContext: HostContext,
1573+
): boolean {
1574+
let outsideHostContainerContext: boolean;
1575+
let namespace: string;
1576+
if (__DEV__) {
1577+
const hostContextDev: HostContextDev = (hostContext: any);
1578+
// We can only render resources when we are not within the host container context
1579+
outsideHostContainerContext = !hostContextDev.ancestorInfo
1580+
.containerTagInScope;
1581+
namespace = hostContextDev.namespace;
1582+
} else {
1583+
const hostContextProd: HostContextProd = (hostContext: any);
1584+
namespace = hostContextProd;
1585+
}
1586+
switch (type) {
1587+
case 'base':
1588+
case 'meta': {
1589+
return true;
1590+
}
1591+
case 'title': {
1592+
return namespace !== SVG_NAMESPACE;
1593+
}
1594+
case 'link': {
1595+
const {onLoad, onError} = props;
1596+
if (onLoad || onError) {
1597+
if (__DEV__) {
1598+
if (outsideHostContainerContext) {
1599+
console.error(
1600+
'Cannot render a <link> with onLoad or onError listeners outside the main document.' +
1601+
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1602+
' somewhere in the <body>.',
1603+
);
1604+
} else if (namespace === SVG_NAMESPACE) {
1605+
console.error(
1606+
'Cannot render a <link> with onLoad or onError listeners as a descendent of <svg>.' +
1607+
' Try removing onLoad={...} and onError={...} or moving it above the <svg> ancestor.',
1608+
);
1609+
}
1610+
}
1611+
return false;
1612+
}
1613+
switch (props.rel) {
1614+
case 'stylesheet': {
1615+
const {href, precedence, disabled} = props;
1616+
if (__DEV__) {
1617+
validateLinkPropsForStyleResource(props);
1618+
if (typeof precedence !== 'string') {
1619+
if (outsideHostContainerContext) {
1620+
console.error(
1621+
'Cannot render a <link rel="stylesheet" /> outside the main document without knowing its precedence.' +
1622+
' Consider adding precedence="default" or moving it into the root <head> tag.',
1623+
);
1624+
} else if (namespace === SVG_NAMESPACE) {
1625+
console.error(
1626+
'Cannot render a <link rel="stylesheet" /> as a descendent of an <svg> element without knowing its precedence.' +
1627+
' Consider adding precedence="default" or moving it above the <svg> ancestor.',
1628+
);
1629+
}
1630+
}
1631+
}
1632+
return (
1633+
typeof href === 'string' &&
1634+
typeof precedence === 'string' &&
1635+
disabled == null
1636+
);
1637+
}
1638+
default: {
1639+
const {rel, href} = props;
1640+
return typeof href === 'string' && typeof rel === 'string';
1641+
}
1642+
}
1643+
}
1644+
case 'script': {
1645+
// We don't validate because it is valid to use async with onLoad/onError unlike combining
1646+
// precedence with these for style resources
1647+
const {src, async, onLoad, onError} = props;
1648+
if (__DEV__) {
1649+
if (async !== true) {
1650+
if (outsideHostContainerContext) {
1651+
console.error(
1652+
'Cannot render a sync or defer <script> outside the main document without knowing its order.' +
1653+
' Try adding async="" or moving it into the root <head> tag.',
1654+
);
1655+
} else if (namespace === SVG_NAMESPACE) {
1656+
console.error(
1657+
'Cannot render a sync or defer <script> as a descendent of an <svg> element.' +
1658+
' Try adding async="" or moving it above the ancestor <svg> element.',
1659+
);
1660+
}
1661+
} else if (onLoad || onError) {
1662+
if (outsideHostContainerContext) {
1663+
console.error(
1664+
'Cannot render a <script> with onLoad or onError listeners outside the main document.' +
1665+
' Try removing onLoad={...} and onError={...} or moving it into the root <head> tag or' +
1666+
' somewhere in the <body>.',
1667+
);
1668+
} else if (namespace === SVG_NAMESPACE) {
1669+
console.error(
1670+
'Cannot render a <script> with onLoad or onError listeners as a descendent of an <svg> element.' +
1671+
' Try removing onLoad={...} and onError={...} or moving it above the ancestor <svg> element.',
1672+
);
1673+
}
1674+
}
1675+
}
1676+
return (async: any) && typeof src === 'string' && !onLoad && !onError;
1677+
}
1678+
case 'noscript':
1679+
case 'template':
1680+
case 'style': {
1681+
if (__DEV__) {
1682+
if (outsideHostContainerContext) {
1683+
console.error(
1684+
'Cannot render <%s> outside the main document. Try moving it into the root <head> tag.',
1685+
type,
1686+
);
1687+
}
1688+
}
1689+
return false;
1690+
}
1691+
}
1692+
return false;
1693+
}
15771694

15781695
export function prepareRendererToRender(rootContainer: Container) {
15791696
if (enableFloat) {

0 commit comments

Comments
 (0)