fix($rootScope): TTL exception does not clear $$phase

When $digest() throws infinite digest exception it
does not properly clear the $phase leaving the scope
in an inconsistent state.

Closes #979
This commit is contained in:
Misko Hevery 2012-05-23 14:49:01 -07:00
parent 5214c1d0cb
commit 989446ecee
2 changed files with 45 additions and 12 deletions

View file

@ -372,7 +372,7 @@ function $RootScopeProvider(){
watchLog = [], watchLog = [],
logIdx, logMsg; logIdx, logMsg;
flagPhase(target, '$digest'); beginPhase('$digest');
do { do {
dirty = false; dirty = false;
@ -429,12 +429,13 @@ function $RootScopeProvider(){
} while ((current = next)); } while ((current = next));
if(dirty && !(ttl--)) { if(dirty && !(ttl--)) {
clearPhase();
throw Error(TTL + ' $digest() iterations reached. Aborting!\n' + throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
} }
} while (dirty || asyncQueue.length); } while (dirty || asyncQueue.length);
this.$root.$$phase = null; clearPhase();
}, },
@ -469,7 +470,7 @@ function $RootScopeProvider(){
* perform any necessary cleanup. * perform any necessary cleanup.
*/ */
$destroy: function() { $destroy: function() {
if (this.$root == this) return; // we can't remove the root node; if ($rootScope == this) return; // we can't remove the root node;
var parent = this.$parent; var parent = this.$parent;
this.$broadcast('$destroy'); this.$broadcast('$destroy');
@ -586,13 +587,18 @@ function $RootScopeProvider(){
*/ */
$apply: function(expr) { $apply: function(expr) {
try { try {
flagPhase(this, '$apply'); beginPhase('$apply');
return this.$eval(expr); return this.$eval(expr);
} catch (e) { } catch (e) {
$exceptionHandler(e); $exceptionHandler(e);
} finally { } finally {
this.$root.$$phase = null; clearPhase();
this.$root.$digest(); try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
} }
}, },
@ -754,18 +760,22 @@ function $RootScopeProvider(){
} }
}; };
var $rootScope = new Scope();
function flagPhase(scope, phase) { return $rootScope;
var root = scope.$root;
if (root.$$phase) {
throw Error(root.$$phase + ' already in progress'); function beginPhase(phase) {
if ($rootScope.$$phase) {
throw Error($rootScope.$$phase + ' already in progress');
} }
root.$$phase = phase; $rootScope.$$phase = phase;
} }
return new Scope(); function clearPhase() {
$rootScope.$$phase = null;
}
function compileToFn(exp, name) { function compileToFn(exp, name) {
var fn = $parse(exp); var fn = $parse(exp);

View file

@ -210,6 +210,8 @@ describe('Scope', function() {
'["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' + '["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' +
'["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' + '["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' +
'["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]'); '["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]');
expect($rootScope.$$phase).toBeNull();
}); });
}); });
@ -492,6 +494,27 @@ describe('Scope', function() {
}); });
it('should log exceptions from $digest', function() {
module(function($rootScopeProvider, $exceptionHandlerProvider) {
$rootScopeProvider.digestTtl(2);
$exceptionHandlerProvider.mode('log');
});
inject(function($rootScope, $exceptionHandler) {
$rootScope.$watch('a', function() {$rootScope.b++;});
$rootScope.$watch('b', function() {$rootScope.a++;});
$rootScope.a = $rootScope.b = 0;
expect(function() {
$rootScope.$apply();
}).toThrow();
expect($exceptionHandler.errors[0]).toBeDefined();
expect($rootScope.$$phase).toBeNull();
});
});
describe('exceptions', function() { describe('exceptions', function() {
var log; var log;
beforeEach(module(function($exceptionHandlerProvider) { beforeEach(module(function($exceptionHandlerProvider) {