From ba3737c092bdfbf60afdf3d232740d01c01e419a Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Tue, 20 May 2014 14:23:02 -0700 Subject: [PATCH] fix(Scope): $broadcast and $emit should set event.currentScope to null When a event is finished propagating through Scope hierarchy the event's `currentScope` property should be reset to `null` to avoid accidental use of this property in asynchronous event handlers. In the previous code, the event's property would contain a reference to the last Scope instance that was visited during the traversal, which is unlikely what the code trying to grab scope reference expects. BREAKING CHANGE: $broadcast and $emit will now reset the `currentScope` property of the event to null once the event finished propagating. If any code depends on asynchronously accessing thei `currentScope` property, it should be migrated to use `targetScope` instead. All of these cases should be considered programming bugs. Closes #7445 --- src/ng/rootScope.js | 9 ++++++++- test/ng/rootScopeSpec.js | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 68f8ede6f61c..8c6ab14d79c6 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -1065,11 +1065,16 @@ function $RootScopeProvider(){ } } //if any listener on the current scope stops propagation, prevent bubbling - if (stopPropagation) return event; + if (stopPropagation) { + event.currentScope = null; + return event; + } //traverse upwards scope = scope.$parent; } while (scope); + event.currentScope = null; + return event; }, @@ -1142,6 +1147,8 @@ function $RootScopeProvider(){ } } + event.currentScope = null; + return event; } }; diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 3613869c0741..63ff1028f237 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -1563,15 +1563,30 @@ describe('Scope', function() { describe('event object', function() { it('should have methods/properties', function() { - var event; + var eventFired = false; + child.$on('myEvent', function(e) { expect(e.targetScope).toBe(grandChild); expect(e.currentScope).toBe(child); expect(e.name).toBe('myEvent'); + eventFired = true; + }); + grandChild.$emit('myEvent'); + expect(eventFired).toBeDefined(); + }); + + + it("should have it's `currentScope` property set to null after emit", function() { + var event; + + child.$on('myEvent', function(e) { event = e; }); grandChild.$emit('myEvent'); - expect(event).toBeDefined(); + + expect(event.currentScope).toBe(null); + expect(event.targetScope).toBe(grandChild); + expect(event.name).toBe('myEvent'); }); @@ -1584,6 +1599,7 @@ describe('Scope', function() { }); event = grandChild.$emit('myEvent'); expect(event.defaultPrevented).toBe(true); + expect(event.currentScope).toBe(null); }); }); }); @@ -1698,6 +1714,24 @@ describe('Scope', function() { describe('listener', function() { it('should receive event object', inject(function($rootScope) { + var scope = $rootScope, + child = scope.$new(), + eventFired = false; + + child.$on('fooEvent', function(event) { + eventFired = true; + expect(event.name).toBe('fooEvent'); + expect(event.targetScope).toBe(scope); + expect(event.currentScope).toBe(child); + }); + scope.$broadcast('fooEvent'); + + expect(eventFired).toBe(true); + })); + + + it("should have the event's `currentScope` property set to null after broadcast", + inject(function($rootScope) { var scope = $rootScope, child = scope.$new(), event; @@ -1709,7 +1743,7 @@ describe('Scope', function() { expect(event.name).toBe('fooEvent'); expect(event.targetScope).toBe(scope); - expect(event.currentScope).toBe(child); + expect(event.currentScope).toBe(null); }));