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

Commit 6ecf4e7

Browse files
committed
fix(scope): check on the listeners methods to handle destroyed scopes
Add several checks on the methods that handle listeners so they are able to handle destroyed scopes that now do not have the references to the `$$listeners` and `$$listenersCount` when destroyed Closes #6897
1 parent 908ab52 commit 6ecf4e7

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

src/ng/rootScope.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ function $RootScopeProvider(){
751751
}
752752
// recreate the $$destroyed flag
753753
this.$$destroyed = true;
754+
754755
},
755756

756757
/**
@@ -921,6 +922,7 @@ function $RootScopeProvider(){
921922
* @returns {function()} Returns a deregistration function for this listener.
922923
*/
923924
$on: function(name, listener) {
925+
if (this.$$destroyed) return noop;
924926
var namedListeners = this.$$listeners[name];
925927
if (!namedListeners) {
926928
this.$$listeners[name] = namedListeners = [];
@@ -933,10 +935,11 @@ function $RootScopeProvider(){
933935
current.$$listenerCount[name] = 0;
934936
}
935937
current.$$listenerCount[name]++;
936-
} while ((current = current.$parent));
938+
} while ((current = current.$parent) && !current.$$destroyed);
937939

938940
var self = this;
939941
return function() {
942+
if (self.$$destroyed) return;
940943
namedListeners[indexOf(namedListeners, listener)] = null;
941944
decrementListenerCount(self, 1, name);
942945
};
@@ -982,7 +985,7 @@ function $RootScopeProvider(){
982985
listenerArgs = concat([event], arguments, 1),
983986
i, length;
984987

985-
do {
988+
while (scope && !scope.$$destroyed) {
986989
namedListeners = scope.$$listeners[name] || empty;
987990
event.currentScope = scope;
988991
for (i=0, length=namedListeners.length; i<length; i++) {
@@ -1005,7 +1008,7 @@ function $RootScopeProvider(){
10051008
if (stopPropagation) return event;
10061009
//traverse upwards
10071010
scope = scope.$parent;
1008-
} while (scope);
1011+
}
10091012

10101013
return event;
10111014
},
@@ -1113,7 +1116,7 @@ function $RootScopeProvider(){
11131116
if (current.$$listenerCount[name] === 0) {
11141117
delete current.$$listenerCount[name];
11151118
}
1116-
} while ((current = current.$parent));
1119+
} while ((current = current.$parent) && !current.$$destroyed);
11171120
}
11181121

11191122
/**

test/ng/rootScopeSpec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,64 @@ describe('Scope', function() {
874874
$rootScope.$broadcast(EVENT);
875875
expect(spy.callCount).toBe(1);
876876
}));
877+
878+
it('should not throw when trying to listen to an event on a child scope of an already destroyed scope', inject(function($rootScope) {
879+
var parent = $rootScope.$new(),
880+
child = parent.$new();
881+
882+
parent.$destroy();
883+
expect(function() {
884+
var fn = child.$on('someEvent', angular.noop);
885+
fn();
886+
}).not.toThrow();
887+
}));
888+
889+
it('should not throw when deregistering a listener on a child scope of an already destroyed scope', inject(function($rootScope) {
890+
var parent = $rootScope.$new(),
891+
child = parent.$new(),
892+
fn = child.$on('someEvent', angular.noop);
893+
894+
parent.$destroy();
895+
expect(function() { fn(); }).not.toThrow();
896+
}));
897+
898+
it('should not throw when trying to destroy a child insolated scope of an already destroyed scope', inject(function($rootScope) {
899+
var parent = $rootScope.$new(),
900+
child = parent.$new(true),
901+
fn = child.$on('someEvent', angular.noop);
902+
903+
parent.$destroy();
904+
expect(function() { child.$destroy(); }).not.toThrow();
905+
}));
906+
907+
it('should not throw when trying to listen to an event on an insolated child scope of an already destroyed scope', inject(function($rootScope) {
908+
var parent = $rootScope.$new(),
909+
child = parent.$new(true);
910+
911+
parent.$destroy();
912+
expect(function() {
913+
var fn = child.$on('someEvent', angular.noop);
914+
fn();
915+
}).not.toThrow();
916+
}));
917+
918+
it('should not throw when deregistering a listener on an insolated child scope of an already destroyed scope', inject(function($rootScope) {
919+
var parent = $rootScope.$new(),
920+
child = parent.$new(true),
921+
fn = child.$on('someEvent', angular.noop);
922+
923+
parent.$destroy();
924+
expect(function() { fn(); }).not.toThrow();
925+
}));
926+
927+
it('should not throw when trying to emit from an insolated child scope of a destroyed scope', inject(function($rootScope) {
928+
var parent = $rootScope.$new(),
929+
child = parent.$new(true),
930+
fn = child.$on('someEvent', angular.noop);
931+
932+
parent.$destroy();
933+
expect(function() { child.$emit('someEvent'); }).not.toThrow();
934+
}));
877935
});
878936

879937

0 commit comments

Comments
 (0)