From e571a7dee0402c83600515c1ecd1dc47773b86a4 Mon Sep 17 00:00:00 2001 From: Jan Martin Date: Tue, 27 Jan 2026 15:10:36 -0800 Subject: [PATCH 1/5] Upgrade enableTrustedTypesIntegration to __EXPERIMENTAL__ Sets the dynamic fork to VARIANT to ensure all tests exercise that code path and enables the integration in experimental releases. --- .../__tests__/trustedTypes-test.internal.js | 53 ++++++++++++++----- packages/shared/ReactFeatureFlags.js | 2 +- .../forks/ReactFeatureFlags.www-dynamic.js | 3 +- 3 files changed, 41 insertions(+), 17 deletions(-) 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 5a43a9ec2f0..da4181d0a95 100644 --- a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js +++ b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js @@ -34,7 +34,6 @@ describe('when Trusted Types are available in global object', () => { isScriptURL: () => false, }; ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableTrustedTypesIntegration = true; React = require('react'); ReactDOMClient = require('react-dom/client'); ({act, assertConsoleErrorDev} = require('internal-test-utils')); @@ -118,7 +117,11 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('data-foo'); // Ensure it didn't get stringified when passed to a DOM sink: - expect(setAttributeCalls[0][2]).toBe(ttObject1); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + expect(setAttributeCalls[0][2]).toBe(ttObject1); + } else { + expect(setAttributeCalls[0][2]).toBe('Hi'); + } setAttributeCalls.length = 0; await act(() => { @@ -129,7 +132,11 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('data-foo'); // Ensure it didn't get stringified when passed to a DOM sink: - expect(setAttributeCalls[0][2]).toBe(ttObject2); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + expect(setAttributeCalls[0][2]).toBe(ttObject2); + } else { + expect(setAttributeCalls[0][2]).toBe('Bye'); + } } finally { Element.prototype.setAttribute = setAttribute; } @@ -153,7 +160,11 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('class'); // Ensure it didn't get stringified when passed to a DOM sink: - expect(setAttributeCalls[0][2]).toBe(ttObject1); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + expect(setAttributeCalls[0][2]).toBe(ttObject1); + } else { + expect(setAttributeCalls[0][2]).toBe('Hi'); + } setAttributeCalls.length = 0; await act(() => { @@ -164,7 +175,11 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('class'); // Ensure it didn't get stringified when passed to a DOM sink: - expect(setAttributeCalls[0][2]).toBe(ttObject2); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + expect(setAttributeCalls[0][2]).toBe(ttObject2); + } else { + expect(setAttributeCalls[0][2]).toBe('Bye'); + } } finally { Element.prototype.setAttribute = setAttribute; } @@ -189,7 +204,11 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink'); expect(setAttributeNSCalls[0][2]).toBe('xlink:href'); // Ensure it didn't get stringified when passed to a DOM sink: - expect(setAttributeNSCalls[0][3]).toBe(ttObject1); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + expect(setAttributeNSCalls[0][3]).toBe(ttObject1); + } else { + expect(setAttributeNSCalls[0][3]).toBe('Hi'); + } setAttributeNSCalls.length = 0; await act(() => { @@ -201,7 +220,11 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink'); expect(setAttributeNSCalls[0][2]).toBe('xlink:href'); // Ensure it didn't get stringified when passed to a DOM sink: - expect(setAttributeNSCalls[0][3]).toBe(ttObject2); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + expect(setAttributeNSCalls[0][3]).toBe(ttObject2); + } else { + expect(setAttributeNSCalls[0][3]).toBe('Bye'); + } } finally { Element.prototype.setAttributeNS = setAttributeNS; } @@ -212,13 +235,15 @@ describe('when Trusted Types are available in global object', () => { await act(() => { root.render(); }); - assertConsoleErrorDev([ - 'Encountered a script tag while rendering React component. ' + - 'Scripts inside React components are never executed when rendering ' + - 'on the client. Consider using template tag instead ' + - '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' + - ' in script (at **)', - ]); + if (ReactFeatureFlags.enableTrustedTypesIntegration) { + assertConsoleErrorDev([ + 'Encountered a script tag while rendering React component. ' + + 'Scripts inside React components are never executed when rendering ' + + 'on the client. Consider using template tag instead ' + + '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' + + ' in script (at **)', + ]); + } // check that the warning is printed only once await act(() => { diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 6542fc9da6a..b66cc65a60f 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -205,7 +205,7 @@ export const disableLegacyMode: boolean = true; // in open source, but www codebase still relies on it. Need to remove. export const disableCommentsAsDOMContainers: boolean = true; -export const enableTrustedTypesIntegration: boolean = false; +export const enableTrustedTypesIntegration: boolean = __EXPERIMENTAL__; // Prevent the value and checked attributes from syncing with their related // DOM properties diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index a8d829ee3e3..3a6c456c928 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -35,12 +35,11 @@ export const enableScrollEndPolyfill: boolean = __VARIANT__; export const enableFragmentRefs: boolean = __VARIANT__; export const enableFragmentRefsScrollIntoView: boolean = __VARIANT__; export const enableAsyncDebugInfo: boolean = __VARIANT__; - export const enableInternalInstanceMap: boolean = __VARIANT__; +export const enableTrustedTypesIntegration: boolean = __VARIANT__; // TODO: These flags are hard-coded to the default values used in open source. // Update the tests so that they pass in either mode, then set these // to __VARIANT__. -export const enableTrustedTypesIntegration: boolean = false; // You probably *don't* want to add more hardcoded ones. // Instead, try to add them above with the __VARIANT__ value. From e3709b311888f4d5ca6aa230f004035a9c94265c Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 27 Jan 2026 20:51:10 -0500 Subject: [PATCH 2/5] use gate() --- .../__tests__/trustedTypes-test.internal.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) 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 da4181d0a95..06744581ae9 100644 --- a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js +++ b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js @@ -12,7 +12,6 @@ describe('when Trusted Types are available in global object', () => { let React; let ReactDOMClient; - let ReactFeatureFlags; let act; let assertConsoleErrorDev; let container; @@ -33,7 +32,6 @@ describe('when Trusted Types are available in global object', () => { isScript: () => false, isScriptURL: () => false, }; - ReactFeatureFlags = require('shared/ReactFeatureFlags'); React = require('react'); ReactDOMClient = require('react-dom/client'); ({act, assertConsoleErrorDev} = require('internal-test-utils')); @@ -117,7 +115,7 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('data-foo'); // Ensure it didn't get stringified when passed to a DOM sink: - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { expect(setAttributeCalls[0][2]).toBe(ttObject1); } else { expect(setAttributeCalls[0][2]).toBe('Hi'); @@ -132,7 +130,7 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('data-foo'); // Ensure it didn't get stringified when passed to a DOM sink: - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { expect(setAttributeCalls[0][2]).toBe(ttObject2); } else { expect(setAttributeCalls[0][2]).toBe('Bye'); @@ -160,7 +158,7 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('class'); // Ensure it didn't get stringified when passed to a DOM sink: - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { expect(setAttributeCalls[0][2]).toBe(ttObject1); } else { expect(setAttributeCalls[0][2]).toBe('Hi'); @@ -175,7 +173,7 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeCalls[0][0]).toBe(container.firstChild); expect(setAttributeCalls[0][1]).toBe('class'); // Ensure it didn't get stringified when passed to a DOM sink: - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { expect(setAttributeCalls[0][2]).toBe(ttObject2); } else { expect(setAttributeCalls[0][2]).toBe('Bye'); @@ -204,7 +202,7 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink'); expect(setAttributeNSCalls[0][2]).toBe('xlink:href'); // Ensure it didn't get stringified when passed to a DOM sink: - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { expect(setAttributeNSCalls[0][3]).toBe(ttObject1); } else { expect(setAttributeNSCalls[0][3]).toBe('Hi'); @@ -220,7 +218,7 @@ describe('when Trusted Types are available in global object', () => { expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink'); expect(setAttributeNSCalls[0][2]).toBe('xlink:href'); // Ensure it didn't get stringified when passed to a DOM sink: - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { expect(setAttributeNSCalls[0][3]).toBe(ttObject2); } else { expect(setAttributeNSCalls[0][3]).toBe('Bye'); @@ -235,7 +233,7 @@ describe('when Trusted Types are available in global object', () => { await act(() => { root.render(); }); - if (ReactFeatureFlags.enableTrustedTypesIntegration) { + if (gate('enableTrustedTypesIntegration')) { assertConsoleErrorDev([ 'Encountered a script tag while rendering React component. ' + 'Scripts inside React components are never executed when rendering ' + From caca078e1a6c5fba3808aa17782c2457bb6d1140 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 27 Jan 2026 20:51:25 -0500 Subject: [PATCH 3/5] revert __EXPERIMENTAL__ --- packages/shared/ReactFeatureFlags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index b66cc65a60f..6542fc9da6a 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -205,7 +205,7 @@ export const disableLegacyMode: boolean = true; // in open source, but www codebase still relies on it. Need to remove. export const disableCommentsAsDOMContainers: boolean = true; -export const enableTrustedTypesIntegration: boolean = __EXPERIMENTAL__; +export const enableTrustedTypesIntegration: boolean = false; // Prevent the value and checked attributes from syncing with their related // DOM properties From 2c0c217b680e6633be18deaeb94e89022af862dd Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 27 Jan 2026 21:31:54 -0500 Subject: [PATCH 4/5] Add more gates --- .../src/__tests__/ReactDOMFloat-test.js | 16 ++++++++++++++++ .../react-dom/src/__tests__/ReactDOMForm-test.js | 12 +++++++++--- ...ReactDOMServerIntegrationUntrustedURL-test.js | 5 +++++ .../src/__tests__/ReactEmptyComponent-test.js | 14 ++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index ed3d3e08805..e2065efa69c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -608,6 +608,14 @@ describe('ReactDOMFloat', () => { '> '); + if (gate('enableTrustedTypesIntegration')) { + assertConsoleErrorDev([ + 'Encountered a script tag while rendering React component. ' + + 'Scripts inside React components are never executed when rendering on the client. ' + + 'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' + + ' in script (at **)\n' + + ' in TogglingComponent (at **)', + ]); + } + const container2 = document.createElement('div'); const root2 = ReactDOMClient.createRoot(container2); expect(() => { @@ -189,6 +202,7 @@ describe('ReactEmptyComponent', () => { 'mount SCRIPT', 'update undefined', ]); + expect(container2.innerHTML).toBe(''); }); it( From a84f095d69295d8b78a090a4d0105d1612938c9b Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 27 Jan 2026 21:49:51 -0500 Subject: [PATCH 5/5] Add more gates --- .../react-dom/src/__tests__/ReactDOMAttribute-test.js | 8 +++++++- packages/shared/CheckStringCoercion.js | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js index cd1d055c09d..63baa38c710 100644 --- a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js @@ -171,7 +171,13 @@ describe('ReactDOM unknown attribute', () => { const test = () => testUnknownAttributeAssignment(new TemporalLike(), null); - await expect(test).rejects.toThrowError(new TypeError('prod message')); + if (gate('enableTrustedTypesIntegration') && !__DEV__) { + // TODO: this still throws in DEV even though it's not toString'd in prod. + await expect(test).rejects.toThrowError('2020-01-01'); + } else { + await expect(test).rejects.toThrowError(new TypeError('prod message')); + } + assertConsoleErrorDev([ 'The provided `unknown` attribute is an unsupported type TemporalLike.' + ' This value must be coerced to a string before using it here.\n' + diff --git a/packages/shared/CheckStringCoercion.js b/packages/shared/CheckStringCoercion.js index a186d6755d9..0165d8b19c1 100644 --- a/packages/shared/CheckStringCoercion.js +++ b/packages/shared/CheckStringCoercion.js @@ -76,6 +76,8 @@ export function checkAttributeStringCoercion( attributeName: string, ): void | string { if (__DEV__) { + // TODO: for enableTrustedTypesIntegration we don't toString this + // so we shouldn't need the DEV warning. if (willCoercionThrow(value)) { console.error( 'The provided `%s` attribute is an unsupported type %s.' +