diff --git a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js
index ad14b6498c83..42ae4b0775fe 100644
--- a/packages/react-dom-bindings/src/client/DOMPropertyOperations.js
+++ b/packages/react-dom-bindings/src/client/DOMPropertyOperations.js
@@ -8,10 +8,7 @@
*/
import isAttributeNameSafe from '../shared/isAttributeNameSafe';
-import {
- enableTrustedTypesIntegration,
- enableCustomElementPropertySupport,
-} from 'shared/ReactFeatureFlags';
+import {enableCustomElementPropertySupport} from 'shared/ReactFeatureFlags';
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';
@@ -133,10 +130,7 @@ export function setValueForAttribute(
if (__DEV__) {
checkAttributeStringCoercion(value, name);
}
- node.setAttribute(
- name,
- enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
- );
+ node.setAttribute(name, (value: any));
}
}
@@ -161,10 +155,7 @@ export function setValueForKnownAttribute(
if (__DEV__) {
checkAttributeStringCoercion(value, name);
}
- node.setAttribute(
- name,
- enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
- );
+ node.setAttribute(name, (value: any));
}
export function setValueForNamespacedAttribute(
@@ -189,11 +180,7 @@ export function setValueForNamespacedAttribute(
if (__DEV__) {
checkAttributeStringCoercion(value, name);
}
- node.setAttributeNS(
- namespace,
- name,
- enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
- );
+ node.setAttributeNS(namespace, name, (value: any));
}
export function setValueForPropertyOnCustomComponent(
diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js
index a94c99073391..27f07139d846 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js
@@ -70,7 +70,6 @@ import {
enableClientRenderFallbackOnTextMismatch,
enableFormActions,
disableIEWorkarounds,
- enableTrustedTypesIntegration,
enableFilterEmptyStringAttributesDOM,
} from 'shared/ReactFeatureFlags';
import {
@@ -473,9 +472,8 @@ function setProp(
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
- const sanitizedValue = (sanitizeURL(
- enableTrustedTypesIntegration ? value : '' + (value: any),
- ): any);
+ const attributeValue = (value: any);
+ const sanitizedValue = (sanitizeURL(attributeValue): any);
domElement.setAttribute(key, sanitizedValue);
break;
}
@@ -561,9 +559,8 @@ function setProp(
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
- const sanitizedValue = (sanitizeURL(
- enableTrustedTypesIntegration ? value : '' + (value: any),
- ): any);
+ const attributeValue = (value: any);
+ const sanitizedValue = (sanitizeURL(attributeValue): any);
domElement.setAttribute(key, sanitizedValue);
break;
}
@@ -662,9 +659,7 @@ function setProp(
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
- const sanitizedValue = (sanitizeURL(
- enableTrustedTypesIntegration ? value : '' + (value: any),
- ): any);
+ const sanitizedValue = (sanitizeURL((value: any)): any);
domElement.setAttributeNS(xlinkNamespace, 'xlink:href', sanitizedValue);
break;
}
@@ -690,10 +685,7 @@ function setProp(
if (__DEV__) {
checkAttributeStringCoercion(value, key);
}
- domElement.setAttribute(
- key,
- enableTrustedTypesIntegration ? (value: any) : '' + (value: any),
- );
+ domElement.setAttribute(key, (value: any));
} else {
domElement.removeAttribute(key);
}
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 777ef41756ce..1e8eb236f54a 100644
--- a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
+++ b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
@@ -16,11 +16,24 @@ describe('when Trusted Types are available in global object', () => {
let container;
let ttObject1;
let ttObject2;
+ let fakeTTObjects;
+
+ const expectToReject = fn => {
+ let msg;
+ try {
+ fn();
+ } catch (x) {
+ msg = x.message;
+ }
+ expect(msg).toContain(
+ 'React has blocked a javascript: URL as a security precaution.',
+ );
+ };
beforeEach(() => {
jest.resetModules();
container = document.createElement('div');
- const fakeTTObjects = new Set();
+ fakeTTObjects = new Set();
window.trustedTypes = {
isHTML: function (value) {
if (this !== window.trustedTypes) {
@@ -28,11 +41,12 @@ describe('when Trusted Types are available in global object', () => {
}
return fakeTTObjects.has(value);
},
- isScript: () => false,
- isScriptURL: () => false,
+ isScript: value => fakeTTObjects.has(value),
+ isScriptURL: value => fakeTTObjects.has(value),
};
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableTrustedTypesIntegration = true;
+ ReactFeatureFlags.disableJavaScriptURLs = true;
React = require('react');
ReactDOM = require('react-dom');
ttObject1 = {
@@ -152,6 +166,51 @@ describe('when Trusted Types are available in global object', () => {
}
});
+ it('should not stringify attributes that go through sanitizeURL', () => {
+ const setAttribute = Element.prototype.setAttribute;
+ try {
+ const setAttributeCalls = [];
+ Element.prototype.setAttribute = function (name, value) {
+ setAttributeCalls.push([this, name.toLowerCase(), value]);
+ return setAttribute.apply(this, arguments);
+ };
+ const trustedScriptUrlHttps = {
+ toString: () => 'https://ok.example',
+ };
+ fakeTTObjects.add(trustedScriptUrlHttps);
+ // It's not a matching type (under Trusted Types a.href a string will do),
+ // but a.href undergoes URL and we're only testing if the value was
+ // passed unmodified to setAttribute.
+ ReactDOM.render(, container);
+ expect(setAttributeCalls.length).toBe(1);
+ expect(setAttributeCalls[0][0]).toBe(container.firstChild);
+ expect(setAttributeCalls[0][1]).toBe('href');
+ // Ensure it didn't get stringified when passed to a DOM sink:
+ expect(setAttributeCalls[0][2]).toBe(trustedScriptUrlHttps);
+ } finally {
+ Element.prototype.setAttribute = setAttribute;
+ }
+ });
+
+ it('should sanitize attributes even if they are Trusted Types', () => {
+ const setAttribute = Element.prototype.setAttribute;
+ try {
+ const trustedScriptUrlJavascript = {
+ // eslint-disable-next-line no-script-url
+ toString: () => 'javascript:notfine',
+ };
+ fakeTTObjects.add(trustedScriptUrlJavascript);
+ // Assert that the URL sanitization will correctly unwrap and verify the
+ // value.
+ ReactDOM.render(, container);
+ expect(container.innerHTML).toBe(
+ '',
+ );
+ } finally {
+ Element.prototype.setAttribute = setAttribute;
+ }
+ });
+
it('should not stringify trusted values for setAttributeNS', () => {
const setAttributeNS = Element.prototype.setAttributeNS;
try {