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

Commit f547b0d

Browse files
committed
fixup! feat($compile): add support for arbitrary property and event bindings
1 parent 3e94bd1 commit f547b0d

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

src/ng/compile.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,7 +1620,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16201620
*
16211621
* Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58
16221622
* Changing:
1623-
* - SecurityContext.* => $sce.*
1623+
* - SecurityContext.* => SCE_CONTEXTS/$sce.*
16241624
* - STYLE => CSS
16251625
* - various URL => MEDIA_URL
16261626
*/
@@ -3488,13 +3488,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34883488
directives.push({
34893489
priority: 100,
34903490
compile: function ngPropCompileFn(_, attr) {
3491-
var fn = $parse(attr[attrName]);
3492-
return {
3493-
pre: function ngPropPreLinkFn(scope, $element) {
3494-
scope.$watch(fn, function propertyWatchActionFn(value) {
3495-
$element.prop(propName, sanitizer(value));
3496-
});
3497-
}
3491+
var ngPropGetter = $parse(attr[attrName]);
3492+
var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) {
3493+
// Unwrap the value to compare the actual inner safe value, not the wrapper object.
3494+
return $sce.valueOf(val);
3495+
});
3496+
3497+
return function ngPropPreLinkFn(scope, $element) {
3498+
scope.$watch(ngPropWatch, function propertyWatchActionFn() {
3499+
var value = ngPropGetter(scope);
3500+
$element.prop(propName, sanitizer(value));
3501+
});
34983502
};
34993503
}
35003504
});

test/ng/compileSpec.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12719,6 +12719,127 @@ describe('$compile', function() {
1271912719
$rootScope.$digest();
1272012720
}));
1272112721
});
12722+
12723+
describe('*[innerHTML]', function() {
12724+
describe('SCE disabled', function() {
12725+
beforeEach(function() {
12726+
module(function($sceProvider) { $sceProvider.enabled(false); });
12727+
});
12728+
12729+
it('should set html', inject(function($rootScope, $compile) {
12730+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12731+
$rootScope.html = '<div onclick="">hello</div>';
12732+
$rootScope.$digest();
12733+
expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>');
12734+
}));
12735+
12736+
it('should update html', inject(function($rootScope, $compile, $sce) {
12737+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12738+
$rootScope.html = 'hello';
12739+
$rootScope.$digest();
12740+
expect(lowercase(element.html())).toEqual('hello');
12741+
$rootScope.html = 'goodbye';
12742+
$rootScope.$digest();
12743+
expect(lowercase(element.html())).toEqual('goodbye');
12744+
}));
12745+
12746+
it('should one-time bind if the expression starts with two colons', inject(function($rootScope, $compile) {
12747+
element = $compile('<div ng-prop-inner_h_t_m_l="::html"></div>')($rootScope);
12748+
$rootScope.html = '<div onclick="">hello</div>';
12749+
expect($rootScope.$$watchers.length).toEqual(1);
12750+
$rootScope.$digest();
12751+
expect(element.text()).toEqual('hello');
12752+
expect($rootScope.$$watchers.length).toEqual(0);
12753+
$rootScope.html = '<div onclick="">hello</div>';
12754+
$rootScope.$digest();
12755+
expect(element.text()).toEqual('hello');
12756+
}));
12757+
});
12758+
12759+
12760+
describe('SCE enabled', function() {
12761+
it('should NOT set html for untrusted values', inject(function($rootScope, $compile) {
12762+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12763+
$rootScope.html = '<div onclick="">hello</div>';
12764+
expect(function() { $rootScope.$digest(); }).toThrow();
12765+
}));
12766+
12767+
it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) {
12768+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12769+
$rootScope.html = $sce.trustAsCss('<div onclick="">hello</div>');
12770+
expect(function() { $rootScope.$digest(); }).toThrow();
12771+
}));
12772+
12773+
it('should set html for trusted values', inject(function($rootScope, $compile, $sce) {
12774+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12775+
$rootScope.html = $sce.trustAsHtml('<div onclick="">hello</div>');
12776+
$rootScope.$digest();
12777+
expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>');
12778+
}));
12779+
12780+
it('should update html', inject(function($rootScope, $compile, $sce) {
12781+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12782+
$rootScope.html = $sce.trustAsHtml('hello');
12783+
$rootScope.$digest();
12784+
expect(lowercase(element.html())).toEqual('hello');
12785+
$rootScope.html = $sce.trustAsHtml('goodbye');
12786+
$rootScope.$digest();
12787+
expect(lowercase(element.html())).toEqual('goodbye');
12788+
}));
12789+
12790+
it('should not cause infinite recursion for trustAsHtml object watches',
12791+
inject(function($rootScope, $compile, $sce) {
12792+
// Ref: https://github.com/angular/angular.js/issues/3932
12793+
// If the binding is a function that creates a new value on every call via trustAs, we'll
12794+
// trigger an infinite digest if we don't take care of it.
12795+
element = $compile('<div ng-prop-inner_h_t_m_l="getHtml()"></div>')($rootScope);
12796+
$rootScope.getHtml = function() {
12797+
return $sce.trustAsHtml('<div onclick="">hello</div>');
12798+
};
12799+
$rootScope.$digest();
12800+
expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>');
12801+
}));
12802+
12803+
it('should handle custom $sce objects', function() {
12804+
function MySafeHtml(val) { this.val = val; }
12805+
12806+
module(function($provide) {
12807+
$provide.decorator('$sce', function($delegate) {
12808+
$delegate.trustAsHtml = function(html) { return new MySafeHtml(html); };
12809+
$delegate.getTrusted = function(type, mySafeHtml) { return mySafeHtml.val; };
12810+
$delegate.valueOf = function(v) { return v instanceof MySafeHtml ? v.val : v; };
12811+
return $delegate;
12812+
});
12813+
});
12814+
12815+
inject(function($rootScope, $compile, $sce) {
12816+
// Ref: https://github.com/angular/angular.js/issues/14526
12817+
// Previous code used toString for change detection, which fails for custom objects
12818+
// that don't override toString.
12819+
element = $compile('<div ng-prop-inner_h_t_m_l="getHtml()"></div>')($rootScope);
12820+
var html = 'hello';
12821+
$rootScope.getHtml = function() { return $sce.trustAsHtml(html); };
12822+
$rootScope.$digest();
12823+
expect(lowercase(element.html())).toEqual('hello');
12824+
html = 'goodbye';
12825+
$rootScope.$digest();
12826+
expect(lowercase(element.html())).toEqual('goodbye');
12827+
});
12828+
});
12829+
12830+
describe('when $sanitize is available', function() {
12831+
beforeEach(function() { module('ngSanitize'); });
12832+
12833+
it('should sanitize untrusted html', inject(function($rootScope, $compile) {
12834+
element = $compile('<div ng-prop-inner_h_t_m_l="html"></div>')($rootScope);
12835+
$rootScope.html = '<div onclick="">hello</div>';
12836+
$rootScope.$digest();
12837+
expect(lowercase(element.html())).toEqual('<div>hello</div>');
12838+
}));
12839+
});
12840+
});
12841+
12842+
})
1272212843
});
1272312844

1272412845
describe('addPropertySecurityContext', function() {

0 commit comments

Comments
 (0)