diff --git a/lib/change_detection/watch_group.dart b/lib/change_detection/watch_group.dart index b3085562a..65866e497 100644 --- a/lib/change_detection/watch_group.dart +++ b/lib/change_detection/watch_group.dart @@ -291,8 +291,7 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { // Unlink the _watchRecord _EvalWatchRecord firstEvalWatch = _evalWatchHead; - _EvalWatchRecord lastEvalWatch = - (_watchGroupTail == null ? this : _watchGroupTail)._evalWatchTail; + _EvalWatchRecord lastEvalWatch = _childWatchGroupTail._evalWatchTail; _EvalWatchRecord previous = firstEvalWatch._prevEvalWatch; _EvalWatchRecord next = lastEvalWatch._nextEvalWatch; if (previous != null) previous._nextEvalWatch = next; diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 707d59568..050a9a514 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -133,6 +133,8 @@ class ScopeLocals implements Map { * for change detection, change processing and memory management. */ class Scope { + final String id; + int _childScopeNextId = 0; /** * The default execution context for [watch]es [observe]ers, and [eval]uation. @@ -182,7 +184,7 @@ class Scope { bool get hasOwnStreams => _streams != null && _streams._scope == this; Scope(Object this.context, this.rootScope, this._parentScope, - this._readWriteGroup, this._readOnlyGroup); + this._readWriteGroup, this._readOnlyGroup, this.id); /** * A [watch] sets up a watch in the [digest] phase of the [apply] cycle. @@ -271,7 +273,8 @@ class Scope { assert(isAttached); var child = new Scope(childContext, rootScope, this, _readWriteGroup.newGroup(childContext), - _readOnlyGroup.newGroup(childContext)); + _readOnlyGroup.newGroup(childContext), + '$id:${_childScopeNextId++}'); var prev = _childTail; child._prev = prev; @@ -301,6 +304,26 @@ class Scope { _readWriteGroup.remove(); _readOnlyGroup.remove(); _parentScope = null; + + assert((() { + var scopes = [this]; + while(scopes.isNotEmpty) { + Scope scope = scopes.removeAt(0); + Scope childScope = scope._childHead; + while(childScope != null) { + scopes.add(childScope); + childScope = childScope._next; + } + + scope._next = scope._prev = null; + scope._childHead = scope._childTail = null; + if (scope._streams != null) scope._streams._release(); + scope._streams = null; + } + + return true; + })()); + } _assertInternalStateConsistency() { @@ -422,7 +445,8 @@ class RootScope extends Scope { this._scopeStats) : super(context, null, null, new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context), - new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context)) + new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context), + '') { _zone.onTurnDone = apply; _zone.onError = (e, s, ls) => _exceptionHandler(e, s); @@ -595,7 +619,7 @@ class RootScope extends Scope { class _Streams { final ExceptionHandler _exceptionHandler; /// Scope we belong to. - final Scope _scope; + /*final*/ Scope _scope; /// [Stream]s for [_scope] only final _streams = new Map(); /// Child [Scope] event counts. @@ -729,6 +753,11 @@ class _Streams { scope = scope._parentScope; } } + + void _release() { + _scope = null; + _streams.clear(); + } } class ScopeStream extends async.Stream { diff --git a/test/change_detection/watch_group_spec.dart b/test/change_detection/watch_group_spec.dart index b18aaa2d7..84d9503ac 100644 --- a/test/change_detection/watch_group_spec.dart +++ b/test/change_detection/watch_group_spec.dart @@ -593,7 +593,7 @@ void main() { expect(watchGrp.totalFieldCost).toEqual(2); }); - it('should remove all method watches in group and group\'s children', () { + iit('should remove all method watches in group and group\'s children', () { context['my'] = new MyClass(logger); AST countMethod = new MethodAST(parse('my'), 'count', []); watchGrp.watch(countMethod, (v, p) => logger('0a')); @@ -602,6 +602,7 @@ void main() { var child1a = watchGrp.newGroup(new PrototypeMap(context)); var child1b = watchGrp.newGroup(new PrototypeMap(context)); var child2 = child1a.newGroup(new PrototypeMap(context)); + var child3 = child2.newGroup(new PrototypeMap(context)); child1a.watch(countMethod, (v, p) => logger('1a')); expectOrder(['0a', '1a']); child1b.watch(countMethod, (v, p) => logger('1b')); @@ -612,12 +613,14 @@ void main() { expectOrder(['0a', '0A', '1a', '1A', '1b']); child2.watch(countMethod, (v, p) => logger('2A')); expectOrder(['0a', '0A', '1a', '1A', '2A', '1b']); + child3.watch(countMethod, (v, p) => logger('3')); + expectOrder(['0a', '0A', '1a', '1A', '2A', '3', '1b']); // flush initial reaction functions - expect(watchGrp.detectChanges()).toEqual(6); - expectOrder(['0a', '0A', '1a', '1A', '2A', '1b']); + expect(watchGrp.detectChanges()).toEqual(7); + expectOrder(['0a', '0A', '1a', '1A', '2A', '3', '1b']); - child1a.remove(); // should also remove child2 + child1a.remove(); // should also remove child2 and child 3 expect(watchGrp.detectChanges()).toEqual(3); expectOrder(['0a', '0A', '1b']); }); diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index c2d2259bb..6d1cc484f 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -224,11 +224,13 @@ void main() { describe('parent', () { it('should not have parent', inject((RootScope rootScope) { expect(rootScope.parentScope).toEqual(null); + expect(rootScope.id).toEqual(''); })); it('should point to parent', inject((RootScope rootScope) { var child = rootScope.createChild(new PrototypeMap(rootScope.context)); + expect(child.id).toEqual(':0'); expect(rootScope.parentScope).toEqual(null); expect(child.parentScope).toEqual(rootScope); expect(child.createChild(new PrototypeMap(rootScope.context)).parentScope).toEqual(child);