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,