diff options
| -rw-r--r-- | src/ng/rootScope.js | 33 | ||||
| -rw-r--r-- | test/ng/rootScopeSpec.js | 126 | 
2 files changed, 136 insertions, 23 deletions
| diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index a56abc5c..1bb12869 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -133,6 +133,7 @@ function $RootScopeProvider(){        this.$$asyncQueue = [];        this.$$postDigestQueue = [];        this.$$listeners = {}; +      this.$$listenerCount = {};        this.$$isolateBindings = {};      } @@ -192,6 +193,7 @@ function $RootScopeProvider(){          }          child['this'] = child;          child.$$listeners = {}; +        child.$$listenerCount = {};          child.$parent = this;          child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;          child.$$prevSibling = this.$$childTail; @@ -696,6 +698,8 @@ function $RootScopeProvider(){          this.$$destroyed = true;          if (this === $rootScope) return; +        forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); +          if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;          if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;          if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; @@ -885,8 +889,18 @@ function $RootScopeProvider(){          }          namedListeners.push(listener); +        var current = this; +        do { +          if (!current.$$listenerCount[name]) { +            current.$$listenerCount[name] = 0; +          } +          current.$$listenerCount[name]++; +        } while ((current = current.$parent)); + +        var self = this;          return function() {            namedListeners[indexOf(namedListeners, listener)] = null; +          decrementListenerCount(self, 1, name);          };        }, @@ -998,8 +1012,7 @@ function $RootScopeProvider(){              listeners, i, length;          //down while you can, then up and next sibling or up and next sibling until back at root -        do { -          current = next; +        while ((current = next)) {            event.currentScope = current;            listeners = current.$$listeners[name] || [];            for (i=0, length = listeners.length; i<length; i++) { @@ -1021,12 +1034,14 @@ function $RootScopeProvider(){            // Insanity Warning: scope depth-first traversal            // 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 $digest -          if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { +          // (though it differs due to having the extra check for $$listenerCount) +          if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || +              (current !== target && current.$$nextSibling)))) {              while(current !== target && !(next = current.$$nextSibling)) {                current = current.$parent;              }            } -        } while ((current = next)); +        }          return event;        } @@ -1055,6 +1070,16 @@ function $RootScopeProvider(){        return fn;      } +    function decrementListenerCount(current, count, name) { +      do { +        current.$$listenerCount[name] -= count; + +        if (current.$$listenerCount[name] === 0) { +          delete current.$$listenerCount[name]; +        } +      } while ((current = current.$parent)); +    } +      /**       * function used as an initial value for watchers.       * because it's unique we can easily tell it apart from other values diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index cc6727c2..3677ccc8 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -730,6 +730,28 @@ describe('Scope', function() {        first.$apply();        expect(log).toBe('1232323');      })); + + +    it('should decrement anscestor $$listenerCount entries', inject(function($rootScope) { +      var EVENT = 'fooEvent', +          spy = jasmine.createSpy('listener'), +          firstSecond = first.$new(); + +      firstSecond.$on(EVENT, spy); +      firstSecond.$on(EVENT, spy); +      middle.$on(EVENT, spy); + +      expect($rootScope.$$listenerCount[EVENT]).toBe(3); +      expect(first.$$listenerCount[EVENT]).toBe(2); + +      firstSecond.$destroy(); + +      expect($rootScope.$$listenerCount[EVENT]).toBe(1); +      expect(first.$$listenerCount[EVENT]).toBeUndefined(); + +      $rootScope.$broadcast(EVENT); +      expect(spy.callCount).toBe(1); +    }));    }); @@ -1091,29 +1113,78 @@ describe('Scope', function() {        })); -      it('should return a function that deregisters the listener', inject(function($rootScope) { -        var log = '', -            child = $rootScope.$new(), -            listenerRemove; - -        function eventFn() { -          log += 'X'; -        } +      it('should increment ancestor $$listenerCount entries', inject(function($rootScope) { +        var child1 = $rootScope.$new(), +            child2 = child1.$new(), +            spy = jasmine.createSpy(); -        listenerRemove = child.$on('abc', eventFn); -        expect(log).toEqual(''); -        expect(listenerRemove).toBeDefined(); +        $rootScope.$on('event1', spy); +        expect($rootScope.$$listenerCount).toEqual({event1: 1}); -        child.$emit('abc'); -        child.$broadcast('abc'); -        expect(log).toEqual('XX'); +        child1.$on('event1', spy); +        expect($rootScope.$$listenerCount).toEqual({event1: 2}); +        expect(child1.$$listenerCount).toEqual({event1: 1}); -        log = ''; -        listenerRemove(); -        child.$emit('abc'); -        child.$broadcast('abc'); -        expect(log).toEqual(''); +        child2.$on('event2', spy); +        expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1}); +        expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1}); +        expect(child2.$$listenerCount).toEqual({event2: 1});        })); + + +      describe('deregistration', function() { + +        it('should return a function that deregisters the listener', inject(function($rootScope) { +          var log = '', +              child = $rootScope.$new(), +              listenerRemove; + +          function eventFn() { +            log += 'X'; +          } + +          listenerRemove = child.$on('abc', eventFn); +          expect(log).toEqual(''); +          expect(listenerRemove).toBeDefined(); + +          child.$emit('abc'); +          child.$broadcast('abc'); +          expect(log).toEqual('XX'); +          expect($rootScope.$$listenerCount['abc']).toBe(1); + +          log = ''; +          listenerRemove(); +          child.$emit('abc'); +          child.$broadcast('abc'); +          expect(log).toEqual(''); +          expect($rootScope.$$listenerCount['abc']).toBeUndefined(); +        })); + + +        it('should decrement ancestor $$listenerCount entries', inject(function($rootScope) { +          var child1 = $rootScope.$new(), +              child2 = child1.$new(), +              spy = jasmine.createSpy(); + +          $rootScope.$on('event1', spy); +          expect($rootScope.$$listenerCount).toEqual({event1: 1}); + +          child1.$on('event1', spy); +          expect($rootScope.$$listenerCount).toEqual({event1: 2}); +          expect(child1.$$listenerCount).toEqual({event1: 1}); + +          var deregisterEvent2Listener = child2.$on('event2', spy); +          expect($rootScope.$$listenerCount).toEqual({event1: 2, event2: 1}); +          expect(child1.$$listenerCount).toEqual({event1: 1, event2: 1}); +          expect(child2.$$listenerCount).toEqual({event2: 1}); + +          deregisterEvent2Listener(); + +          expect($rootScope.$$listenerCount).toEqual({event1: 2}); +          expect(child1.$$listenerCount).toEqual({event1: 1}); +          expect(child2.$$listenerCount).toEqual({}); +        })) +      });      }); @@ -1360,6 +1431,23 @@ describe('Scope', function() {          })); +        it('should not descend past scopes with a $$listerCount of 0 or undefined', +            inject(function($rootScope) { +          var EVENT = 'fooEvent', +              spy = jasmine.createSpy('listener'); + +          // Precondition: There should be no listeners for fooEvent. +          expect($rootScope.$$listenerCount[EVENT]).toBeUndefined(); + +          // Add a spy listener to a child scope. +          $rootScope.$$childHead.$$listeners[EVENT] = [spy]; + +          // $rootScope's count for 'fooEvent' is undefined, so spy should not be called. +          $rootScope.$broadcast(EVENT); +          expect(spy).not.toHaveBeenCalled(); +        })); + +          it('should return event object', function() {            var result = child1.$broadcast('some'); | 
