diff --git a/.eslintrc.js b/.eslintrc.js
index c91ba7e6e7fc4..603f20a3cacba 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -149,5 +149,6 @@ module.exports = {
spyOnProd: true,
__PROFILE__: true,
__UMD__: true,
+ trustedTypes: true,
},
};
diff --git a/packages/react-dom/src/client/DOMPropertyOperations.js b/packages/react-dom/src/client/DOMPropertyOperations.js
index 6a2d0f7d244a6..72c99e67aa6ec 100644
--- a/packages/react-dom/src/client/DOMPropertyOperations.js
+++ b/packages/react-dom/src/client/DOMPropertyOperations.js
@@ -16,7 +16,9 @@ import {
OVERLOADED_BOOLEAN,
} from '../shared/DOMProperty';
import sanitizeURL from '../shared/sanitizeURL';
+import {toStringOrTrustedType} from './ToStringValue';
import {disableJavaScriptURLs} from 'shared/ReactFeatureFlags';
+import {setAttribute, setAttributeNS} from './setAttribute';
import type {PropertyInfo} from '../shared/DOMProperty';
@@ -142,7 +144,7 @@ export function setValueForProperty(
if (value === null) {
node.removeAttribute(attributeName);
} else {
- node.setAttribute(attributeName, '' + (value: any));
+ setAttribute(node, attributeName, toStringOrTrustedType(value));
}
}
return;
@@ -168,19 +170,21 @@ export function setValueForProperty(
const {type} = propertyInfo;
let attributeValue;
if (type === BOOLEAN || (type === OVERLOADED_BOOLEAN && value === true)) {
+ // If attribute type is boolean, we know for sure it won't be an execution sink
+ // and we won't require Trusted Type here.
attributeValue = '';
} else {
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
- attributeValue = '' + (value: any);
+ attributeValue = toStringOrTrustedType(value);
if (propertyInfo.sanitizeURL) {
- sanitizeURL(attributeValue);
+ sanitizeURL(attributeValue.toString());
}
}
if (attributeNamespace) {
- node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
+ setAttributeNS(node, attributeNamespace, attributeName, attributeValue);
} else {
- node.setAttribute(attributeName, attributeValue);
+ setAttribute(node, attributeName, attributeValue);
}
}
}
diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js
index ea178c13ab644..48b52eb195ea9 100644
--- a/packages/react-dom/src/client/ReactDOMComponent.js
+++ b/packages/react-dom/src/client/ReactDOMComponent.js
@@ -85,11 +85,16 @@ import possibleStandardNames from '../shared/possibleStandardNames';
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
+import {toStringOrTrustedType} from './ToStringValue';
-import {enableFlareAPI} from 'shared/ReactFeatureFlags';
+import {
+ enableFlareAPI,
+ enableTrustedTypesIntegration,
+} from 'shared/ReactFeatureFlags';
let didWarnInvalidHydration = false;
let didWarnShadyDOM = false;
+let didWarnScriptTags = false;
const DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
const SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
@@ -422,6 +427,18 @@ export function createElement(
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
const div = ownerDocument.createElement('div');
+ if (__DEV__) {
+ if (enableTrustedTypesIntegration && !didWarnScriptTags) {
+ warning(
+ false,
+ '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).',
+ );
+ didWarnScriptTags = true;
+ }
+ }
div.innerHTML = ', container);
+ }).toWarnDev(
+ 'Warning: 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 print only once
+ ReactDOM.render(, container);
+ });
+});
diff --git a/packages/react-dom/src/client/setAttribute.js b/packages/react-dom/src/client/setAttribute.js
new file mode 100644
index 0000000000000..8b9b0bd42a656
--- /dev/null
+++ b/packages/react-dom/src/client/setAttribute.js
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import type {TrustedValue} from './ToStringValue';
+
+/**
+ * Set attribute for a node. The attribute value can be either string or
+ * Trusted value (if application uses Trusted Types).
+ */
+export function setAttribute(
+ node: Element,
+ attributeName: string,
+ attributeValue: string | TrustedValue,
+) {
+ node.setAttribute(attributeName, (attributeValue: any));
+}
+
+/**
+ * Set attribute with namespace for a node. The attribute value can be either string or
+ * Trusted value (if application uses Trusted Types).
+ */
+export function setAttributeNS(
+ node: Element,
+ attributeNamespace: string,
+ attributeName: string,
+ attributeValue: string | TrustedValue,
+) {
+ node.setAttributeNS(attributeNamespace, attributeName, (attributeValue: any));
+}
diff --git a/packages/react-dom/src/client/setInnerHTML.js b/packages/react-dom/src/client/setInnerHTML.js
index 0c328b17e0b2f..4fa94a4e95c92 100644
--- a/packages/react-dom/src/client/setInnerHTML.js
+++ b/packages/react-dom/src/client/setInnerHTML.js
@@ -9,6 +9,9 @@
import {Namespaces} from '../shared/DOMNamespaces';
import createMicrosoftUnsafeLocalFunction from '../shared/createMicrosoftUnsafeLocalFunction';
+import warning from 'shared/warning';
+import type {TrustedValue} from './ToStringValue';
+import {enableTrustedTypesIntegration} from 'shared/ReactFeatureFlags';
// SVG temp container for IE lacking innerHTML
let reusableSVGContainer;
@@ -22,25 +25,41 @@ let reusableSVGContainer;
*/
const setInnerHTML = createMicrosoftUnsafeLocalFunction(function(
node: Element,
- html: string,
+ html: string | TrustedValue,
): void {
// IE does not have innerHTML for SVG nodes, so instead we inject the
// new markup in a temp node and then move the child nodes across into
// the target node
-
- if (node.namespaceURI === Namespaces.svg && !('innerHTML' in node)) {
- reusableSVGContainer =
- reusableSVGContainer || document.createElement('div');
- reusableSVGContainer.innerHTML = '';
- const svgNode = reusableSVGContainer.firstChild;
- while (node.firstChild) {
- node.removeChild(node.firstChild);
+ if (node.namespaceURI === Namespaces.svg) {
+ if (enableTrustedTypesIntegration && __DEV__) {
+ warning(
+ // $FlowExpectedError - trustedTypes are defined only in some browsers or with polyfill
+ typeof trustedTypes === 'undefined',
+ "Using 'dangerouslySetInnerHTML' in an svg element with " +
+ 'Trusted Types enabled in an Internet Explorer will cause ' +
+ 'the trusted value to be converted to string. Assigning string ' +
+ "to 'innerHTML' will throw an error if Trusted Types are enforced. " +
+ "You can try to wrap your svg element inside a div and use 'dangerouslySetInnerHTML' " +
+ 'on the enclosing div instead.',
+ );
}
- while (svgNode.firstChild) {
- node.appendChild(svgNode.firstChild);
+ if (!('innerHTML' in node)) {
+ reusableSVGContainer =
+ reusableSVGContainer || document.createElement('div');
+ reusableSVGContainer.innerHTML =
+ '';
+ const svgNode = reusableSVGContainer.firstChild;
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+ while (svgNode.firstChild) {
+ node.appendChild(svgNode.firstChild);
+ }
+ } else {
+ node.innerHTML = (html: any);
}
} else {
- node.innerHTML = html;
+ node.innerHTML = (html: any);
}
});
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 4121f9b6d7ef0..c7789e65fa0e2 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -100,3 +100,5 @@ export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+
+export const enableTrustedTypesIntegration = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index c29f0c210a4d3..12e700b0dc46b 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -43,6 +43,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrustedTypesIntegration = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 876063c2c9287..cd7a5d91b13d5 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -38,6 +38,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrustedTypesIntegration = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js
index dc6540b5d8199..0fb0154932e20 100644
--- a/packages/shared/forks/ReactFeatureFlags.persistent.js
+++ b/packages/shared/forks/ReactFeatureFlags.persistent.js
@@ -38,6 +38,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrustedTypesIntegration = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 7aa962fa46e95..c30a8a2081be8 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -38,6 +38,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrustedTypesIntegration = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 6083850599982..61144269cbca8 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrustedTypesIntegration = false;
// Only used in www builds.
export function addUserTimingListener() {
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 7abb9c69e730f..a3d2295974de1 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -22,6 +22,7 @@ export const {
enableUserBlockingEvents,
disableLegacyContext,
disableSchedulerTimeoutBasedOnReactExpirationTime,
+ enableTrustedTypesIntegration,
warnAboutStringRefs,
warnAboutDefaultPropsOnFunctionComponents,
} = require('ReactFeatureFlags');
diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js
index 2347fbb6cfe8d..6d1089b73788e 100644
--- a/scripts/rollup/validate/eslintrc.cjs.js
+++ b/scripts/rollup/validate/eslintrc.cjs.js
@@ -21,6 +21,8 @@ module.exports = {
process: true,
setImmediate: true,
Buffer: true,
+ // Trusted Types
+ trustedTypes: true,
// Scheduler profiling
SharedArrayBuffer: true,
diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js
index a32b7e38f0a37..57daf502c2218 100644
--- a/scripts/rollup/validate/eslintrc.fb.js
+++ b/scripts/rollup/validate/eslintrc.fb.js
@@ -22,6 +22,8 @@ module.exports = {
// Node.js Server Rendering
setImmediate: true,
Buffer: true,
+ // Trusted Types
+ trustedTypes: true,
// Scheduler profiling
SharedArrayBuffer: true,
diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js
index f211d9573f42e..9906dfb5b8ab6 100644
--- a/scripts/rollup/validate/eslintrc.rn.js
+++ b/scripts/rollup/validate/eslintrc.rn.js
@@ -21,6 +21,8 @@ module.exports = {
// Fabric. See https://github.com/facebook/react/pull/15490
// for more information
nativeFabricUIManager: true,
+ // Trusted Types
+ trustedTypes: true,
// Scheduler profiling
SharedArrayBuffer: true,
diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js
index 5b96526bba8ed..57645c7a34c67 100644
--- a/scripts/rollup/validate/eslintrc.umd.js
+++ b/scripts/rollup/validate/eslintrc.umd.js
@@ -24,6 +24,8 @@ module.exports = {
define: true,
require: true,
global: true,
+ // Trusted Types
+ trustedTypes: true,
// Scheduler profiling
SharedArrayBuffer: true,