[flags] make `enableTrustedTypesIntegration` dynamic (#35646)
Co-authored-by: Rick Hanlon <rickhanlonii@meta.com>
Jan Olaf Martin committed
Jan 28, 2026 at 10:15 UTC
d4d099f05bead14cc78787f97f005b00feae56f9
8 files changed
+93
-22
packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
+7
-1
@@ -171,7 +171,13 @@ describe('ReactDOM unknown attribute', () => {
171
const test = () =>
172
testUnknownAttributeAssignment(new TemporalLike(), null);
173
174
- await expect(test).rejects.toThrowError(new TypeError('prod message'));
174
+ if (gate('enableTrustedTypesIntegration') && !__DEV__) {
175
+ // TODO: this still throws in DEV even though it's not toString'd in prod.
176
+ await expect(test).rejects.toThrowError('2020-01-01');
177
+ } else {
178
+ await expect(test).rejects.toThrowError(new TypeError('prod message'));
179
+ }
180
+
181
assertConsoleErrorDev([
182
'The provided `unknown` attribute is an unsupported type TemporalLike.' +
183
' This value must be coerced to a string before using it here.\n' +
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+16
@@ -602,6 +602,14 @@ describe('ReactDOMFloat', () => {
602
'> <script href="foo">\n' +
603
'\n' +
604
' in script (at **)',
605
+ ...(gate('enableTrustedTypesIntegration')
606
+ ? [
607
+ 'Encountered a script tag while rendering React component. ' +
608
+ 'Scripts inside React components are never executed when rendering on the client. ' +
609
+ 'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
610
+ ' in script (at **)',
611
+ ]
612
+ : []),
613
]);
614
615
root.render(
@@ -2745,6 +2753,14 @@ body {
2753
'> <script itemProp="foo">\n' +
2754
'\n' +
2755
' in script (at **)',
2756
+ ...(gate('enableTrustedTypesIntegration')
2757
+ ? [
2758
+ 'Encountered a script tag while rendering React component. ' +
2759
+ 'Scripts inside React components are never executed when rendering on the client. ' +
2760
+ 'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
2761
+ ' in script (at **)',
2762
+ ]
2763
+ : []),
2764
]);
2765
});
2766
packages/react-dom/src/__tests__/ReactDOMForm-test.js
+9
-3
@@ -2277,15 +2277,21 @@ describe('ReactDOMForm', () => {
2277
await submit(formRef.current);
2278
assertLog([actionFn]);
2279
2280
- // Everything else is toString-ed
2280
+ // Everything else is toString-ed, unless trusted types are enabled.
2281
class MyAction {
2282
toString() {
2283
return 'stringified action';
2284
}
2285
}
2286
- await act(() => root.render(<Form action={new MyAction()} />));
2286
+ const instance = new MyAction();
2287
+
2288
+ await act(() => root.render(<Form action={instance} />));
2289
await submit(formRef.current);
2288
- assertLog(['stringified action']);
2290
+ assertLog(
2291
+ gate('enableTrustedTypesIntegration')
2292
+ ? [instance]
2293
+ : ['stringified action'],
2294
+ );
2295
});
2296
2297
it('form actions should retain status when nested state changes', async () => {
packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.js
+5
@@ -212,6 +212,11 @@ describe('ReactDOMServerIntegration - Untrusted URLs', () => {
212
expectedToStringCalls *= 2;
213
}
214
215
+ if (gate('enableTrustedTypesIntegration') && render === clientCleanRender) {
216
+ // Trusted types does another toString.
217
+ expectedToStringCalls += 1;
218
+ }
219
+
220
let toStringCalls = 0;
221
const firstIsSafe = {
222
toString() {
packages/react-dom/src/__tests__/ReactEmptyComponent-test.js
+14
@@ -17,6 +17,7 @@ let TogglingComponent;
17
let act;
18
let Scheduler;
19
let assertLog;
20
+let assertConsoleErrorDev;
21
22
let container;
23
@@ -34,6 +35,7 @@ describe('ReactEmptyComponent', () => {
35
const InternalTestUtils = require('internal-test-utils');
36
act = InternalTestUtils.act;
37
assertLog = InternalTestUtils.assertLog;
38
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
39
40
container = document.createElement('div');
41
@@ -175,6 +177,17 @@ describe('ReactEmptyComponent', () => {
177
});
178
}).not.toThrow();
179
180
+ expect(container.innerHTML).toBe('<script></script>');
181
+ if (gate('enableTrustedTypesIntegration')) {
182
+ assertConsoleErrorDev([
183
+ 'Encountered a script tag while rendering React component. ' +
184
+ 'Scripts inside React components are never executed when rendering on the client. ' +
185
+ 'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
186
+ ' in script (at **)\n' +
187
+ ' in TogglingComponent (at **)',
188
+ ]);
189
+ }
190
+
191
const container2 = document.createElement('div');
192
const root2 = ReactDOMClient.createRoot(container2);
193
expect(() => {
@@ -189,6 +202,7 @@ describe('ReactEmptyComponent', () => {
202
'mount SCRIPT',
203
'update undefined',
204
]);
205
+ expect(container2.innerHTML).toBe('');
206
});
207
208
it(
packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
+39
-16
@@ -12,7 +12,6 @@
12
describe('when Trusted Types are available in global object', () => {
13
let React;
14
let ReactDOMClient;
15
- let ReactFeatureFlags;
15
let act;
16
let assertConsoleErrorDev;
17
let container;
@@ -33,8 +32,6 @@ describe('when Trusted Types are available in global object', () => {
32
isScript: () => false,
33
isScriptURL: () => false,
34
};
36
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
37
- ReactFeatureFlags.enableTrustedTypesIntegration = true;
35
React = require('react');
36
ReactDOMClient = require('react-dom/client');
37
({act, assertConsoleErrorDev} = require('internal-test-utils'));
@@ -118,7 +115,11 @@ describe('when Trusted Types are available in global object', () => {
115
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
116
expect(setAttributeCalls[0][1]).toBe('data-foo');
117
// Ensure it didn't get stringified when passed to a DOM sink:
121
- expect(setAttributeCalls[0][2]).toBe(ttObject1);
118
+ if (gate('enableTrustedTypesIntegration')) {
119
+ expect(setAttributeCalls[0][2]).toBe(ttObject1);
120
+ } else {
121
+ expect(setAttributeCalls[0][2]).toBe('<b>Hi</b>');
122
+ }
123
124
setAttributeCalls.length = 0;
125
await act(() => {
@@ -129,7 +130,11 @@ describe('when Trusted Types are available in global object', () => {
130
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
131
expect(setAttributeCalls[0][1]).toBe('data-foo');
132
// Ensure it didn't get stringified when passed to a DOM sink:
132
- expect(setAttributeCalls[0][2]).toBe(ttObject2);
133
+ if (gate('enableTrustedTypesIntegration')) {
134
+ expect(setAttributeCalls[0][2]).toBe(ttObject2);
135
+ } else {
136
+ expect(setAttributeCalls[0][2]).toBe('<b>Bye</b>');
137
+ }
138
} finally {
139
Element.prototype.setAttribute = setAttribute;
140
}
@@ -153,7 +158,11 @@ describe('when Trusted Types are available in global object', () => {
158
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
159
expect(setAttributeCalls[0][1]).toBe('class');
160
// Ensure it didn't get stringified when passed to a DOM sink:
156
- expect(setAttributeCalls[0][2]).toBe(ttObject1);
161
+ if (gate('enableTrustedTypesIntegration')) {
162
+ expect(setAttributeCalls[0][2]).toBe(ttObject1);
163
+ } else {
164
+ expect(setAttributeCalls[0][2]).toBe('<b>Hi</b>');
165
+ }
166
167
setAttributeCalls.length = 0;
168
await act(() => {
@@ -164,7 +173,11 @@ describe('when Trusted Types are available in global object', () => {
173
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
174
expect(setAttributeCalls[0][1]).toBe('class');
175
// Ensure it didn't get stringified when passed to a DOM sink:
167
- expect(setAttributeCalls[0][2]).toBe(ttObject2);
176
+ if (gate('enableTrustedTypesIntegration')) {
177
+ expect(setAttributeCalls[0][2]).toBe(ttObject2);
178
+ } else {
179
+ expect(setAttributeCalls[0][2]).toBe('<b>Bye</b>');
180
+ }
181
} finally {
182
Element.prototype.setAttribute = setAttribute;
183
}
@@ -189,7 +202,11 @@ describe('when Trusted Types are available in global object', () => {
202
expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink');
203
expect(setAttributeNSCalls[0][2]).toBe('xlink:href');
204
// Ensure it didn't get stringified when passed to a DOM sink:
192
- expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
205
+ if (gate('enableTrustedTypesIntegration')) {
206
+ expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
207
+ } else {
208
+ expect(setAttributeNSCalls[0][3]).toBe('<b>Hi</b>');
209
+ }
210
211
setAttributeNSCalls.length = 0;
212
await act(() => {
@@ -201,7 +218,11 @@ describe('when Trusted Types are available in global object', () => {
218
expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink');
219
expect(setAttributeNSCalls[0][2]).toBe('xlink:href');
220
// Ensure it didn't get stringified when passed to a DOM sink:
204
- expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
221
+ if (gate('enableTrustedTypesIntegration')) {
222
+ expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
223
+ } else {
224
+ expect(setAttributeNSCalls[0][3]).toBe('<b>Bye</b>');
225
+ }
226
} finally {
227
Element.prototype.setAttributeNS = setAttributeNS;
228
}
@@ -212,13 +233,15 @@ describe('when Trusted Types are available in global object', () => {
233
await act(() => {
234
root.render(<script>alert("I am not executed")</script>);
235
});
215
- assertConsoleErrorDev([
216
- 'Encountered a script tag while rendering React component. ' +
217
- 'Scripts inside React components are never executed when rendering ' +
218
- 'on the client. Consider using template tag instead ' +
219
- '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
220
- ' in script (at **)',
221
- ]);
236
+ if (gate('enableTrustedTypesIntegration')) {
237
+ assertConsoleErrorDev([
238
+ 'Encountered a script tag while rendering React component. ' +
239
+ 'Scripts inside React components are never executed when rendering ' +
240
+ 'on the client. Consider using template tag instead ' +
241
+ '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
242
+ ' in script (at **)',
243
+ ]);
244
+ }
245
246
// check that the warning is printed only once
247
await act(() => {
packages/shared/CheckStringCoercion.js
+2
@@ -76,6 +76,8 @@ export function checkAttributeStringCoercion(
76
attributeName: string,
77
): void | string {
78
if (__DEV__) {
79
+ // TODO: for enableTrustedTypesIntegration we don't toString this
80
+ // so we shouldn't need the DEV warning.
81
if (willCoercionThrow(value)) {
82
console.error(
83
'The provided `%s` attribute is an unsupported type %s.' +
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
-2
@@ -35,12 +35,11 @@ export const enableScrollEndPolyfill: boolean = __VARIANT__;
35
export const enableFragmentRefs: boolean = __VARIANT__;
36
export const enableFragmentRefsScrollIntoView: boolean = __VARIANT__;
37
export const enableAsyncDebugInfo: boolean = __VARIANT__;
38
-
38
export const enableInternalInstanceMap: boolean = __VARIANT__;
39
+export const enableTrustedTypesIntegration: boolean = __VARIANT__;
40
41
// TODO: These flags are hard-coded to the default values used in open source.
42
// Update the tests so that they pass in either mode, then set these
43
// to __VARIANT__.
44
-export const enableTrustedTypesIntegration: boolean = false;
44
// You probably *don't* want to add more hardcoded ones.
45
// Instead, try to add them above with the __VARIANT__ value.