mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(ivy): property bindings use correct indices (#30129)
- Extracts and documents code that will be common to interpolation instructions - Ensures that binding indices are updated at the proper time during compilation - Adds additional tests Related #30011 PR Close #30129
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
import {assertEqual, assertLessThan} from '../../util/assert';
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {BINDING_INDEX, TVIEW} from '../interfaces/view';
|
||||
import {getLView, getSelectedIndex} from '../state';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
@@ -15,6 +16,7 @@ import {renderStringify} from '../util/misc_utils';
|
||||
import {TsickleIssue1009, elementPropertyInternal, storeBindingMetadata} from './shared';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create interpolation bindings with a variable number of expressions.
|
||||
*
|
||||
@@ -290,12 +292,6 @@ export function ɵɵinterpolation8(
|
||||
/// NEW INSTRUCTIONS
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Shared reference to a string, used in `ɵɵpropertyInterpolate`.
|
||||
*/
|
||||
const EMPTY_STRING = '';
|
||||
|
||||
/**
|
||||
*
|
||||
* Update an interpolated property on an element with a lone bound value
|
||||
@@ -321,11 +317,13 @@ const EMPTY_STRING = '';
|
||||
* @param prefix Static value used for concatenation only.
|
||||
* @param v0 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate(propName: string, v0: any): TsickleIssue1009 {
|
||||
ɵɵpropertyInterpolate1(propName, EMPTY_STRING, v0, EMPTY_STRING);
|
||||
export function ɵɵpropertyInterpolate(
|
||||
propName: string, v0: any, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
ɵɵpropertyInterpolate1(propName, '', v0, '', sanitizer);
|
||||
return ɵɵpropertyInterpolate;
|
||||
}
|
||||
|
||||
@@ -354,13 +352,15 @@ export function ɵɵpropertyInterpolate(propName: string, v0: any): TsickleIssue
|
||||
* @param prefix Static value used for concatenation only.
|
||||
* @param v0 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate1(
|
||||
propName: string, prefix: string, v0: any, suffix: string): TsickleIssue1009 {
|
||||
propName: string, prefix: string, v0: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolation1(prefix, v0, suffix));
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolation1(prefix, v0, suffix), sanitizer);
|
||||
return ɵɵpropertyInterpolate1;
|
||||
}
|
||||
|
||||
@@ -390,14 +390,15 @@ export function ɵɵpropertyInterpolate1(
|
||||
* @param i0 Static value used for concatenation only.
|
||||
* @param v1 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate2(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any,
|
||||
suffix: string): TsickleIssue1009 {
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolation2(prefix, v0, i0, v1, suffix));
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolation2(prefix, v0, i0, v1, suffix), sanitizer);
|
||||
return ɵɵpropertyInterpolate2;
|
||||
}
|
||||
|
||||
@@ -430,14 +431,16 @@ export function ɵɵpropertyInterpolate2(
|
||||
* @param i1 Static value used for concatenation only.
|
||||
* @param v2 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate3(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any,
|
||||
suffix: string): TsickleIssue1009 {
|
||||
suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix));
|
||||
elementPropertyInternal(
|
||||
index, propName, ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix), sanitizer);
|
||||
return ɵɵpropertyInterpolate3;
|
||||
}
|
||||
|
||||
@@ -472,15 +475,16 @@ export function ɵɵpropertyInterpolate3(
|
||||
* @param i2 Static value used for concatenation only.
|
||||
* @param v3 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate4(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, suffix: string): TsickleIssue1009 {
|
||||
v3: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(
|
||||
index, propName, ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix));
|
||||
index, propName, ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix), sanitizer);
|
||||
return ɵɵpropertyInterpolate4;
|
||||
}
|
||||
|
||||
@@ -517,15 +521,17 @@ export function ɵɵpropertyInterpolate4(
|
||||
* @param i3 Static value used for concatenation only.
|
||||
* @param v4 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate5(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, suffix: string): TsickleIssue1009 {
|
||||
v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(
|
||||
index, propName, ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix));
|
||||
index, propName, ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix),
|
||||
sanitizer);
|
||||
return ɵɵpropertyInterpolate5;
|
||||
}
|
||||
|
||||
@@ -564,16 +570,18 @@ export function ɵɵpropertyInterpolate5(
|
||||
* @param i4 Static value used for concatenation only.
|
||||
* @param v5 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate6(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): TsickleIssue1009 {
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(
|
||||
index, propName,
|
||||
ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix));
|
||||
index, propName, ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix),
|
||||
sanitizer);
|
||||
return ɵɵpropertyInterpolate6;
|
||||
}
|
||||
|
||||
@@ -614,17 +622,19 @@ export function ɵɵpropertyInterpolate6(
|
||||
* @param i5 Static value used for concatenation only.
|
||||
* @param v6 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate7(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any,
|
||||
suffix: string): TsickleIssue1009 {
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string,
|
||||
sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(
|
||||
index, propName,
|
||||
ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix));
|
||||
ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix),
|
||||
sanitizer);
|
||||
return ɵɵpropertyInterpolate7;
|
||||
}
|
||||
|
||||
@@ -667,17 +677,19 @@ export function ɵɵpropertyInterpolate7(
|
||||
* @param i6 Static value used for concatenation only.
|
||||
* @param v7 Value checked for change.
|
||||
* @param suffix Static value used for concatenation only.
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolate8(
|
||||
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
|
||||
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
|
||||
suffix: string): TsickleIssue1009 {
|
||||
suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
elementPropertyInternal(
|
||||
index, propName,
|
||||
ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix));
|
||||
ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix),
|
||||
sanitizer);
|
||||
return ɵɵpropertyInterpolate8;
|
||||
}
|
||||
|
||||
@@ -707,12 +719,14 @@ export function ɵɵpropertyInterpolate8(
|
||||
* @param values The a collection of values and the strings inbetween those values, beginning with a
|
||||
* string prefix and ending with a string suffix.
|
||||
* (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
|
||||
* @param sanitizer An optional sanitizer function
|
||||
* @returns itself, so that it may be chained.
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyInterpolateV(propName: string, values: any[]): TsickleIssue1009 {
|
||||
export function ɵɵpropertyInterpolateV(
|
||||
propName: string, values: any[], sanitizer?: SanitizerFn): TsickleIssue1009 {
|
||||
const index = getSelectedIndex();
|
||||
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolationV(values));
|
||||
elementPropertyInternal(index, propName, ɵɵinterpolationV(values), sanitizer);
|
||||
return ɵɵpropertyInterpolateV;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ import {Component, Input} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {of } from 'rxjs';
|
||||
|
||||
describe('elementProperty', () => {
|
||||
describe('property instructions', () => {
|
||||
it('should bind to properties whose names do not correspond to their attribute names', () => {
|
||||
@Component({template: '<label [for]="forValue"></label>'})
|
||||
class MyComp {
|
||||
@@ -33,6 +34,25 @@ describe('elementProperty', () => {
|
||||
expect(labelNode.nativeElement.getAttribute('for')).toBe('some-textarea');
|
||||
});
|
||||
|
||||
it('should not allow unsanitary urls in bound properties', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<img [src]="naughty">
|
||||
`
|
||||
})
|
||||
class App {
|
||||
naughty = 'javascript:alert("haha, I am taking over your computer!!!");';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const img = fixture.nativeElement.querySelector('img');
|
||||
|
||||
expect(img.src.indexOf('unsafe:')).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should not map properties whose names do not correspond to their attribute names, ' +
|
||||
'if they correspond to inputs',
|
||||
() => {
|
||||
@@ -60,4 +80,136 @@ describe('elementProperty', () => {
|
||||
expect(myCompNode.nativeElement.getAttribute('for')).toBeFalsy();
|
||||
expect(myCompNode.componentInstance.for).toBe('hej');
|
||||
});
|
||||
|
||||
it('should handle all flavors of interpolated properties', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<div title="a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g{{seven}}h{{eight}}i{{nine}}j"></div>
|
||||
<div title="a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g{{seven}}h{{eight}}i"></div>
|
||||
<div title="a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g{{seven}}h"></div>
|
||||
<div title="a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f{{six}}g"></div>
|
||||
<div title="a{{one}}b{{two}}c{{three}}d{{four}}e{{five}}f"></div>
|
||||
<div title="a{{one}}b{{two}}c{{three}}d{{four}}e"></div>
|
||||
<div title="a{{one}}b{{two}}c{{three}}d"></div>
|
||||
<div title="a{{one}}b{{two}}c"></div>
|
||||
<div title="a{{one}}b"></div>
|
||||
<div title="{{one}}"></div>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
one = 1;
|
||||
two = 2;
|
||||
three = 3;
|
||||
four = 4;
|
||||
five = 5;
|
||||
six = 6;
|
||||
seven = 7;
|
||||
eight = 8;
|
||||
nine = 9;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
const titles = Array.from(fixture.nativeElement.querySelectorAll('div[title]'))
|
||||
.map((div: HTMLDivElement) => div.title);
|
||||
|
||||
expect(titles).toEqual([
|
||||
'a1b2c3d4e5f6g7h8i9j',
|
||||
'a1b2c3d4e5f6g7h8i',
|
||||
'a1b2c3d4e5f6g7h',
|
||||
'a1b2c3d4e5f6g',
|
||||
'a1b2c3d4e5f',
|
||||
'a1b2c3d4e',
|
||||
'a1b2c3d',
|
||||
'a1b2c',
|
||||
'a1b',
|
||||
'1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle pipes in interpolated properties', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<img title="{{(details | async)?.title}}" src="{{(details | async)?.url}}" />
|
||||
`
|
||||
})
|
||||
class App {
|
||||
details = of ({
|
||||
title: 'cool image',
|
||||
url: 'http://somecooldomain:1234/cool_image.png',
|
||||
});
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
const img: HTMLImageElement = fixture.nativeElement.querySelector('img');
|
||||
expect(img.src).toBe('http://somecooldomain:1234/cool_image.png');
|
||||
expect(img.title).toBe('cool image');
|
||||
});
|
||||
|
||||
// From https://angular-team.atlassian.net/browse/FW-1287
|
||||
it('should handle multiple elvis operators', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<img src="{{leadSurgeon?.getCommonInfo()?.getPhotoUrl() }}">
|
||||
`
|
||||
})
|
||||
class App {
|
||||
/** Clearly this is a doctor of heavy metals. */
|
||||
leadSurgeon = {
|
||||
getCommonInfo() {
|
||||
return {getPhotoUrl() { return 'http://somecooldomain:1234/cool_image.png'; }};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const img = fixture.nativeElement.querySelector('img');
|
||||
|
||||
expect(img.src).toBe('http://somecooldomain:1234/cool_image.png');
|
||||
});
|
||||
|
||||
it('should not allow unsanitary urls in interpolated properties', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<img src="{{naughty}}">
|
||||
`
|
||||
})
|
||||
class App {
|
||||
naughty = 'javascript:alert("haha, I am taking over your computer!!!");';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const img: HTMLImageElement = fixture.nativeElement.querySelector('img');
|
||||
|
||||
expect(img.src.indexOf('unsafe:')).toBe(0);
|
||||
});
|
||||
|
||||
it('should not allow unsanitary urls in interpolated properties, even if you are tricky', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<img src="{{ja}}{{va}}script:{{naughty}}">
|
||||
`
|
||||
})
|
||||
class App {
|
||||
ja = 'ja';
|
||||
va = 'va';
|
||||
naughty = 'alert("I am a h4xx0rz1!!");';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const img = fixture.nativeElement.querySelector('img');
|
||||
|
||||
expect(img.src.indexOf('unsafe:')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user