');
+ expect(innerHTMLCalls.length).toBe(1);
+ // Ensure it didn't get stringified when passed to a DOM sink:
+ expect(innerHTMLCalls[0]).toBe(ttObject1);
+
+ innerHTMLCalls.length = 0;
+ ReactDOM.render(
+ ,
+ container,
+ );
+ expect(container.innerHTML).toBe('
Bye
');
+ expect(innerHTMLCalls.length).toBe(1);
+ // Ensure it didn't get stringified when passed to a DOM sink:
+ expect(innerHTMLCalls[0]).toBe(ttObject2);
+ } finally {
+ Object.defineProperty(
+ Element.prototype,
+ 'innerHTML',
+ innerHTMLDescriptor,
+ );
+ }
+ });
+
+ it('should not stringify trusted values for setAttribute (unknown attribute)', () => {
+ let setAttribute = Element.prototype.setAttribute;
+ try {
+ const setAttributeCalls = [];
+ Element.prototype.setAttribute = function(name, value) {
+ setAttributeCalls.push([this, name.toLowerCase(), value]);
+ return setAttribute.apply(this, arguments);
+ };
+ ReactDOM.render(, container);
+ expect(container.innerHTML).toBe('');
+ expect(setAttributeCalls.length).toBe(1);
+ 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);
+
+ setAttributeCalls.length = 0;
+ ReactDOM.render(, container);
+ expect(setAttributeCalls.length).toBe(1);
+ 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);
+ } finally {
+ Element.prototype.setAttribute = setAttribute;
}
+ });
+
+ it('should not stringify trusted values for setAttribute (known attribute)', () => {
+ let setAttribute = Element.prototype.setAttribute;
+ try {
+ const setAttributeCalls = [];
+ Element.prototype.setAttribute = function(name, value) {
+ setAttributeCalls.push([this, name.toLowerCase(), value]);
+ return setAttribute.apply(this, arguments);
+ };
+ ReactDOM.render(, container);
+ expect(container.innerHTML).toBe('');
+ expect(setAttributeCalls.length).toBe(1);
+ 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);
+
+ setAttributeCalls.length = 0;
+ ReactDOM.render(, container);
+ expect(setAttributeCalls.length).toBe(1);
+ 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);
+ } finally {
+ Element.prototype.setAttribute = setAttribute;
+ }
+ });
- const isHTMLSpy = jest.spyOn(window.trustedTypes, ['isHTML']);
- const instance = ReactDOM.render(, container);
- instance.setState({inner: trustedObject});
+ it('should not stringify trusted values for setAttributeNS', () => {
+ let setAttributeNS = Element.prototype.setAttributeNS;
+ try {
+ const setAttributeNSCalls = [];
+ Element.prototype.setAttributeNS = function(ns, name, value) {
+ setAttributeNSCalls.push([this, ns, name, value]);
+ return setAttributeNS.apply(this, arguments);
+ };
+ ReactDOM.render(, container);
+ expect(container.innerHTML).toBe('');
+ expect(setAttributeNSCalls.length).toBe(1);
+ expect(setAttributeNSCalls[0][0]).toBe(container.firstChild);
+ 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);
- expect(container.firstChild.innerHTML).toBe(trustedObject.toString());
- expect(isHTMLSpy).toHaveBeenCalledWith(trustedObject);
+ setAttributeNSCalls.length = 0;
+ ReactDOM.render(, container);
+ expect(setAttributeNSCalls.length).toBe(1);
+ expect(setAttributeNSCalls[0][0]).toBe(container.firstChild);
+ 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);
+ } finally {
+ Element.prototype.setAttributeNS = setAttributeNS;
+ }
});
describe('dangerouslySetInnerHTML in svg elements in Internet Explorer', () => {
@@ -81,6 +219,7 @@ describe('when Trusted Types are available in global object', () => {
"You can try to wrap your svg element inside a div and use 'dangerouslySetInnerHTML' " +
'on the enclosing div instead.',
);
+ expect(container.innerHTML).toBe('');
});
});
@@ -95,7 +234,7 @@ describe('when Trusted Types are available in global object', () => {
' in script (at **)',
);
- // check that the warning is print only once
+ // check that the warning is printed only once
ReactDOM.render(, container);
});
});
diff --git a/packages/react-dom/src/client/setInnerHTML.js b/packages/react-dom/src/client/setInnerHTML.js
index 4fa94a4e95c92..2b3227bdcaba1 100644
--- a/packages/react-dom/src/client/setInnerHTML.js
+++ b/packages/react-dom/src/client/setInnerHTML.js
@@ -27,23 +27,26 @@ const setInnerHTML = createMicrosoftUnsafeLocalFunction(function(
node: Element,
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) {
- 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.',
- );
+ if (__DEV__) {
+ if (enableTrustedTypesIntegration) {
+ // TODO: reconsider the text of this warning and when it should show
+ // before enabling the feature flag.
+ warning(
+ 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.',
+ );
+ }
}
if (!('innerHTML' in node)) {
+ // 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
reusableSVGContainer =
reusableSVGContainer || document.createElement('div');
reusableSVGContainer.innerHTML =
@@ -55,12 +58,10 @@ const setInnerHTML = createMicrosoftUnsafeLocalFunction(function(
while (svgNode.firstChild) {
node.appendChild(svgNode.firstChild);
}
- } else {
- node.innerHTML = (html: any);
+ return;
}
- } else {
- node.innerHTML = (html: any);
}
+ node.innerHTML = (html: any);
});
export default setInnerHTML;
diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js
index 8a0806c4a9744..8eeda9784000d 100644
--- a/scripts/flow/environment.js
+++ b/scripts/flow/environment.js
@@ -16,6 +16,14 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
inject: ?((stuff: Object) => void)
};*/
+declare var trustedTypes: {|
+ isHTML: (value: any) => boolean,
+ isScript: (value: any) => boolean,
+ isScriptURL: (value: any) => boolean,
+ // TrustedURLs are deprecated and will be removed soon: https://github.com/WICG/trusted-types/pull/204
+ isURL?: (value: any) => boolean,
+|};
+
// ReactFeatureFlags www fork
declare module 'ReactFeatureFlags' {
declare module.exports: any;