diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index a004f10d81..13952a0761 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -8,24 +8,27 @@ import './ng_dev_mode'; +import {Sanitizer} from '../sanitization/security'; + import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert'; +import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; +import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {LContainer} from './interfaces/container'; +import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; +import {ObjectOrientedRenderer3, ProceduralRenderer3, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {CurrentMatchesList, LView, LViewFlags, RootContext, TData, TView} from './interfaces/view'; import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; -import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; -import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; import {ViewRef} from './view_ref'; -import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; -import {Sanitizer} from '../sanitization/security'; + + /** * Directive (D) sets a property on all component instances using this constant as a key and the @@ -550,6 +553,28 @@ function getRenderFlags(view: LView): RenderFlags { RenderFlags.Update; } +////////////////////////// +//// Namespace +////////////////////////// +let _currentNS: string|null = null; + +export function setNS(namespace: string) { + _currentNS = namespace; +} + +export function setHtmlNS() { + _currentNS = null; +} + +export function setSvgNS() { + _currentNS = 'http://www.w3.org/2000/svg'; +} + +export function setMathML() { + _currentNS = 'http://www.w3.org/1998/Math/MathML'; +} + + ////////////////////////// //// Element ////////////////////////// @@ -573,7 +598,11 @@ export function elementStart( assertEqual(currentView.bindingIndex, -1, 'elements should be created before any bindings'); ngDevMode && ngDevMode.rendererCreateElement++; - const native: RElement = renderer.createElement(name); + + const native: RElement = _currentNS === null || isProceduralRenderer(renderer) ? + renderer.createElement(name) : + (renderer as ObjectOrientedRenderer3).createElementNS(_currentNS, name); + ngDevMode && assertDataInRange(index - 1); const node: LElementNode = diff --git a/packages/core/src/render3/interfaces/renderer.ts b/packages/core/src/render3/interfaces/renderer.ts index 54983d0a3b..49709897dc 100644 --- a/packages/core/src/render3/interfaces/renderer.ts +++ b/packages/core/src/render3/interfaces/renderer.ts @@ -36,6 +36,7 @@ export type Renderer3 = ObjectOrientedRenderer3 | ProceduralRenderer3; * */ export interface ObjectOrientedRenderer3 { createElement(tagName: string): RElement; + createElementNS(namespace: string, name: string): RElement; createTextNode(data: string): RText; querySelector(selectors: string): RElement|null; diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 64f8a6e020..2b63963bd6 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -176,6 +176,9 @@ { "name": "_currentInjector" }, + { + "name": "_currentNS" + }, { "name": "_devMode" }, diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts index 027a36f9f1..5da89f3af4 100644 --- a/packages/core/test/linker/integration_spec.ts +++ b/packages/core/test/linker/integration_spec.ts @@ -1820,7 +1820,7 @@ function declareTests({useJit}: {useJit: boolean}) { .toEqual('http://www.w3.org/2000/svg'); const firstAttribute = getDOM().getProperty(use, 'attributes')[0]; - expect(firstAttribute.name).toEqual('xlink:href'); + expect(firstAttribute.name).toEqual('href'); expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink'); }); diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index 7a59ec8834..751ab50bd8 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common'; import {RenderFlags, directiveInject} from '../../src/render3'; import {defineComponent} from '../../src/render3/definition'; -import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, setHtmlNS, setSvgNS, text, textBinding} from '../../src/render3/instructions'; import {LElementNode, LNode} from '../../src/render3/interfaces/node'; import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization'; @@ -392,6 +392,40 @@ describe('instructions', () => { expect(s.lastSanitizedValue).toBeFalsy(); }); }); + + describe('namespace', () => { + it('should render SVG', () => { + const t = new TemplateFixture(() => { + elementStart(0, 'div', ['id', 'container']); + setSvgNS(); + elementStart(1, 'svg', [ + // id="display" + 'id', + 'display', + // width="400" + 'width', + '400', + // height="300" + 'height', + '300', + ]); + elementStart(2, 'circle', ['cx', '200', 'cy', '150', 'fill', '#0000ff']); + elementEnd(); + elementEnd(); + setHtmlNS(); + elementEnd(); + }); + + + // Most browsers will print , some will print , both are valid + const standardHTML = + '
'; + const ie11HTML = + '
'; + + expect([standardHTML, ie11HTML]).toContain(t.html); + }); + }); }); class LocalSanitizedValue { diff --git a/packages/platform-browser/src/dom/dom_renderer.ts b/packages/platform-browser/src/dom/dom_renderer.ts index 6715d20513..a4684a1248 100644 --- a/packages/platform-browser/src/dom/dom_renderer.ts +++ b/packages/platform-browser/src/dom/dom_renderer.ts @@ -110,7 +110,7 @@ class DefaultDomRenderer2 implements Renderer2 { createElement(name: string, namespace?: string): any { if (namespace) { - return document.createElementNS(NAMESPACE_URIS[namespace], name); + return document.createElementNS(NAMESPACE_URIS[namespace] || namespace, name); } return document.createElement(name); @@ -150,13 +150,8 @@ class DefaultDomRenderer2 implements Renderer2 { setAttribute(el: any, name: string, value: string, namespace?: string): void { if (namespace) { - name = `${namespace}:${name}`; - const namespaceUri = NAMESPACE_URIS[namespace]; - if (namespaceUri) { - el.setAttributeNS(namespaceUri, name, value); - } else { - el.setAttribute(name, value); - } + const namespaceUri = NAMESPACE_URIS[namespace] || namespace; + el.setAttributeNS(namespaceUri, name, value); } else { el.setAttribute(name, value); } @@ -164,12 +159,8 @@ class DefaultDomRenderer2 implements Renderer2 { removeAttribute(el: any, name: string, namespace?: string): void { if (namespace) { - const namespaceUri = NAMESPACE_URIS[namespace]; - if (namespaceUri) { - el.removeAttributeNS(namespaceUri, name); - } else { - el.removeAttribute(`${namespace}:${name}`); - } + const namespaceUri = NAMESPACE_URIS[namespace] || namespace; + el.removeAttributeNS(namespaceUri, name); } else { el.removeAttribute(name); }