diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt
index ee3849f47efe7..8ac1c726f5b11 100644
--- a/scripts/fiber/tests-passing.txt
+++ b/scripts/fiber/tests-passing.txt
@@ -504,6 +504,7 @@ src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
* should render one portal
* should render many portals
* should render nested portals
+* should not apply SVG mode across portals
* should pass portal context when rendering subtree elsewhere
* should update portal context if it changes due to setState
* should update portal context if it changes due to re-render
@@ -654,6 +655,8 @@ src/renderers/dom/shared/__tests__/ReactDOMInvalidARIAHook-test.js
src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
* creates initial namespaced markup
+* creates elements with SVG namespace inside SVG tag during mount
+* creates elements with SVG namespace inside SVG tag during update
src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
* updates a mounted text component in place
diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js
index c5da108c5a828..45dc4665ae06b 100644
--- a/src/renderers/dom/fiber/ReactDOMFiber.js
+++ b/src/renderers/dom/fiber/ReactDOMFiber.js
@@ -33,6 +33,7 @@ var warning = require('warning');
var {
createElement,
+ getChildNamespace,
setInitialProperties,
updateProperties,
} = ReactDOMFiberComponent;
@@ -60,6 +61,11 @@ let selectionInformation : ?mixed = null;
var DOMRenderer = ReactFiberReconciler({
+ getChildHostContext(parentHostContext : string | null, type : string) {
+ const parentNamespace = parentHostContext;
+ return getChildNamespace(parentNamespace, type);
+ },
+
prepareForCommit() : void {
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
@@ -76,11 +82,11 @@ var DOMRenderer = ReactFiberReconciler({
createInstance(
type : string,
props : Props,
- internalInstanceHandle : Object
+ rootContainerInstance : Container,
+ hostContext : string | null,
+ internalInstanceHandle : Object,
) : Instance {
- const root = document.documentElement; // HACK
-
- const domElement : Instance = createElement(type, props, root);
+ const domElement : Instance = createElement(type, props, rootContainerInstance, hostContext);
precacheFiberNode(internalInstanceHandle, domElement);
return domElement;
},
@@ -89,10 +95,18 @@ var DOMRenderer = ReactFiberReconciler({
parentInstance.appendChild(child);
},
- finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
- const root = document.documentElement; // HACK
-
- setInitialProperties(domElement, type, props, root);
+ finalizeInitialChildren(
+ domElement : Instance,
+ props : Props,
+ rootContainerInstance : Container,
+ ) : void {
+ // TODO: we normalize here because DOM renderer expects tag to be lowercase.
+ // We can change DOM renderer to compare special case against upper case,
+ // and use tagName (which is upper case for HTML DOM elements). Or we could
+ // let the renderer "normalize" the fiber type so we don't have to read
+ // the type from DOM. However we need to remember SVG is case-sensitive.
+ var tag = domElement.tagName.toLowerCase();
+ setInitialProperties(domElement, tag, props, rootContainerInstance);
},
prepareUpdate(
@@ -107,14 +121,19 @@ var DOMRenderer = ReactFiberReconciler({
domElement : Instance,
oldProps : Props,
newProps : Props,
- internalInstanceHandle : Object
+ rootContainerInstance : Container,
+ internalInstanceHandle : Object,
) : void {
- var type = domElement.tagName.toLowerCase(); // HACK
- var root = document.documentElement; // HACK
+ // TODO: we normalize here because DOM renderer expects tag to be lowercase.
+ // We can change DOM renderer to compare special case against upper case,
+ // and use tagName (which is upper case for HTML DOM elements). Or we could
+ // let the renderer "normalize" the fiber type so we don't have to read
+ // the type from DOM. However we need to remember SVG is case-sensitive.
+ var tag = domElement.tagName.toLowerCase();
// Update the internal instance handle so that we know which props are
// the current ones.
precacheFiberNode(internalInstanceHandle, domElement);
- updateProperties(domElement, type, oldProps, newProps, root);
+ updateProperties(domElement, tag, oldProps, newProps, rootContainerInstance);
},
createTextInstance(text : string, internalInstanceHandle : Object) : TextInstance {
diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js
index 7c6ebd9b3c65f..fdb3877196298 100644
--- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js
+++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js
@@ -46,6 +46,11 @@ var CHILDREN = 'children';
var STYLE = 'style';
var HTML = '__html';
+var {
+ svg: SVG_NAMESPACE,
+ math: MATH_NAMESPACE,
+} = DOMNamespaces;
+
// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
var DOC_FRAGMENT_TYPE = 11;
@@ -451,45 +456,49 @@ function updateDOMProperties(
}
}
-var ReactDOMFiberComponent = {
+// Assumes there is no parent namespace.
+function getIntrinsicNamespace(type : string) : string | null {
+ switch (type) {
+ case 'svg':
+ return SVG_NAMESPACE;
+ case 'math':
+ return MATH_NAMESPACE;
+ default:
+ return null;
+ }
+}
- // TODO: Use this to keep track of changes to the host context and use this
- // to determine whether we switch to svg and back.
- // TODO: Does this need to check the current namespace? In case these tags
- // happen to be valid in some other namespace.
- isNewHostContainer(tag : string) {
- return tag === 'svg' || tag === 'foreignobject';
+var ReactDOMFiberComponent = {
+ getChildNamespace(parentNamespace : string | null, type : string) : string | null {
+ if (parentNamespace == null) {
+ // No parent namespace: potential entry point.
+ return getIntrinsicNamespace(type);
+ }
+ if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
+ // We're leaving SVG.
+ return null;
+ }
+ // By default, pass namespace below.
+ return parentNamespace;
},
createElement(
- tag : string,
+ type : string,
props : Object,
- rootContainerElement : Element
+ rootContainerElement : Element,
+ parentNamespace : string | null
) : Element {
- validateDangerousTag(tag);
+ validateDangerousTag(type);
// TODO:
- // tag.toLowerCase(); Do we need to apply lower case only on non-custom elements?
+ // const tag = type.toLowerCase(); Do we need to apply lower case only on non-custom elements?
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
- var namespaceURI = rootContainerElement.namespaceURI;
- if (namespaceURI == null ||
- namespaceURI === DOMNamespaces.svg &&
- rootContainerElement.tagName === 'foreignObject') {
- namespaceURI = DOMNamespaces.html;
- }
- if (namespaceURI === DOMNamespaces.html) {
- if (tag === 'svg') {
- namespaceURI = DOMNamespaces.svg;
- } else if (tag === 'math') {
- namespaceURI = DOMNamespaces.mathml;
- }
- // TODO: Make this a new root container element.
- }
-
var ownerDocument = rootContainerElement.ownerDocument;
var domElement : Element;
- if (namespaceURI === DOMNamespaces.html) {
+ var namespaceURI = parentNamespace || getIntrinsicNamespace(type);
+ if (namespaceURI == null) {
+ const tag = type.toLowerCase();
if (tag === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
@@ -499,17 +508,17 @@ var ReactDOMFiberComponent = {
var firstChild = ((div.firstChild : any) : HTMLScriptElement);
domElement = div.removeChild(firstChild);
} else if (props.is) {
- domElement = ownerDocument.createElement(tag, props.is);
+ domElement = ownerDocument.createElement(type, props.is);
} else {
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
- domElement = ownerDocument.createElement(tag);
+ domElement = ownerDocument.createElement(type);
}
} else {
domElement = ownerDocument.createElementNS(
namespaceURI,
- tag
+ type
);
}
diff --git a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
index 13f6a41499b3e..04c290495e4e6 100644
--- a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
+++ b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
@@ -333,6 +333,62 @@ describe('ReactDOMFiber', () => {
expect(container.innerHTML).toBe('');
});
+ it('should not apply SVG mode across portals', () => {
+ var portalContainer = document.createElement('div');
+
+ ReactDOM.render(
+ ,
+ container
+ );
+
+ const div = portalContainer.childNodes[0];
+ const image1 = container.firstChild.childNodes[0];
+ const image2 = container.firstChild.childNodes[1];
+ expect(div.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
+ expect(div.tagName).toBe('DIV');
+ expect(image1.namespaceURI).toBe('http://www.w3.org/2000/svg');
+ expect(image1.tagName).toBe('image');
+ expect(
+ image1.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
+ ).toBe('http://i.imgur.com/w7GCRPb.png');
+ expect(image2.namespaceURI).toBe('http://www.w3.org/2000/svg');
+ expect(image2.tagName).toBe('image');
+ expect(
+ image2.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
+ ).toBe('http://i.imgur.com/w7GCRPb.png');
+
+ ReactDOM.render(
+ ,
+ container
+ );
+
+ const span = portalContainer.childNodes[0];
+ expect(span.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
+ expect(span.tagName).toBe('SPAN');
+ expect(container.firstChild.childNodes[0]).toBe(image1);
+ const g = container.firstChild.childNodes[1];
+ expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg');
+ expect(g.tagName).toBe('g');
+
+ ReactDOM.unmountComponentAtNode(container);
+ expect(portalContainer.innerHTML).toBe('');
+ expect(container.innerHTML).toBe('');
+ });
+
it('should pass portal context when rendering subtree elsewhere', () => {
var portalContainer = document.createElement('div');
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js b/src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
index 30760d3ae88db..37818dc3fa949 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
@@ -12,12 +12,14 @@
'use strict';
var React;
+var ReactDOM;
var ReactDOMServer;
describe('ReactDOMSVG', () => {
beforeEach(() => {
React = require('React');
+ ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
});
@@ -30,4 +32,100 @@ describe('ReactDOMSVG', () => {
expect(markup).toContain('xlink:href="http://i.imgur.com/w7GCRPb.png"');
});
+ it('creates elements with SVG namespace inside SVG tag during mount', () => {
+ var node = document.createElement('div');
+ var div, foreignDiv, foreignObject, g, image, image2, p, svg;
+ ReactDOM.render(
+