Compare commits

...

3 Commits

Author SHA1 Message Date
Ben Lesh
b1d8784f79 feat(ivy): add element instruction
Adds a simplified element instruction that can be used if an element
has no children.
2018-05-30 15:13:39 -07:00
Ben Lesh
6a4ad11e87 feat(ivy): added namespaced attributes 2018-05-30 15:11:07 -07:00
Ben Lesh
a1a3af9e23 feat(ivy): add namespace instructions for SVG and others 2018-05-30 13:56:52 -07:00
21 changed files with 344 additions and 135 deletions

View File

@ -17,10 +17,12 @@ export class Identifiers {
static PATCH_DEPS = 'patchedDeps'; static PATCH_DEPS = 'patchedDeps';
/* Instructions */ /* Instructions */
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE}; static elementStart: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE}; static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE};
static element: o.ExternalReference = {name: 'ɵEe', moduleName: CORE};
static elementProperty: o.ExternalReference = {name: 'ɵp', moduleName: CORE}; static elementProperty: o.ExternalReference = {name: 'ɵp', moduleName: CORE};
static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE}; static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE};

View File

@ -314,11 +314,20 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (i18nMessages.length > 0) { if (i18nMessages.length > 0) {
this._creationCode.push(...i18nMessages); this._creationCode.push(...i18nMessages);
} }
this.instruction(
this._creationCode, element.sourceSpan, R3.createElement, ...trimTrailingNulls(parameters)); let isSelfClosingElement = element.outputs.length === 0 && element.children.length === 0;
const implicit = o.variable(CONTEXT_NAME); const implicit = o.variable(CONTEXT_NAME);
if (isSelfClosingElement) {
this.instruction(
this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
} else {
this.instruction(
this._creationCode, element.sourceSpan, R3.elementStart,
...trimTrailingNulls(parameters));
// Generate Listeners (outputs) // Generate Listeners (outputs)
element.outputs.forEach((outputAst: t.BoundEvent) => { element.outputs.forEach((outputAst: t.BoundEvent) => {
const elName = sanitizeIdentifier(element.name); const elName = sanitizeIdentifier(element.name);
@ -331,7 +340,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
}); });
const bindingExpr = convertActionBinding( const bindingExpr = convertActionBinding(
bindingScope, implicit, outputAst.handler, 'b', () => error('Unexpected interpolation')); bindingScope, implicit, outputAst.handler, 'b',
() => error('Unexpected interpolation'));
const handler = o.fn( const handler = o.fn(
[new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts], [new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts],
o.INFERRED_TYPE, null, functionName); o.INFERRED_TYPE, null, functionName);
@ -339,7 +349,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name),
handler); handler);
}); });
}
// Generate element input bindings // Generate element input bindings
element.inputs.forEach((input: t.BoundAttribute) => { element.inputs.forEach((input: t.BoundAttribute) => {
@ -367,10 +377,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
t.visitAll(this, element.children); t.visitAll(this, element.children);
} }
if (!isSelfClosingElement) {
// Finish element construction mode. // Finish element construction mode.
this.instruction( this.instruction(
this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd); this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd);
}
// Restore the state before exiting this node // Restore the state before exiting this node
this._inI18nSection = wasInI18nSection; this._inI18nSection = wasInI18nSection;
} }

View File

@ -90,8 +90,7 @@ describe('compiler compliance', () => {
const template = ` const template = `
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'div'); $r3$.ɵEe(0, 'div');
$r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'id', $r3$.ɵb(ctx.id)); $r3$.ɵp(0, 'id', $r3$.ɵb(ctx.id));
@ -135,9 +134,8 @@ describe('compiler compliance', () => {
const template = ` const template = `
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'div'); $r3$.ɵEe(0, 'div');
$r3$.ɵPp(1,'pipe'); $r3$.ɵPp(1,'pipe');
$r3$.ɵe();
$r3$.ɵrS(10); $r3$.ɵrS(10);
} }
if (rf & 2) { if (rf & 2) {
@ -181,8 +179,7 @@ describe('compiler compliance', () => {
const template = ` const template = `
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'div'); $r3$.ɵEe(0, 'div');
$r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵkn(0, 'error', $r3$.ɵb(ctx.error)); $r3$.ɵkn(0, 'error', $r3$.ɵb(ctx.error));
@ -254,8 +251,7 @@ describe('compiler compliance', () => {
factory: function MyComponent_Factory() { return new MyComponent(); }, factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'child', $c1$); $r3$.ɵEe(0, 'child', $c1$);
$r3$.ɵe();
$r3$.ɵT(1, '!'); $r3$.ɵT(1, '!');
} }
}, },
@ -461,8 +457,7 @@ describe('compiler compliance', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵEe(0, 'my-comp');
$r3$.ɵe();
$r3$.ɵrS(2); $r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
@ -541,8 +536,7 @@ describe('compiler compliance', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵEe(0, 'my-comp');
$r3$.ɵe();
$r3$.ɵrS(10); $r3$.ɵrS(10);
} }
if (rf & 2) { if (rf & 2) {
@ -603,8 +597,7 @@ describe('compiler compliance', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'object-comp'); $r3$.ɵEe(0, 'object-comp');
$r3$.ɵe();
$r3$.ɵrS(2); $r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
@ -669,8 +662,7 @@ describe('compiler compliance', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'nested-comp'); $r3$.ɵEe(0, 'nested-comp');
$r3$.ɵe();
$r3$.ɵrS(7); $r3$.ɵrS(7);
} }
if (rf & 2) { if (rf & 2) {
@ -814,8 +806,7 @@ describe('compiler compliance', () => {
var $tmp$: $any$; var $tmp$: $any$;
if (rf & 1) { if (rf & 1) {
$r3$.ɵQ(0, SomeDirective, true); $r3$.ɵQ(0, SomeDirective, true);
$r3$.ɵE(1, 'div', $e0_attrs$); $r3$.ɵEe(1, 'div', $e0_attrs$);
$r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first)); ($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first));
@ -1009,8 +1000,7 @@ describe('compiler compliance', () => {
factory: function MyComponent_Factory() { return new MyComponent(); }, factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'input', null, $c1$); $r3$.ɵEe(0, 'input', null, $c1$);
$r3$.ɵe();
$r3$.ɵT(2); $r3$.ɵT(2);
} }
const $user$ = $r3$.ɵld(1); const $user$ = $r3$.ɵld(1);
@ -1089,10 +1079,8 @@ describe('compiler compliance', () => {
factory: function SimpleLayout_Factory() { return new SimpleLayout(); }, factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
template: function SimpleLayout_Template(rf: IDENT, ctx: IDENT) { template: function SimpleLayout_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'lifecycle-comp'); $r3$.ɵEe(0, 'lifecycle-comp');
$r3$.ɵe(); $r3$.ɵEe(1, 'lifecycle-comp');
$r3$.ɵE(1, 'lifecycle-comp');
$r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));

View File

@ -75,8 +75,7 @@ describe('compiler compliance: bindings', () => {
const template = ` const template = `
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
if (rf & 1) { if (rf & 1) {
$i0$.ɵE(0, 'a'); $i0$.ɵEe(0, 'a');
$i0$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title)); $i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title));
@ -108,8 +107,7 @@ describe('compiler compliance: bindings', () => {
const template = ` const template = `
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
if (rf & 1) { if (rf & 1) {
$i0$.ɵE(0, 'a'); $i0$.ɵEe(0, 'a');
$i0$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, '')); $i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, ''));

View File

@ -148,8 +148,7 @@ describe('i18n support in the view compiler', () => {
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'div', $c1$); $r3$.ɵEe(0, 'div', $c1$);
$r3$.ɵe();
} }
} }
`; `;

View File

@ -109,4 +109,43 @@ describe('compiler compliance: template', () => {
expectEmit(result.source, template, 'Incorrect template'); expectEmit(result.source, template, 'Incorrect template');
}); });
// tslint:disable-next-line:ban
describe('element with no children', () => {
it('should just use the element instruction', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: 'my-component',
template: \`
<div></div>\`
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent], imports: [CommonModule]})
export class MyModule {}
`
}
};
const template = `
// ...
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
if (rf & 1) {
$i0$.ɵEe(0,'div');
}
}`;
debugger;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
});
}); });

View File

