diff --git a/src/browser/__tests__/ReactDOMSVG-test.js b/src/browser/__tests__/ReactDOMSVG-test.js new file mode 100644 index 0000000000..1ce05d6c80 --- /dev/null +++ b/src/browser/__tests__/ReactDOMSVG-test.js @@ -0,0 +1,41 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +/*jslint evil: true */ + +'use strict'; + +var React; +var ReactTestUtils; + +var SVGDOMNamespaces = { + xlink: 'http://www.w3.org/1999/xlink', + xml: 'http://www.w3.org/XML/1998/namespace', + xmlns: 'http://www.w3.org/2000/xmlns/' +}; + +describe('ReactDOMSVG', function() { + + beforeEach(function() { + React = require('React'); + ReactTestUtils = require('ReactTestUtils'); + }); + + it('creates initial namespaced markup', function() { + var markup = React.renderToString( + + + + ); + expect(markup).toContain('xlink:href="http://i.imgur.com/w7GCRPb.png"'); + }); + +}); diff --git a/src/browser/ui/dom/DOMProperty.js b/src/browser/ui/dom/DOMProperty.js index d6cc399e8a..c5b7b80b80 100644 --- a/src/browser/ui/dom/DOMProperty.js +++ b/src/browser/ui/dom/DOMProperty.js @@ -50,6 +50,9 @@ var DOMPropertyInjection = { * attribute name. Attribute names not specified use the **lowercase** * normalized name. * + * DOMAttributeNamespaces: object mapping React attribute name to the DOM + * attribute namespace URL. (Attribute names not specified use no namespace.) + * * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. * Property names not specified use the normalized name. * @@ -60,6 +63,7 @@ var DOMPropertyInjection = { */ injectDOMPropertyConfig: function(domPropertyConfig) { var Properties = domPropertyConfig.Properties || {}; + var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {}; var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; @@ -93,6 +97,13 @@ var DOMPropertyInjection = { DOMProperty.getAttributeName[propName] = lowerCased; } + if (DOMAttributeNamespaces.hasOwnProperty(propName)) { + DOMProperty.getAttributeNamespace[propName] = + DOMAttributeNamespaces[propName]; + } else { + DOMProperty.getAttributeNamespace[propName] = null; + } + DOMProperty.getPropertyName[propName] = DOMPropertyNames.hasOwnProperty(propName) ? DOMPropertyNames[propName] : @@ -182,6 +193,12 @@ var DOMProperty = { */ getAttributeName: {}, + /** + * Mapping from normalized names to namespaces. + * @type {Object} + */ + getAttributeNamespace: {}, + /** * Mapping from normalized names to properties on DOM node instances. * (This includes properties that mutate due to external factors.) diff --git a/src/browser/ui/dom/DOMPropertyOperations.js b/src/browser/ui/dom/DOMPropertyOperations.js index f049403184..b25f62c01d 100644 --- a/src/browser/ui/dom/DOMPropertyOperations.js +++ b/src/browser/ui/dom/DOMPropertyOperations.js @@ -126,9 +126,15 @@ var DOMPropertyOperations = { } else if (shouldIgnoreValue(name, value)) { this.deleteValueForProperty(node, name); } else if (DOMProperty.mustUseAttribute[name]) { + var attributeName = DOMProperty.getAttributeName[name]; + var namespace = DOMProperty.getAttributeNamespace[name]; // `setAttribute` with objects becomes only `[object]` in IE8/9, // ('' + value) makes it output the correct toString()-value. - node.setAttribute(DOMProperty.getAttributeName[name], '' + value); + if (namespace) { + node.setAttributeNS(namespace, attributeName, '' + value); + } else { + node.setAttribute(attributeName, '' + value); + } } else { var propName = DOMProperty.getPropertyName[name]; // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the diff --git a/src/browser/ui/dom/HTMLDOMPropertyConfig.js b/src/browser/ui/dom/HTMLDOMPropertyConfig.js index b814f3b611..d8095e03e7 100644 --- a/src/browser/ui/dom/HTMLDOMPropertyConfig.js +++ b/src/browser/ui/dom/HTMLDOMPropertyConfig.js @@ -39,7 +39,6 @@ if (ExecutionEnvironment.canUseDOM) { ); } - var HTMLDOMPropertyConfig = { isCustomAttribute: RegExp.prototype.test.bind( /^(data|aria)-[a-z_][a-z\d_.\-]*$/ diff --git a/src/browser/ui/dom/SVGDOMPropertyConfig.js b/src/browser/ui/dom/SVGDOMPropertyConfig.js index ef2f9c3ffc..528ab2bd45 100644 --- a/src/browser/ui/dom/SVGDOMPropertyConfig.js +++ b/src/browser/ui/dom/SVGDOMPropertyConfig.js @@ -17,6 +17,11 @@ var DOMProperty = require('DOMProperty'); var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE; +var NS = { + xlink: 'http://www.w3.org/1999/xlink', + xml: 'http://www.w3.org/XML/1998/namespace' +}; + var SVGDOMPropertyConfig = { Properties: { clipPath: MUST_USE_ATTRIBUTE, @@ -60,10 +65,32 @@ var SVGDOMPropertyConfig = { x1: MUST_USE_ATTRIBUTE, x2: MUST_USE_ATTRIBUTE, x: MUST_USE_ATTRIBUTE, + xlinkActuate: MUST_USE_ATTRIBUTE, + xlinkArcrole: MUST_USE_ATTRIBUTE, + xlinkHref: MUST_USE_ATTRIBUTE, + xlinkRole: MUST_USE_ATTRIBUTE, + xlinkShow: MUST_USE_ATTRIBUTE, + xlinkTitle: MUST_USE_ATTRIBUTE, + xlinkType: MUST_USE_ATTRIBUTE, + xmlBase: MUST_USE_ATTRIBUTE, + xmlLang: MUST_USE_ATTRIBUTE, + xmlSpace: MUST_USE_ATTRIBUTE, y1: MUST_USE_ATTRIBUTE, y2: MUST_USE_ATTRIBUTE, y: MUST_USE_ATTRIBUTE }, + DOMAttributeNamespaces: { + xlinkActuate: NS.xlink, + xlinkArcrole: NS.xlink, + xlinkHref: NS.xlink, + xlinkRole: NS.xlink, + xlinkShow: NS.xlink, + xlinkTitle: NS.xlink, + xlinkType: NS.xlink, + xmlBase: NS.xml, + xmlLang: NS.xml, + xmlSpace: NS.xml + }, DOMAttributeNames: { clipPath: 'clip-path', fillOpacity: 'fill-opacity', @@ -85,7 +112,17 @@ var SVGDOMPropertyConfig = { strokeOpacity: 'stroke-opacity', strokeWidth: 'stroke-width', textAnchor: 'text-anchor', - viewBox: 'viewBox' + viewBox: 'viewBox', + xlinkActuate: 'xlink:actuate', + xlinkArcrole: 'xlink:arcrole', + xlinkHref: 'xlink:href', + xlinkRole: 'xlink:role', + xlinkShow: 'xlink:show', + xlinkTitle: 'xlink:title', + xlinkType: 'xlink:type', + xmlBase: 'xml:base', + xmlLang: 'xml:lang', + xmlSpace: 'xml:space' } }; diff --git a/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js b/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js index 8cadb40d1f..27b57aed6d 100644 --- a/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js +++ b/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js @@ -197,6 +197,18 @@ describe('DOMPropertyOperations', function() { expect(stubNode.role).toBeUndefined(); }); + it('should set values as namespace attributes if necessary', function() { + spyOn(stubNode, 'setAttributeNS'); + DOMPropertyOperations.setValueForProperty( + stubNode, + 'xlinkHref', + 'about:blank' + ); + expect(stubNode.setAttributeNS.argsForCall.length).toBe(1); + expect(stubNode.setAttributeNS.argsForCall[0]) + .toEqual(['http://www.w3.org/1999/xlink', 'xlink:href', 'about:blank']); + }); + it('should convert attribute values to string first', function() { // Browsers default to this behavior, but some test environments do not. // This ensures that we have consistent behavior.