From e6f1c33acf81d9865863ab149d24726f43a56db0 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Tue, 3 Mar 2026 19:38:41 +0100 Subject: [PATCH 1/2] [Fiber] Don't warn when rendering data block scripts (#35953) --- .../src/client/ReactFiberConfigDOM.js | 47 ++++++++++++++++++- .../__tests__/trustedTypes-test.internal.js | 20 ++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 94d37cfc902d..fc1039faaa0c 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -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', @@ -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, } = { @@ -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 ' + diff --git a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js index 06744581ae95..2d7fa85880e7 100644 --- a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js +++ b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js @@ -248,4 +248,24 @@ describe('when Trusted Types are available in global object', () => { root.render(); }); }); + + it('should not warn when rendering a data block script tag', async () => { + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( + , + ); + }); + }); + + it('should not warn when rendering a ld+json script tag', async () => { + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( + , + ); + }); + }); }); From 9c0323e2cf9be543d6eaa44419598af56922603f Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Tue, 3 Mar 2026 15:44:11 -0500 Subject: [PATCH 2/2] Stabilize reactFragments host node handle (#35642) https://github.com/facebook/react/pull/34935 Introduced `unstable_reactFragments` handle on DOM nodes to enable caching of Observers. This has been tested in production and is stable so it can be rolled out with the Fragment Refs feature. --- .../src/client/ReactFiberConfigDOM.js | 12 ++++----- .../__tests__/ReactDOMFragmentRefs-test.js | 26 +++++++------------ .../src/ReactFiberConfigFabric.js | 12 ++++----- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index fc1039faaa0c..4cb4e8e4273a 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -218,7 +218,7 @@ export type Instance = Element; export type TextInstance = Text; type InstanceWithFragmentHandles = Instance & { - unstable_reactFragments?: Set, + reactFragments?: Set, }; declare class ActivityInterface extends Comment {} @@ -3578,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); } } @@ -3647,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); } } } diff --git a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js index 603af9072b6e..aed4f5252f84 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js @@ -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()); 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', () => { @@ -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) || []; diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 26f921bf4297..18c4eaddd9ea 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -124,7 +124,7 @@ export type TextInstance = { export type HydratableInstance = Instance | TextInstance; export type PublicInstance = ReactNativePublicInstance; type PublicInstanceWithFragmentHandles = PublicInstance & { - unstable_reactFragments?: Set, + reactFragments?: Set, }; export type Container = { containerTag: number, @@ -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); } } @@ -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); } } }