[flags] make enableTrustedTypesIntegration dynamic (#35646)

Co-authored-by: Rick Hanlon <rickhanlonii@meta.com>
This commit is contained in:
Jan Olaf Martin
2026-01-28 10:15:33 -08:00
committed by GitHub
parent c0c37063e2
commit d4d099f05b
8 changed files with 93 additions and 22 deletions

View File

@@ -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' +

View File

@@ -602,6 +602,14 @@ describe('ReactDOMFloat', () => {
'> <script href="foo">\n' +
'\n' +
' in script (at **)',
...(gate('enableTrustedTypesIntegration')
? [
'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 **)',
]
: []),
]);
root.render(
@@ -2745,6 +2753,14 @@ body {
'> <script itemProp="foo">\n' +
'\n' +
' in script (at **)',
...(gate('enableTrustedTypesIntegration')
? [
'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 **)',
]
: []),
]);
});

View File

@@ -2277,15 +2277,21 @@ describe('ReactDOMForm', () => {
await submit(formRef.current);
assertLog([actionFn]);
// Everything else is toString-ed
// Everything else is toString-ed, unless trusted types are enabled.
class MyAction {
toString() {
return 'stringified action';
}
}
await act(() => root.render(<Form action={new MyAction()} />));
const instance = new MyAction();
await act(() => root.render(<Form action={instance} />));
await submit(formRef.current);
assertLog(['stringified action']);
assertLog(
gate('enableTrustedTypesIntegration')
? [instance]
: ['stringified action'],
);
});
it('form actions should retain status when nested state changes', async () => {

View File

@@ -212,6 +212,11 @@ describe('ReactDOMServerIntegration - Untrusted URLs', () => {
expectedToStringCalls *= 2;
}
if (gate('enableTrustedTypesIntegration') && render === clientCleanRender) {
// Trusted types does another toString.
expectedToStringCalls += 1;
}
let toStringCalls = 0;
const firstIsSafe = {
toString() {

View File

@@ -17,6 +17,7 @@ let TogglingComponent;
let act;
let Scheduler;
let assertLog;
let assertConsoleErrorDev;
let container;
@@ -34,6 +35,7 @@ describe('ReactEmptyComponent', () => {
const InternalTestUtils = require('internal-test-utils');
act = InternalTestUtils.act;
assertLog = InternalTestUtils.assertLog;
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
container = document.createElement('div');
@@ -175,6 +177,17 @@ describe('ReactEmptyComponent', () => {
});
}).not.toThrow();
expect(container.innerHTML).toBe('<script></script>');
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(

View File

@@ -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,8 +32,6 @@ describe('when Trusted Types are available in global object', () => {
isScript: () => false,
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 +115,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 (gate('enableTrustedTypesIntegration')) {
expect(setAttributeCalls[0][2]).toBe(ttObject1);
} else {
expect(setAttributeCalls[0][2]).toBe('<b>Hi</b>');
}
setAttributeCalls.length = 0;
await act(() => {
@@ -129,7 +130,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 (gate('enableTrustedTypesIntegration')) {
expect(setAttributeCalls[0][2]).toBe(ttObject2);
} else {
expect(setAttributeCalls[0][2]).toBe('<b>Bye</b>');
}
} finally {
Element.prototype.setAttribute = setAttribute;
}
@@ -153,7 +158,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 (gate('enableTrustedTypesIntegration')) {
expect(setAttributeCalls[0][2]).toBe(ttObject1);
} else {
expect(setAttributeCalls[0][2]).toBe('<b>Hi</b>');
}
setAttributeCalls.length = 0;
await act(() => {
@@ -164,7 +173,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 (gate('enableTrustedTypesIntegration')) {
expect(setAttributeCalls[0][2]).toBe(ttObject2);
} else {
expect(setAttributeCalls[0][2]).toBe('<b>Bye</b>');
}
} finally {
Element.prototype.setAttribute = setAttribute;
}
@@ -189,7 +202,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 (gate('enableTrustedTypesIntegration')) {
expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
} else {
expect(setAttributeNSCalls[0][3]).toBe('<b>Hi</b>');
}
setAttributeNSCalls.length = 0;
await act(() => {
@@ -201,7 +218,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 (gate('enableTrustedTypesIntegration')) {
expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
} else {
expect(setAttributeNSCalls[0][3]).toBe('<b>Bye</b>');
}
} finally {
Element.prototype.setAttributeNS = setAttributeNS;
}
@@ -212,13 +233,15 @@ describe('when Trusted Types are available in global object', () => {
await act(() => {
root.render(<script>alert("I am not executed")</script>);
});
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 (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 **)',
]);
}
// check that the warning is printed only once
await act(() => {

View File

@@ -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.' +

View File

@@ -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.