Skip to content
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
59 changes: 52 additions & 7 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export type Props = {
checked?: boolean,
defaultChecked?: boolean,
multiple?: boolean,
type?: string,
src?: string | Blob | MediaSource | MediaStream, // TODO: Response
srcSet?: string,
loading?: 'eager' | 'lazy',
Expand Down Expand Up @@ -217,7 +218,7 @@ export type Instance = Element;
export type TextInstance = Text;

type InstanceWithFragmentHandles = Instance & {
unstable_reactFragments?: Set<FragmentInstanceType>,
reactFragments?: Set<FragmentInstanceType>,
};

declare class ActivityInterface extends Comment {}
Expand Down Expand Up @@ -469,6 +470,44 @@ export function createHoistableInstance(
}

let didWarnScriptTags = false;
function isScriptDataBlock(props: Props): boolean {
const scriptType = props.type;
if (typeof scriptType !== 'string' || scriptType === '') {
return false;
}
const lower = scriptType.toLowerCase();
// Special non-MIME keywords recognized by the HTML spec
// TODO: May be fine to also not warn about having these types be parsed as "parser-inserted"
if (
lower === 'module' ||
lower === 'importmap' ||
lower === 'speculationrules'
) {
return false;
}
// JavaScript MIME types per https://mimesniff.spec.whatwg.org/#javascript-mime-type
switch (lower) {
case 'application/ecmascript':
case 'application/javascript':
case 'application/x-ecmascript':
case 'application/x-javascript':
case 'text/ecmascript':
case 'text/javascript':
case 'text/javascript1.0':
case 'text/javascript1.1':
case 'text/javascript1.2':
case 'text/javascript1.3':
case 'text/javascript1.4':
case 'text/javascript1.5':
case 'text/jscript':
case 'text/livescript':
case 'text/x-ecmascript':
case 'text/x-javascript':
return false;
}
// Any other non-empty type value means this is a data block
return true;
}
const warnedUnknownTags: {
[key: string]: boolean,
} = {
Expand Down Expand Up @@ -526,7 +565,13 @@ export function createInstance(
// set to true and it does not execute
const div = ownerDocument.createElement('div');
if (__DEV__) {
if (enableTrustedTypesIntegration && !didWarnScriptTags) {
if (
enableTrustedTypesIntegration &&
!didWarnScriptTags &&
// Data block scripts are not executed by UAs anyway so
// we don't need to warn: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-type
!isScriptDataBlock(props)
) {
console.error(
'Encountered a script tag while rendering React component. ' +
'Scripts inside React components are never executed when rendering ' +
Expand Down Expand Up @@ -3533,10 +3578,10 @@ function addFragmentHandleToInstance(
fragmentInstance: FragmentInstanceType,
): void {
if (enableFragmentRefsInstanceHandles) {
if (instance.unstable_reactFragments == null) {
instance.unstable_reactFragments = new Set();
if (instance.reactFragments == null) {
instance.reactFragments = new Set();
}
instance.unstable_reactFragments.add(fragmentInstance);
instance.reactFragments.add(fragmentInstance);
}
}

Expand Down Expand Up @@ -3602,8 +3647,8 @@ export function deleteChildFromFragmentInstance(
}
}
if (enableFragmentRefsInstanceHandles) {
if (instance.unstable_reactFragments != null) {
instance.unstable_reactFragments.delete(fragmentInstance);
if (instance.reactFragments != null) {
instance.reactFragments.delete(fragmentInstance);
}
}
}
Expand Down
26 changes: 9 additions & 17 deletions packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,26 +137,18 @@ describe('FragmentRefs', () => {
const childB = document.querySelector('#childB');
const childC = document.querySelector('#childC');

expect(childA.unstable_reactFragments.has(fragmentRef.current)).toBe(true);
expect(childB.unstable_reactFragments.has(fragmentRef.current)).toBe(true);
expect(childC.unstable_reactFragments.has(fragmentRef.current)).toBe(false);
expect(childA.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
true,
);
expect(childB.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
true,
);
expect(childC.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
true,
);
expect(childA.reactFragments.has(fragmentRef.current)).toBe(true);
expect(childB.reactFragments.has(fragmentRef.current)).toBe(true);
expect(childC.reactFragments.has(fragmentRef.current)).toBe(false);
expect(childA.reactFragments.has(fragmentParentRef.current)).toBe(true);
expect(childB.reactFragments.has(fragmentParentRef.current)).toBe(true);
expect(childC.reactFragments.has(fragmentParentRef.current)).toBe(true);

await act(() => root.render(<Test show={true} />));

const childD = document.querySelector('#childD');
expect(childD.unstable_reactFragments.has(fragmentRef.current)).toBe(false);
expect(childD.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
true,
);
expect(childD.reactFragments.has(fragmentRef.current)).toBe(false);
expect(childD.reactFragments.has(fragmentParentRef.current)).toBe(true);
});

describe('focus methods', () => {
Expand Down Expand Up @@ -1104,7 +1096,7 @@ describe('FragmentRefs', () => {
}
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const fragmentInstances = entry.target.unstable_reactFragments;
const fragmentInstances = entry.target.reactFragments;
if (fragmentInstances) {
Array.from(fragmentInstances).forEach(fInstance => {
const cbs = targetToCallbackMap.get(fInstance) || [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,24 @@ describe('when Trusted Types are available in global object', () => {
root.render(<script>alert("I am not executed")</script>);
});
});

it('should not warn when rendering a data block script tag', async () => {
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<script type="application/json">{'{"key": "value"}'}</script>,
);
});
});

it('should not warn when rendering a ld+json script tag', async () => {
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<script type="application/ld+json">
{'{"@context": "https://schema.org"}'}
</script>,
);
});
});
});
12 changes: 6 additions & 6 deletions packages/react-native-renderer/src/ReactFiberConfigFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export type TextInstance = {
export type HydratableInstance = Instance | TextInstance;
export type PublicInstance = ReactNativePublicInstance;
type PublicInstanceWithFragmentHandles = PublicInstance & {
unstable_reactFragments?: Set<FragmentInstanceType>,
reactFragments?: Set<FragmentInstanceType>,
};
export type Container = {
containerTag: number,
Expand Down Expand Up @@ -856,10 +856,10 @@ function addFragmentHandleToInstance(
fragmentInstance: FragmentInstanceType,
): void {
if (enableFragmentRefsInstanceHandles) {
if (instance.unstable_reactFragments == null) {
instance.unstable_reactFragments = new Set();
if (instance.reactFragments == null) {
instance.reactFragments = new Set();
}
instance.unstable_reactFragments.add(fragmentInstance);
instance.reactFragments.add(fragmentInstance);
}
}

Expand Down Expand Up @@ -924,8 +924,8 @@ export function deleteChildFromFragmentInstance(
instance,
): any): PublicInstanceWithFragmentHandles);
if (enableFragmentRefsInstanceHandles) {
if (publicInstance.unstable_reactFragments != null) {
publicInstance.unstable_reactFragments.delete(fragmentInstance);
if (publicInstance.reactFragments != null) {
publicInstance.reactFragments.delete(fragmentInstance);
}
}
}
Expand Down
Loading