Skip to content

Commit 3e2ec31

Browse files
committed
WIP: feat(scope): enable $watch on $sce trusted values
1 parent 6d324c7 commit 3e2ec31

File tree

5 files changed

+49
-21
lines changed

5 files changed

+49
-21
lines changed

src/ng/directive/ngBind.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,11 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
134134
* @element ANY
135135
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
136136
*/
137-
var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
137+
var ngBindHtmlDirective = ['$sce', function($sce) {
138138
return function(scope, element, attr) {
139139
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
140-
141-
var parsed = $parse(attr.ngBindHtml);
142-
function getStringValue() { return (parsed(scope) || '').toString(); }
143-
144-
scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) {
145-
element.html($sce.getTrustedHtml(parsed(scope)) || '');
140+
scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
141+
element.html($sce.getTrustedHtml(value) || '');
146142
});
147143
};
148144
}];

src/ng/directive/ngInclude.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
177177
}
178178
};
179179

180-
scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) {
180+
scope.$watch(srcExp, function ngIncludeWatchAction(srcUntrusted) {
181+
var src = $sce.getTrustedResourceUrl(srcUntrusted);
181182
var thisChangeId = ++changeCounter;
182183

183184
if (src) {

src/ng/rootScope.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,21 @@ function $RootScopeProvider(){
7171
return TTL;
7272
};
7373

74-
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
75-
function( $injector, $exceptionHandler, $parse, $browser) {
74+
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', '$sce',
75+
function( $injector, $exceptionHandler, $parse, $browser, $sce) {
76+
77+
function sceAwareCompare(a, b, compareValue) {
78+
if (a === b) {
79+
return true;
80+
}
81+
var rawA = $sce.valueOf(a),
82+
rawB = $sce.valueOf(b);
83+
return !((a === rawA) ^ (b === rawB)) && (
84+
(rawA === rawB) || (compareValue
85+
? equals(rawA, rawB)
86+
: (typeof rawA == 'number' && typeof rawB == 'number' &&
87+
isNaN(rawA) && isNaN(rawB))));
88+
}
7689

7790
/**
7891
* @ngdoc function
@@ -375,6 +388,9 @@ function $RootScopeProvider(){
375388
newValue = objGetter(self);
376389
var newLength, key;
377390

391+
// ckck: with my change, a NaN in a collection compares equal to another NaN
392+
// ckck: add test for that.
393+
// ckck: isObject(rawValue)
378394
if (!isObject(newValue)) {
379395
if (oldValue !== newValue) {
380396
oldValue = newValue;
@@ -397,7 +413,7 @@ function $RootScopeProvider(){
397413
}
398414
// copy the items to oldValue and look for changes.
399415
for (var i = 0; i < newLength; i++) {
400-
if (oldValue[i] !== newValue[i]) {
416+
if (!sceAwareCompare(oldValue[i], newValue[i])) {
401417
changeDetected++;
402418
oldValue[i] = newValue[i];
403419
}
@@ -415,7 +431,7 @@ function $RootScopeProvider(){
415431
if (newValue.hasOwnProperty(key)) {
416432
newLength++;
417433
if (oldValue.hasOwnProperty(key)) {
418-
if (oldValue[key] !== newValue[key]) {
434+
if (!sceAwareCompare(oldValue[key], newValue[key])) {
419435
changeDetected++;
420436
oldValue[key] = newValue[key];
421437
}
@@ -529,14 +545,12 @@ function $RootScopeProvider(){
529545
watch = watchers[length];
530546
// Most common watches are on primitives, in which case we can short
531547
// circuit it with === operator, only when === fails do we use .equals
532-
if (watch && (value = watch.get(current)) !== (last = watch.last) &&
533-
!(watch.eq
534-
? equals(value, last)
535-
: (typeof value == 'number' && typeof last == 'number'
536-
&& isNaN(value) && isNaN(last)))) {
548+
if (watch && !sceAwareCompare(value = watch.get(current), last = watch.last, watch.eq)) {
537549
dirty = true;
550+
// ckck
538551
watch.last = watch.eq ? copy(value) : value;
539-
watch.fn(value, ((last === initWatchVal) ? value : last), current);
552+
// ckck - should use raw values in === ?
553+
watch.fn(value, sceAwareCompare(last, initWatchVal) ? value : last, current);
540554
if (ttl < 5) {
541555
logIdx = 4 - ttl;
542556
if (!watchLog[logIdx]) watchLog[logIdx] = [];

test/ng/directive/ngBindSpec.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,13 @@ describe('ngBind*', function() {
107107
// If the binding is a function that creates a new value on every call via trustAs, we'll
108108
// trigger an infinite digest if we don't take care of it.
109109
element = $compile('<div ng-bind-html="getHtml()"></div>')($rootScope);
110-
$rootScope.getHtml = function() {
111-
return $sce.trustAsHtml('<div onclick="">hello</div>');
112-
};
110+
var html = '<div onclick="">hello</div>';
111+
$rootScope.getHtml = function() { return $sce.trustAsHtml(html); };
113112
$rootScope.$digest();
114113
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>');
114+
var html = '<div onclick="">hello2</div>';
115+
$rootScope.$digest();
116+
expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello2</div>');
115117
}));
116118

117119
describe('when $sanitize is available', function() {

test/ng/rootScopeSpec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,21 @@ describe('Scope', function() {
463463
});
464464

465465

466+
it('CKCK: SCE: should not trigger change when object in collection changes', inject(function($sce) {
467+
$rootScope.obj = [$sce.trustAsHtml('ckck')];
468+
$rootScope.$digest();
469+
expect(log).toEqual(['[{}]']);
470+
471+
$rootScope.obj[0] = $sce.trustAsHtml('ckck');
472+
$rootScope.$digest();
473+
expect(log).toEqual(['[{}]']);
474+
475+
$rootScope.obj[0] = $sce.trustAsHtml('ckck2');
476+
$rootScope.$digest();
477+
expect(log).toEqual(['[{}]', '[{}]']);
478+
}));
479+
480+
466481
it('should watch array properties', function() {
467482
$rootScope.obj = [];
468483
$rootScope.$digest();

0 commit comments

Comments
 (0)