refactor(ivy): remove styleSanitizer instruction in favor of an inline param (#34480)

This patch removes the need for the styleSanitizer() instruction in
favor of passing the sanitizer into directly into the styleProp
instruction.

This patch also increases the binding index size for all style/class bindings in preparation for #34418

PR Close #34480
This commit is contained in:
Matias Niemelä
2019-12-18 19:01:00 -08:00
parent 0d8309509b
commit 84d24c08e1
11 changed files with 128 additions and 135 deletions

View File

@ -122,7 +122,6 @@ export {
ɵɵelementContainerEnd,
ɵɵelementContainer,
ɵɵstyleMap,
ɵɵstyleSanitizer,
ɵɵclassMap,
ɵɵclassMapInterpolate1,
ɵɵclassMapInterpolate2,

View File

@ -115,7 +115,6 @@ export {
ɵɵstylePropInterpolate8,
ɵɵstylePropInterpolateV,
ɵɵstyleSanitizer,
ɵɵtemplate,
ɵɵtext,

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {SafeValue} from '../../sanitization/bypass';
import {ɵɵdefaultStyleSanitizer} from '../../sanitization/sanitization';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {throwErrorIfNoChangesMode} from '../errors';
import {setInputsForProperty} from '../instructions/shared';
@ -15,7 +16,7 @@ import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfa
import {isDirectiveHost} from '../interfaces/type_checks';
import {LView, RENDERER, TVIEW} from '../interfaces/view';
import {getActiveDirectiveId, getCheckNoChangesMode, getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, nextBindingIndex, resetCurrentStyleSanitizer, setCurrentStyleSanitizer, setElementExitFn} from '../state';
import {applyStylingMapDirectly, applyStylingValueDirectly, flushStyling, setClass, setStyle, updateClassViaContext, updateStyleViaContext} from '../styling/bindings';
import {applyStylingMapDirectly, applyStylingValueDirectly, flushStyling, updateClassViaContext, updateStyleViaContext} from '../styling/bindings';
import {activateStylingMapFeature} from '../styling/map_based_bindings';
import {attachStylingDebugObject} from '../styling/styling_debug';
import {NO_CHANGE} from '../tokens';
@ -35,26 +36,6 @@ import {getNativeByTNode, getTNode} from '../util/view_utils';
* --------
*/
/**
* 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
* `advance(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: StyleSanitizeFn | null): void {
setCurrentStyleSanitizer(sanitizer);
}
/**
* Update a style binding on an element with the provided value.
*
@ -78,8 +59,8 @@ export function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void {
*/
export function ɵɵstyleProp(
prop: string, value: string | number | SafeValue | null,
suffix?: string | null): typeof ɵɵstyleProp {
stylePropInternal(getSelectedIndex(), prop, value, suffix);
suffixOrSanitizer?: StyleSanitizeFn | string | null): typeof ɵɵstyleProp {
stylePropInternal(getSelectedIndex(), prop, value, suffixOrSanitizer);
return ɵɵstyleProp;
}
@ -91,7 +72,7 @@ export function ɵɵstyleProp(
*/
export function stylePropInternal(
elementIndex: number, prop: string, value: string | number | SafeValue | null,
suffix?: string | null | undefined): void {
suffixOrSanitizer?: StyleSanitizeFn | string | null): void {
// if a value is interpolated then it may render a `NO_CHANGE` value.
// in this case we do not need to do anything, but the binding index
// still needs to be incremented because all styling binding values
@ -109,9 +90,12 @@ export function stylePropInternal(
patchHostStylingFlag(tNode, isHostStyling(), false);
}
const isString = typeof suffixOrSanitizer === 'string';
const suffix = isString ? (suffixOrSanitizer as string) : null;
const sanitizer = isString ? null : (suffixOrSanitizer as StyleSanitizeFn | null | undefined);
const updated = stylingProp(
tNode, firstUpdatePass, lView, bindingIndex, prop, resolveStylePropValue(value, suffix),
false);
false, sanitizer);
if (ngDevMode) {
ngDevMode.styleProp++;
if (updated) {
@ -154,7 +138,8 @@ export function ɵɵclassProp(className: string, value: boolean | null): typeof
patchHostStylingFlag(tNode, isHostStyling(), true);
}
const updated = stylingProp(tNode, firstUpdatePass, lView, bindingIndex, className, value, true);
const updated =
stylingProp(tNode, firstUpdatePass, lView, bindingIndex, className, value, true, null);
if (ngDevMode) {
ngDevMode.classProp++;
if (updated) {
@ -177,12 +162,15 @@ export function ɵɵclassProp(className: string, value: boolean | null): typeof
function stylingProp(
tNode: TNode, firstUpdatePass: boolean, lView: LView, bindingIndex: number, prop: string,
value: boolean | number | SafeValue | string | null | undefined | NO_CHANGE,
isClassBased: boolean): boolean {
isClassBased: boolean, sanitizer: StyleSanitizeFn | null | undefined): boolean {
let updated = false;
if (sanitizer) {
setCurrentStyleSanitizer(sanitizer);
}
const native = getNativeByTNode(tNode, lView) as RElement;
const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode);
const sanitizer = isClassBased ? null : getCurrentStyleSanitizer();
// [style.prop] and [class.name] bindings do not use `bind()` and will
// therefore manage accessing and updating the new value in the lView directly.
@ -283,7 +271,8 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu
}
stylingMap(
context, tNode, firstUpdatePass, lView, bindingIndex, styles, false, hasDirectiveInput);
context, tNode, firstUpdatePass, lView, bindingIndex, styles, false, hasDirectiveInput,
ɵɵdefaultStyleSanitizer);
}
/**
@ -346,7 +335,7 @@ export function classMapInternal(
}
stylingMap(
context, tNode, firstUpdatePass, lView, bindingIndex, classes, true, hasDirectiveInput);
context, tNode, firstUpdatePass, lView, bindingIndex, classes, true, hasDirectiveInput, null);
}
/**
@ -358,11 +347,11 @@ export function classMapInternal(
function stylingMap(
context: TStylingContext, tNode: TNode, firstUpdatePass: boolean, lView: LView,
bindingIndex: number, value: {[key: string]: any} | string | null, isClassBased: boolean,
hasDirectiveInput: boolean): void {
hasDirectiveInput: boolean, sanitizer: StyleSanitizeFn | null): void {
const directiveIndex = getActiveDirectiveId();
const native = getNativeByTNode(tNode, lView) as RElement;
const oldValue = getValue(lView, bindingIndex);
const sanitizer = getCurrentStyleSanitizer();
setCurrentStyleSanitizer(ɵɵdefaultStyleSanitizer);
const valueHasChanged = hasValueChanged(oldValue, value);
// [style] and [class] bindings do not use `bind()` and will therefore

View File

@ -297,12 +297,14 @@ import {LView} from './view';
*
* It is enabled in two cases:
*
* 1. The `styleSanitizer(sanitizerFn)` instruction was called (just before any other
* styling instructions are run).
* 1. One or more styleProp instructions are generated (a sanitizer is passed in to each one).
*
* 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).
* 2. the `styleMap` instruction runs (it uses it by default internally).
*
* Sanitization can be enabled in the cases above, however, if a sanitizer is attached
* in the `LView` then that sanitizer can be used to override whatever sanitizer is
* passed in the examples above (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
@ -310,18 +312,6 @@ import {LView} from './view';
*
* 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|StylingMapArray|{}> {

View File

@ -129,7 +129,6 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵstylePropInterpolate7': r3.ɵɵstylePropInterpolate7,
'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8,
'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV,
'ɵɵstyleSanitizer': r3.ɵɵstyleSanitizer,
'ɵɵclassProp': r3.ɵɵclassProp,
'ɵɵselect': r3.ɵɵselect,
'ɵɵadvance': r3.ɵɵadvance,

View File

@ -97,7 +97,8 @@ export function updateStyleViaContext(
context: TStylingContext, tNode: TStylingNode, data: LStylingData, element: RElement,
directiveIndex: number, prop: string | null, bindingIndex: number,
value: string | number | SafeValue | null | undefined | StylingMapArray | NO_CHANGE,
sanitizer: StyleSanitizeFn | null, forceUpdate: boolean, firstUpdatePass: boolean): boolean {
sanitizer: StyleSanitizeFn | null | undefined, forceUpdate: boolean,
firstUpdatePass: boolean): boolean {
const isMapBased = !prop;
const state = getStylingState(element, directiveIndex);
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++;

View File

@ -9,7 +9,7 @@
import {NgForOfContext} from '@angular/common';
import {ɵɵdefineComponent} from '../../src/render3/definition';
import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
import {AttributeMarker} from '../../src/render3/interfaces/node';
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, getSanitizationBypassType, unwrapSafeValue} from '../../src/sanitization/bypass';
import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization';
@ -137,20 +137,22 @@ describe('instructions', () => {
describe('styleProp', () => {
it('should automatically sanitize unless a bypass operation is applied', () => {
const t = new TemplateFixture(() => { return createDiv(); }, () => {}, 1);
t.update(() => {
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
ɵɵstyleProp('background-image', 'url("http://server")');
});
// nothing is set because sanitizer suppresses it.
expect(t.html).toEqual('<div></div>');
const t = new TemplateFixture(
() => { return createDiv(); },
() => {
ɵɵstyleProp('background-image', 'url("http://server")', ɵɵdefaultStyleSanitizer);
},
1);
const element = t.hostElement.firstChild as HTMLElement;
expect(element.style.getPropertyValue('background-image')).toEqual('');
t.update(() => {
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
ɵɵstyleProp('background-image', bypassSanitizationTrustStyle('url("http://server2")'));
ɵɵstyleProp(
'background-image', bypassSanitizationTrustStyle('url("http://server2")'),
ɵɵdefaultStyleSanitizer);
});
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
.toEqual('url("http://server2")');
expect(element.style.getPropertyValue('background-image')).toEqual('url("http://server2")');
});
});
@ -160,10 +162,14 @@ describe('instructions', () => {
function createDivWithStyle() { ɵɵelement(0, 'div', 0); }
it('should add style', () => {
const fixture = new TemplateFixture(
createDivWithStyle, () => {}, 1, 0, null, null, null, undefined, attrs);
fixture.update(() => { ɵɵstyleMap({'background-color': 'red'}); });
expect(fixture.html).toEqual('<div style="background-color: red; height: 10px;"></div>');
const fixture = new TemplateFixture(createDivWithStyle, () => {
ɵɵstyleMap({'background-color': 'red'});
}, 1, 0, null, null, null, undefined, attrs);
fixture.update();
const targetDiv = fixture.hostElement.querySelector('div') !;
const style = targetDiv.style as{[key: string]: any};
expect(style['background-color']).toEqual('red');
expect(style['height']).toEqual('10px');
});
it('should sanitize new styles that may contain `url` properties', () => {
@ -173,7 +179,6 @@ describe('instructions', () => {
const fixture = new TemplateFixture(
() => { return createDiv(); }, //
() => {
ɵɵstyleSanitizer(sanitizerInterceptor.getStyleSanitizer());
ɵɵstyleMap({
'background-image': 'background-image',
'background': 'background',