Skip to content

Commit 96f9bab

Browse files
committed
Don't warn about casing in SSR for non-HTML NS
Fixes #10415.
1 parent 9921e57 commit 96f9bab

File tree

9 files changed

+121
-58
lines changed

9 files changed

+121
-58
lines changed

src/renderers/dom/fiber/ReactDOMFiberComponent.js

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ var STYLE = 'style';
5959
var HTML = '__html';
6060

6161
var {
62-
html: HTML_NAMESPACE,
63-
svg: SVG_NAMESPACE,
64-
mathml: MATH_NAMESPACE,
62+
Namespaces: {
63+
html: HTML_NAMESPACE,
64+
},
65+
getIntrinsicNamespace,
6566
} = DOMNamespaces;
6667

6768
if (__DEV__) {
@@ -295,32 +296,7 @@ function updateDOMProperties(
295296
}
296297
}
297298

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-
310299
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-
324300
createElement(
325301
type: *,
326302
props: Object,
@@ -343,6 +319,8 @@ var ReactDOMFiberComponent = {
343319
}
344320
if (namespaceURI === HTML_NAMESPACE) {
345321
if (__DEV__) {
322+
// Should this check be gated by parent namespace? Not sure we want to
323+
// allow <SVG> or <mATH>.
346324
warning(
347325
isCustomComponentTag || type === type.toLowerCase(),
348326
'<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +

src/renderers/dom/fiber/ReactDOMFiberEntry.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {Fiber} from 'ReactFiber';
1616
import type {ReactNodeList} from 'ReactTypes';
1717

1818
require('checkReact');
19+
var DOMNamespaces = require('DOMNamespaces');
1920
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
2021
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
2122
var ReactControlledComponent = require('ReactControlledComponent');
@@ -45,8 +46,10 @@ var findDOMNode = require('findDOMNode');
4546
var invariant = require('fbjs/lib/invariant');
4647

4748
var {
48-
createElement,
4949
getChildNamespace,
50+
} = DOMNamespaces;
51+
var {
52+
createElement,
5053
setInitialProperties,
5154
diffProperties,
5255
updateProperties,

src/renderers/dom/shared/DOMNamespaces.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,41 @@
1111

1212
'use strict';
1313

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,
1822
};
1923

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(parentNamespace: string | null, type: string): string {
37+
if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
38+
// No (or default) parent namespace: potential entry point.
39+
return getIntrinsicNamespace(type);
40+
}
41+
if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
42+
// We're leaving SVG.
43+
return HTML_NAMESPACE;
44+
}
45+
// By default, pass namespace below.
46+
return parentNamespace;
47+
}
48+
49+
exports.Namespaces = Namespaces;
50+
exports.getIntrinsicNamespace = getIntrinsicNamespace;
51+
exports.getChildNamespace = getChildNamespace;

src/renderers/dom/shared/__tests__/ReactServerRendering-test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,4 +772,36 @@ describe('ReactDOMServer', () => {
772772
);
773773
}).toThrowError(/Cannot assign to read only property.*/);
774774
});
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+
});
775807
});

src/renderers/dom/shared/setInnerHTML.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
'use strict';
1313

14-
var DOMNamespaces = require('DOMNamespaces');
14+
var Namespaces = require('DOMNamespaces').Namespaces;
1515
var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
1616

1717
// SVG temp container for IE lacking innerHTML
@@ -28,7 +28,7 @@ var setInnerHTML = createMicrosoftUnsafeLocalFunction(function(node, html) {
2828
// IE does not have innerHTML for SVG nodes, so instead we inject the
2929
// new markup in a temp node and then move the child nodes across into
3030
// the target node
31-
if (node.namespaceURI === DOMNamespaces.svg && !('innerHTML' in node)) {
31+
if (node.namespaceURI === Namespaces.svg && !('innerHTML' in node)) {
3232
reusableSVGContainer =
3333
reusableSVGContainer || document.createElement('div');
3434
reusableSVGContainer.innerHTML = '<svg>' + html + '</svg>';

src/renderers/dom/shared/utils/__tests__/setInnerHTML-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
// TODO: can we express this test with only public API?
1515
var setInnerHTML = require('setInnerHTML');
16-
var DOMNamespaces = require('DOMNamespaces');
16+
var Namespaces = require('DOMNamespaces').Namespaces;
1717

1818
describe('setInnerHTML', () => {
1919
describe('when the node has innerHTML property', () => {
@@ -31,7 +31,7 @@ describe('setInnerHTML', () => {
3131
xit('sets innerHTML on it', () => {
3232
// Create a mock node that looks like an SVG in IE (without innerHTML)
3333
var node = {
34-
namespaceURI: DOMNamespaces.svg,
34+
namespaceURI: Namespaces.svg,
3535
appendChild: jasmine.createSpy(),
3636
};
3737

src/renderers/dom/stack/client/DOMLazyTree.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
'use strict';
1313

14-
var DOMNamespaces = require('DOMNamespaces');
14+
var Namespaces = require('DOMNamespaces').Namespaces;
1515
var setInnerHTML = require('setInnerHTML');
1616
var {DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE} = require('HTMLNodeType');
1717
var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
@@ -68,7 +68,7 @@ var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function(
6868
(tree.node.nodeType === ELEMENT_NODE &&
6969
tree.node.nodeName.toLowerCase() === 'object' &&
7070
(tree.node.namespaceURI == null ||
71-
tree.node.namespaceURI === DOMNamespaces.html))
71+
tree.node.namespaceURI === Namespaces.html))
7272
) {
7373
insertTreeChildren(tree);
7474
parentNode.insertBefore(tree.node, referenceNode);

src/renderers/dom/stack/client/ReactDOMComponent.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
var AutoFocusUtils = require('AutoFocusUtils');
1515
var CSSPropertyOperations = require('CSSPropertyOperations');
1616
var DOMLazyTree = require('DOMLazyTree');
17-
var DOMNamespaces = require('DOMNamespaces');
17+
var Namespaces = require('DOMNamespaces').Namespaces;
1818
var DOMMarkupOperations = require('DOMMarkupOperations');
1919
var DOMProperty = require('DOMProperty');
2020
var DOMPropertyOperations = require('DOMPropertyOperations');
@@ -512,11 +512,11 @@ ReactDOMComponent.Mixin = {
512512
}
513513
if (
514514
namespaceURI == null ||
515-
(namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject')
515+
(namespaceURI === Namespaces.svg && parentTag === 'foreignobject')
516516
) {
517-
namespaceURI = DOMNamespaces.html;
517+
namespaceURI = Namespaces.html;
518518
}
519-
if (namespaceURI === DOMNamespaces.html) {
519+
if (namespaceURI === Namespaces.html) {
520520
if (__DEV__) {
521521
warning(
522522
isCustomComponentTag || this._tag === this._currentElement.type,
@@ -526,9 +526,9 @@ ReactDOMComponent.Mixin = {
526526
);
527527
}
528528
if (this._tag === 'svg') {
529-
namespaceURI = DOMNamespaces.svg;
529+
namespaceURI = Namespaces.svg;
530530
} else if (this._tag === 'math') {
531-
namespaceURI = DOMNamespaces.mathml;
531+
namespaceURI = Namespaces.mathml;
532532
}
533533
}
534534
this._namespaceURI = namespaceURI;
@@ -557,7 +557,7 @@ ReactDOMComponent.Mixin = {
557557
if (transaction.useCreateElement) {
558558
var ownerDocument = hostContainerInfo._ownerDocument;
559559
var el;
560-
if (namespaceURI === DOMNamespaces.html) {
560+
if (namespaceURI === Namespaces.html) {
561561
if (this._tag === 'script') {
562562
// Create the script via .innerHTML so its "parser-inserted" flag is
563563
// set to true and it does not execute
@@ -587,7 +587,7 @@ ReactDOMComponent.Mixin = {
587587
);
588588
didWarnShadyDOM = true;
589589
}
590-
if (this._namespaceURI === DOMNamespaces.html) {
590+
if (this._namespaceURI === Namespaces.html) {
591591
if (
592592
!isCustomComponentTag &&
593593
Object.prototype.toString.call(el) ===

src/renderers/shared/server/ReactPartialRenderer.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
'use strict';
1313

14+
var {
15+
Namespaces,
16+
getIntrinsicNamespace,
17+
getChildNamespace,
18+
} = require('DOMNamespaces');
1419
var DOMMarkupOperations = require('DOMMarkupOperations');
1520
var React = require('react');
1621
var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
@@ -443,6 +448,9 @@ class ReactDOMServerRenderer {
443448
constructor(element, makeStaticMarkup) {
444449
var children = React.isValidElement(element) ? [element] : toArray(element);
445450
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,
446454
children,
447455
childIndex: 0,
448456
context: emptyObject,
@@ -483,7 +491,7 @@ class ReactDOMServerRenderer {
483491
if (__DEV__) {
484492
setCurrentDebugStack(this.stack);
485493
}
486-
out += this.render(child, frame.context);
494+
out += this.render(child, frame.context, frame.domNamespace);
487495
if (__DEV__) {
488496
// TODO: Handle reentrant server render calls. This doesn't.
489497
resetCurrentDebugStack();
@@ -492,7 +500,7 @@ class ReactDOMServerRenderer {
492500
return out;
493501
}
494502

495-
render(child, context) {
503+
render(child, context, parentNamespace) {
496504
if (typeof child === 'string' || typeof child === 'number') {
497505
var text = '' + child;
498506
if (text === '') {
@@ -512,10 +520,11 @@ class ReactDOMServerRenderer {
512520
return '';
513521
} else {
514522
if (React.isValidElement(child)) {
515-
return this.renderDOM(child, context);
523+
return this.renderDOM(child, context, parentNamespace);
516524
} else {
517525
var children = toArray(child);
518526
var frame = {
527+
domNamespace: parentNamespace,
519528
children,
520529
childIndex: 0,
521530
context: context,
@@ -531,16 +540,25 @@ class ReactDOMServerRenderer {
531540
}
532541
}
533542

534-
renderDOM(element, context) {
543+
renderDOM(element, context, parentNamespace) {
535544
var tag = element.type.toLowerCase();
536545

546+
let namespace = parentNamespace;
547+
if (parentNamespace === Namespaces.html) {
548+
namespace = getIntrinsicNamespace(tag);
549+
}
550+
537551
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+
}
544562
}
545563

546564
validateDangerousTag(tag);
@@ -801,6 +819,7 @@ class ReactDOMServerRenderer {
801819
children = toArray(props.children);
802820
}
803821
var frame = {
822+
domNamespace: getChildNamespace(parentNamespace, element.type),
804823
tag,
805824
children,
806825
childIndex: 0,

0 commit comments

Comments
 (0)