Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit fbf5eb3

Browse files
fix(interpolate): do 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
1 parent b4e409b commit fbf5eb3

File tree

4 files changed

+58
-3
lines changed

4 files changed

+58
-3
lines changed

src/ng/compile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3871,6 +3871,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
38713871
compile: function() {
38723872
return {
38733873
pre: function attrInterpolatePreLinkFn(scope, element, attr) {
3874+
38743875
var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
38753876

38763877
// If the attribute has changed since last $interpolate()ed

src/ng/directive/attrs.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
408408
// ng-src, ng-srcset, ng-href are interpolated
409409
forEach(['src', 'srcset', 'href'], function(attrName) {
410410
var normalized = directiveNormalize('ng-' + attrName);
411-
ngAttributeAliasDirectives[normalized] = function() {
411+
ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
412412
return {
413413
priority: 99, // it needs to run after the attributes are interpolated
414414
link: function(scope, element, attr) {
@@ -422,6 +422,10 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
422422
propName = null;
423423
}
424424

425+
// We need to sanitize the url at least once, in case it is a constant
426+
// non-interpolated attribute.
427+
attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));
428+
425429
attr.$observe(normalized, function(value) {
426430
if (!value) {
427431
if (attrName === 'href') {
@@ -441,5 +445,5 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
441445
});
442446
}
443447
};
444-
};
448+
}];
445449
});

src/ng/interpolate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ function $InterpolateProvider() {
242242

243243
// Provide a quick exit and simplified result function for text with no interpolation
244244
if (!text.length || text.indexOf(startSymbol) === -1) {
245-
if (mustHaveExpression && !contextAllowsConcatenation) return;
245+
if (mustHaveExpression) return;
246246

247247
var unescapedText = unescapeText(text);
248248
if (contextAllowsConcatenation) {

test/ng/compileSpec.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3577,6 +3577,15 @@ describe('$compile', function() {
35773577
})
35783578
);
35793579

3580+
it('should support non-interpolated `src` and `data-src` on the same element',
3581+
inject(function($rootScope, $compile) {
3582+
var element = $compile('<img src="abc" data-src="123">')($rootScope);
3583+
expect(element.attr('src')).toEqual('abc');
3584+
expect(element.attr('data-src')).toEqual('123');
3585+
$rootScope.$digest();
3586+
expect(element.attr('src')).toEqual('abc');
3587+
expect(element.attr('data-src')).toEqual('123');
3588+
}));
35803589

35813590
it('should call observer only when the attribute value changes', function() {
35823591
module(function() {
@@ -12109,6 +12118,47 @@ describe('$compile', function() {
1210912118
expect(element.attr('dash-test4')).toBe('JamieMason');
1211012119
}));
1211112120

12121+
it('should work with img[src]', inject(function() {
12122+
$rootScope.name = 'some-image.png';
12123+
element = $compile('<img ng-attr-src="{{name}}">')($rootScope);
12124+
expect(element.attr('src')).toBeUndefined();
12125+
12126+
$rootScope.$digest();
12127+
expect(element.attr('src')).toBe('some-image.png');
12128+
12129+
$rootScope.name = 'other-image.png';
12130+
$rootScope.$digest();
12131+
expect(element.attr('src')).toBe('other-image.png');
12132+
}));
12133+
12134+
it('should work with img[data-src]', inject(function() {
12135+
$rootScope.name = 'some-image.png';
12136+
element = $compile('<img ng-attr-data-src="{{name}}">')($rootScope);
12137+
expect(element.attr('data-src')).toBeUndefined();
12138+
12139+
$rootScope.$digest();
12140+
expect(element.attr('data-src')).toBe('some-image.png');
12141+
12142+
$rootScope.name = 'other-image.png';
12143+
$rootScope.$digest();
12144+
expect(element.attr('data-src')).toBe('other-image.png');
12145+
}));
12146+
12147+
it('should compile img with constant [src]-attribute and [ng-attr-data-src] attribute', inject(function() {
12148+
$rootScope.name = 'some-image.png';
12149+
element = $compile('<img src="constant.png" ng-attr-data-src="{{name}}">')($rootScope);
12150+
expect(element.attr('data-src')).toBeUndefined();
12151+
12152+
$rootScope.$digest();
12153+
expect(element.attr('src')).toBe('constant.png');
12154+
expect(element.attr('data-src')).toBe('some-image.png');
12155+
12156+
$rootScope.name = 'other-image.png';
12157+
$rootScope.$digest();
12158+
expect(element.attr('src')).toBe('constant.png');
12159+
expect(element.attr('data-src')).toBe('other-image.png');
12160+
}));
12161+
1211212162
it('should keep attributes ending with -start single-element directives', function() {
1211312163
module(function($compileProvider) {
1211412164
$compileProvider.directive('dashStarter', function(log) {

0 commit comments

Comments
 (0)