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:
parent
b7ff38b1ef
commit
4005815114
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue