@samitouri / QOS-React / commits / 5e85f0065b

Don't warn about casing in SSR for non-HTML NS (#10452)

Fixes #10415.

Ben Alpert committed Aug 13, 2017 at 16:17 UTC 5e85f0065b80b6fbfa9715bc1e6f190ad719ce82
9 files changed +119 -60
src/renderers/dom/fiber/ReactDOMFiberComponent.js
+3 -30
@@ -58,11 +58,7 @@ var CHILDREN = 'children';
58 var STYLE = 'style';
59 var HTML = '__html';
60
61 -var {
62 - html: HTML_NAMESPACE,
63 - svg: SVG_NAMESPACE,
64 - mathml: MATH_NAMESPACE,
65 -} = DOMNamespaces;
61 +var {Namespaces: {html: HTML_NAMESPACE}, getIntrinsicNamespace} = DOMNamespaces;
62
63 if (__DEV__) {
64 var warnedUnknownTags = {
@@ -295,32 +291,7 @@ function updateDOMProperties(
291 }
292 }
293
298 -// Assumes there is no parent namespace.
299 -function getIntrinsicNamespace(type: string): string {
300 - switch (type) {
301 - case 'svg':
302 - return SVG_NAMESPACE;
303 - case 'math':
304 - return MATH_NAMESPACE;
305 - default:
306 - return HTML_NAMESPACE;
307 - }
308 -}
309 -
294 var ReactDOMFiberComponent = {
311 - getChildNamespace(parentNamespace: string | null, type: string): string {
312 - if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
313 - // No (or default) parent namespace: potential entry point.
314 - return getIntrinsicNamespace(type);
315 - }
316 - if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
317 - // We're leaving SVG.
318 - return HTML_NAMESPACE;
319 - }
320 - // By default, pass namespace below.
321 - return parentNamespace;
322 - },
323 -
295 createElement(
296 type: *,
297 props: Object,
@@ -343,6 +314,8 @@ var ReactDOMFiberComponent = {
314 }
315 if (namespaceURI === HTML_NAMESPACE) {
316 if (__DEV__) {
317 + // Should this check be gated by parent namespace? Not sure we want to
318 + // allow <SVG> or <mATH>.
319 warning(
320 isCustomComponentTag || type === type.toLowerCase(),
321 '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
src/renderers/dom/fiber/ReactDOMFiberEntry.js
+2 -1
@@ -16,6 +16,7 @@ import type {Fiber} from 'ReactFiber';
16 import type {ReactNodeList} from 'ReactTypes';
17
18 require('checkReact');
19 +var DOMNamespaces = require('DOMNamespaces');
20 var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
21 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
22 var ReactControlledComponent = require('ReactControlledComponent');
@@ -44,9 +45,9 @@ var {ROOT_ATTRIBUTE_NAME} = require('DOMProperty');
45 var findDOMNode = require('findDOMNode');
46 var invariant = require('fbjs/lib/invariant');
47
48 +var {getChildNamespace} = DOMNamespaces;
49 var {
50 createElement,
49 - getChildNamespace,
51 setInitialProperties,
52 diffProperties,
53 updateProperties,
src/renderers/dom/shared/DOMNamespaces.js
+39 -5
@@ -11,10 +11,44 @@
11
12 'use strict';
13
14 -var DOMNamespaces = {
15 - html: 'http://www.w3.org/1999/xhtml',
16 - mathml: 'http://www.w3.org/1998/Math/MathML',
17 - svg: 'http://www.w3.org/2000/svg',
14 +const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
15 +const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
16 +const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
17 +
18 +const Namespaces = {
19 + html: HTML_NAMESPACE,
20 + mathml: MATH_NAMESPACE,
21 + svg: SVG_NAMESPACE,
22 };
23
20 -module.exports = DOMNamespaces;
24 +// Assumes there is no parent namespace.
25 +function getIntrinsicNamespace(type: string): string {
26 + switch (type) {
27 + case 'svg':
28 + return SVG_NAMESPACE;
29 + case 'math':
30 + return MATH_NAMESPACE;
31 + default:
32 + return HTML_NAMESPACE;
33 + }
34 +}
35 +
36 +function getChildNamespace(
37 + parentNamespace: string | null,
38 + type: string,
39 +): string {
40 + if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
41 + // No (or default) parent namespace: potential entry point.
42 + return getIntrinsicNamespace(type);
43 + }
44 + if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
45 + // We're leaving SVG.
46 + return HTML_NAMESPACE;
47 + }
48 + // By default, pass namespace below.
49 + return parentNamespace;
50 +}
51 +
52 +exports.Namespaces = Namespaces;
53 +exports.getIntrinsicNamespace = getIntrinsicNamespace;
54 +exports.getChildNamespace = getChildNamespace;
src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
+32
@@ -772,4 +772,36 @@ describe('ReactDOMServer', () => {
772 );
773 }).toThrowError(/Cannot assign to read only property.*/);
774 });
775 +
776 + it('warns about lowercase html but not in svg tags', () => {
777 + spyOn(console, 'error');
778 + function CompositeG(props) {
779 + // Make sure namespace passes through composites
780 + return <g>{props.children}</g>;
781 + }
782 + ReactDOMServer.renderToStaticMarkup(
783 + <div>
784 + <inPUT />
785 + <svg>
786 + <CompositeG>
787 + <linearGradient />
788 + <foreignObject>
789 + {/* back to HTML */}
790 + <iFrame />
791 + </foreignObject>
792 + </CompositeG>
793 + </svg>
794 + </div>,
795 + );
796 + expect(console.error.calls.count()).toBe(2);
797 + expect(console.error.calls.argsFor(0)[0]).toBe(
798 + 'Warning: <inPUT /> is using uppercase HTML. Always use lowercase ' +
799 + 'HTML tags in React.',
800 + );
801 + // linearGradient doesn't warn
802 + expect(console.error.calls.argsFor(1)[0]).toBe(
803 + 'Warning: <iFrame /> is using uppercase HTML. Always use lowercase ' +
804 + 'HTML tags in React.',
805 + );
806 + });
807 });
src/renderers/dom/shared/setInnerHTML.js
+2 -2
@@ -11,7 +11,7 @@
11
12 'use strict';
13
14 -var DOMNamespaces = require('DOMNamespaces');
14 +var Namespaces = require('DOMNamespaces').Namespaces;
15 var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
16
17 // SVG temp container for IE lacking innerHTML
@@ -28,7 +28,7 @@ var setInnerHTML = createMicrosoftUnsafeLocalFunction(function(node, html) {
28 // IE does not have innerHTML for SVG nodes, so instead we inject the
29 // new markup in a temp node and then move the child nodes across into
30 // the target node
31 - if (node.namespaceURI === DOMNamespaces.svg && !('innerHTML' in node)) {
31 + if (node.namespaceURI === Namespaces.svg && !('innerHTML' in node)) {
32 reusableSVGContainer =
33 reusableSVGContainer || document.createElement('div');
34 reusableSVGContainer.innerHTML = '<svg>' + html + '</svg>';
src/renderers/dom/shared/utils/__tests__/setInnerHTML-test.js
+2 -2
@@ -13,7 +13,7 @@
13
14 // TODO: can we express this test with only public API?
15 var setInnerHTML = require('setInnerHTML');
16 -var DOMNamespaces = require('DOMNamespaces');
16 +var Namespaces = require('DOMNamespaces').Namespaces;
17
18 describe('setInnerHTML', () => {
19 describe('when the node has innerHTML property', () => {
@@ -31,7 +31,7 @@ describe('setInnerHTML', () => {
31 xit('sets innerHTML on it', () => {
32 // Create a mock node that looks like an SVG in IE (without innerHTML)
33 var node = {
34 - namespaceURI: DOMNamespaces.svg,
34 + namespaceURI: Namespaces.svg,
35 appendChild: jasmine.createSpy(),
36 };
37
src/renderers/dom/stack/client/DOMLazyTree.js
+2 -2
@@ -11,7 +11,7 @@
11
12 'use strict';
13
14 -var DOMNamespaces = require('DOMNamespaces');
14 +var Namespaces = require('DOMNamespaces').Namespaces;
15 var setInnerHTML = require('setInnerHTML');
16 var {DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE} = require('HTMLNodeType');
17 var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
@@ -68,7 +68,7 @@ var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function(
68 (tree.node.nodeType === ELEMENT_NODE &&
69 tree.node.nodeName.toLowerCase() === 'object' &&
70 (tree.node.namespaceURI == null ||
71 - tree.node.namespaceURI === DOMNamespaces.html))
71 + tree.node.namespaceURI === Namespaces.html))
72 ) {
73 insertTreeChildren(tree);
74 parentNode.insertBefore(tree.node, referenceNode);
src/renderers/dom/stack/client/ReactDOMComponent.js
+8 -8
@@ -14,7 +14,7 @@
14 var AutoFocusUtils = require('AutoFocusUtils');
15 var CSSPropertyOperations = require('CSSPropertyOperations');
16 var DOMLazyTree = require('DOMLazyTree');
17 -var DOMNamespaces = require('DOMNamespaces');
17 +var Namespaces = require('DOMNamespaces').Namespaces;
18 var DOMMarkupOperations = require('DOMMarkupOperations');
19 var DOMProperty = require('DOMProperty');
20 var DOMPropertyOperations = require('DOMPropertyOperations');
@@ -512,11 +512,11 @@ ReactDOMComponent.Mixin = {
512 }
513 if (
514 namespaceURI == null ||
515 - (namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject')
515 + (namespaceURI === Namespaces.svg && parentTag === 'foreignobject')
516 ) {
517 - namespaceURI = DOMNamespaces.html;
517 + namespaceURI = Namespaces.html;
518 }
519 - if (namespaceURI === DOMNamespaces.html) {
519 + if (namespaceURI === Namespaces.html) {
520 if (__DEV__) {
521 warning(
522 isCustomComponentTag || this._tag === this._currentElement.type,
@@ -526,9 +526,9 @@ ReactDOMComponent.Mixin = {
526 );
527 }
528 if (this._tag === 'svg') {
529 - namespaceURI = DOMNamespaces.svg;
529 + namespaceURI = Namespaces.svg;
530 } else if (this._tag === 'math') {
531 - namespaceURI = DOMNamespaces.mathml;
531 + namespaceURI = Namespaces.mathml;
532 }
533 }
534 this._namespaceURI = namespaceURI;
@@ -557,7 +557,7 @@ ReactDOMComponent.Mixin = {
557 if (transaction.useCreateElement) {
558 var ownerDocument = hostContainerInfo._ownerDocument;
559 var el;
560 - if (namespaceURI === DOMNamespaces.html) {
560 + if (namespaceURI === Namespaces.html) {
561 if (this._tag === 'script') {
562 // Create the script via .innerHTML so its "parser-inserted" flag is
563 // set to true and it does not execute
@@ -587,7 +587,7 @@ ReactDOMComponent.Mixin = {
587 );
588 didWarnShadyDOM = true;
589 }
590 - if (this._namespaceURI === DOMNamespaces.html) {
590 + if (this._namespaceURI === Namespaces.html) {
591 if (
592 !isCustomComponentTag &&
593 Object.prototype.toString.call(el) ===
src/renderers/shared/server/ReactPartialRenderer.js
+29 -10
@@ -11,6 +11,11 @@
11
12 'use strict';
13
14 +var {
15 + Namespaces,
16 + getIntrinsicNamespace,
17 + getChildNamespace,
18 +} = require('DOMNamespaces');
19 var DOMMarkupOperations = require('DOMMarkupOperations');
20 var React = require('react');
21 var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
@@ -443,6 +448,9 @@ class ReactDOMServerRenderer {
448 constructor(element, makeStaticMarkup) {
449 var children = React.isValidElement(element) ? [element] : toArray(element);
450 var topFrame = {
451 + // Assume all trees start in the HTML namespace (not totally true, but
452 + // this is what we did historically)
453 + domNamespace: Namespaces.html,
454 children,
455 childIndex: 0,
456 context: emptyObject,
@@ -483,7 +491,7 @@ class ReactDOMServerRenderer {
491 if (__DEV__) {
492 setCurrentDebugStack(this.stack);
493 }
486 - out += this.render(child, frame.context);
494 + out += this.render(child, frame.context, frame.domNamespace);
495 if (__DEV__) {
496 // TODO: Handle reentrant server render calls. This doesn't.
497 resetCurrentDebugStack();
@@ -492,7 +500,7 @@ class ReactDOMServerRenderer {
500 return out;
501 }
502
495 - render(child, context) {
503 + render(child, context, parentNamespace) {
504 if (typeof child === 'string' || typeof child === 'number') {
505 var text = '' + child;
506 if (text === '') {
@@ -512,10 +520,11 @@ class ReactDOMServerRenderer {
520 return '';
521 } else {
522 if (React.isValidElement(child)) {
515 - return this.renderDOM(child, context);
523 + return this.renderDOM(child, context, parentNamespace);
524 } else {
525 var children = toArray(child);
526 var frame = {
527 + domNamespace: parentNamespace,
528 children,
529 childIndex: 0,
530 context: context,
@@ -531,16 +540,25 @@ class ReactDOMServerRenderer {
540 }
541 }
542
534 - renderDOM(element, context) {
543 + renderDOM(element, context, parentNamespace) {
544 var tag = element.type.toLowerCase();
545
546 + let namespace = parentNamespace;
547 + if (parentNamespace === Namespaces.html) {
548 + namespace = getIntrinsicNamespace(tag);
549 + }
550 +
551 if (__DEV__) {
538 - warning(
539 - tag === element.type,
540 - '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
541 - 'in React.',
542 - element.type,
543 - );
552 + if (namespace === Namespaces.html) {
553 + // Should this check be gated by parent namespace? Not sure we want to
554 + // allow <SVG> or <mATH>.
555 + warning(
556 + tag === element.type,
557 + '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
558 + 'in React.',
559 + element.type,
560 + );
561 + }
562 }
563
564 validateDangerousTag(tag);
@@ -801,6 +819,7 @@ class ReactDOMServerRenderer {
819 children = toArray(props.children);
820 }
821 var frame = {
822 + domNamespace: getChildNamespace(parentNamespace, element.type),
823 tag,
824 children,
825 childIndex: 0,