refactor(ivy): enable sanitization support for the new styling algorithm (#30667)
This patch is one of the final patches to refactor the styling algorithm to be more efficient, performant and less complex. This patch enables sanitization support for map-based and prop-based style bindings. PR Close #30667
This commit is contained in:
parent
d72479b628
commit
82682bb93f
|
@ -7,7 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AttributeMarker, ViewEncapsulation} from '@angular/compiler/src/core';
|
import {AttributeMarker, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||||
|
import {CompilerStylingMode, compilerSetStylingMode} from '@angular/compiler/src/render3/view/styling_state';
|
||||||
import {setup} from '@angular/compiler/test/aot/test_util';
|
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||||
|
|
||||||
import {compile, expectEmit} from './mock_compile';
|
import {compile, expectEmit} from './mock_compile';
|
||||||
|
|
||||||
describe('compiler compliance: styling', () => {
|
describe('compiler compliance: styling', () => {
|
||||||
|
@ -1463,4 +1465,122 @@ describe('compiler compliance: styling', () => {
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
expectEmit(result.source, template, 'Incorrect template');
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('new styling refactor', () => {
|
||||||
|
beforeEach(() => { compilerSetStylingMode(CompilerStylingMode.UseNew); });
|
||||||
|
|
||||||
|
afterEach(() => { compilerSetStylingMode(CompilerStylingMode.UseOld); });
|
||||||
|
|
||||||
|
it('should generate a `styleSanitizer` instruction when one or more sanitizable style properties are statically detected',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: \`
|
||||||
|
<div [style.background-image]="bgExp"></div>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class MyAppComp {
|
||||||
|
bgExp = '';
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
template: function MyAppComp_Template(rf, ctx) {
|
||||||
|
…
|
||||||
|
if (rf & 2) {
|
||||||
|
$r3$.ɵɵselect(0);
|
||||||
|
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||||
|
$r3$.ɵɵstyleProp(0, ctx.bgExp);
|
||||||
|
$r3$.ɵɵstylingApply();
|
||||||
|
}
|
||||||
|
…
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a `styleSanitizer` instruction when a `styleMap` instruction is used',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: \`
|
||||||
|
<div [style]="mapExp"></div>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class MyAppComp {
|
||||||
|
mapExp = {};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
template: function MyAppComp_Template(rf, ctx) {
|
||||||
|
…
|
||||||
|
if (rf & 2) {
|
||||||
|
$r3$.ɵɵselect(0);
|
||||||
|
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||||
|
$r3$.ɵɵstyleMap(ctx.mapExp);
|
||||||
|
$r3$.ɵɵstylingApply();
|
||||||
|
}
|
||||||
|
…
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t generate a `styleSanitizer` instruction when class-based instructions are used',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: \`
|
||||||
|
<div [class]="mapExp" [class.name]="nameExp"></div>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class MyAppComp {
|
||||||
|
mapExp = {};
|
||||||
|
nameExp = true;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
template: function MyAppComp_Template(rf, ctx) {
|
||||||
|
…
|
||||||
|
if (rf & 2) {
|
||||||
|
$r3$.ɵɵselect(0);
|
||||||
|
$r3$.ɵɵclassMap(ctx.mapExp);
|
||||||
|
$r3$.ɵɵclassProp(0, ctx.nameExp);
|
||||||
|
$r3$.ɵɵstylingApply();
|
||||||
|
}
|
||||||
|
…
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -80,6 +80,8 @@ export class Identifiers {
|
||||||
|
|
||||||
static stylingApply: o.ExternalReference = {name: 'ɵɵstylingApply', moduleName: CORE};
|
static stylingApply: o.ExternalReference = {name: 'ɵɵstylingApply', moduleName: CORE};
|
||||||
|
|
||||||
|
static styleSanitizer: o.ExternalReference = {name: 'ɵɵstyleSanitizer', moduleName: CORE};
|
||||||
|
|
||||||
static elementHostAttrs: o.ExternalReference = {name: 'ɵɵelementHostAttrs', moduleName: CORE};
|
static elementHostAttrs: o.ExternalReference = {name: 'ɵɵelementHostAttrs', moduleName: CORE};
|
||||||
|
|
||||||
static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE};
|
static containerCreate: o.ExternalReference = {name: 'ɵɵcontainer', moduleName: CORE};
|
||||||
|
|
|
@ -89,6 +89,7 @@ export class StylingBuilder {
|
||||||
/** an array of each [class.name] input */
|
/** an array of each [class.name] input */
|
||||||
private _singleClassInputs: BoundStylingEntry[]|null = null;
|
private _singleClassInputs: BoundStylingEntry[]|null = null;
|
||||||
private _lastStylingInput: BoundStylingEntry|null = null;
|
private _lastStylingInput: BoundStylingEntry|null = null;
|
||||||
|
private _firstStylingInput: BoundStylingEntry|null = null;
|
||||||
|
|
||||||
// maps are used instead of hash maps because a Map will
|
// maps are used instead of hash maps because a Map will
|
||||||
// retain the ordering of the keys
|
// retain the ordering of the keys
|
||||||
|
@ -181,6 +182,7 @@ export class StylingBuilder {
|
||||||
registerIntoMap(this._stylesIndex, property);
|
registerIntoMap(this._stylesIndex, property);
|
||||||
}
|
}
|
||||||
this._lastStylingInput = entry;
|
this._lastStylingInput = entry;
|
||||||
|
this._firstStylingInput = this._firstStylingInput || entry;
|
||||||
this.hasBindings = true;
|
this.hasBindings = true;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -200,6 +202,7 @@ export class StylingBuilder {
|
||||||
registerIntoMap(this._classesIndex, property);
|
registerIntoMap(this._classesIndex, property);
|
||||||
}
|
}
|
||||||
this._lastStylingInput = entry;
|
this._lastStylingInput = entry;
|
||||||
|
this._firstStylingInput = this._firstStylingInput || entry;
|
||||||
this.hasBindings = true;
|
this.hasBindings = true;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -453,6 +456,15 @@ export class StylingBuilder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _buildSanitizerFn() {
|
||||||
|
return {
|
||||||
|
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
|
||||||
|
reference: R3.styleSanitizer,
|
||||||
|
allocateBindingSlots: 0,
|
||||||
|
buildParams: () => [o.importExpr(R3.defaultStyleSanitizer)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs all instructions which contain the expressions that will be placed
|
* Constructs all instructions which contain the expressions that will be placed
|
||||||
* into the update block of a template function or a directive hostBindings function.
|
* into the update block of a template function or a directive hostBindings function.
|
||||||
|
@ -460,6 +472,9 @@ export class StylingBuilder {
|
||||||
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
|
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
|
||||||
const instructions: Instruction[] = [];
|
const instructions: Instruction[] = [];
|
||||||
if (this.hasBindings) {
|
if (this.hasBindings) {
|
||||||
|
if (compilerIsNewStylingInUse() && this._useDefaultSanitizer) {
|
||||||
|
instructions.push(this._buildSanitizerFn());
|
||||||
|
}
|
||||||
const styleMapInstruction = this.buildStyleMapInstruction(valueConverter);
|
const styleMapInstruction = this.buildStyleMapInstruction(valueConverter);
|
||||||
if (styleMapInstruction) {
|
if (styleMapInstruction) {
|
||||||
instructions.push(styleMapInstruction);
|
instructions.push(styleMapInstruction);
|
||||||
|
|
|
@ -195,8 +195,8 @@ export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|nul
|
||||||
let styles: DebugNewStyling|null = null;
|
let styles: DebugNewStyling|null = null;
|
||||||
let classes: DebugNewStyling|null = null;
|
let classes: DebugNewStyling|null = null;
|
||||||
if (runtimeIsNewStylingInUse()) {
|
if (runtimeIsNewStylingInUse()) {
|
||||||
styles = tNode.newStyles ? new NodeStylingDebug(tNode.newStyles, lView) : null;
|
styles = tNode.newStyles ? new NodeStylingDebug(tNode.newStyles, lView, false) : null;
|
||||||
classes = tNode.newClasses ? new NodeStylingDebug(tNode.newClasses, lView) : null;
|
classes = tNode.newClasses ? new NodeStylingDebug(tNode.newClasses, lView, true) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugNodes.push({
|
debugNodes.push({
|
||||||
|
|
|
@ -102,6 +102,7 @@ export {
|
||||||
ɵɵselect,
|
ɵɵselect,
|
||||||
ɵɵstyleMap,
|
ɵɵstyleMap,
|
||||||
ɵɵstyleProp,
|
ɵɵstyleProp,
|
||||||
|
ɵɵstyleSanitizer,
|
||||||
ɵɵstyling,
|
ɵɵstyling,
|
||||||
ɵɵstylingApply,
|
ɵɵstylingApply,
|
||||||
ɵɵtemplate,
|
ɵɵtemplate,
|
||||||
|
|
|
@ -45,5 +45,6 @@ export * from './property';
|
||||||
export * from './property_interpolation';
|
export * from './property_interpolation';
|
||||||
export * from './select';
|
export * from './select';
|
||||||
export * from './styling';
|
export * from './styling';
|
||||||
|
export {styleSanitizer as ɵɵstyleSanitizer} from '../styling_next/instructions';
|
||||||
export * from './text';
|
export * from './text';
|
||||||
export * from './text_interpolation';
|
export * from './text_interpolation';
|
||||||
|
|
|
@ -121,6 +121,7 @@ export const angularCoreEnv: {[name: string]: Function} =
|
||||||
'ɵɵstyling': r3.ɵɵstyling,
|
'ɵɵstyling': r3.ɵɵstyling,
|
||||||
'ɵɵstyleMap': r3.ɵɵstyleMap,
|
'ɵɵstyleMap': r3.ɵɵstyleMap,
|
||||||
'ɵɵstyleProp': r3.ɵɵstyleProp,
|
'ɵɵstyleProp': r3.ɵɵstyleProp,
|
||||||
|
'ɵɵstyleSanitizer': r3.ɵɵstyleSanitizer,
|
||||||
'ɵɵstylingApply': r3.ɵɵstylingApply,
|
'ɵɵstylingApply': r3.ɵɵstylingApply,
|
||||||
'ɵɵclassProp': r3.ɵɵclassProp,
|
'ɵɵclassProp': r3.ɵɵclassProp,
|
||||||
'ɵɵselect': r3.ɵɵselect,
|
'ɵɵselect': r3.ɵɵselect,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 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 {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||||
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||||
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
||||||
import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player';
|
import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player';
|
||||||
|
@ -943,7 +943,9 @@ function updateSingleStylingValue(
|
||||||
if (currDirective !== directiveIndex) {
|
if (currDirective !== directiveIndex) {
|
||||||
const prop = getProp(context, singleIndex);
|
const prop = getProp(context, singleIndex);
|
||||||
const sanitizer = getStyleSanitizer(context, directiveIndex);
|
const sanitizer = getStyleSanitizer(context, directiveIndex);
|
||||||
setSanitizeFlag(context, singleIndex, (sanitizer && sanitizer(prop)) ? true : false);
|
setSanitizeFlag(
|
||||||
|
context, singleIndex,
|
||||||
|
(sanitizer && sanitizer(prop, null, StyleSanitizeMode.ValidateProperty)) ? true : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the value will always get updated (even if the dirty flag is skipped)
|
// the value will always get updated (even if the dirty flag is skipped)
|
||||||
|
@ -1141,7 +1143,8 @@ export function setStyle(
|
||||||
native: any, prop: string, value: string | null, renderer: Renderer3,
|
native: any, prop: string, value: string | null, renderer: Renderer3,
|
||||||
sanitizer: StyleSanitizeFn | null, store?: BindingStore | null,
|
sanitizer: StyleSanitizeFn | null, store?: BindingStore | null,
|
||||||
playerBuilder?: ClassAndStylePlayerBuilder<any>| null) {
|
playerBuilder?: ClassAndStylePlayerBuilder<any>| null) {
|
||||||
value = sanitizer && value ? sanitizer(prop, value) : value;
|
value =
|
||||||
|
sanitizer && value ? sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) : value;
|
||||||
if (store || playerBuilder) {
|
if (store || playerBuilder) {
|
||||||
if (store) {
|
if (store) {
|
||||||
store.setValue(prop, value);
|
store.setValue(prop, value);
|
||||||
|
@ -1461,7 +1464,9 @@ function valueExists(value: string | null | boolean, isClassBased?: boolean) {
|
||||||
function prepareInitialFlag(
|
function prepareInitialFlag(
|
||||||
context: StylingContext, prop: string, entryIsClassBased: boolean,
|
context: StylingContext, prop: string, entryIsClassBased: boolean,
|
||||||
sanitizer?: StyleSanitizeFn | null) {
|
sanitizer?: StyleSanitizeFn | null) {
|
||||||
let flag = (sanitizer && sanitizer(prop)) ? StylingFlags.Sanitize : StylingFlags.None;
|
let flag = (sanitizer && sanitizer(prop, null, StyleSanitizeMode.ValidateProperty)) ?
|
||||||
|
StylingFlags.Sanitize :
|
||||||
|
StylingFlags.None;
|
||||||
|
|
||||||
let initialIndex: number;
|
let initialIndex: number;
|
||||||
if (entryIsClassBased) {
|
if (entryIsClassBased) {
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
* 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 {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||||
import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
|
import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
|
||||||
|
|
||||||
import {ApplyStylingFn, LStylingData, LStylingMap, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex} from './interfaces';
|
import {ApplyStylingFn, LStylingData, LStylingMap, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
|
||||||
import {allowStylingFlush, getBindingValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasValueChanged, isContextLocked, isStylingValueDefined, lockContext} from './util';
|
import {allowStylingFlush, getBindingValue, getGuardMask, getProp, getPropValuesStartPosition, getValuesCount, hasValueChanged, isContextLocked, isSanitizationRequired, isStylingValueDefined, lockContext, setGuardMask} from './util';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +50,7 @@ let currentStyleIndex = STYLING_INDEX_START_VALUE;
|
||||||
let currentClassIndex = STYLING_INDEX_START_VALUE;
|
let currentClassIndex = STYLING_INDEX_START_VALUE;
|
||||||
let stylesBitMask = 0;
|
let stylesBitMask = 0;
|
||||||
let classesBitMask = 0;
|
let classesBitMask = 0;
|
||||||
let deferredBindingQueue: (TStylingContext | number | string | null)[] = [];
|
let deferredBindingQueue: (TStylingContext | number | string | null | boolean)[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visits a class-based binding and updates the new value (if changed).
|
* Visits a class-based binding and updates the new value (if changed).
|
||||||
|
@ -64,11 +65,11 @@ let deferredBindingQueue: (TStylingContext | number | string | null)[] = [];
|
||||||
export function updateClassBinding(
|
export function updateClassBinding(
|
||||||
context: TStylingContext, data: LStylingData, prop: string | null, bindingIndex: number,
|
context: TStylingContext, data: LStylingData, prop: string | null, bindingIndex: number,
|
||||||
value: boolean | string | null | undefined | LStylingMap, deferRegistration: boolean,
|
value: boolean | string | null | undefined | LStylingMap, deferRegistration: boolean,
|
||||||
forceUpdate?: boolean): void {
|
forceUpdate: boolean): void {
|
||||||
const isMapBased = !prop;
|
const isMapBased = !prop;
|
||||||
const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : currentClassIndex++;
|
const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : currentClassIndex++;
|
||||||
const updated = updateBindingData(
|
const updated = updateBindingData(
|
||||||
context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate);
|
context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate, false);
|
||||||
if (updated || forceUpdate) {
|
if (updated || forceUpdate) {
|
||||||
classesBitMask |= 1 << index;
|
classesBitMask |= 1 << index;
|
||||||
}
|
}
|
||||||
|
@ -86,12 +87,16 @@ export function updateClassBinding(
|
||||||
*/
|
*/
|
||||||
export function updateStyleBinding(
|
export function updateStyleBinding(
|
||||||
context: TStylingContext, data: LStylingData, prop: string | null, bindingIndex: number,
|
context: TStylingContext, data: LStylingData, prop: string | null, bindingIndex: number,
|
||||||
value: String | string | number | null | undefined | LStylingMap, deferRegistration: boolean,
|
value: String | string | number | null | undefined | LStylingMap,
|
||||||
forceUpdate?: boolean): void {
|
sanitizer: StyleSanitizeFn | null, deferRegistration: boolean, forceUpdate: boolean): void {
|
||||||
const isMapBased = !prop;
|
const isMapBased = !prop;
|
||||||
const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : currentStyleIndex++;
|
const index = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : currentStyleIndex++;
|
||||||
|
const sanitizationRequired = isMapBased ?
|
||||||
|
true :
|
||||||
|
(sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false);
|
||||||
const updated = updateBindingData(
|
const updated = updateBindingData(
|
||||||
context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate);
|
context, data, index, prop, bindingIndex, value, deferRegistration, forceUpdate,
|
||||||
|
sanitizationRequired);
|
||||||
if (updated || forceUpdate) {
|
if (updated || forceUpdate) {
|
||||||
stylesBitMask |= 1 << index;
|
stylesBitMask |= 1 << index;
|
||||||
}
|
}
|
||||||
|
@ -114,10 +119,10 @@ function updateBindingData(
|
||||||
context: TStylingContext, data: LStylingData, counterIndex: number, prop: string | null,
|
context: TStylingContext, data: LStylingData, counterIndex: number, prop: string | null,
|
||||||
bindingIndex: number,
|
bindingIndex: number,
|
||||||
value: string | String | number | boolean | null | undefined | LStylingMap,
|
value: string | String | number | boolean | null | undefined | LStylingMap,
|
||||||
deferRegistration?: boolean, forceUpdate?: boolean): boolean {
|
deferRegistration: boolean, forceUpdate: boolean, sanitizationRequired: boolean): boolean {
|
||||||
if (!isContextLocked(context)) {
|
if (!isContextLocked(context)) {
|
||||||
if (deferRegistration) {
|
if (deferRegistration) {
|
||||||
deferBindingRegistration(context, counterIndex, prop, bindingIndex);
|
deferBindingRegistration(context, counterIndex, prop, bindingIndex, sanitizationRequired);
|
||||||
} else {
|
} else {
|
||||||
deferredBindingQueue.length && flushDeferredBindings();
|
deferredBindingQueue.length && flushDeferredBindings();
|
||||||
|
|
||||||
|
@ -127,7 +132,7 @@ function updateBindingData(
|
||||||
// update pass is executed (remember that all styling instructions
|
// update pass is executed (remember that all styling instructions
|
||||||
// are run in the update phase, and, as a result, are no more
|
// are run in the update phase, and, as a result, are no more
|
||||||
// styling instructions that are run in the creation phase).
|
// styling instructions that are run in the creation phase).
|
||||||
registerBinding(context, counterIndex, prop, bindingIndex);
|
registerBinding(context, counterIndex, prop, bindingIndex, sanitizationRequired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,8 +155,9 @@ function updateBindingData(
|
||||||
* after the inheritance chain exits.
|
* after the inheritance chain exits.
|
||||||
*/
|
*/
|
||||||
function deferBindingRegistration(
|
function deferBindingRegistration(
|
||||||
context: TStylingContext, counterIndex: number, prop: string | null, bindingIndex: number) {
|
context: TStylingContext, counterIndex: number, prop: string | null, bindingIndex: number,
|
||||||
deferredBindingQueue.splice(0, 0, context, counterIndex, prop, bindingIndex);
|
sanitizationRequired: boolean) {
|
||||||
|
deferredBindingQueue.unshift(context, counterIndex, prop, bindingIndex, sanitizationRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -165,7 +171,8 @@ function flushDeferredBindings() {
|
||||||
const count = deferredBindingQueue[i++] as number;
|
const count = deferredBindingQueue[i++] as number;
|
||||||
const prop = deferredBindingQueue[i++] as string;
|
const prop = deferredBindingQueue[i++] as string;
|
||||||
const bindingIndex = deferredBindingQueue[i++] as number | null;
|
const bindingIndex = deferredBindingQueue[i++] as number | null;
|
||||||
registerBinding(context, count, prop, bindingIndex);
|
const sanitizationRequired = deferredBindingQueue[i++] as boolean;
|
||||||
|
registerBinding(context, count, prop, bindingIndex, sanitizationRequired);
|
||||||
}
|
}
|
||||||
deferredBindingQueue.length = 0;
|
deferredBindingQueue.length = 0;
|
||||||
}
|
}
|
||||||
|
@ -208,7 +215,7 @@ function flushDeferredBindings() {
|
||||||
*/
|
*/
|
||||||
export function registerBinding(
|
export function registerBinding(
|
||||||
context: TStylingContext, countId: number, prop: string | null,
|
context: TStylingContext, countId: number, prop: string | null,
|
||||||
bindingValue: number | null | string | boolean) {
|
bindingValue: number | null | string | boolean, sanitizationRequired?: boolean) {
|
||||||
// prop-based bindings (e.g `<div [style.width]="w" [class.foo]="f">`)
|
// prop-based bindings (e.g `<div [style.width]="w" [class.foo]="f">`)
|
||||||
if (prop) {
|
if (prop) {
|
||||||
let found = false;
|
let found = false;
|
||||||
|
@ -220,7 +227,7 @@ export function registerBinding(
|
||||||
if (found) {
|
if (found) {
|
||||||
// all style/class bindings are sorted by property name
|
// all style/class bindings are sorted by property name
|
||||||
if (prop < p) {
|
if (prop < p) {
|
||||||
allocateNewContextEntry(context, i, prop);
|
allocateNewContextEntry(context, i, prop, sanitizationRequired);
|
||||||
}
|
}
|
||||||
addBindingIntoContext(context, false, i, bindingValue, countId);
|
addBindingIntoContext(context, false, i, bindingValue, countId);
|
||||||
break;
|
break;
|
||||||
|
@ -229,7 +236,7 @@ export function registerBinding(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
allocateNewContextEntry(context, context.length, prop);
|
allocateNewContextEntry(context, context.length, prop, sanitizationRequired);
|
||||||
addBindingIntoContext(context, false, i, bindingValue, countId);
|
addBindingIntoContext(context, false, i, bindingValue, countId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,15 +248,18 @@ export function registerBinding(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function allocateNewContextEntry(context: TStylingContext, index: number, prop: string) {
|
function allocateNewContextEntry(
|
||||||
|
context: TStylingContext, index: number, prop: string, sanitizationRequired?: boolean) {
|
||||||
// 1,2: splice index locations
|
// 1,2: splice index locations
|
||||||
// 3: each entry gets a guard mask value that is used to check against updates
|
// 3: each entry gets a config value (guard mask + flags)
|
||||||
// 4. each entry gets a size value (which is always one because there is always a default binding
|
// 4. each entry gets a size value (which is always one because there is always a default binding
|
||||||
// value)
|
// value)
|
||||||
// 5. the property that is getting allocated into the context
|
// 5. the property that is getting allocated into the context
|
||||||
// 6. the default binding value (usually `null`)
|
// 6. the default binding value (usually `null`)
|
||||||
context.splice(
|
const config = sanitizationRequired ? TStylingContextPropConfigFlags.SanitizationRequired :
|
||||||
index, 0, DEFAULT_GUARD_MASK_VALUE, DEFAULT_SIZE_VALUE, prop, DEFAULT_BINDING_VALUE);
|
TStylingContextPropConfigFlags.Default;
|
||||||
|
context.splice(index, 0, config, DEFAULT_SIZE_VALUE, prop, DEFAULT_BINDING_VALUE);
|
||||||
|
setGuardMask(context, index, DEFAULT_GUARD_MASK_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -285,7 +295,12 @@ function addBindingIntoContext(
|
||||||
if (typeof bindingValue === 'number') {
|
if (typeof bindingValue === 'number') {
|
||||||
context.splice(lastValueIndex, 0, bindingValue);
|
context.splice(lastValueIndex, 0, bindingValue);
|
||||||
(context[index + TStylingContextIndex.ValuesCountOffset] as number)++;
|
(context[index + TStylingContextIndex.ValuesCountOffset] as number)++;
|
||||||
(context[index + TStylingContextIndex.GuardOffset] as number) |= 1 << countId;
|
|
||||||
|
// now that a new binding index has been added to the property
|
||||||
|
// the guard mask bit value (at the `countId` position) needs
|
||||||
|
// to be included into the existing mask value.
|
||||||
|
const guardMask = getGuardMask(context, index) | (1 << countId);
|
||||||
|
setGuardMask(context, index, guardMask);
|
||||||
} else if (typeof bindingValue === 'string' && context[lastValueIndex] == null) {
|
} else if (typeof bindingValue === 'string' && context[lastValueIndex] == null) {
|
||||||
context[lastValueIndex] = bindingValue;
|
context[lastValueIndex] = bindingValue;
|
||||||
}
|
}
|
||||||
|
@ -294,37 +309,49 @@ function addBindingIntoContext(
|
||||||
/**
|
/**
|
||||||
* Applies all class entries in the provided context to the provided element and resets
|
* Applies all class entries in the provided context to the provided element and resets
|
||||||
* any counter and/or bitMask values associated with class bindings.
|
* any counter and/or bitMask values associated with class bindings.
|
||||||
|
*
|
||||||
|
* @returns whether or not the classes were flushed to the element.
|
||||||
*/
|
*/
|
||||||
export function applyClasses(
|
export function applyClasses(
|
||||||
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, context: TStylingContext,
|
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, context: TStylingContext,
|
||||||
element: RElement, directiveIndex: number) {
|
element: RElement, directiveIndex: number): boolean {
|
||||||
|
let classesFlushed = false;
|
||||||
if (allowStylingFlush(context, directiveIndex)) {
|
if (allowStylingFlush(context, directiveIndex)) {
|
||||||
const isFirstPass = !isContextLocked(context);
|
const isFirstPass = !isContextLocked(context);
|
||||||
isFirstPass && lockContext(context);
|
isFirstPass && lockContext(context);
|
||||||
if (classesBitMask) {
|
if (classesBitMask) {
|
||||||
applyStyling(context, renderer, element, data, classesBitMask, setClass);
|
// there is no way to sanitize a class value therefore `sanitizer=null`
|
||||||
|
applyStyling(context, renderer, element, data, classesBitMask, setClass, null);
|
||||||
classesBitMask = 0;
|
classesBitMask = 0;
|
||||||
|
classesFlushed = true;
|
||||||
}
|
}
|
||||||
currentClassIndex = STYLING_INDEX_START_VALUE;
|
currentClassIndex = STYLING_INDEX_START_VALUE;
|
||||||
}
|
}
|
||||||
|
return classesFlushed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies all style entries in the provided context to the provided element and resets
|
* Applies all style entries in the provided context to the provided element and resets
|
||||||
* any counter and/or bitMask values associated with style bindings.
|
* any counter and/or bitMask values associated with style bindings.
|
||||||
|
*
|
||||||
|
* @returns whether or not the styles were flushed to the element.
|
||||||
*/
|
*/
|
||||||
export function applyStyles(
|
export function applyStyles(
|
||||||
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, context: TStylingContext,
|
renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, context: TStylingContext,
|
||||||
element: RElement, directiveIndex: number) {
|
element: RElement, directiveIndex: number, sanitizer: StyleSanitizeFn | null): boolean {
|
||||||
|
let stylesFlushed = false;
|
||||||
if (allowStylingFlush(context, directiveIndex)) {
|
if (allowStylingFlush(context, directiveIndex)) {
|
||||||
const isFirstPass = !isContextLocked(context);
|
const isFirstPass = !isContextLocked(context);
|
||||||
isFirstPass && lockContext(context);
|
isFirstPass && lockContext(context);
|
||||||
if (stylesBitMask) {
|
if (stylesBitMask) {
|
||||||
applyStyling(context, renderer, element, data, stylesBitMask, setStyle);
|
applyStyling(context, renderer, element, data, stylesBitMask, setStyle, sanitizer);
|
||||||
stylesBitMask = 0;
|
stylesBitMask = 0;
|
||||||
|
stylesFlushed = true;
|
||||||
}
|
}
|
||||||
currentStyleIndex = STYLING_INDEX_START_VALUE;
|
currentStyleIndex = STYLING_INDEX_START_VALUE;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return stylesFlushed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -355,7 +382,8 @@ export function applyStyles(
|
||||||
*/
|
*/
|
||||||
export function applyStyling(
|
export function applyStyling(
|
||||||
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||||
bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn) {
|
bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn,
|
||||||
|
sanitizer: StyleSanitizeFn | null) {
|
||||||
deferredBindingQueue.length && flushDeferredBindings();
|
deferredBindingQueue.length && flushDeferredBindings();
|
||||||
|
|
||||||
const bitMask = normalizeBitMaskValue(bitMaskValue);
|
const bitMask = normalizeBitMaskValue(bitMaskValue);
|
||||||
|
@ -380,9 +408,12 @@ export function applyStyling(
|
||||||
// value gets set for the styling binding
|
// value gets set for the styling binding
|
||||||
for (let j = 0; j < valuesCountUpToDefault; j++) {
|
for (let j = 0; j < valuesCountUpToDefault; j++) {
|
||||||
const bindingIndex = getBindingValue(context, i, j) as number;
|
const bindingIndex = getBindingValue(context, i, j) as number;
|
||||||
const valueToApply = bindingData[bindingIndex];
|
const value = bindingData[bindingIndex];
|
||||||
if (isStylingValueDefined(valueToApply)) {
|
if (isStylingValueDefined(value)) {
|
||||||
applyStylingFn(renderer, element, prop, valueToApply, bindingIndex);
|
const finalValue = sanitizer && isSanitizationRequired(context, i) ?
|
||||||
|
sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) :
|
||||||
|
value;
|
||||||
|
applyStylingFn(renderer, element, prop, finalValue, bindingIndex);
|
||||||
valueApplied = true;
|
valueApplied = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -397,7 +428,8 @@ export function applyStyling(
|
||||||
const mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp :
|
const mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp :
|
||||||
StylingMapsSyncMode.ApplyTargetProp);
|
StylingMapsSyncMode.ApplyTargetProp);
|
||||||
const valueAppliedWithinMap = stylingMapsSyncFn(
|
const valueAppliedWithinMap = stylingMapsSyncFn(
|
||||||
context, renderer, element, bindingData, applyStylingFn, mode, prop, defaultValue);
|
context, renderer, element, bindingData, applyStylingFn, sanitizer, mode, prop,
|
||||||
|
defaultValue);
|
||||||
valueApplied = valueApplied || valueAppliedWithinMap;
|
valueApplied = valueApplied || valueAppliedWithinMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +449,7 @@ export function applyStyling(
|
||||||
// values. For this reason, one more call to the sync function
|
// values. For this reason, one more call to the sync function
|
||||||
// needs to be issued at the end.
|
// needs to be issued at the end.
|
||||||
if (stylingMapsSyncFn) {
|
if (stylingMapsSyncFn) {
|
||||||
stylingMapsSyncFn(context, renderer, element, bindingData, applyStylingFn, mapsMode);
|
stylingMapsSyncFn(context, renderer, element, bindingData, applyStylingFn, sanitizer, mapsMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,24 @@
|
||||||
* 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 {Sanitizer} from '../../sanitization/security';
|
||||||
|
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||||
import {LContainer} from '../interfaces/container';
|
import {LContainer} from '../interfaces/container';
|
||||||
import {AttributeMarker, TAttributes, TNode, TNodeType} from '../interfaces/node';
|
import {AttributeMarker, TAttributes, TNode, TNodeType} from '../interfaces/node';
|
||||||
import {RElement} from '../interfaces/renderer';
|
import {RElement} from '../interfaces/renderer';
|
||||||
import {StylingContext as OldStylingContext, StylingIndex as OldStylingIndex} from '../interfaces/styling';
|
import {StylingContext as OldStylingContext, StylingIndex as OldStylingIndex} from '../interfaces/styling';
|
||||||
import {BINDING_INDEX, HEADER_OFFSET, HOST, LView, RENDERER} from '../interfaces/view';
|
import {BINDING_INDEX, HEADER_OFFSET, HOST, LView, RENDERER, SANITIZER} from '../interfaces/view';
|
||||||
import {getActiveDirectiveId, getActiveDirectiveSuperClassDepth, getActiveDirectiveSuperClassHeight, getLView, getSelectedIndex} from '../state';
|
import {getActiveDirectiveId, getActiveDirectiveSuperClassDepth, getActiveDirectiveSuperClassHeight, getLView, getSelectedIndex} from '../state';
|
||||||
import {NO_CHANGE} from '../tokens';
|
import {NO_CHANGE} from '../tokens';
|
||||||
|
import {renderStringify} from '../util/misc_utils';
|
||||||
import {getTNode, isStylingContext as isOldStylingContext} from '../util/view_utils';
|
import {getTNode, isStylingContext as isOldStylingContext} from '../util/view_utils';
|
||||||
|
|
||||||
import {applyClasses, applyStyles, registerBinding, updateClassBinding, updateStyleBinding} from './bindings';
|
import {applyClasses, applyStyles, registerBinding, updateClassBinding, updateStyleBinding} from './bindings';
|
||||||
import {TStylingContext} from './interfaces';
|
import {TStylingContext} from './interfaces';
|
||||||
import {activeStylingMapFeature, normalizeIntoStylingMap} from './map_based_bindings';
|
import {activeStylingMapFeature, normalizeIntoStylingMap} from './map_based_bindings';
|
||||||
|
import {getCurrentStyleSanitizer, setCurrentStyleSanitizer} from './state';
|
||||||
import {attachStylingDebugObject} from './styling_debug';
|
import {attachStylingDebugObject} from './styling_debug';
|
||||||
import {allocStylingContext, hasValueChanged, updateContextDirectiveIndex} from './util';
|
import {allocTStylingContext, getCurrentOrLViewSanitizer, hasValueChanged, updateContextDirectiveIndex} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,12 +55,32 @@ export function stylingInit() {
|
||||||
updateLastDirectiveIndex(tNode, getActiveDirectiveStylingIndex());
|
updateLastDirectiveIndex(tNode, getActiveDirectiveStylingIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current style sanitizer function which will then be used
|
||||||
|
* within all follow-up prop and map-based style binding instructions
|
||||||
|
* for the given element.
|
||||||
|
*
|
||||||
|
* Note that once styling has been applied to the element (i.e. once
|
||||||
|
* `select(n)` is executed or the hostBindings/template function exits)
|
||||||
|
* then the active `sanitizerFn` will be set to `null`. This means that
|
||||||
|
* once styling is applied to another element then a another call to
|
||||||
|
* `styleSanitizer` will need to be made.
|
||||||
|
*
|
||||||
|
* @param sanitizerFn The sanitization function that will be used to
|
||||||
|
* process style prop/value entries.
|
||||||
|
*
|
||||||
|
* @codeGenApi
|
||||||
|
*/
|
||||||
|
export function styleSanitizer(sanitizer: Sanitizer | StyleSanitizeFn | null): void {
|
||||||
|
setCurrentStyleSanitizer(sanitizer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirror implementation of the `styleProp()` instruction (found in `instructions/styling.ts`).
|
* Mirror implementation of the `styleProp()` instruction (found in `instructions/styling.ts`).
|
||||||
*/
|
*/
|
||||||
export function styleProp(
|
export function styleProp(
|
||||||
prop: string, value: string | number | String | null, suffix?: string | null): void {
|
prop: string, value: string | number | String | null, suffix?: string | null): void {
|
||||||
_stylingProp(prop, value, false);
|
_stylingProp(prop, resolveStylePropValue(value, suffix), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,10 +103,12 @@ function _stylingProp(
|
||||||
if (isClassBased) {
|
if (isClassBased) {
|
||||||
updateClassBinding(
|
updateClassBinding(
|
||||||
getClassesContext(tNode), lView, prop, bindingIndex, value as string | boolean | null,
|
getClassesContext(tNode), lView, prop, bindingIndex, value as string | boolean | null,
|
||||||
defer);
|
defer, false);
|
||||||
} else {
|
} else {
|
||||||
|
const sanitizer = getCurrentOrLViewSanitizer(lView);
|
||||||
updateStyleBinding(
|
updateStyleBinding(
|
||||||
getStylesContext(tNode), lView, prop, bindingIndex, value as string | null, defer);
|
getStylesContext(tNode), lView, prop, bindingIndex, value as string | null, sanitizer,
|
||||||
|
defer, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +148,10 @@ function _stylingMap(value: {[key: string]: any} | string | null, isClassBased:
|
||||||
updateClassBinding(
|
updateClassBinding(
|
||||||
getClassesContext(tNode), lView, null, bindingIndex, lStylingMap, defer, valueHasChanged);
|
getClassesContext(tNode), lView, null, bindingIndex, lStylingMap, defer, valueHasChanged);
|
||||||
} else {
|
} else {
|
||||||
|
const sanitizer = getCurrentOrLViewSanitizer(lView);
|
||||||
updateStyleBinding(
|
updateStyleBinding(
|
||||||
getStylesContext(tNode), lView, null, bindingIndex, lStylingMap, defer, valueHasChanged);
|
getStylesContext(tNode), lView, null, bindingIndex, lStylingMap, sanitizer, defer,
|
||||||
|
valueHasChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +179,11 @@ export function stylingApply() {
|
||||||
const native = getNativeFromLView(index, lView);
|
const native = getNativeFromLView(index, lView);
|
||||||
const directiveIndex = getActiveDirectiveStylingIndex();
|
const directiveIndex = getActiveDirectiveStylingIndex();
|
||||||
applyClasses(renderer, lView, getClassesContext(tNode), native, directiveIndex);
|
applyClasses(renderer, lView, getClassesContext(tNode), native, directiveIndex);
|
||||||
applyStyles(renderer, lView, getStylesContext(tNode), native, directiveIndex);
|
|
||||||
|
const sanitizer = getCurrentOrLViewSanitizer(lView);
|
||||||
|
applyStyles(renderer, lView, getStylesContext(tNode), native, directiveIndex, sanitizer);
|
||||||
|
|
||||||
|
setCurrentStyleSanitizer(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,10 +234,10 @@ export function registerInitialStylingIntoContext(
|
||||||
mode = attr;
|
mode = attr;
|
||||||
} else if (mode == AttributeMarker.Classes) {
|
} else if (mode == AttributeMarker.Classes) {
|
||||||
classesContext = classesContext || getClassesContext(tNode);
|
classesContext = classesContext || getClassesContext(tNode);
|
||||||
registerBinding(classesContext, -1, attr as string, true);
|
registerBinding(classesContext, -1, attr as string, true, false);
|
||||||
} else if (mode == AttributeMarker.Styles) {
|
} else if (mode == AttributeMarker.Styles) {
|
||||||
stylesContext = stylesContext || getStylesContext(tNode);
|
stylesContext = stylesContext || getStylesContext(tNode);
|
||||||
registerBinding(stylesContext, -1, attr as string, attrs[++i] as string);
|
registerBinding(stylesContext, -1, attr as string, attrs[++i] as string, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,7 +286,7 @@ function getClassesContext(tNode: TNode): TStylingContext {
|
||||||
function getContext(tNode: TNode, isClassBased: boolean) {
|
function getContext(tNode: TNode, isClassBased: boolean) {
|
||||||
let context = isClassBased ? tNode.newClasses : tNode.newStyles;
|
let context = isClassBased ? tNode.newClasses : tNode.newStyles;
|
||||||
if (!context) {
|
if (!context) {
|
||||||
context = allocStylingContext();
|
context = allocTStylingContext();
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
attachStylingDebugObject(context);
|
attachStylingDebugObject(context);
|
||||||
}
|
}
|
||||||
|
@ -266,3 +298,22 @@ function getContext(tNode: TNode, isClassBased: boolean) {
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveStylePropValue(
|
||||||
|
value: string | number | String | null, suffix: string | null | undefined) {
|
||||||
|
let resolvedValue: string|null = null;
|
||||||
|
if (value !== null) {
|
||||||
|
if (suffix) {
|
||||||
|
// when a suffix is applied then it will bypass
|
||||||
|
// sanitization entirely (b/c a new string is created)
|
||||||
|
resolvedValue = renderStringify(value) + suffix;
|
||||||
|
} else {
|
||||||
|
// sanitization happens by dealing with a String value
|
||||||
|
// this means that the string value will be passed through
|
||||||
|
// into the style rendering later (which is where the value
|
||||||
|
// will be sanitized before it is applied)
|
||||||
|
resolvedValue = value as any as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolvedValue;
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* 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 {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||||
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
|
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
|
||||||
import {LView} from '../interfaces/view';
|
import {LView} from '../interfaces/view';
|
||||||
|
|
||||||
|
@ -200,16 +201,16 @@ import {LView} from '../interfaces/view';
|
||||||
* In order to figure out which value to apply, the following
|
* In order to figure out which value to apply, the following
|
||||||
* binding prioritization is adhered to:
|
* binding prioritization is adhered to:
|
||||||
*
|
*
|
||||||
* 1. First template-level styling bindings are applied (if present).
|
* 1. First template-level styling bindings are applied (if present).
|
||||||
* This includes things like `[style.width]` and `[class.active]`.
|
* This includes things like `[style.width]` and `[class.active]`.
|
||||||
*
|
*
|
||||||
* 2. Second are styling-level host bindings present in directives.
|
* 2. Second are styling-level host bindings present in directives.
|
||||||
* (if there are sub/super directives present then the sub directives
|
* (if there are sub/super directives present then the sub directives
|
||||||
* are applied first).
|
* are applied first).
|
||||||
*
|
*
|
||||||
* 3. Third are styling-level host bindings present in components.
|
* 3. Third are styling-level host bindings present in components.
|
||||||
* (if there are sub/super components present then the sub directives
|
* (if there are sub/super components present then the sub directives
|
||||||
* are applied first).
|
* are applied first).
|
||||||
*
|
*
|
||||||
* This means that in the code above the styling binding present in the
|
* This means that in the code above the styling binding present in the
|
||||||
* template is applied first and, only if its falsy, then the directive
|
* template is applied first and, only if its falsy, then the directive
|
||||||
|
@ -225,7 +226,39 @@ import {LView} from '../interfaces/view';
|
||||||
* For the algorithm to apply styling values efficiently, the
|
* For the algorithm to apply styling values efficiently, the
|
||||||
* styling map entries must be applied in sync (property by property)
|
* styling map entries must be applied in sync (property by property)
|
||||||
* with prop-based bindings. (The map-based algorithm is described
|
* with prop-based bindings. (The map-based algorithm is described
|
||||||
* more inside of the `render3/stlying_next/map_based_bindings.ts` file.)
|
* more inside of the `render3/styling_next/map_based_bindings.ts` file.)
|
||||||
|
*
|
||||||
|
* ## Sanitization
|
||||||
|
* Sanitization is used to prevent invalid style values from being applied to
|
||||||
|
* the element.
|
||||||
|
*
|
||||||
|
* It is enabled in two cases:
|
||||||
|
*
|
||||||
|
* 1. The `styleSanitizer(sanitizerFn)` instruction was called (just before any other
|
||||||
|
* styling instructions are run).
|
||||||
|
*
|
||||||
|
* 2. The component/directive `LView` instance has a sanitizer object attached to it
|
||||||
|
* (this happens when `renderComponent` is executed with a `sanitizer` value or
|
||||||
|
* if the ngModule contains a sanitizer provider attached to it).
|
||||||
|
*
|
||||||
|
* If and when sanitization is active then all property/value entries will be evaluated
|
||||||
|
* through the active sanitizer before they are applied to the element (or the styling
|
||||||
|
* debug handler).
|
||||||
|
*
|
||||||
|
* If a `Sanitizer` object is used (via the `LView[SANITIZER]` value) then that object
|
||||||
|
* will be used for every property.
|
||||||
|
*
|
||||||
|
* If a `StyleSanitizerFn` function is used (via the `styleSanitizer`) then it will be
|
||||||
|
* called in two ways:
|
||||||
|
*
|
||||||
|
* 1. property validation mode: this will be called early to mark whether a property
|
||||||
|
* should be sanitized or not at during the flushing stage.
|
||||||
|
*
|
||||||
|
* 2. value sanitization mode: this will be called during the flushing stage and will
|
||||||
|
* run the sanitizer function against the value before applying it to the element.
|
||||||
|
*
|
||||||
|
* If sanitization returns an empty value then that empty value will be applied
|
||||||
|
* to the element.
|
||||||
*/
|
*/
|
||||||
export interface TStylingContext extends Array<number|string|number|boolean|null|LStylingMap> {
|
export interface TStylingContext extends Array<number|string|number|boolean|null|LStylingMap> {
|
||||||
/** Configuration data for the context */
|
/** Configuration data for the context */
|
||||||
|
@ -289,12 +322,22 @@ export const enum TStylingContextIndex {
|
||||||
|
|
||||||
// each tuple entry in the context
|
// each tuple entry in the context
|
||||||
// (mask, count, prop, ...bindings||default-value)
|
// (mask, count, prop, ...bindings||default-value)
|
||||||
GuardOffset = 0,
|
ConfigAndGuardOffset = 0,
|
||||||
ValuesCountOffset = 1,
|
ValuesCountOffset = 1,
|
||||||
PropOffset = 2,
|
PropOffset = 2,
|
||||||
BindingsStartOffset = 3,
|
BindingsStartOffset = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A series of flags used for each property entry within the `TStylingContext`.
|
||||||
|
*/
|
||||||
|
export const enum TStylingContextPropConfigFlags {
|
||||||
|
Default = 0b0,
|
||||||
|
SanitizationRequired = 0b1,
|
||||||
|
TotalBits = 1,
|
||||||
|
Mask = 0b1,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function used to apply or remove styling from an element for a given property.
|
* A function used to apply or remove styling from an element for a given property.
|
||||||
*/
|
*/
|
||||||
|
@ -370,8 +413,8 @@ export const enum LStylingMapIndex {
|
||||||
*/
|
*/
|
||||||
export interface SyncStylingMapsFn {
|
export interface SyncStylingMapsFn {
|
||||||
(context: TStylingContext, renderer: Renderer3|ProceduralRenderer3|null, element: RElement,
|
(context: TStylingContext, renderer: Renderer3|ProceduralRenderer3|null, element: RElement,
|
||||||
data: LStylingData, applyStylingFn: ApplyStylingFn, mode: StylingMapsSyncMode,
|
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn|null,
|
||||||
targetProp?: string|null, defaultValue?: string|null): boolean;
|
mode: StylingMapsSyncMode, targetProp?: string|null, defaultValue?: string|null): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* 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 {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||||
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
|
import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer';
|
||||||
|
|
||||||
import {setStylingMapsSyncFn} from './bindings';
|
import {setStylingMapsSyncFn} from './bindings';
|
||||||
|
@ -103,8 +104,9 @@ import {getBindingValue, getValuesCount, isStylingValueDefined} from './util';
|
||||||
*/
|
*/
|
||||||
export const syncStylingMap: SyncStylingMapsFn =
|
export const syncStylingMap: SyncStylingMapsFn =
|
||||||
(context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
(context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||||
data: LStylingData, applyStylingFn: ApplyStylingFn, mode: StylingMapsSyncMode,
|
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
|
||||||
targetProp?: string | null, defaultValue?: string | null): boolean => {
|
mode: StylingMapsSyncMode, targetProp?: string | null,
|
||||||
|
defaultValue?: string | null): boolean => {
|
||||||
let targetPropValueWasApplied = false;
|
let targetPropValueWasApplied = false;
|
||||||
|
|
||||||
// once the map-based styling code is activate it is never deactivated. For this reason a
|
// once the map-based styling code is activate it is never deactivated. For this reason a
|
||||||
|
@ -125,8 +127,8 @@ export const syncStylingMap: SyncStylingMapsFn =
|
||||||
|
|
||||||
if (runTheSyncAlgorithm) {
|
if (runTheSyncAlgorithm) {
|
||||||
targetPropValueWasApplied = innerSyncStylingMap(
|
targetPropValueWasApplied = innerSyncStylingMap(
|
||||||
context, renderer, element, data, applyStylingFn, mode, targetProp || null, 0,
|
context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp || null,
|
||||||
defaultValue || null);
|
0, defaultValue || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loopUntilEnd) {
|
if (loopUntilEnd) {
|
||||||
|
@ -148,8 +150,9 @@ export const syncStylingMap: SyncStylingMapsFn =
|
||||||
*/
|
*/
|
||||||
function innerSyncStylingMap(
|
function innerSyncStylingMap(
|
||||||
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement,
|
||||||
data: LStylingData, applyStylingFn: ApplyStylingFn, mode: StylingMapsSyncMode,
|
data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null,
|
||||||
targetProp: string | null, currentMapIndex: number, defaultValue: string | null): boolean {
|
mode: StylingMapsSyncMode, targetProp: string | null, currentMapIndex: number,
|
||||||
|
defaultValue: string | null): boolean {
|
||||||
let targetPropValueWasApplied = false;
|
let targetPropValueWasApplied = false;
|
||||||
|
|
||||||
const totalMaps = getValuesCount(context, TStylingContextIndex.MapBindingsPosition);
|
const totalMaps = getValuesCount(context, TStylingContextIndex.MapBindingsPosition);
|
||||||
|
@ -176,7 +179,7 @@ function innerSyncStylingMap(
|
||||||
iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched);
|
iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched);
|
||||||
const innerProp = iteratedTooFar ? targetProp : prop;
|
const innerProp = iteratedTooFar ? targetProp : prop;
|
||||||
let valueApplied = innerSyncStylingMap(
|
let valueApplied = innerSyncStylingMap(
|
||||||
context, renderer, element, data, applyStylingFn, innerMode, innerProp,
|
context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp,
|
||||||
currentMapIndex + 1, defaultValue);
|
currentMapIndex + 1, defaultValue);
|
||||||
|
|
||||||
if (iteratedTooFar) {
|
if (iteratedTooFar) {
|
||||||
|
@ -187,7 +190,10 @@ function innerSyncStylingMap(
|
||||||
const useDefault = isTargetPropMatched && !valueIsDefined;
|
const useDefault = isTargetPropMatched && !valueIsDefined;
|
||||||
const valueToApply = useDefault ? defaultValue : value;
|
const valueToApply = useDefault ? defaultValue : value;
|
||||||
const bindingIndexToApply = useDefault ? bindingIndex : null;
|
const bindingIndexToApply = useDefault ? bindingIndex : null;
|
||||||
applyStylingFn(renderer, element, prop, valueToApply, bindingIndexToApply);
|
const finalValue = sanitizer ?
|
||||||
|
sanitizer(prop, valueToApply, StyleSanitizeMode.ValidateAndSanitize) :
|
||||||
|
valueToApply;
|
||||||
|
applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply);
|
||||||
valueApplied = true;
|
valueApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,21 @@
|
||||||
* 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 {Sanitizer} from '../../sanitization/security';
|
||||||
|
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------
|
||||||
|
*
|
||||||
|
* This file contains temporary code to incorporate the new styling refactor
|
||||||
|
* code to work alongside the existing instruction set.
|
||||||
|
*
|
||||||
|
* This file will be removed once `select(n)` is fully functional (once
|
||||||
|
* it is able to evaluate host bindings in sync element-by-element
|
||||||
|
* with template code).
|
||||||
|
*
|
||||||
|
* --------
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A temporary enum of states that inform the core whether or not
|
* A temporary enum of states that inform the core whether or not
|
||||||
|
@ -35,3 +50,12 @@ export function runtimeIsNewStylingInUse() {
|
||||||
export function runtimeAllowOldStyling() {
|
export function runtimeAllowOldStyling() {
|
||||||
return _stylingMode < RuntimeStylingMode.UseNew;
|
return _stylingMode < RuntimeStylingMode.UseNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _currentSanitizer: Sanitizer|StyleSanitizeFn|null;
|
||||||
|
export function setCurrentStyleSanitizer(sanitizer: Sanitizer | StyleSanitizeFn | null) {
|
||||||
|
_currentSanitizer = sanitizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentStyleSanitizer() {
|
||||||
|
return _currentSanitizer;
|
||||||
|
}
|
||||||
|
|
|
@ -5,13 +5,19 @@
|
||||||
* 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 {Sanitizer} from '../../sanitization/security';
|
||||||
|
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||||
import {RElement} from '../interfaces/renderer';
|
import {RElement} from '../interfaces/renderer';
|
||||||
|
import {LView, SANITIZER} from '../interfaces/view';
|
||||||
import {attachDebugObject} from '../util/debug_utils';
|
import {attachDebugObject} from '../util/debug_utils';
|
||||||
|
|
||||||
import {applyStyling} from './bindings';
|
import {applyStyling} from './bindings';
|
||||||
import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex} from './interfaces';
|
import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex} from './interfaces';
|
||||||
import {activeStylingMapFeature} from './map_based_bindings';
|
import {activeStylingMapFeature} from './map_based_bindings';
|
||||||
import {getDefaultValue, getGuardMask, getProp, getValuesCount, isContextLocked, isMapBased} from './util';
|
import {getCurrentStyleSanitizer} from './state';
|
||||||
|
import {getCurrentOrLViewSanitizer, getDefaultValue, getGuardMask, getProp, getValuesCount, isContextLocked, isMapBased, isSanitizationRequired} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------
|
* --------
|
||||||
|
@ -59,6 +65,11 @@ export interface DebugStyling {
|
||||||
* runtime values.
|
* runtime values.
|
||||||
*/
|
*/
|
||||||
values: {[key: string]: string | number | null | boolean};
|
values: {[key: string]: string | number | null | boolean};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the sanitizer used to process styles.
|
||||||
|
*/
|
||||||
|
overrideSanitizer(sanitizer: StyleSanitizeFn|null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,6 +88,11 @@ export interface TStylingTupleSummary {
|
||||||
*/
|
*/
|
||||||
guardMask: number;
|
guardMask: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the entry requires sanitization
|
||||||
|
*/
|
||||||
|
sanitizationRequired: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default value that will be applied if any bindings are falsy.
|
* The default value that will be applied if any bindings are falsy.
|
||||||
*/
|
*/
|
||||||
|
@ -127,6 +143,7 @@ class TStylingContextDebug {
|
||||||
const prop = getProp(context, i);
|
const prop = getProp(context, i);
|
||||||
const guardMask = getGuardMask(context, i);
|
const guardMask = getGuardMask(context, i);
|
||||||
const defaultValue = getDefaultValue(context, i);
|
const defaultValue = getDefaultValue(context, i);
|
||||||
|
const sanitizationRequired = isSanitizationRequired(context, i);
|
||||||
const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset;
|
const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset;
|
||||||
|
|
||||||
const sources: (number | string | null)[] = [];
|
const sources: (number | string | null)[] = [];
|
||||||
|
@ -134,7 +151,7 @@ class TStylingContextDebug {
|
||||||
sources.push(context[bindingsStartPosition + j] as number | string | null);
|
sources.push(context[bindingsStartPosition + j] as number | string | null);
|
||||||
}
|
}
|
||||||
|
|
||||||
entries[prop] = {prop, guardMask, valuesCount, defaultValue, sources};
|
entries[prop] = {prop, guardMask, sanitizationRequired, valuesCount, defaultValue, sources};
|
||||||
}
|
}
|
||||||
|
|
||||||
i += TStylingContextIndex.BindingsStartOffset + valuesCount;
|
i += TStylingContextIndex.BindingsStartOffset + valuesCount;
|
||||||
|
@ -150,7 +167,16 @@ class TStylingContextDebug {
|
||||||
* application has `ngDevMode` activated.
|
* application has `ngDevMode` activated.
|
||||||
*/
|
*/
|
||||||
export class NodeStylingDebug implements DebugStyling {
|
export class NodeStylingDebug implements DebugStyling {
|
||||||
constructor(public context: TStylingContext, private _data: LStylingData) {}
|
private _sanitizer: StyleSanitizeFn|null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public context: TStylingContext, private _data: LStylingData,
|
||||||
|
private _isClassBased?: boolean) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the sanitizer used to process styles.
|
||||||
|
*/
|
||||||
|
overrideSanitizer(sanitizer: StyleSanitizeFn|null) { this._sanitizer = sanitizer; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a detailed summary of each styling entry in the context and
|
* Returns a detailed summary of each styling entry in the context and
|
||||||
|
@ -190,6 +216,8 @@ export class NodeStylingDebug implements DebugStyling {
|
||||||
fn(prop, value, bindingIndex || null);
|
fn(prop, value, bindingIndex || null);
|
||||||
};
|
};
|
||||||
|
|
||||||
applyStyling(this.context, null, mockElement, this._data, true, mapFn);
|
const sanitizer = this._isClassBased ? null : (this._sanitizer ||
|
||||||
|
getCurrentOrLViewSanitizer(this._data as LView));
|
||||||
|
applyStyling(this.context, null, mockElement, this._data, true, mapFn, sanitizer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
* 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 {Sanitizer, SecurityContext} from '../../sanitization/security';
|
||||||
|
import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer';
|
||||||
import {StylingContext} from '../interfaces/styling';
|
import {StylingContext} from '../interfaces/styling';
|
||||||
|
import {LView, SANITIZER} from '../interfaces/view';
|
||||||
import {getProp as getOldProp, getSinglePropIndexValue as getOldSinglePropIndexValue} from '../styling/class_and_style_bindings';
|
import {getProp as getOldProp, getSinglePropIndexValue as getOldSinglePropIndexValue} from '../styling/class_and_style_bindings';
|
||||||
|
|
||||||
import {LStylingMap, LStylingMapIndex, TStylingConfigFlags, TStylingContext, TStylingContextIndex} from './interfaces';
|
import {LStylingMap, LStylingMapIndex, TStylingConfigFlags, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from './interfaces';
|
||||||
|
import {getCurrentStyleSanitizer, setCurrentStyleSanitizer} from './state';
|
||||||
|
|
||||||
const MAP_BASED_ENTRY_PROP_NAME = '--MAP--';
|
const MAP_BASED_ENTRY_PROP_NAME = '--MAP--';
|
||||||
|
|
||||||
|
@ -18,8 +22,14 @@ const MAP_BASED_ENTRY_PROP_NAME = '--MAP--';
|
||||||
* This function will also pre-fill the context with data
|
* This function will also pre-fill the context with data
|
||||||
* for map-based bindings.
|
* for map-based bindings.
|
||||||
*/
|
*/
|
||||||
export function allocStylingContext(): TStylingContext {
|
export function allocTStylingContext(): TStylingContext {
|
||||||
return [TStylingConfigFlags.Initial, 0, 0, 0, MAP_BASED_ENTRY_PROP_NAME];
|
// because map-based bindings deal with a dynamic set of values, there
|
||||||
|
// is no way to know ahead of time whether or not sanitization is required.
|
||||||
|
// For this reason the configuration will always mark sanitization as active
|
||||||
|
// (this means that when map-based values are applied then sanitization will
|
||||||
|
// be checked against each property).
|
||||||
|
const mapBasedConfig = TStylingContextPropConfigFlags.SanitizationRequired;
|
||||||
|
return [TStylingConfigFlags.Initial, 0, mapBasedConfig, 0, MAP_BASED_ENTRY_PROP_NAME];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,8 +63,24 @@ export function getProp(context: TStylingContext, index: number) {
|
||||||
return context[index + TStylingContextIndex.PropOffset] as string;
|
return context[index + TStylingContextIndex.PropOffset] as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPropConfig(context: TStylingContext, index: number): number {
|
||||||
|
return (context[index + TStylingContextIndex.ConfigAndGuardOffset] as number) &
|
||||||
|
TStylingContextPropConfigFlags.Mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSanitizationRequired(context: TStylingContext, index: number) {
|
||||||
|
return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function getGuardMask(context: TStylingContext, index: number) {
|
export function getGuardMask(context: TStylingContext, index: number) {
|
||||||
return context[index + TStylingContextIndex.GuardOffset] as number;
|
const configGuardValue = context[index + TStylingContextIndex.ConfigAndGuardOffset] as number;
|
||||||
|
return configGuardValue >> TStylingContextPropConfigFlags.TotalBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setGuardMask(context: TStylingContext, index: number, maskValue: number) {
|
||||||
|
const config = getPropConfig(context, index);
|
||||||
|
const guardMask = maskValue << TStylingContextPropConfigFlags.TotalBits;
|
||||||
|
context[index + TStylingContextIndex.ConfigAndGuardOffset] = config | guardMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getValuesCount(context: TStylingContext, index: number) {
|
export function getValuesCount(context: TStylingContext, index: number) {
|
||||||
|
@ -115,3 +141,36 @@ export function isStylingValueDefined(value: any) {
|
||||||
// set a value to an empty string to remove it.
|
// set a value to an empty string to remove it.
|
||||||
return value != null && value !== '';
|
return value != null && value !== '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current style sanitizer function for the given view.
|
||||||
|
*
|
||||||
|
* The default style sanitizer (which lives inside of `LView`) will
|
||||||
|
* be returned depending on whether the `styleSanitizer` instruction
|
||||||
|
* was called or not prior to any styling instructions running.
|
||||||
|
*/
|
||||||
|
export function getCurrentOrLViewSanitizer(lView: LView): StyleSanitizeFn|null {
|
||||||
|
const sanitizer: StyleSanitizeFn|null = (getCurrentStyleSanitizer() || lView[SANITIZER]) as any;
|
||||||
|
if (sanitizer && typeof sanitizer !== 'function') {
|
||||||
|
setCurrentStyleSanitizer(sanitizer);
|
||||||
|
return sanitizeUsingSanitizerObject;
|
||||||
|
}
|
||||||
|
return sanitizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Style sanitization function that internally uses a `Sanitizer` instance to handle style
|
||||||
|
* sanitization.
|
||||||
|
*/
|
||||||
|
const sanitizeUsingSanitizerObject: StyleSanitizeFn =
|
||||||
|
(prop: string, value: string, mode: StyleSanitizeMode) => {
|
||||||
|
const sanitizer = getCurrentStyleSanitizer() as Sanitizer;
|
||||||
|
if (sanitizer) {
|
||||||
|
if (mode & StyleSanitizeMode.SanitizeOnly) {
|
||||||
|
return sanitizer.sanitize(SecurityContext.STYLE, value);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {renderStringify} from '../render3/util/misc_utils';
|
||||||
import {BypassType, allowSanitizationBypass} from './bypass';
|
import {BypassType, allowSanitizationBypass} from './bypass';
|
||||||
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
||||||
import {Sanitizer, SecurityContext} from './security';
|
import {Sanitizer, SecurityContext} from './security';
|
||||||
import {StyleSanitizeFn, _sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
|
import {StyleSanitizeFn, StyleSanitizeMode, _sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
|
||||||
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,15 +183,22 @@ export function ɵɵsanitizeUrlOrResourceUrl(unsafeUrl: any, tag: string, prop:
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export const ɵɵdefaultStyleSanitizer = (function(prop: string, value?: string): string | boolean {
|
export const ɵɵdefaultStyleSanitizer =
|
||||||
if (value === undefined) {
|
(function(prop: string, value: string|null, mode?: StyleSanitizeMode): string | boolean | null {
|
||||||
return prop === 'background-image' || prop === 'background' || prop === 'border-image' ||
|
mode = mode || StyleSanitizeMode.ValidateAndSanitize;
|
||||||
prop === 'filter' || prop === 'list-style' || prop === 'list-style-image' ||
|
let doSanitizeValue = true;
|
||||||
prop === 'clip-path';
|
if (mode & StyleSanitizeMode.ValidateProperty) {
|
||||||
}
|
doSanitizeValue = prop === 'background-image' || prop === 'background' ||
|
||||||
|
prop === 'border-image' || prop === 'filter' || prop === 'list-style' ||
|
||||||
|
prop === 'list-style-image' || prop === 'clip-path';
|
||||||
|
}
|
||||||
|
|
||||||
return ɵɵsanitizeStyle(value);
|
if (mode & StyleSanitizeMode.SanitizeOnly) {
|
||||||
} as StyleSanitizeFn);
|
return doSanitizeValue ? ɵɵsanitizeStyle(value) : value;
|
||||||
|
} else {
|
||||||
|
return doSanitizeValue;
|
||||||
|
}
|
||||||
|
} as StyleSanitizeFn);
|
||||||
|
|
||||||
export function validateAgainstEventProperties(name: string) {
|
export function validateAgainstEventProperties(name: string) {
|
||||||
if (name.toLowerCase().startsWith('on')) {
|
if (name.toLowerCase().startsWith('on')) {
|
||||||
|
|
|
@ -103,6 +103,30 @@ export function _sanitizeStyle(value: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A series of flags to instruct a style sanitizer to either validate
|
||||||
|
* or sanitize a value.
|
||||||
|
*
|
||||||
|
* Because sanitization is dependent on the style property (i.e. style
|
||||||
|
* sanitization for `width` is much different than for `background-image`)
|
||||||
|
* the sanitization function (e.g. `StyleSanitizerFn`) needs to check a
|
||||||
|
* property value first before it actually sanitizes any values.
|
||||||
|
*
|
||||||
|
* This enum exist to allow a style sanitization function to either only
|
||||||
|
* do validation (check the property to see whether a value will be
|
||||||
|
* sanitized or not) or to sanitize the value (or both).
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export const enum StyleSanitizeMode {
|
||||||
|
/** Just check to see if the property is required to be sanitized or not */
|
||||||
|
ValidateProperty = 0b01,
|
||||||
|
/** Skip checking the property; just sanitize the value */
|
||||||
|
SanitizeOnly = 0b10,
|
||||||
|
/** Check the property and (if true) then sanitize the value */
|
||||||
|
ValidateAndSanitize = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to intercept and sanitize style values before they are written to the renderer.
|
* Used to intercept and sanitize style values before they are written to the renderer.
|
||||||
*
|
*
|
||||||
|
@ -111,9 +135,5 @@ export function _sanitizeStyle(value: string): string {
|
||||||
* If a value is provided then the sanitized version of that will be returned.
|
* If a value is provided then the sanitized version of that will be returned.
|
||||||
*/
|
*/
|
||||||
export interface StyleSanitizeFn {
|
export interface StyleSanitizeFn {
|
||||||
/** This mode is designed to instruct whether the property will be used for sanitization
|
(prop: string, value: string|null, mode?: StyleSanitizeMode): any;
|
||||||
* at a later point */
|
|
||||||
(prop: string): boolean;
|
|
||||||
/** This mode is designed to sanitize the provided value */
|
|
||||||
(prop: string, value: string): string;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,15 @@
|
||||||
*/
|
*/
|
||||||
import {CompilerStylingMode, compilerSetStylingMode} from '@angular/compiler/src/render3/view/styling_state';
|
import {CompilerStylingMode, compilerSetStylingMode} from '@angular/compiler/src/render3/view/styling_state';
|
||||||
import {Component, Directive, HostBinding, Input, ViewChild} from '@angular/core';
|
import {Component, Directive, HostBinding, Input, ViewChild} from '@angular/core';
|
||||||
|
import {SecurityContext} from '@angular/core/src/core';
|
||||||
|
import {getLContext} from '@angular/core/src/render3/context_discovery';
|
||||||
import {DebugNode, LViewDebug, toDebug} from '@angular/core/src/render3/debug';
|
import {DebugNode, LViewDebug, toDebug} from '@angular/core/src/render3/debug';
|
||||||
|
import {SANITIZER} from '@angular/core/src/render3/interfaces/view';
|
||||||
import {RuntimeStylingMode, runtimeSetStylingMode} from '@angular/core/src/render3/styling_next/state';
|
import {RuntimeStylingMode, runtimeSetStylingMode} from '@angular/core/src/render3/styling_next/state';
|
||||||
import {loadLContextFromNode} from '@angular/core/src/render3/util/discovery_utils';
|
import {loadLContextFromNode} from '@angular/core/src/render3/util/discovery_utils';
|
||||||
import {ngDevModeResetPerfCounters as resetStylingCounters} from '@angular/core/src/util/ng_dev_mode';
|
import {ngDevModeResetPerfCounters as resetStylingCounters} from '@angular/core/src/util/ng_dev_mode';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
import {DomSanitizer, SafeStyle} from '@angular/platform-browser';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
import {onlyInIvy} from '@angular/private/testing';
|
import {onlyInIvy} from '@angular/private/testing';
|
||||||
|
|
||||||
|
@ -574,6 +578,161 @@ describe('new styling integration', () => {
|
||||||
assertStyle(element, 'color', 'blue');
|
assertStyle(element, 'color', 'blue');
|
||||||
assertStyle(element, 'opacity', '');
|
assertStyle(element, 'opacity', '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onlyInIvy('only ivy has style/class bindings debugging support')
|
||||||
|
.it('should sanitize style values before writing them', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div [style.width]="widthExp"
|
||||||
|
[style.background-image]="bgImageExp"
|
||||||
|
[style]="styleMapExp"></div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
widthExp = '';
|
||||||
|
bgImageExp = '';
|
||||||
|
styleMapExp: any = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const element = fixture.nativeElement.querySelector('div');
|
||||||
|
const node = getDebugNode(element) !;
|
||||||
|
const styles = node.styles !;
|
||||||
|
|
||||||
|
const lastSanitizedProps: any[] = [];
|
||||||
|
styles.overrideSanitizer((prop, value) => {
|
||||||
|
lastSanitizedProps.push(prop);
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
comp.bgImageExp = '123';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(styles.values).toEqual({
|
||||||
|
'background-image': '123',
|
||||||
|
'width': null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(lastSanitizedProps).toEqual(['background-image']);
|
||||||
|
lastSanitizedProps.length = 0;
|
||||||
|
|
||||||
|
comp.styleMapExp = {'clip-path': '456'};
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(styles.values).toEqual({
|
||||||
|
'background-image': '123',
|
||||||
|
'clip-path': '456',
|
||||||
|
'width': null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(lastSanitizedProps).toEqual(['background-image', 'clip-path']);
|
||||||
|
lastSanitizedProps.length = 0;
|
||||||
|
|
||||||
|
comp.widthExp = '789px';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(styles.values).toEqual({
|
||||||
|
'background-image': '123',
|
||||||
|
'clip-path': '456',
|
||||||
|
'width': '789px',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(lastSanitizedProps).toEqual(['background-image', 'clip-path']);
|
||||||
|
lastSanitizedProps.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
onlyInIvy('only ivy has style/class bindings debugging support')
|
||||||
|
.it('should apply a unit to a style before writing it', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div [style.width.px]="widthExp"
|
||||||
|
[style.height.em]="heightExp"></div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
widthExp: string|number|null = '';
|
||||||
|
heightExp: string|number|null = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const element = fixture.nativeElement.querySelector('div');
|
||||||
|
const node = getDebugNode(element) !;
|
||||||
|
const styles = node.styles !;
|
||||||
|
|
||||||
|
comp.widthExp = '200';
|
||||||
|
comp.heightExp = 10;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(styles.values).toEqual({
|
||||||
|
'width': '200px',
|
||||||
|
'height': '10em',
|
||||||
|
});
|
||||||
|
|
||||||
|
comp.widthExp = 0;
|
||||||
|
comp.heightExp = null;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(styles.values).toEqual({
|
||||||
|
'width': '0px',
|
||||||
|
'height': null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onlyInIvy('only ivy has style/class bindings debugging support')
|
||||||
|
.it('should pick up and use the sanitizer present in the lView', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div [style.width]="w"></div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
w = '100px';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const element = fixture.nativeElement.querySelector('div');
|
||||||
|
const lView = getLContext(element) !.lView;
|
||||||
|
lView[SANITIZER] = new MockSanitizer(value => { return `${value}-safe`; });
|
||||||
|
|
||||||
|
comp.w = '200px';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const node = getDebugNode(element) !;
|
||||||
|
const styles = node.styles !;
|
||||||
|
expect(styles.values['width']).toEqual('200px-safe');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to bind a SafeValue to clip-path', () => {
|
||||||
|
@Component({template: '<div [style.clip-path]="path"></div>'})
|
||||||
|
class Cmp {
|
||||||
|
path !: SafeStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const sanitizer: DomSanitizer = TestBed.get(DomSanitizer);
|
||||||
|
|
||||||
|
fixture.componentInstance.path = sanitizer.bypassSecurityTrustStyle('url("#test")');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const html = fixture.nativeElement.innerHTML;
|
||||||
|
|
||||||
|
// Note that check the raw HTML, because (at the time of writing) the Node-based renderer
|
||||||
|
// that we use to run tests doesn't support `clip-path` in `CSSStyleDeclaration`.
|
||||||
|
expect(html).toMatch(/style=["|']clip-path:\s*url\(.*#test.*\)/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertStyleCounters(countForSet: number, countForRemove: number) {
|
function assertStyleCounters(countForSet: number, countForRemove: number) {
|
||||||
|
@ -597,3 +756,8 @@ function getDebugNode(element: Node): DebugNode|null {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockSanitizer {
|
||||||
|
constructor(private _interceptorFn: ((value: any) => any)) {}
|
||||||
|
sanitize(context: SecurityContext, value: any): string|null { return this._interceptorFn(value); }
|
||||||
|
}
|
||||||
|
|
|
@ -192,7 +192,7 @@
|
||||||
"name": "allocStylingContext"
|
"name": "allocStylingContext"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "allocStylingContext"
|
"name": "allocTStylingContext"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "allocateNewContextEntry"
|
"name": "allocateNewContextEntry"
|
||||||
|
@ -362,6 +362,9 @@
|
||||||
{
|
{
|
||||||
"name": "getGlobal"
|
"name": "getGlobal"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getGuardMask"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getHighestElementOrICUContainer"
|
"name": "getHighestElementOrICUContainer"
|
||||||
},
|
},
|
||||||
|
@ -434,6 +437,9 @@
|
||||||
{
|
{
|
||||||
"name": "getProp"
|
"name": "getProp"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getPropConfig"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getPropValuesStartPosition"
|
"name": "getPropValuesStartPosition"
|
||||||
},
|
},
|
||||||
|
@ -686,6 +692,9 @@
|
||||||
{
|
{
|
||||||
"name": "setCurrentQueryIndex"
|
"name": "setCurrentQueryIndex"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setGuardMask"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setHostBindings"
|
"name": "setHostBindings"
|
||||||
},
|
},
|
||||||
|
|
|
@ -230,6 +230,9 @@
|
||||||
{
|
{
|
||||||
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
|
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SecurityContext"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "SkipSelf"
|
"name": "SkipSelf"
|
||||||
},
|
},
|
||||||
|
@ -444,7 +447,7 @@
|
||||||
"name": "allocStylingContext"
|
"name": "allocStylingContext"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "allocStylingContext"
|
"name": "allocTStylingContext"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "allocateNewContextEntry"
|
"name": "allocateNewContextEntry"
|
||||||
|
@ -785,6 +788,12 @@
|
||||||
{
|
{
|
||||||
"name": "getContextLView"
|
"name": "getContextLView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getCurrentOrLViewSanitizer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getCurrentStyleSanitizer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getDebugContext"
|
"name": "getDebugContext"
|
||||||
},
|
},
|
||||||
|
@ -935,6 +944,9 @@
|
||||||
{
|
{
|
||||||
"name": "getProp"
|
"name": "getProp"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getPropConfig"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getPropValuesStartPosition"
|
"name": "getPropValuesStartPosition"
|
||||||
},
|
},
|
||||||
|
@ -1151,6 +1163,9 @@
|
||||||
{
|
{
|
||||||
"name": "isRootView"
|
"name": "isRootView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "isSanitizationRequired"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "isStylingContext"
|
"name": "isStylingContext"
|
||||||
},
|
},
|
||||||
|
@ -1343,6 +1358,9 @@
|
||||||
{
|
{
|
||||||
"name": "runtimeIsNewStylingInUse"
|
"name": "runtimeIsNewStylingInUse"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sanitizeUsingSanitizerObject"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "saveNameToExportMap"
|
"name": "saveNameToExportMap"
|
||||||
},
|
},
|
||||||
|
@ -1391,12 +1409,18 @@
|
||||||
{
|
{
|
||||||
"name": "setCurrentQueryIndex"
|
"name": "setCurrentQueryIndex"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setCurrentStyleSanitizer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setDirty"
|
"name": "setDirty"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "setFlag"
|
"name": "setFlag"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setGuardMask"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setHostBindings"
|
"name": "setHostBindings"
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {registerHostDirective} from '../../../src/render3/styling/host_instructi
|
||||||
import {BoundPlayerFactory, bindPlayerFactory} from '../../../src/render3/styling/player_factory';
|
import {BoundPlayerFactory, bindPlayerFactory} from '../../../src/render3/styling/player_factory';
|
||||||
import {allocStylingContext, createEmptyStylingContext} from '../../../src/render3/styling/util';
|
import {allocStylingContext, createEmptyStylingContext} from '../../../src/render3/styling/util';
|
||||||
import {ɵɵdefaultStyleSanitizer} from '../../../src/sanitization/sanitization';
|
import {ɵɵdefaultStyleSanitizer} from '../../../src/sanitization/sanitization';
|
||||||
import {StyleSanitizeFn} from '../../../src/sanitization/style_sanitizer';
|
import {StyleSanitizeFn, StyleSanitizeMode} from '../../../src/sanitization/style_sanitizer';
|
||||||
import {ComponentFixture, renderToHtml} from '../render_util';
|
import {ComponentFixture, renderToHtml} from '../render_util';
|
||||||
|
|
||||||
import {MockPlayer} from './mock_player';
|
import {MockPlayer} from './mock_player';
|
||||||
|
@ -2991,11 +2991,16 @@ describe('style and class based bindings', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sanitize styles before they are passed into the player', () => {
|
it('should sanitize styles before they are passed into the player', () => {
|
||||||
const sanitizer = (function(prop: string, value?: string): string | boolean {
|
const sanitizer = (function(prop: string, value: string, mode: StyleSanitizeMode): any {
|
||||||
if (value === undefined) {
|
let allow = true;
|
||||||
return prop === 'width' || prop === 'height';
|
if (mode & StyleSanitizeMode.ValidateProperty) {
|
||||||
|
allow = prop === 'width' || prop === 'height';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode & StyleSanitizeMode.SanitizeOnly) {
|
||||||
|
return allow ? `${value}-safe!` : value;
|
||||||
} else {
|
} else {
|
||||||
return `${value}-safe!`;
|
return allow;
|
||||||
}
|
}
|
||||||
}) as StyleSanitizeFn;
|
}) as StyleSanitizeFn;
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import {DEFAULT_GUARD_MASK_VALUE, registerBinding} from '@angular/core/src/render3/styling_next/bindings';
|
import {DEFAULT_GUARD_MASK_VALUE, registerBinding} from '@angular/core/src/render3/styling_next/bindings';
|
||||||
import {attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug';
|
import {attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug';
|
||||||
|
import {allocTStylingContext} from '../../../src/render3/styling_next/util';
|
||||||
import {allocStylingContext} from '../../../src/render3/styling_next/util';
|
|
||||||
|
|
||||||
describe('styling context', () => {
|
describe('styling context', () => {
|
||||||
it('should register a series of entries into the context', () => {
|
it('should register a series of entries into the context', () => {
|
||||||
|
@ -20,6 +19,7 @@ describe('styling context', () => {
|
||||||
expect(debug.entries['width']).toEqual({
|
expect(debug.entries['width']).toEqual({
|
||||||
prop: 'width',
|
prop: 'width',
|
||||||
valuesCount: 1,
|
valuesCount: 1,
|
||||||
|
sanitizationRequired: false,
|
||||||
guardMask: buildGuardMask(),
|
guardMask: buildGuardMask(),
|
||||||
defaultValue: '100px',
|
defaultValue: '100px',
|
||||||
sources: ['100px'],
|
sources: ['100px'],
|
||||||
|
@ -28,6 +28,7 @@ describe('styling context', () => {
|
||||||
registerBinding(context, 2, 'width', 20);
|
registerBinding(context, 2, 'width', 20);
|
||||||
expect(debug.entries['width']).toEqual({
|
expect(debug.entries['width']).toEqual({
|
||||||
prop: 'width',
|
prop: 'width',
|
||||||
|
sanitizationRequired: false,
|
||||||
valuesCount: 2,
|
valuesCount: 2,
|
||||||
guardMask: buildGuardMask(2),
|
guardMask: buildGuardMask(2),
|
||||||
defaultValue: '100px',
|
defaultValue: '100px',
|
||||||
|
@ -39,6 +40,7 @@ describe('styling context', () => {
|
||||||
expect(debug.entries['height']).toEqual({
|
expect(debug.entries['height']).toEqual({
|
||||||
prop: 'height',
|
prop: 'height',
|
||||||
valuesCount: 3,
|
valuesCount: 3,
|
||||||
|
sanitizationRequired: false,
|
||||||
guardMask: buildGuardMask(3, 4),
|
guardMask: buildGuardMask(3, 4),
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
sources: [10, 15, null],
|
sources: [10, 15, null],
|
||||||
|
@ -50,9 +52,11 @@ describe('styling context', () => {
|
||||||
const context = debug.context;
|
const context = debug.context;
|
||||||
|
|
||||||
registerBinding(context, 1, 'width', null);
|
registerBinding(context, 1, 'width', null);
|
||||||
|
const x = debug.entries['width'];
|
||||||
expect(debug.entries['width']).toEqual({
|
expect(debug.entries['width']).toEqual({
|
||||||
prop: 'width',
|
prop: 'width',
|
||||||
valuesCount: 1,
|
valuesCount: 1,
|
||||||
|
sanitizationRequired: false,
|
||||||
guardMask: buildGuardMask(),
|
guardMask: buildGuardMask(),
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
sources: [null]
|
sources: [null]
|
||||||
|
@ -62,6 +66,7 @@ describe('styling context', () => {
|
||||||
expect(debug.entries['width']).toEqual({
|
expect(debug.entries['width']).toEqual({
|
||||||
prop: 'width',
|
prop: 'width',
|
||||||
valuesCount: 1,
|
valuesCount: 1,
|
||||||
|
sanitizationRequired: false,
|
||||||
guardMask: buildGuardMask(),
|
guardMask: buildGuardMask(),
|
||||||
defaultValue: '100px',
|
defaultValue: '100px',
|
||||||
sources: ['100px']
|
sources: ['100px']
|
||||||
|
@ -71,6 +76,7 @@ describe('styling context', () => {
|
||||||
expect(debug.entries['width']).toEqual({
|
expect(debug.entries['width']).toEqual({
|
||||||
prop: 'width',
|
prop: 'width',
|
||||||
valuesCount: 1,
|
valuesCount: 1,
|
||||||
|
sanitizationRequired: false,
|
||||||
guardMask: buildGuardMask(),
|
guardMask: buildGuardMask(),
|
||||||
defaultValue: '100px',
|
defaultValue: '100px',
|
||||||
sources: ['100px']
|
sources: ['100px']
|
||||||
|
@ -79,7 +85,7 @@ describe('styling context', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeContextWithDebug() {
|
function makeContextWithDebug() {
|
||||||
const ctx = allocStylingContext();
|
const ctx = allocTStylingContext();
|
||||||
return attachStylingDebugObject(ctx);
|
return attachStylingDebugObject(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import {registerBinding} from '@angular/core/src/render3/styling_next/bindings';
|
import {registerBinding} from '@angular/core/src/render3/styling_next/bindings';
|
||||||
import {NodeStylingDebug, attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug';
|
import {NodeStylingDebug, attachStylingDebugObject} from '@angular/core/src/render3/styling_next/styling_debug';
|
||||||
import {allocStylingContext} from '@angular/core/src/render3/styling_next/util';
|
import {allocTStylingContext} from '@angular/core/src/render3/styling_next/util';
|
||||||
|
|
||||||
describe('styling debugging tools', () => {
|
describe('styling debugging tools', () => {
|
||||||
describe('NodeStylingDebug', () => {
|
describe('NodeStylingDebug', () => {
|
||||||
|
@ -64,6 +64,6 @@ describe('styling debugging tools', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeContextWithDebug() {
|
function makeContextWithDebug() {
|
||||||
const ctx = allocStylingContext();
|
const ctx = allocTStylingContext();
|
||||||
return attachStylingDebugObject(ctx);
|
return attachStylingDebugObject(ctx);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue