feat(ivy): add namespace instructions for SVG and others (#23899)

PR Close #23899
This commit is contained in:
Ben Lesh 2018-05-25 13:14:49 -07:00 committed by Victor Berchet
parent c494d3cf60
commit 81e4b2a4bf
6 changed files with 80 additions and 22 deletions

View File

@ -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 =

View File

@ -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;

View File

@ -176,6 +176,9 @@
{
"name": "_currentInjector"
},
{
"name": "_currentNS"
},
{
"name": "_devMode"
},

View File

@ -1820,7 +1820,7 @@ function declareTests({useJit}: {useJit: boolean}) {
.toEqual('http://www.w3.org/2000/svg');
const firstAttribute = getDOM().getProperty(<Element>use, 'attributes')[0];
expect(firstAttribute.name).toEqual('xlink:href');
expect(firstAttribute.name).toEqual('href');
expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink');
});

View File

@ -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 <circle></circle>, some will print <circle />, both are valid
const standardHTML =
'<div id="container"><svg id="display" width="400" height="300"><circle cx="200" cy="150" fill="#0000ff"></circle></svg></div>';
const ie11HTML =
'<div id="container"><svg xmlns="http://www.w3.org/2000/svg" id="display" width="400" height="300"><circle fill="#0000ff" cx="200" cy="150" /></svg></div>';
expect([standardHTML, ie11HTML]).toContain(t.html);
});
});
});
class LocalSanitizedValue {

View File

@ -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);
}