mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-09 23:34:42 +00:00
feat(scope): throw exception when recursive $apply
This commit is contained in:
parent
acb4338b70
commit
0bf611087b
2 changed files with 87 additions and 22 deletions
|
|
@ -36,7 +36,8 @@
|
||||||
*/
|
*/
|
||||||
function $RootScopeProvider(){
|
function $RootScopeProvider(){
|
||||||
this.$get = ['$injector', '$exceptionHandler', '$parse',
|
this.$get = ['$injector', '$exceptionHandler', '$parse',
|
||||||
function( $injector, $exceptionHandler, $parse){
|
function( $injector, $exceptionHandler, $parse) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc function
|
* @ngdoc function
|
||||||
* @name angular.module.ng.$rootScope.Scope
|
* @name angular.module.ng.$rootScope.Scope
|
||||||
|
|
@ -152,8 +153,7 @@ function $RootScopeProvider(){
|
||||||
child.$parent = this;
|
child.$parent = this;
|
||||||
child.$id = nextUid();
|
child.$id = nextUid();
|
||||||
child.$$asyncQueue = [];
|
child.$$asyncQueue = [];
|
||||||
child.$$phase = child.$$watchers =
|
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
||||||
child.$$nextSibling = child.$$childHead = child.$$childTail = null;
|
|
||||||
child.$$prevSibling = this.$$childTail;
|
child.$$prevSibling = this.$$childTail;
|
||||||
if (this.$$childHead) {
|
if (this.$$childHead) {
|
||||||
this.$$childTail.$$nextSibling = child;
|
this.$$childTail.$$nextSibling = child;
|
||||||
|
|
@ -326,15 +326,12 @@ function $RootScopeProvider(){
|
||||||
watchLog = [],
|
watchLog = [],
|
||||||
logIdx, logMsg;
|
logIdx, logMsg;
|
||||||
|
|
||||||
if (target.$$phase) {
|
flagPhase(target, '$digest');
|
||||||
throw Error(target.$$phase + ' already in progress');
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
|
|
||||||
|
do {
|
||||||
dirty = false;
|
dirty = false;
|
||||||
current = target;
|
current = target;
|
||||||
do {
|
do {
|
||||||
current.$$phase = '$digest';
|
|
||||||
asyncQueue = current.$$asyncQueue;
|
asyncQueue = current.$$asyncQueue;
|
||||||
while(asyncQueue.length) {
|
while(asyncQueue.length) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -356,7 +353,7 @@ function $RootScopeProvider(){
|
||||||
watch.last = copy(value);
|
watch.last = copy(value);
|
||||||
watch.fn(current, value, ((last === initWatchVal) ? value : last));
|
watch.fn(current, value, ((last === initWatchVal) ? value : last));
|
||||||
if (ttl < 5) {
|
if (ttl < 5) {
|
||||||
logIdx = 4-ttl;
|
logIdx = 4 - ttl;
|
||||||
if (!watchLog[logIdx]) watchLog[logIdx] = [];
|
if (!watchLog[logIdx]) watchLog[logIdx] = [];
|
||||||
logMsg = (isFunction(watch.exp))
|
logMsg = (isFunction(watch.exp))
|
||||||
? 'fn: ' + (watch.exp.name || watch.exp.toString())
|
? 'fn: ' + (watch.exp.name || watch.exp.toString())
|
||||||
|
|
@ -371,8 +368,6 @@ function $RootScopeProvider(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current.$$phase = null;
|
|
||||||
|
|
||||||
// Insanity Warning: scope depth-first traversal
|
// Insanity Warning: scope depth-first traversal
|
||||||
// yes, this code is a bit crazy, but it works and we have tests to prove it!
|
// yes, this code is a bit crazy, but it works and we have tests to prove it!
|
||||||
// this piece should be kept in sync with the traversal in $broadcast
|
// this piece should be kept in sync with the traversal in $broadcast
|
||||||
|
|
@ -388,6 +383,8 @@ function $RootScopeProvider(){
|
||||||
'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;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -524,10 +521,12 @@ function $RootScopeProvider(){
|
||||||
*/
|
*/
|
||||||
$apply: function(expr) {
|
$apply: function(expr) {
|
||||||
try {
|
try {
|
||||||
|
flagPhase(this, '$apply');
|
||||||
return this.$eval(expr);
|
return this.$eval(expr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$exceptionHandler(e);
|
$exceptionHandler(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
this.$root.$$phase = null;
|
||||||
this.$root.$digest();
|
this.$root.$digest();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -671,6 +670,17 @@ function $RootScopeProvider(){
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function flagPhase(scope, phase) {
|
||||||
|
var root = scope.$root;
|
||||||
|
|
||||||
|
if (root.$$phase) {
|
||||||
|
throw Error(root.$$phase + ' already in progress');
|
||||||
|
}
|
||||||
|
|
||||||
|
root.$$phase = phase;
|
||||||
|
}
|
||||||
|
|
||||||
return new Scope();
|
return new Scope();
|
||||||
|
|
||||||
function compileToFn(exp, name) {
|
function compileToFn(exp, name) {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,6 @@
|
||||||
|
|
||||||
describe('Scope', function() {
|
describe('Scope', function() {
|
||||||
|
|
||||||
beforeEach(inject(function($exceptionHandlerProvider) {
|
|
||||||
$exceptionHandlerProvider.mode('log');
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
describe('$root', function() {
|
describe('$root', function() {
|
||||||
it('should point to itself', inject(function($rootScope) {
|
it('should point to itself', inject(function($rootScope) {
|
||||||
expect($rootScope.$root).toEqual($rootScope);
|
expect($rootScope.$root).toEqual($rootScope);
|
||||||
|
|
@ -122,7 +117,9 @@ describe('Scope', function() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should delegate exceptions', inject(function($rootScope, $exceptionHandler, $log) {
|
it('should delegate exceptions', inject(function($exceptionHandlerProvider) {
|
||||||
|
$exceptionHandlerProvider.mode('log');
|
||||||
|
}, function($rootScope, $exceptionHandler, $log) {
|
||||||
$rootScope.$watch('a', function() {throw new Error('abc');});
|
$rootScope.$watch('a', function() {throw new Error('abc');});
|
||||||
$rootScope.a = 1;
|
$rootScope.a = 1;
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
|
|
@ -227,7 +224,7 @@ describe('Scope', function() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should prevent infinite recurcion and print print watcher function name or body',
|
it('should prevent infinite recursion and print print watcher function name or body',
|
||||||
inject(function($rootScope) {
|
inject(function($rootScope) {
|
||||||
$rootScope.$watch(function watcherA() {return $rootScope.a;}, function(self) {self.b++;});
|
$rootScope.$watch(function watcherA() {return $rootScope.a;}, function(self) {self.b++;});
|
||||||
$rootScope.$watch(function() {return $rootScope.b;}, function(self) {self.a++;});
|
$rootScope.$watch(function() {return $rootScope.b;}, function(self) {self.a++;});
|
||||||
|
|
@ -277,7 +274,7 @@ describe('Scope', function() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should prevent recursion', inject(function($rootScope) {
|
it('should prevent $digest recursion', inject(function($rootScope) {
|
||||||
var callCount = 0;
|
var callCount = 0;
|
||||||
$rootScope.$watch('name', function() {
|
$rootScope.$watch('name', function() {
|
||||||
expect(function() {
|
expect(function() {
|
||||||
|
|
@ -462,7 +459,9 @@ describe('Scope', function() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should catch exceptions', inject(function($rootScope, $exceptionHandler, $log) {
|
it('should catch exceptions', inject(function($exceptionHandlerProvider) {
|
||||||
|
$exceptionHandlerProvider.mode('log');
|
||||||
|
}, function($rootScope, $exceptionHandler, $log) {
|
||||||
var log = '';
|
var log = '';
|
||||||
var child = $rootScope.$new();
|
var child = $rootScope.$new();
|
||||||
$rootScope.$watch('a', function(scope, a) { log += '1'; });
|
$rootScope.$watch('a', function(scope, a) { log += '1'; });
|
||||||
|
|
@ -476,7 +475,9 @@ describe('Scope', function() {
|
||||||
|
|
||||||
describe('exceptions', function() {
|
describe('exceptions', function() {
|
||||||
var log;
|
var log;
|
||||||
beforeEach(inject(function($rootScope) {
|
beforeEach(inject(function($exceptionHandlerProvider) {
|
||||||
|
$exceptionHandlerProvider.mode('log');
|
||||||
|
}, function($rootScope) {
|
||||||
log = '';
|
log = '';
|
||||||
$rootScope.$watch(function() { log += '$digest;'; });
|
$rootScope.$watch(function() { log += '$digest;'; });
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
|
|
@ -502,6 +503,57 @@ describe('Scope', function() {
|
||||||
expect($exceptionHandler.errors).toEqual([error]);
|
expect($exceptionHandler.errors).toEqual([error]);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('recursive $apply protection', function() {
|
||||||
|
it('should throw an exception if $apply is called while an $apply is in progress', inject(
|
||||||
|
function($rootScope) {
|
||||||
|
expect(function() {
|
||||||
|
$rootScope.$apply(function() {
|
||||||
|
$rootScope.$apply();
|
||||||
|
});
|
||||||
|
}).toThrow('$apply already in progress');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw an exception if $apply is called while flushing evalAsync queue', inject(
|
||||||
|
function($rootScope) {
|
||||||
|
expect(function() {
|
||||||
|
$rootScope.$apply(function() {
|
||||||
|
$rootScope.$evalAsync(function() {
|
||||||
|
$rootScope.$apply();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).toThrow('$digest already in progress');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should throw an exception if $apply is called while a watch is being initialized', inject(
|
||||||
|
function($rootScope) {
|
||||||
|
var childScope1 = $rootScope.$new();
|
||||||
|
childScope1.$watch('x', function() {
|
||||||
|
childScope1.$apply();
|
||||||
|
});
|
||||||
|
expect(function() { childScope1.$apply(); }).toThrow('$digest already in progress');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should thrown an exception if $apply in called from a watch fn (after init)', inject(
|
||||||
|
function($rootScope) {
|
||||||
|
var childScope2 = $rootScope.$new();
|
||||||
|
childScope2.$apply(function() {
|
||||||
|
childScope2.$watch('x', function(scope, newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
childScope2.$apply();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(function() { childScope2.$apply(function() {
|
||||||
|
childScope2.x = 'something';
|
||||||
|
}); }).toThrow('$digest already in progress');
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -561,7 +613,10 @@ describe('Scope', function() {
|
||||||
log += event.currentScope.id + '>';
|
log += event.currentScope.id + '>';
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(inject(function($rootScope) {
|
beforeEach(inject(
|
||||||
|
function($exceptionHandlerProvider) {
|
||||||
|
$exceptionHandlerProvider.mode('log');
|
||||||
|
}, function($rootScope) {
|
||||||
log = '';
|
log = '';
|
||||||
child = $rootScope.$new();
|
child = $rootScope.$new();
|
||||||
grandChild = child.$new();
|
grandChild = child.$new();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue