revert: fix($rootScope): fix potential memory leak when removing scope listeners
This reverts commit 817ac56719.
This commit is contained in:
committed by
Martin Staffa
parent
41d5c90f17
commit
e5fb92978f
@@ -1,22 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $rootScope:inevt
|
||||
@fullName Recursive $emit/$broadcast event
|
||||
@description
|
||||
|
||||
This error occurs when the an event is `$emit`ed or `$broadcast`ed recursively on a scope.
|
||||
|
||||
For example, when an event listener fires the same event being listened to.
|
||||
|
||||
```
|
||||
$scope.$on('foo', function() {
|
||||
$scope.$emit('foo');
|
||||
});
|
||||
```
|
||||
|
||||
Or when a parent element causes indirect recursion.
|
||||
|
||||
```
|
||||
$scope.$on('foo', function() {
|
||||
$rootScope.$broadcast('foo');
|
||||
});
|
||||
```
|
||||
+44
-33
@@ -1271,14 +1271,10 @@ function $RootScopeProvider() {
|
||||
|
||||
var self = this;
|
||||
return function() {
|
||||
var index = arrayRemove(namedListeners, listener);
|
||||
if (index >= 0) {
|
||||
var indexOfListener = namedListeners.indexOf(listener);
|
||||
if (indexOfListener !== -1) {
|
||||
namedListeners[indexOfListener] = null;
|
||||
decrementListenerCount(self, 1, name);
|
||||
// We are removing a listener while iterating over the list of listeners.
|
||||
// Update the current $$index if necessary to ensure no listener is skipped.
|
||||
if (index <= namedListeners.$$index) {
|
||||
namedListeners.$$index--;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -1307,7 +1303,9 @@ function $RootScopeProvider() {
|
||||
* @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
|
||||
*/
|
||||
$emit: function(name, args) {
|
||||
var scope = this,
|
||||
var empty = [],
|
||||
namedListeners,
|
||||
scope = this,
|
||||
stopPropagation = false,
|
||||
event = {
|
||||
name: name,
|
||||
@@ -1318,11 +1316,28 @@ function $RootScopeProvider() {
|
||||
},
|
||||
defaultPrevented: false
|
||||
},
|
||||
listenerArgs = concat([event], arguments, 1);
|
||||
listenerArgs = concat([event], arguments, 1),
|
||||
i, length;
|
||||
|
||||
do {
|
||||
invokeListeners(scope, event, listenerArgs, name);
|
||||
namedListeners = scope.$$listeners[name] || empty;
|
||||
event.currentScope = scope;
|
||||
for (i = 0, length = namedListeners.length; i < length; i++) {
|
||||
|
||||
// if listeners were deregistered, defragment the array
|
||||
if (!namedListeners[i]) {
|
||||
namedListeners.splice(i, 1);
|
||||
i--;
|
||||
length--;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
//allow all listeners attached to the current scope to run
|
||||
namedListeners[i].apply(null, listenerArgs);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
//if any listener on the current scope stops propagation, prevent bubbling
|
||||
if (stopPropagation) {
|
||||
break;
|
||||
@@ -1373,11 +1388,28 @@ function $RootScopeProvider() {
|
||||
|
||||
if (!target.$$listenerCount[name]) return event;
|
||||
|
||||
var listenerArgs = concat([event], arguments, 1);
|
||||
var listenerArgs = concat([event], arguments, 1),
|
||||
listeners, i, length;
|
||||
|
||||
//down while you can, then up and next sibling or up and next sibling until back at root
|
||||
while ((current = next)) {
|
||||
invokeListeners(current, event, listenerArgs, name);
|
||||
event.currentScope = current;
|
||||
listeners = current.$$listeners[name] || [];
|
||||
for (i = 0, length = listeners.length; i < length; i++) {
|
||||
// if listeners were deregistered, defragment the array
|
||||
if (!listeners[i]) {
|
||||
listeners.splice(i, 1);
|
||||
i--;
|
||||
length--;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
listeners[i].apply(null, listenerArgs);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Insanity Warning: scope depth-first traversal
|
||||
// yes, this code is a bit crazy, but it works and we have tests to prove it!
|
||||
@@ -1408,27 +1440,6 @@ function $RootScopeProvider() {
|
||||
|
||||
return $rootScope;
|
||||
|
||||
function invokeListeners(scope, event, listenerArgs, name) {
|
||||
var listeners = scope.$$listeners[name];
|
||||
if (listeners) {
|
||||
if (listeners.$$index !== undefined) {
|
||||
throw $rootScopeMinErr('inevt', '{0} already $emit/$broadcast-ing on scope ({1})', name, scope.$id);
|
||||
}
|
||||
event.currentScope = scope;
|
||||
try {
|
||||
for (listeners.$$index = 0; listeners.$$index < listeners.length; listeners.$$index++) {
|
||||
try {
|
||||
//allow all listeners attached to the current scope to run
|
||||
listeners[listeners.$$index].apply(null, listenerArgs);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
listeners.$$index = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function beginPhase(phase) {
|
||||
if ($rootScope.$$phase) {
|
||||
|
||||
+2
-116
@@ -2438,19 +2438,6 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
// See issue https://github.com/angular/angular.js/issues/16135
|
||||
it('should deallocate the listener array entry', inject(function($rootScope) {
|
||||
var remove1 = $rootScope.$on('abc', noop);
|
||||
$rootScope.$on('abc', noop);
|
||||
|
||||
expect($rootScope.$$listeners['abc'].length).toBe(2);
|
||||
|
||||
remove1();
|
||||
|
||||
expect($rootScope.$$listeners['abc'].length).toBe(1);
|
||||
}));
|
||||
|
||||
|
||||
it('should call next listener after removing the current listener via its own handler', inject(function($rootScope) {
|
||||
var listener1 = jasmine.createSpy('listener1').and.callFake(function() { remove1(); });
|
||||
var remove1 = $rootScope.$on('abc', listener1);
|
||||
@@ -2583,107 +2570,6 @@ describe('Scope', function() {
|
||||
expect($rootScope.$$listenerCount).toEqual({abc: 1});
|
||||
expect(child.$$listenerCount).toEqual({abc: 1});
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on recursive $broadcast', inject(function($rootScope) {
|
||||
$rootScope.$on('e', function() { $rootScope.$broadcast('e'); });
|
||||
|
||||
expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on nested recursive $broadcast', inject(function($rootScope) {
|
||||
$rootScope.$on('e2', function() { $rootScope.$broadcast('e'); });
|
||||
$rootScope.$on('e', function() { $rootScope.$broadcast('e2'); });
|
||||
|
||||
expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on recursive $emit', inject(function($rootScope) {
|
||||
$rootScope.$on('e', function() { $rootScope.$emit('e'); });
|
||||
|
||||
expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on nested recursive $emit', inject(function($rootScope) {
|
||||
$rootScope.$on('e2', function() { $rootScope.$emit('e'); });
|
||||
$rootScope.$on('e', function() { $rootScope.$emit('e2'); });
|
||||
|
||||
expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on recursive $broadcast on child listener', inject(function($rootScope) {
|
||||
var child = $rootScope.$new();
|
||||
child.$on('e', function() { $rootScope.$broadcast('e'); });
|
||||
|
||||
expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)');
|
||||
expect(function() { child.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on nested recursive $broadcast on child listener', inject(function($rootScope) {
|
||||
var child = $rootScope.$new();
|
||||
child.$on('e2', function() { $rootScope.$broadcast('e'); });
|
||||
child.$on('e', function() { $rootScope.$broadcast('e2'); });
|
||||
|
||||
expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)');
|
||||
expect(function() { child.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on recursive $emit parent listener', inject(function($rootScope) {
|
||||
var child = $rootScope.$new();
|
||||
$rootScope.$on('e', function() { child.$emit('e'); });
|
||||
|
||||
expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
}));
|
||||
|
||||
|
||||
it('should throw on nested recursive $emit parent listener', inject(function($rootScope) {
|
||||
var child = $rootScope.$new();
|
||||
$rootScope.$on('e2', function() { child.$emit('e'); });
|
||||
$rootScope.$on('e', function() { child.$emit('e2'); });
|
||||
|
||||
expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)');
|
||||
}));
|
||||
|
||||
|
||||
it('should clear recursive state of $broadcast if $exceptionHandler rethrows', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('rethrow');
|
||||
});
|
||||
inject(function($rootScope) {
|
||||
var throwingListener = jasmine.createSpy('thrower').and.callFake(function() {
|
||||
throw new Error('Listener Error!');
|
||||
});
|
||||
var secondListener = jasmine.createSpy('second');
|
||||
|
||||
$rootScope.$on('e', throwingListener);
|
||||
$rootScope.$on('e', secondListener);
|
||||
|
||||
expect(function() { $rootScope.$broadcast('e'); }).toThrowError('Listener Error!');
|
||||
expect(throwingListener).toHaveBeenCalled();
|
||||
expect(secondListener).not.toHaveBeenCalled();
|
||||
|
||||
throwingListener.calls.reset();
|
||||
secondListener.calls.reset();
|
||||
|
||||
expect(function() { $rootScope.$broadcast('e'); }).toThrowError('Listener Error!');
|
||||
expect(throwingListener).toHaveBeenCalled();
|
||||
expect(secondListener).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2773,7 +2659,7 @@ describe('Scope', function() {
|
||||
expect(spy1).toHaveBeenCalledOnce();
|
||||
expect(spy2).toHaveBeenCalledOnce();
|
||||
expect(spy3).toHaveBeenCalledOnce();
|
||||
expect(child.$$listeners['evt'].length).toBe(2);
|
||||
expect(child.$$listeners['evt'].length).toBe(3); // cleanup will happen on next $emit
|
||||
|
||||
spy1.calls.reset();
|
||||
spy2.calls.reset();
|
||||
@@ -2807,7 +2693,7 @@ describe('Scope', function() {
|
||||
expect(spy1).toHaveBeenCalledOnce();
|
||||
expect(spy2).toHaveBeenCalledOnce();
|
||||
expect(spy3).toHaveBeenCalledOnce();
|
||||
expect(child.$$listeners['evt'].length).toBe(2);
|
||||
expect(child.$$listeners['evt'].length).toBe(3); //cleanup will happen on next $broadcast
|
||||
|
||||
spy1.calls.reset();
|
||||
spy2.calls.reset();
|
||||
|
||||
Reference in New Issue
Block a user