fix(interpolate): do not create directives for constant media URL attributes

By creating attribute directives that watch the value of
media url attributes (e.g. `img[src]`) we caused a conflict
when both `src` and `data-src` were appearing on the
same element. As each directive was trying to write to the
attributes on the element, where AngularJS treats `src` and
`data-src` as synonymous.

This commit ensures that we do not create create such directives
when the media url attribute is a constant (no interpolation).

Because of this (and because we no longer sanitize URLs in the
`$attr.$set()` method, this commit also updates `ngHref` and
`ngSrc` to do a preliminary sanitization of URLs in case there
is no interpolation in the attribute value.

Fixes #16734
This commit is contained in:
Pete Bacon Darwin
2018-10-24 21:43:14 +01:00
parent 2dfb6b4af6
commit 622d32e805
4 changed files with 73 additions and 3 deletions
+6 -2
View File
@@ -408,7 +408,7 @@ forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
var normalized = directiveNormalize('ng-' + attrName);
ngAttributeAliasDirectives[normalized] = function() {
ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
return {
priority: 99, // it needs to run after the attributes are interpolated
link: function(scope, element, attr) {
@@ -422,6 +422,10 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
propName = null;
}
// We need to sanitize the url at least once, in case it is a constant
// non-interpolated attribute.
attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));
attr.$observe(normalized, function(value) {
if (!value) {
if (attrName === 'href') {
@@ -441,5 +445,5 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
});
}
};
};
}];
});
+1 -1
View File
@@ -242,7 +242,7 @@ function $InterpolateProvider() {
// Provide a quick exit and simplified result function for text with no interpolation
if (!text.length || text.indexOf(startSymbol) === -1) {
if (mustHaveExpression && !contextAllowsConcatenation) return;
if (mustHaveExpression) return;
var unescapedText = unescapeText(text);
if (contextAllowsConcatenation) {
+52
View File
@@ -3577,6 +3577,15 @@ describe('$compile', function() {
})
);
it('should support non-interpolated `src` and `data-src` on the same element',
inject(function($rootScope, $compile) {
var element = $compile('<img src="abc" data-src="123">')($rootScope);
expect(element.attr('src')).toEqual('abc');
expect(element.attr('data-src')).toEqual('123');
$rootScope.$digest();
expect(element.attr('src')).toEqual('abc');
expect(element.attr('data-src')).toEqual('123');
}));
it('should call observer only when the attribute value changes', function() {
module(function() {
@@ -12084,6 +12093,49 @@ describe('$compile', function() {
expect(element.attr('test6')).toBe('Misko');
}));
describe('with media url attributes', function() {
it('should work with interpolated ng-attr-src', inject(function() {
$rootScope.name = 'some-image.png';
element = $compile('<img ng-attr-src="{{name}}">')($rootScope);
expect(element.attr('src')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('src')).toBe('some-image.png');
$rootScope.name = 'other-image.png';
$rootScope.$digest();
expect(element.attr('src')).toBe('other-image.png');
}));
it('should work with interpolated ng-attr-data-src', inject(function() {
$rootScope.name = 'some-image.png';
element = $compile('<img ng-attr-data-src="{{name}}">')($rootScope);
expect(element.attr('data-src')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('data-src')).toBe('some-image.png');
$rootScope.name = 'other-image.png';
$rootScope.$digest();
expect(element.attr('data-src')).toBe('other-image.png');
}));
it('should work alongside constant [src]-attribute and [ng-attr-data-src] attributes', inject(function() {
$rootScope.name = 'some-image.png';
element = $compile('<img src="constant.png" ng-attr-data-src="{{name}}">')($rootScope);
expect(element.attr('data-src')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('src')).toBe('constant.png');
expect(element.attr('data-src')).toBe('some-image.png');
$rootScope.name = 'other-image.png';
$rootScope.$digest();
expect(element.attr('src')).toBe('constant.png');
expect(element.attr('data-src')).toBe('other-image.png');
}));
});
describe('when an attribute has a dash-separated name', function() {
it('should work with different prefixes', inject(function() {
$rootScope.name = 'JamieMason';
+14
View File
@@ -78,6 +78,20 @@ describe('ngSrc', function() {
expect(element.prop('src')).toEqual('http://somewhere/abc');
}));
}
it('should work with `src` attribute on the same element', inject(function($rootScope, $compile) {
$rootScope.imageUrl = 'dynamic';
element = $compile('<img ng-src="{{imageUrl}}" src="static">')($rootScope);
expect(element.attr('src')).toBe('static');
$rootScope.$digest();
expect(element.attr('src')).toBe('dynamic');
dealoc(element);
element = $compile('<img src="static" ng-src="{{imageUrl}}">')($rootScope);
expect(element.attr('src')).toBe('static');
$rootScope.$digest();
expect(element.attr('src')).toBe('dynamic');
}));
});
describe('iframe[ng-src]', function() {