Skip to content

Commit

Permalink
fix(scope): check on the listeners methods to handle destroyed scopes
Browse files Browse the repository at this point in the history
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 angular#6897
  • Loading branch information
lgalfaso committed Mar 31, 2014
1 parent 908ab52 commit 6ecf4e7
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 4 deletions.
11 changes: 7 additions & 4 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ function $RootScopeProvider(){
}
// recreate the $$destroyed flag
this.$$destroyed = true;

},

/**
Expand Down Expand Up @@ -921,6 +922,7 @@ function $RootScopeProvider(){
* @returns {function()} Returns a deregistration function for this listener.
*/
$on: function(name, listener) {
if (this.$$destroyed) return noop;
var namedListeners = this.$$listeners[name];
if (!namedListeners) {
this.$$listeners[name] = namedListeners = [];
Expand All @@ -933,10 +935,11 @@ function $RootScopeProvider(){
current.$$listenerCount[name] = 0;
}
current.$$listenerCount[name]++;
} while ((current = current.$parent));
} while ((current = current.$parent) && !current.$$destroyed);

var self = this;
return function() {
if (self.$$destroyed) return;
namedListeners[indexOf(namedListeners, listener)] = null;
decrementListenerCount(self, 1, name);
};
Expand Down Expand Up @@ -982,7 +985,7 @@ function $RootScopeProvider(){
listenerArgs = concat([event], arguments, 1),
i, length;

do {
while (scope && !scope.$$destroyed) {
namedListeners = scope.$$listeners[name] || empty;
event.currentScope = scope;
for (i=0, length=namedListeners.length; i<length; i++) {
Expand All @@ -1005,7 +1008,7 @@ function $RootScopeProvider(){
if (stopPropagation) return event;
//traverse upwards
scope = scope.$parent;
} while (scope);
}

return event;
},
Expand Down Expand Up @@ -1113,7 +1116,7 @@ function $RootScopeProvider(){
if (current.$$listenerCount[name] === 0) {
delete current.$$listenerCount[name];
}
} while ((current = current.$parent));
} while ((current = current.$parent) && !current.$$destroyed);
}

/**
Expand Down
58 changes: 58 additions & 0 deletions test/ng/rootScopeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,64 @@ describe('Scope', function() {
$rootScope.$broadcast(EVENT);
expect(spy.callCount).toBe(1);
}));

it('should not throw when trying to listen to an event on a child scope of an already destroyed scope', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new();

parent.$destroy();
expect(function() {
var fn = child.$on('someEvent', angular.noop);
fn();
}).not.toThrow();
}));

it('should not throw when deregistering a listener on a child scope of an already destroyed scope', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(),
fn = child.$on('someEvent', angular.noop);

parent.$destroy();
expect(function() { fn(); }).not.toThrow();
}));

it('should not throw when trying to destroy a child insolated scope of an already destroyed scope', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(true),
fn = child.$on('someEvent', angular.noop);

parent.$destroy();
expect(function() { child.$destroy(); }).not.toThrow();
}));

it('should not throw when trying to listen to an event on an insolated child scope of an already destroyed scope', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(true);

parent.$destroy();
expect(function() {
var fn = child.$on('someEvent', angular.noop);
fn();
}).not.toThrow();
}));

it('should not throw when deregistering a listener on an insolated child scope of an already destroyed scope', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(true),
fn = child.$on('someEvent', angular.noop);

parent.$destroy();
expect(function() { fn(); }).not.toThrow();
}));

it('should not throw when trying to emit from an insolated child scope of a destroyed scope', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(true),
fn = child.$on('someEvent', angular.noop);

parent.$destroy();
expect(function() { child.$emit('someEvent'); }).not.toThrow();
}));
});


Expand Down

0 comments on commit 6ecf4e7

Please sign in to comment.