@ -28,6 +28,7 @@ export {
NC as ɵNC, NC as ɵNC,
C as ɵC, C as ɵC,
E as ɵE, E as ɵE,
Ee as ɵEe,
L as ɵL, L as ɵL,
T as ɵT, T as ɵT,
V as ɵV, V as ɵV,

View File

@ -260,8 +260,11 @@ export function injectAttribute(attrNameToInject: string): string|undefined {
const attrs = tElement.attrs; const attrs = tElement.attrs;
if (attrs) { if (attrs) {
for (let i = 0; i < attrs.length; i = i + 2) { for (let i = 0; i < attrs.length; i = i + 2) {
const attrName = attrs[i]; let attrName = attrs[i];
if (attrName === AttributeMarker.SELECT_ONLY) break; if (attrName === AttributeMarker.SELECT_ONLY) break;
if (attrName === 0) { // NS.FULL
attrName = attrs[i += 2];
}
if (attrName == attrNameToInject) { if (attrName == attrNameToInject) {
return attrs[i + 1] as string; return attrs[i + 1] as string;
} }

View File

@ -50,6 +50,7 @@ export {
elementEnd as e, elementEnd as e,
elementProperty as p, elementProperty as p,
elementStart as E, elementStart as E,
element as Ee,
elementStyle as s, elementStyle as s,
elementStyleNamed as sn, elementStyleNamed as sn,

View File

@ -8,24 +8,27 @@
import './ng_dev_mode'; import './ng_dev_mode';
import {Sanitizer} from '../sanitization/security';
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert'; 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 {LContainer} from './interfaces/container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector'; import {LInjector} from './interfaces/injector';
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query'; import {LQueries} from './interfaces/query';
import {ObjectOrientedRenderer3, ProceduralRenderer3, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, 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 {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 {assertNodeType} from './node_assert';
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode} from './node_manipulation'; import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; 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 {isDifferent, stringify} from './util';
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
import {ViewRef} from './view_ref'; 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 * Directive (D) sets a property on all component instances using this constant as a key and the
@ -571,6 +574,28 @@ function getRenderFlags(view: LView): RenderFlags {
RenderFlags.Update; 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 //// Element
////////////////////////// //////////////////////////
@ -595,7 +620,11 @@ export function elementStart(
currentView.bindingStartIndex, -1, 'elements should be created before any bindings'); currentView.bindingStartIndex, -1, 'elements should be created before any bindings');
ngDevMode && ngDevMode.rendererCreateElement++; 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); ngDevMode && assertDataInRange(index - 1);
const node: LElementNode = const node: LElementNode =
@ -823,7 +852,19 @@ export function createTView(
function setUpAttributes(native: RElement, attrs: TAttributes): void { function setUpAttributes(native: RElement, attrs: TAttributes): void {
const isProc = isProceduralRenderer(renderer); const isProc = isProceduralRenderer(renderer);
for (let i = 0; i < attrs.length; i += 2) { for (let i = 0; i < attrs.length; i += 2) {
const attrName = attrs[i]; let attrName = attrs[i];
if (attrName === 0) { // NS.FULL
// Namespaced attribute
const attrNS = attrs[i + 1] as string;
attrName = attrs[i + 2] as string;
const attrVal = attrs[i + 3] as string;
i += 2;
if (isProc) {
(renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, attrNS);
} else {
native.setAttributeNS(attrNS, attrName, attrVal);
}
} else {
if (attrName === AttributeMarker.SELECT_ONLY) break; if (attrName === AttributeMarker.SELECT_ONLY) break;
if (attrName !== NG_PROJECT_AS_ATTR_NAME) { if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
const attrVal = attrs[i + 1]; const attrVal = attrs[i + 1];
@ -834,6 +875,7 @@ function setUpAttributes(native: RElement, attrs: TAttributes): void {
native.setAttribute(attrName as string, attrVal as string); native.setAttribute(attrName as string, attrVal as string);
} }
} }
}
} }
export function createError(text: string, token: any) { export function createError(text: string, token: any) {
@ -964,6 +1006,15 @@ export function elementEnd() {
queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView); queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView);
} }
/** Marks the beginning and end of an element in one call. */
export function element(
index: number, name: string, attrs?: TAttributes | null | undefined,
localRefs?: string[] | null | undefined): RElement {
const relement = elementStart(index, name, attrs, localRefs);
elementEnd();
return relement;
}
/** /**
* Updates the value of removes an attribute on an Element. * Updates the value of removes an attribute on an Element.
* *
@ -1440,7 +1491,8 @@ function generateInitialInputs(
const attrs = tNode.attrs !; const attrs = tNode.attrs !;
for (let i = 0; i < attrs.length; i += 2) { for (let i = 0; i < attrs.length; i += 2) {
const attrName = attrs[i]; const first = attrs[i];
const attrName = first === 0 ? attrs[i += 2] : first; // 0 = NS.FULL
const minifiedInputName = inputs[attrName]; const minifiedInputName = inputs[attrName];
const attrValue = attrs[i + 1]; const attrValue = attrs[i + 1];
@ -1852,7 +1904,7 @@ function appendToProjectionNode(
* - 1 based index of the selector from the {@link projectionDef} * - 1 based index of the selector from the {@link projectionDef}
*/ */
export function projection( export function projection(
nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void { nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: TAttributes): void {
const node = createLNode( const node = createLNode(
nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null}); nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null});

View File

@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {LContainer} from './container'; import {LContainer} from './container';
import {LInjector} from './injector'; import {LInjector} from './injector';
import {LProjection} from './projection'; import {LProjection} from './projection';
@ -14,6 +13,16 @@ import {RElement, RNode, RText} from './renderer';
import {LView, TData, TView} from './view'; import {LView, TData, TView} from './view';
/**
* Namespace attribute flags.
*/
export const enum NS {
/**
* Use the next value as the full namespaces URI, the values after that
* are then the name and the value, respectively.
*/
FULL = 0,
}
/** /**
* TNodeType corresponds to the TNode.type property. It contains information * TNodeType corresponds to the TNode.type property. It contains information
@ -179,7 +188,7 @@ export const enum AttributeMarker {
* - attribute names and values * - attribute names and values
* - special markers acting as flags to alter attributes processing. * - special markers acting as flags to alter attributes processing.
*/ */
export type TAttributes = (string | AttributeMarker)[]; export type TAttributes = (string | AttributeMarker | NS)[];
/** /**
* LNode binding data (flyweight) for a particular node that is shared between all templates * LNode binding data (flyweight) for a particular node that is shared between all templates

View File

@ -36,6 +36,7 @@ export type Renderer3 = ObjectOrientedRenderer3 | ProceduralRenderer3;
* */ * */
export interface ObjectOrientedRenderer3 { export interface ObjectOrientedRenderer3 {
createElement(tagName: string): RElement; createElement(tagName: string): RElement;
createElementNS(namespace: string, name: string): RElement;
createTextNode(data: string): RText; createTextNode(data: string): RText;
querySelector(selectors: string): RElement|null; querySelector(selectors: string): RElement|null;

View File

@ -12,6 +12,7 @@ import {assertNotNull} from './assert';
import {AttributeMarker, TAttributes, TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; import {AttributeMarker, TAttributes, TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection'; import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
const unusedValueToPlacateAjd = unused1 + unused2; const unusedValueToPlacateAjd = unused1 + unused2;
function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean { function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean {
@ -106,8 +107,12 @@ function findAttrIndexInNode(name: string, attrs: TAttributes | null): number {
if (attrs === null) return -1; if (attrs === null) return -1;
for (let i = 0; i < attrs.length; i += step) { for (let i = 0; i < attrs.length; i += step) {
const attrName = attrs[i]; const attrName = attrs[i];
if (attrName === name) return i; if (attrName === 0) {
if (attrName === AttributeMarker.SELECT_ONLY) { // NS.FULL
step = 2;
} else if (attrName === name) {
return i;
} else if (attrName === AttributeMarker.SELECT_ONLY) {
step = 1; step = 1;
} }
} }

View File

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

View File

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

View File

@ -69,8 +69,7 @@ describe('components & directives', () => {
factory: () => new MyComponent(), factory: () => new MyComponent(),
template: function(rf: $RenderFlags$, ctx: $MyComponent$) { template: function(rf: $RenderFlags$, ctx: $MyComponent$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'child', $e0_attrs$); $r3$.ɵEe(0, 'child', $e0_attrs$);
$r3$.ɵe();
$r3$.ɵT(1, '!'); $r3$.ɵT(1, '!');
} }
} }
@ -455,8 +454,7 @@ describe('components & directives', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-array-comp'); $r3$.ɵEe(0, 'my-array-comp');
$r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'names', rf & 1 ? $e0_arr$ : $r3$.ɵNC); $r3$.ɵp(0, 'names', rf & 1 ? $e0_arr$ : $r3$.ɵNC);
@ -500,8 +498,7 @@ describe('components & directives', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-array-comp'); $r3$.ɵEe(0, 'my-array-comp');
$r3$.ɵe();
$r3$.ɵrS(1); $r3$.ɵrS(1);
} }
if (rf & 2) { if (rf & 2) {
@ -606,8 +603,7 @@ describe('components & directives', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-array-comp'); $r3$.ɵEe(0, 'my-array-comp');
$r3$.ɵe();
$r3$.ɵrS(2); $r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
@ -717,8 +713,7 @@ describe('components & directives', () => {
factory: function MyApp_Factory() { return new MyApp(); }, factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(rf: $RenderFlags$, c: $any$) { template: function MyApp_Template(rf: $RenderFlags$, c: $any$) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵEe(0, 'my-comp');
$r3$.ɵe();
$r3$.ɵrS(10); $r3$.ɵrS(10);
} }
if (rf & 2) { if (rf & 2) {

View File

@ -58,8 +58,7 @@ describe('queries', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵQ(0, SomeDirective, false); $r3$.ɵQ(0, SomeDirective, false);
$r3$.ɵQ(1, SomeDirective, false); $r3$.ɵQ(1, SomeDirective, false);
$r3$.ɵE(2, 'div', $e1_attrs$); $r3$.ɵEe(2, 'div', $e1_attrs$);
$r3$.ɵe();
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵqR($tmp$ = $r3$.ɵld<QueryList<any>>(0)) && (ctx.someDir = $tmp$.first); $r3$.ɵqR($tmp$ = $r3$.ɵld<QueryList<any>>(0)) && (ctx.someDir = $tmp$.first);

View File

@ -10,14 +10,14 @@ import {NgForOfContext} from '@angular/common';
import {RenderFlags, directiveInject} from '../../src/render3'; import {RenderFlags, directiveInject} from '../../src/render3';
import {defineComponent} from '../../src/render3/definition'; 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, element, 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 {LElementNode, LNode, NS} from '../../src/render3/interfaces/node';
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization'; import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
import {NgForOf} from './common_with_def'; import {NgForOf} from './common_with_def';
import {ComponentFixture, TemplateFixture} from './render_util'; import {ComponentFixture, TemplateFixture, toHtml} from './render_util';
describe('instructions', () => { describe('instructions', () => {
function createAnchor() { function createAnchor() {
@ -54,6 +54,42 @@ describe('instructions', () => {
rendererSetAttribute: 2 rendererSetAttribute: 2
}); });
}); });
it('should use sanitizer function even on elements with namespaced attributes', () => {
const t = new TemplateFixture(() => {
element(0, 'div', [
NS.FULL,
'http://www.example.com/2004/test',
'whatever',
'abc',
]);
});
t.update(() => elementAttribute(0, 'title', 'javascript:true', sanitizeUrl));
let standardHTML = '<div whatever="abc" title="unsafe:javascript:true"></div>';
let ieHTML = '<div title="unsafe:javascript:true" whatever="abc"></div>';
expect([standardHTML, ieHTML]).toContain(t.html);
t.update(
() => elementAttribute(
0, 'title', bypassSanitizationTrustUrl('javascript:true'), sanitizeUrl));
standardHTML = '<div whatever="abc" title="javascript:true"></div>';
ieHTML = '<div title="javascript:true" whatever="abc"></div>';
expect([standardHTML, ieHTML]).toContain(t.html);
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2,
tView: 1,
rendererCreateElement: 1,
rendererSetAttribute: 2
});
});
}); });
describe('elementProperty', () => { describe('elementProperty', () => {
@ -355,6 +391,101 @@ describe('instructions', () => {
expect(s.lastSanitizedValue).toBeFalsy(); 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',
// test:title="abc"
NS.FULL,
'http://www.example.com/2014/test',
'title',
'abc',
]);
element(2, 'circle', ['cx', '200', 'cy', '150', 'fill', '#0000ff']);
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" title="abc"><circle cx="200" cy="150" fill="#0000ff"></circle></svg></div>';
const ie11HTML =
'<div id="container"><svg xmlns="http://www.w3.org/2000/svg" xmlns:NS1="http://www.example.com/2014/test" NS1:title="abc" id="display" width="400" height="300"><circle fill="#0000ff" cx="200" cy="150" /></svg></div>';
expect([standardHTML, ie11HTML]).toContain(t.html);
});
it('should set an attribute with a namespace', () => {
const t = new TemplateFixture(() => {
element(0, 'div', [
'id',
'container',
// test:title="abc"
NS.FULL,
'http://www.example.com/2014/test',
'title',
'abc',
]);
});
const standardHTML = '<div id="container" title="abc"></div>';
const ie11HTML =
'<div id="container" xmlns:NS1="https://www.example.com/2014/test" NS1:title="abc"></div>';
expect([standardHTML, ie11HTML]).toContain(t.html);
});
it('should set attributes including more than one namespaced attribute', () => {
const t = new TemplateFixture(() => {
element(0, 'div', [
'id',
'container',
// NS1:title="abc"
NS.FULL,
'http://www.example.com/2014/test',
'title',
'abc',
// style="background: #dead11"
'style',
'background: #dead11',
// NS1:whatever="wee"
NS.FULL,
'http://www.example.com/2014/test',
'whatever',
'wee',
// NS2:shazbot="wocka wocka"
NS.FULL,
'http://www.whatever.com/2016/blah',
'shazbot',
'wocka wocka',
]);
});
const standardHTML =
'<div id="container" title="abc" style="background: #dead11" whatever="wee" shazbot="wocka wocka"></div>';
const ieHTML =
'<div id="container" style="background: rgb(222, 173, 17);" title="abc" whatever="wee" shazbot="wocka wocka"></div>';
expect([standardHTML, ieHTML]).toContain(t.html);
});
});
}); });
class LocalSanitizedValue { class LocalSanitizedValue {

View File

@ -70,7 +70,7 @@ describe('render3 integration test', () => {
expect(renderToHtml(Template, undefined)).toEqual(''); expect(renderToHtml(Template, undefined)).toEqual('');
expect(ngDevMode).toHaveProperties({ expect(ngDevMode).toHaveProperties({
firstTemplatePass: 0, firstTemplatePass: 0,
tNode: 0, tNode: 2,
tView: 1, tView: 1,
rendererSetText: 2, rendererSetText: 2,
}); });
@ -90,7 +90,7 @@ describe('render3 integration test', () => {
expect(renderToHtml(Template, null)).toEqual(''); expect(renderToHtml(Template, null)).toEqual('');
expect(ngDevMode).toHaveProperties({ expect(ngDevMode).toHaveProperties({
firstTemplatePass: 0, firstTemplatePass: 0,
tNode: 0, tNode: 2,
tView: 1, tView: 1,
rendererSetText: 2, rendererSetText: 2,
}); });
@ -109,7 +109,7 @@ describe('render3 integration test', () => {
expect(renderToHtml(Template, 'twice')).toEqual('once'); expect(renderToHtml(Template, 'twice')).toEqual('once');
expect(ngDevMode).toHaveProperties({ expect(ngDevMode).toHaveProperties({
firstTemplatePass: 0, firstTemplatePass: 0,
tNode: 0, tNode: 2,
tView: 1, tView: 1,
rendererSetText: 1, rendererSetText: 1,
}); });

View File

@ -110,7 +110,7 @@ class DefaultDomRenderer2 implements Renderer2 {
createElement(name: string, namespace?: string): any { createElement(name: string, namespace?: string): any {
if (namespace) { if (namespace) {
return document.createElementNS(NAMESPACE_URIS[namespace], name); return document.createElementNS(NAMESPACE_URIS[namespace] || namespace, name);
} }
return document.createElement(name); return document.createElement(name);
@ -150,26 +150,17 @@ class DefaultDomRenderer2 implements Renderer2 {
setAttribute(el: any, name: string, value: string, namespace?: string): void { setAttribute(el: any, name: string, value: string, namespace?: string): void {
if (namespace) { if (namespace) {
name = `${namespace}:${name}`; const namespaceUri = NAMESPACE_URIS[namespace] || namespace;
const namespaceUri = NAMESPACE_URIS[namespace];
if (namespaceUri) {
el.setAttributeNS(namespaceUri, name, value); el.setAttributeNS(namespaceUri, name, value);
} else { } else {
el.setAttribute(name, value); el.setAttribute(name, value);
} }
} else {
el.setAttribute(name, value);
}
} }
removeAttribute(el: any, name: string, namespace?: string): void { removeAttribute(el: any, name: string, namespace?: string): void {
if (namespace) { if (namespace) {
const namespaceUri = NAMESPACE_URIS[namespace]; const namespaceUri = NAMESPACE_URIS[namespace] || namespace;
if (namespaceUri) {
el.removeAttributeNS(namespaceUri, name); el.removeAttributeNS(namespaceUri, name);
} else {
el.removeAttribute(`${namespace}:${name}`);
}
} else { } else {
el.removeAttribute(name); el.removeAttribute(name);
} }

View File

@ -28,23 +28,14 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer';
describe('setAttribute', () => { describe('setAttribute', () => {
describe('with namespace', () => { describe('with namespace', () => {
it('xmlns', () => shouldSetAttributeWithNs('xmlns'));
it('xml', () => shouldSetAttributeWithNs('xml')); it('xml', () => shouldSetAttributeWithNs('xml'));
it('svg', () => shouldSetAttributeWithNs('svg')); it('svg', () => shouldSetAttributeWithNs('svg'));
it('xhtml', () => shouldSetAttributeWithNs('xhtml')); it('xhtml', () => shouldSetAttributeWithNs('xhtml'));
it('xlink', () => shouldSetAttributeWithNs('xlink')); it('xlink', () => shouldSetAttributeWithNs('xlink'));
it('custom', () => shouldSetAttributeWithNs('custom'));
it('unknown', () => {
const div = document.createElement('div');
expect(div.hasAttribute('unknown:name')).toBe(false);
renderer.setAttribute(div, 'name', 'value', 'unknown');
expect(div.getAttribute('unknown:name')).toBe('value');
});
function shouldSetAttributeWithNs(namespace: string): void { function shouldSetAttributeWithNs(namespace: string): void {
const namespaceUri = NAMESPACE_URIS[namespace]; const namespaceUri = NAMESPACE_URIS[namespace] || namespace;
const div = document.createElement('div'); const div = document.createElement('div');
expect(div.hasAttributeNS(namespaceUri, 'name')).toBe(false); expect(div.hasAttributeNS(namespaceUri, 'name')).toBe(false);
@ -57,26 +48,16 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer';
describe('removeAttribute', () => { describe('removeAttribute', () => {
describe('with namespace', () => { describe('with namespace', () => {
it('xmlns', () => shouldRemoveAttributeWithNs('xmlns'));
it('xml', () => shouldRemoveAttributeWithNs('xml')); it('xml', () => shouldRemoveAttributeWithNs('xml'));
it('svg', () => shouldRemoveAttributeWithNs('svg')); it('svg', () => shouldRemoveAttributeWithNs('svg'));
it('xhtml', () => shouldRemoveAttributeWithNs('xhtml')); it('xhtml', () => shouldRemoveAttributeWithNs('xhtml'));
it('xlink', () => shouldRemoveAttributeWithNs('xlink')); it('xlink', () => shouldRemoveAttributeWithNs('xlink'));
it('custom', () => shouldRemoveAttributeWithNs('custom'));
it('unknown', () => {
const div = document.createElement('div');
div.setAttribute('unknown:name', 'value');
expect(div.hasAttribute('unknown:name')).toBe(true);
renderer.removeAttribute(div, 'name', 'unknown');
expect(div.hasAttribute('unknown:name')).toBe(false);
});
function shouldRemoveAttributeWithNs(namespace: string): void { function shouldRemoveAttributeWithNs(namespace: string): void {
const namespaceUri = NAMESPACE_URIS[namespace]; const namespaceUri = NAMESPACE_URIS[namespace] || namespace;
const div = document.createElement('div'); const div = document.createElement('div');
div.setAttributeNS(namespaceUri, `${namespace}:name`, 'value'); div.setAttributeNS(namespaceUri, 'name', 'value');
expect(div.hasAttributeNS(namespaceUri, 'name')).toBe(true); expect(div.hasAttributeNS(namespaceUri, 'name')).toBe(true);
renderer.removeAttribute(div, 'name', namespace); renderer.removeAttribute(div, 'name', namespace);