refactor(ivy): generate 2 slots per styling instruction (#34616)

Compiler keeps track of number of slots (`vars`) which are needed for binding instructions. Normally each binding instructions allocates a single slot in the `LView` but styling instructions need to allocate two slots.

PR Close #34616
This commit is contained in:
Matias Niemelä 2019-12-14 19:48:24 -08:00 committed by Miško Hevery
parent b7ff38b1ef
commit 4005815114
6 changed files with 243 additions and 129 deletions

View File

@ -477,7 +477,7 @@ describe('compiler compliance', () => {
const template = `
MyComponent.ɵcmp = i0.ɵɵdefineComponent({type:MyComponent,selectors:[["my-component"]],
decls: 1,
vars: 2,
vars: 4,
template: function MyComponent_Template(rf,ctx){
if (rf & 1) {
$r3$.ɵɵelement(0, "div");

View File

@ -377,8 +377,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵelement(0, "div");
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
$r3$.ɵɵstyleMap($ctx$.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer);
}
}
`;
@ -504,15 +503,14 @@ describe('compiler compliance: styling', () => {
type: MyComponent,
selectors:[["my-component"]],
decls: 1,
vars: 5,
vars: 7,
consts: [[${AttributeMarker.Styles}, "opacity", "1"]],
template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 0);
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
$r3$.ɵɵstyleMap($ctx$.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleProp("width", $ctx$.myWidth)("height", $ctx$.myHeight);
$r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle);
}
@ -551,14 +549,13 @@ describe('compiler compliance: styling', () => {
type: MyComponent,
selectors: [["my-component"]],
decls: 1,
vars: 1,
vars: 2,
template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div");
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleProp("background-image", ctx.myImage);
$r3$.ɵɵstyleProp("background-image", ctx.myImage, $r3$.ɵɵdefaultStyleSanitizer);
}
},
encapsulation: 2
@ -697,7 +694,7 @@ describe('compiler compliance: styling', () => {
type: MyComponent,
selectors:[["my-component"]],
decls: 1,
vars: 5,
vars: 7,
consts: [[${AttributeMarker.Classes}, "grape"]],
template: function MyComponent_Template(rf, $ctx$) {
if (rf & 1) {
@ -816,8 +813,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵelement(0, "div");
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
$r3$.ɵɵstyleMap($ctx$.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap($ctx$.myClassExp);
}
}
@ -858,8 +854,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 4, $ctx$.myStyleExp));
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 4, $ctx$.myStyleExp), $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap($r3$.ɵɵpipeBind1(2, 6, $ctx$.myClassExp));
}
}
@ -911,11 +906,10 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 8, $ctx$.myStyleExp, 1000));
$r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(20, _c0));
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 11, $ctx$.barExp, 3000))("baz", $r3$.ɵɵpipeBind2(3, 14, $ctx$.bazExp, 4000));
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 17, $ctx$.fooExp, 2000));
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 11, $ctx$.myStyleExp, 1000), $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(23, _c0));
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 14, $ctx$.barExp, 3000))("baz", $r3$.ɵɵpipeBind2(3, 17, $ctx$.bazExp, 4000));
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 20, $ctx$.fooExp, 2000));
$r3$.ɵɵadvance(5);
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
}
@ -1014,9 +1008,15 @@ describe('compiler compliance: styling', () => {
hostAttrs: [${AttributeMarker.Classes}, "foo", "baz", ${AttributeMarker.Styles}, "width", "200px", "height", "500px"],
hostVars: 6,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
if (rf & 1) {
$r3$.ɵɵallocHostVars(8);
$r3$.ɵɵelementHostAttrs($e0_attrs$);
}
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyle);
$r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClass);
$r3$.ɵɵstyleProp("color", ctx.myColorProp);
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
@ -1070,9 +1070,14 @@ describe('compiler compliance: styling', () => {
const template = `
hostVars: 8,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
if (rf & 1) {
$r3$.ɵɵallocHostVars(12);
}
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyle);
$r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClasses);
$r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt")("width", ctx.myWidthProp);
$r3$.ɵɵclassProp("bar", ctx.myBarClass)("foo", ctx.myFooClass);
@ -1129,8 +1134,7 @@ describe('compiler compliance: styling', () => {
$r3$.ɵɵelement(0, "div");
}
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyleExp);
$r3$.ɵɵstyleMap(ctx.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClassExp);
$r3$.ɵɵstyleProp("height", ctx.myHeightExp);
$r3$.ɵɵclassProp("bar", ctx.myBarClassExp);
@ -1141,9 +1145,14 @@ describe('compiler compliance: styling', () => {
const hostBindings = `
hostVars: 6,
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
if (rf & 1) {
$r3$.ɵɵallocHostVars(8);
}
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyleExp);
$r3$.ɵɵstyleMap(ctx.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClassExp);
$r3$.ɵɵstyleProp("width", ctx.myWidthExp);
$r3$.ɵɵclassProp("foo", ctx.myFooClassExp);
@ -1412,6 +1421,46 @@ describe('compiler compliance: styling', () => {
expectEmit(result.source, template, 'Incorrect handling of interpolated style properties');
});
it('should generate update instructions for interpolated style properties with a sanitizer',
() => {
const files = {
app: {
'spec.ts': `
import {Component} from '@angular/core';
@Component({
template: \`
<div style.background="url({{ myUrl1 }})"
style.borderImage="url({{ myUrl2 }}) {{ myRepeat }} auto"
style.boxShadow="{{ myBoxX }} {{ myBoxY }} {{ myBoxWidth }} black"></div>
\`
})
export class MyComponent {
myUrl1 = '...';
myUrl2 = '...';
myBoxX = '0px';
myBoxY = '0px';
myBoxWidth = '100px';
myRepeat = 'no-repeat';
}
`
}
};
const template = `
if (rf & 2) {
$r3$.ɵɵstylePropInterpolate1("background", "url(", ctx.myUrl1, ")", $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstylePropInterpolate2("border-image", "url(", ctx.myUrl2, ") ", ctx.myRepeat, " auto", $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstylePropInterpolate3("box-shadow", "", ctx.myBoxX, " ", ctx.myBoxY, " ", ctx.myBoxWidth, " black");
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect handling of interpolated style properties');
});
it('should generate update instructions for interpolated style properties with !important',
() => {
const files = {
@ -1833,8 +1882,7 @@ describe('compiler compliance: styling', () => {
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
if (rf & 2) {
$r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title);
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.myStyle);
$r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵclassMap(ctx.myClass);
}
}
@ -1908,8 +1956,7 @@ describe('compiler compliance: styling', () => {
template: function MyAppComp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleProp("background-image", ctx.bgExp);
$r3$.ɵɵstyleProp("background-image", ctx.bgExp, $r3$.ɵɵdefaultStyleSanitizer);
}
}
@ -1943,8 +1990,7 @@ describe('compiler compliance: styling', () => {
template: function MyAppComp_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
$r3$.ɵɵstyleMap(ctx.mapExp);
$r3$.ɵɵstyleMap(ctx.mapExp, $r3$.ɵɵdefaultStyleSanitizer);
}
}
@ -2035,11 +2081,17 @@ describe('compiler compliance: styling', () => {
const template = `
hostVars: 9,
hostBindings: function MyDir_HostBindings(rf, ctx, elIndex) {
<<<<<<< HEAD
=======
$r3$.ɵɵallocHostVars(10);
>>>>>>> 3a14b06a3b... refactor(ivy): generate 2 slots per styling instruction
if (rf & 2) {
$r3$.ɵɵhostProperty("title", ctx.title);
$r3$.ɵɵupdateSyntheticHostBinding("@anim",
$r3$.ɵɵpureFunction2(6, _c1, ctx._animValue,
$r3$.ɵɵpureFunction2(3, _c0, ctx._animParam1, ctx._animParam2)));
$r3$.ɵɵpureFunction2(7, _c1, ctx._animValue,
$r3$.ɵɵpureFunction2(4, _c0, ctx._animParam1, ctx._animParam2)));
$r3$.ɵɵclassProp("foo", ctx.foo);
}
}

View File

@ -113,8 +113,6 @@ export class Identifiers {
static stylePropInterpolateV:
o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE};
static styleSanitizer: o.ExternalReference = {name: 'ɵɵstyleSanitizer', moduleName: CORE};
static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE};
static nextContext: o.ExternalReference = {name: 'ɵɵnextContext', moduleName: CORE};

View File

@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform';
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api';
import {StylingBuilder, StylingInstructionCall} from './styling_builder';
import {MIN_STYLING_BINDING_SLOTS_REQUIRED, StylingBuilder, StylingInstructionCall} from './styling_builder';
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template';
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
@ -530,8 +530,6 @@ function createHostBindingsFunction(
hostBindingsMetadata: R3HostMetadata, typeSourceSpan: ParseSourceSpan,
bindingParser: BindingParser, constantPool: ConstantPool, selector: string, name: string,
definitionMap: DefinitionMap): o.Expression|null {
// Initialize hostVarsCount to number of bound host properties (interpolations illegal)
const hostVarsCount = Object.keys(hostBindingsMetadata.properties).length;
const elVarExp = o.variable('elIndex');
const bindingContext = o.variable(CONTEXT_NAME);
const styleBuilder = new StylingBuilder(elVarExp, bindingContext);
@ -547,10 +545,38 @@ function createHostBindingsFunction(
const createStatements: o.Statement[] = [];
const updateStatements: o.Statement[] = [];
let totalHostVarsCount = hostVarsCount;
const hostBindingSourceSpan = typeSourceSpan;
const directiveSummary = metadataAsSummary(hostBindingsMetadata);
// Calculate host event bindings
const eventBindings =
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
if (eventBindings && eventBindings.length) {
const listeners = createHostListeners(eventBindings, name);
createStatements.push(...listeners);
}
// Calculate the host property bindings
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
const allOtherBindings: ParsedProperty[] = [];
// We need to calculate the total amount of binding slots required by
// all the instructions together before any value conversions happen.
// Value conversions may require additional slots for interpolation and
// bindings with pipes. These calculates happen after this block.
let totalHostVarsCount = 0;
bindings && bindings.forEach((binding: ParsedProperty) => {
const name = binding.name;
const stylingInputWasSet =
styleBuilder.registerInputBasedOnName(name, binding.expression, binding.sourceSpan);
if (stylingInputWasSet) {
totalHostVarsCount += MIN_STYLING_BINDING_SLOTS_REQUIRED;
} else {
allOtherBindings.push(binding);
totalHostVarsCount++;
}
});
let valueConverter: ValueConverter;
const getValueConverter = () => {
if (!valueConverter) {
@ -568,66 +594,50 @@ function createHostBindingsFunction(
return valueConverter;
};
// Calculate host event bindings
const eventBindings =
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
if (eventBindings && eventBindings.length) {
const listeners = createHostListeners(eventBindings, name);
createStatements.push(...listeners);
}
// Calculate the host property bindings
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
const propertyBindings: o.Expression[][] = [];
const attributeBindings: o.Expression[][] = [];
const syntheticHostBindings: o.Expression[][] = [];
allOtherBindings.forEach((binding: ParsedProperty) => {
// resolve literal arrays and literal objects
const value = binding.expression.visit(getValueConverter());
const bindingExpr = bindingFn(bindingContext, value);
bindings && bindings.forEach((binding: ParsedProperty) => {
const name = binding.name;
const stylingInputWasSet =
styleBuilder.registerInputBasedOnName(name, binding.expression, binding.sourceSpan);
if (!stylingInputWasSet) {
// resolve literal arrays and literal objects
const value = binding.expression.visit(getValueConverter());
const bindingExpr = bindingFn(bindingContext, value);
const {bindingName, instruction, isAttribute} = getBindingNameAndInstruction(binding);
const {bindingName, instruction, isAttribute} = getBindingNameAndInstruction(binding);
const securityContexts =
bindingParser.calcPossibleSecurityContexts(selector, bindingName, isAttribute)
.filter(context => context !== core.SecurityContext.NONE);
const securityContexts =
bindingParser.calcPossibleSecurityContexts(selector, bindingName, isAttribute)
.filter(context => context !== core.SecurityContext.NONE);
let sanitizerFn: o.ExternalExpr|null = null;
if (securityContexts.length) {
if (securityContexts.length === 2 &&
securityContexts.indexOf(core.SecurityContext.URL) > -1 &&
securityContexts.indexOf(core.SecurityContext.RESOURCE_URL) > -1) {
// Special case for some URL attributes (such as "src" and "href") that may be a part
// of different security contexts. In this case we use special santitization function and
// select the actual sanitizer at runtime based on a tag name that is provided while
// invoking sanitization function.
sanitizerFn = o.importExpr(R3.sanitizeUrlOrResourceUrl);
} else {
sanitizerFn = resolveSanitizationFn(securityContexts[0], isAttribute);
}
}
const instructionParams = [o.literal(bindingName), bindingExpr.currValExpr];
if (sanitizerFn) {
instructionParams.push(sanitizerFn);
}
updateStatements.push(...bindingExpr.stmts);
if (instruction === R3.hostProperty) {
propertyBindings.push(instructionParams);
} else if (instruction === R3.attribute) {
attributeBindings.push(instructionParams);
} else if (instruction === R3.updateSyntheticHostBinding) {
syntheticHostBindings.push(instructionParams);
let sanitizerFn: o.ExternalExpr|null = null;
if (securityContexts.length) {
if (securityContexts.length === 2 &&
securityContexts.indexOf(core.SecurityContext.URL) > -1 &&
securityContexts.indexOf(core.SecurityContext.RESOURCE_URL) > -1) {
// Special case for some URL attributes (such as "src" and "href") that may be a part
// of different security contexts. In this case we use special santitization function and
// select the actual sanitizer at runtime based on a tag name that is provided while
// invoking sanitization function.
sanitizerFn = o.importExpr(R3.sanitizeUrlOrResourceUrl);
} else {
updateStatements.push(o.importExpr(instruction).callFn(instructionParams).toStmt());
sanitizerFn = resolveSanitizationFn(securityContexts[0], isAttribute);
}
}
const instructionParams = [o.literal(bindingName), bindingExpr.currValExpr];
if (sanitizerFn) {
instructionParams.push(sanitizerFn);
}
updateStatements.push(...bindingExpr.stmts);
if (instruction === R3.hostProperty) {
propertyBindings.push(instructionParams);
} else if (instruction === R3.attribute) {
attributeBindings.push(instructionParams);
} else if (instruction === R3.updateSyntheticHostBinding) {
syntheticHostBindings.push(instructionParams);
} else {
updateStatements.push(o.importExpr(instruction).callFn(instructionParams).toStmt());
}
});
if (propertyBindings.length > 0) {
@ -664,7 +674,8 @@ function createHostBindingsFunction(
instruction.calls.forEach(call => {
// we subtract a value of `1` here because the binding slot was already allocated
// at the top of this method when all the input bindings were counted.
totalHostVarsCount += Math.max(call.allocateBindingSlots - 1, 0);
totalHostVarsCount +=
Math.max(call.allocateBindingSlots - MIN_STYLING_BINDING_SLOTS_REQUIRED, 0);
calls.push(convertStylingCall(call, bindingContext, bindingFn));
});

View File

@ -20,6 +20,56 @@ import {DefinitionMap, getInterpolationArgsLength} from './util';
const IMPORTANT_FLAG = '!important';
/**
* Minimum amount of binding slots required in the runtime for style/class bindings.
*
* Styling in Angular uses up two slots in the runtime LView/TData data structures to
* record binding data, property information and metadata.
*
* When a binding is registered it will place the following information in the `LView`:
*
* slot 1) binding value
* slot 2) cached value (all other values collected before it in string form)
*
* When a binding is registered it will place the following information in the `TData`:
*
* slot 1) prop name
* slot 2) binding index that points to the previous style/class binding (and some extra config
* values)
*
* Let's imagine we have a binding that looks like so:
*
* ```
* <div [style.width]="x" [style.height]="y">
* ```
*
* Our `LView` and `TData` data-structures look like so:
*
* ```typescript
* LView = [
* // ...
* x, // value of x
* "width: x",
*
* y, // value of y
* "width: x; height: y",
* // ...
* ];
*
* TData = [
* // ...
* "width", // binding slot 20
* 0,
*
* "height",
* 20,
* // ...
* ];
* ```
*
* */
export const MIN_STYLING_BINDING_SLOTS_REQUIRED = 2;
/**
* A styling expression summary that is to be processed by the compiler
*/
@ -44,6 +94,7 @@ interface BoundStylingEntry {
name: string|null;
unit: string|null;
sourceSpan: ParseSourceSpan;
sanitize: boolean;
value: AST;
}
@ -116,10 +167,6 @@ export class StylingBuilder {
private _initialStyleValues: string[] = [];
private _initialClassValues: string[] = [];
// certain style properties ALWAYS need sanitization
// this is checked each time new styles are encountered
private _useDefaultSanitizer = false;
constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {}
/**
@ -179,14 +226,13 @@ export class StylingBuilder {
const {property, hasOverrideFlag, unit: bindingUnit} = parseProperty(name);
const entry: BoundStylingEntry = {
name: property,
sanitize: property ? isStyleSanitizable(property) : true,
unit: unit || bindingUnit, value, sourceSpan, hasOverrideFlag
};
if (isMapBased) {
this._useDefaultSanitizer = true;
this._styleMapInput = entry;
} else {
(this._singleStyleInputs = this._singleStyleInputs || []).push(entry);
this._useDefaultSanitizer = this._useDefaultSanitizer || isStyleSanitizable(name);
registerIntoMap(this._stylesIndex, property);
}
this._lastStylingInput = entry;
@ -202,8 +248,8 @@ export class StylingBuilder {
return null;
}
const {property, hasOverrideFlag} = parseProperty(name);
const entry:
BoundStylingEntry = {name: property, value, sourceSpan, hasOverrideFlag, unit: null};
const entry: BoundStylingEntry =
{name: property, value, sourceSpan, sanitize: false, hasOverrideFlag, unit: null};
if (isMapBased) {
if (this._classMapInput) {
throw new Error(
@ -319,7 +365,7 @@ export class StylingBuilder {
// map-based bindings allocate two slots: one for the
// previous binding value and another for the previous
// className or style attribute value.
let totalBindingSlotsRequired = 2;
let totalBindingSlotsRequired = MIN_STYLING_BINDING_SLOTS_REQUIRED;
// these values must be outside of the update block so that they can
// be evaluated (the AST visit call) during creation time so that any
@ -341,17 +387,23 @@ export class StylingBuilder {
allocateBindingSlots: totalBindingSlotsRequired,
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
const convertResult = convertFn(mapValue);
return Array.isArray(convertResult) ? convertResult : [convertResult];
const params = Array.isArray(convertResult) ? convertResult : [convertResult];
// [style] instructions will sanitize all their values. For this reason we
// need to include the sanitizer as a param.
if (!isClassBased) {
params.push(o.importExpr(R3.defaultStyleSanitizer));
}
return params;
}
}]
};
}
private _buildSingleInputs(
reference: o.ExternalReference, inputs: BoundStylingEntry[], mapIndex: Map<string, number>,
allowUnits: boolean, valueConverter: ValueConverter,
getInterpolationExpressionFn?: (value: Interpolation) => o.ExternalReference):
StylingInstruction[] {
reference: o.ExternalReference, inputs: BoundStylingEntry[], valueConverter: ValueConverter,
getInterpolationExpressionFn: ((value: Interpolation) => o.ExternalReference)|null,
isClassBased: boolean): StylingInstruction[] {
const instructions: StylingInstruction[] = [];
inputs.forEach(input => {
@ -359,7 +411,14 @@ export class StylingBuilder {
instructions[instructions.length - 1];
const value = input.value.visit(valueConverter);
let referenceForCall = reference;
let totalBindingSlotsRequired = 1; // each styling binding value is stored in the LView
// each styling binding value is stored in the LView
// but there are two values stored for each binding:
// 1) the value itself
// 2) an intermediate value (concatenation of style up to this point).
// We need to store the intermediate value so that we don't allocate
// the strings on each CD.
let totalBindingSlotsRequired = MIN_STYLING_BINDING_SLOTS_REQUIRED;
if (value instanceof Interpolation) {
totalBindingSlotsRequired += value.expressions.length;
@ -374,7 +433,7 @@ export class StylingBuilder {
allocateBindingSlots: totalBindingSlotsRequired,
supportsInterpolation: !!getInterpolationExpressionFn,
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
// params => stylingProp(propName, value)
// params => stylingProp(propName, value, suffix|sanitizer)
const params: o.Expression[] = [];
params.push(o.literal(input.name));
@ -385,8 +444,16 @@ export class StylingBuilder {
params.push(convertResult);
}
if (allowUnits && input.unit) {
params.push(o.literal(input.unit));
// [style.prop] bindings may use suffix values (e.g. px, em, etc...) and they
// can also use a sanitizer. Sanitization occurs for url-based entries. Having
// the suffix value and a sanitizer together into the instruction doesn't make
// any sense (url-based entries cannot be sanitized).
if (!isClassBased) {
if (input.unit) {
params.push(o.literal(input.unit));
} else if (input.sanitize) {
params.push(o.importExpr(R3.defaultStyleSanitizer));
}
}
return params;
@ -411,7 +478,7 @@ export class StylingBuilder {
private _buildClassInputs(valueConverter: ValueConverter): StylingInstruction[] {
if (this._singleClassInputs) {
return this._buildSingleInputs(
R3.classProp, this._singleClassInputs, this._classesIndex, false, valueConverter);
R3.classProp, this._singleClassInputs, valueConverter, null, true);
}
return [];
}
@ -419,23 +486,12 @@ export class StylingBuilder {
private _buildStyleInputs(valueConverter: ValueConverter): StylingInstruction[] {
if (this._singleStyleInputs) {
return this._buildSingleInputs(
R3.styleProp, this._singleStyleInputs, this._stylesIndex, true, valueConverter,
getStylePropInterpolationExpression);
R3.styleProp, this._singleStyleInputs, valueConverter,
getStylePropInterpolationExpression, false);
}
return [];
}
private _buildSanitizerFn(): StylingInstruction {
return {
reference: R3.styleSanitizer,
calls: [{
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
allocateBindingSlots: 0,
params: () => [o.importExpr(R3.defaultStyleSanitizer)]
}]
};
}
/**
* Constructs all instructions which contain the expressions that will be placed
* into the update block of a template function or a directive hostBindings function.
@ -443,9 +499,6 @@ export class StylingBuilder {
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
const instructions: StylingInstruction[] = [];
if (this.hasBindings) {
if (this._useDefaultSanitizer) {
instructions.push(this._buildSanitizerFn());
}
const styleMapInstruction = this.buildStyleMapInstruction(valueConverter);
if (styleMapInstruction) {
instructions.push(styleMapInstruction);

View File

@ -684,7 +684,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// the code here will collect all update-level styling instructions and add them to the
// update block of the template function AOT code. Instructions like `styleProp`,
// `styleMap`, `classMap`, `classProp` and `stylingApply`
// `styleMap`, `classMap`, `classProp` and `flushStyling`
// are all generated and assigned in the code below.
const stylingInstructions = stylingBuilder.buildUpdateLevelInstructions(this._valueConverter);
const limit = stylingInstructions.length - 1;