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:
@@ -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) {
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}];
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user