diff --git a/benchmark/block_factory_benchmark.dart b/benchmark/block_factory_benchmark.dart
new file mode 100644
index 000000000..03f99332b
--- /dev/null
+++ b/benchmark/block_factory_benchmark.dart
@@ -0,0 +1,58 @@
+import 'dart:html';
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:angular/angular.dart';
+import 'package:di/di.dart';
+
+class BlockFactoryBenchmark extends BenchmarkBase {
+ BlockFactoryBenchmark() : super("BlockFactory");
+
+ static void main() {
+ new BlockFactoryBenchmark().report();
+ }
+
+ Injector injector;
+ BlockFactory blockFactory;
+
+ void run() {
+ blockFactory(injector);
+ }
+
+ void setup() {
+ var module = new AngularModule()
+ ..directive(TestTranscludingDirective)
+ ..directive(TestTemplatedComponent);
+ injector = new Injector([module]);
+ Compiler compiler = injector.get(Compiler);
+ StringBuffer sb = new StringBuffer();
+ sb.writeln('
');
+ for (int i = 0; i < 100; i++) {
+ sb.writeln('
Hello
');
+ sb.writeln('
Transcluded Content'
+ '
');
+ }
+ sb.writeln('
');
+ blockFactory = compiler([new Element.html(sb.toString())]);
+ }
+
+ void teardown() {
+ injector = null;
+ blockFactory = null;
+ dumpTimerStats();
+ }
+}
+
+@NgDirective(transclude: true)
+class TestTranscludingDirective {
+ TestTranscludingDirective(BoundBlockFactory blockFactory, BlockHole anchor, Scope scope) {
+ blockFactory(scope).insertAfter(anchor);
+ }
+}
+
+@NgComponent(template: 'Hello World!
')
+class TestTemplatedComponent {
+}
+
+main() {
+ BlockFactoryBenchmark.main();
+}
\ No newline at end of file
diff --git a/benchmark/block_factory_benchmark.html b/benchmark/block_factory_benchmark.html
new file mode 100644
index 000000000..2467ede1f
--- /dev/null
+++ b/benchmark/block_factory_benchmark.html
@@ -0,0 +1,12 @@
+
+
+
+
+ block_factory_benchmark
+
+
+
+
+
+
+
diff --git a/benchmark/bootstrap_benchmark.dart b/benchmark/bootstrap_benchmark.dart
new file mode 100644
index 000000000..ddf933ef6
--- /dev/null
+++ b/benchmark/bootstrap_benchmark.dart
@@ -0,0 +1,38 @@
+import 'dart:html';
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:angular/angular.dart';
+import 'package:di/di.dart';
+
+// TODO(pavelgj): Ignore this benchmark for now because it does not give a
+// good measure of the actual boostrap time, specifically the "cold start"
+// of the mirrors. Look into launching boostrap benchmark in an isolate.
+class BootstrapBenchmark extends BenchmarkBase {
+ BootstrapBenchmark() : super("Bootstrap");
+
+ static void main() {
+ new BootstrapBenchmark().report();
+ }
+
+ void run() {
+ var ngApp = new Element.div();
+ ngApp.attributes['ng-app'] = '';
+ ngApp.children.add(new Element.div());
+ query('#sandbox').children.add(ngApp);
+
+ bootstrapAngular([new AngularModule()]);
+
+ ngApp.remove();
+ }
+
+ void setup() {
+ }
+
+ void teardown() {
+ dumpTimerStats();
+ }
+}
+
+main() {
+ BootstrapBenchmark.main();
+}
\ No newline at end of file
diff --git a/benchmark/bootstrap_benchmark.html b/benchmark/bootstrap_benchmark.html
new file mode 100644
index 000000000..12825050b
--- /dev/null
+++ b/benchmark/bootstrap_benchmark.html
@@ -0,0 +1,13 @@
+
+
+
+
+ module_instantiation_benchmark
+
+
+
+
+
+
+
+
diff --git a/benchmark/compiler_benchmark.dart b/benchmark/compiler_benchmark.dart
new file mode 100644
index 000000000..d6dbe22be
--- /dev/null
+++ b/benchmark/compiler_benchmark.dart
@@ -0,0 +1,51 @@
+import 'dart:html';
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:angular/angular.dart';
+import 'package:di/di.dart';
+
+class CompilerBenchmark extends BenchmarkBase {
+ CompilerBenchmark() : super("Compiler");
+
+ static void main() {
+ new CompilerBenchmark().report();
+ }
+
+ Compiler compiler;
+
+ void run() {
+ StringBuffer sb = new StringBuffer();
+ sb.writeln('');
+ for (int i = 0; i < 100; i++) {
+ sb.writeln('Transcluded Content');
+ sb.writeln('');
+ }
+ sb.writeln('
');
+ compiler([new Element.html(sb.toString())]);
+ }
+
+ void setup() {
+ var module = new AngularModule()
+ ..directive(TestTranscludingDirective)
+ ..directive(TestTemplatedComponent);
+ Injector injector = new Injector([module]);
+ compiler = injector.get(Compiler);
+ }
+
+ void teardown() {
+ compiler = null;
+ dumpTimerStats();
+ }
+}
+
+@NgDirective(transclude: true)
+class TestTranscludingDirective {
+}
+
+@NgComponent(template: 'Hello World!
')
+class TestTemplatedComponent {
+}
+
+main() {
+ CompilerBenchmark.main();
+}
\ No newline at end of file
diff --git a/benchmark/compiler_benchmark.html b/benchmark/compiler_benchmark.html
new file mode 100644
index 000000000..5b430c103
--- /dev/null
+++ b/benchmark/compiler_benchmark.html
@@ -0,0 +1,12 @@
+
+
+
+
+ compiler_benchmark
+
+
+
+
+
+
+
diff --git a/benchmark/ng_repeat_benchmark.dart b/benchmark/ng_repeat_benchmark.dart
new file mode 100644
index 000000000..cb9217faf
--- /dev/null
+++ b/benchmark/ng_repeat_benchmark.dart
@@ -0,0 +1,50 @@
+import 'dart:html';
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:angular/angular.dart';
+import 'package:di/di.dart';
+
+class NgRepeatBenchmark extends BenchmarkBase {
+ NgRepeatBenchmark() : super("NgRepeat");
+
+ static void main() {
+ new NgRepeatBenchmark().report();
+ }
+
+ Injector injector;
+ Scope rootScope;
+ BlockFactory blockFactory;
+
+ void run() {
+ var vals = [];
+ for(int i = 0; i < 100; i++) {
+ vals.add(i);
+ }
+ Scope scope = rootScope.$new(true);
+ scope.vals = vals;
+
+ blockFactory(injector.createChild([new ScopeModule(scope)]));
+ scope.$digest();
+ scope.$destroy();
+ }
+
+ void setup() {
+ var module = new AngularModule();
+ injector = new Injector([module]);
+ rootScope = injector.get(Scope);
+ Compiler compiler = injector.get(Compiler);
+ String html = '{{i}}
';
+ blockFactory = compiler([new Element.html(html)]);
+ }
+
+ void teardown() {
+ injector = null;
+ rootScope = null;
+ blockFactory = null;
+ dumpTimerStats();
+ }
+}
+
+main() {
+ NgRepeatBenchmark.main();
+}
\ No newline at end of file
diff --git a/benchmark/ng_repeat_benchmark.html b/benchmark/ng_repeat_benchmark.html
new file mode 100644
index 000000000..afb5a1537
--- /dev/null
+++ b/benchmark/ng_repeat_benchmark.html
@@ -0,0 +1,12 @@
+
+
+
+
+ ng_repeat_benchmark
+
+
+
+
+
+
+
diff --git a/benchmark/scope_digest_benchmark.dart b/benchmark/scope_digest_benchmark.dart
new file mode 100644
index 000000000..c16f489e5
--- /dev/null
+++ b/benchmark/scope_digest_benchmark.dart
@@ -0,0 +1,46 @@
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:angular/angular.dart';
+import 'package:di/di.dart';
+
+class ScopeDigestBenchmark extends BenchmarkBase {
+ ScopeDigestBenchmark() : super("ScopeDigest");
+
+ static void main() {
+ new ScopeDigestBenchmark().report();
+ }
+
+ Interpolate interpolator;
+ Scope rootScope;
+
+ void run() {
+ var testScope = rootScope.$new();
+ // Simulating a repeater repeating a component with a simple template.
+ for (int i = 0; i < 100; i++) {
+ var childScope = testScope.$new();
+ childScope['hello'] = new TestObject();
+ childScope.$watch(interpolator('{{hello.sayHello()}}'), (_) {});
+ }
+ testScope.$digest();
+ testScope.$destroy();
+ }
+
+ void setup() {
+ Injector injector = new Injector([new AngularModule()]);
+ interpolator = injector.get(Interpolate);
+ rootScope = injector.get(Scope);
+ }
+
+ void teardown() {
+ interpolator = null;
+ rootScope = null;
+ dumpTimerStats();
+ }
+}
+
+class TestObject {
+ String sayHello() => 'hello';
+}
+
+main() {
+ ScopeDigestBenchmark.main();
+}
\ No newline at end of file
diff --git a/benchmark/scope_digest_benchmark.html b/benchmark/scope_digest_benchmark.html
new file mode 100644
index 000000000..4cab3623a
--- /dev/null
+++ b/benchmark/scope_digest_benchmark.html
@@ -0,0 +1,12 @@
+
+
+
+
+ scope_digest_benchmark
+
+
+
+
+
+
+
diff --git a/lib/angular.dart b/lib/angular.dart
index e7475b9a3..0b7519337 100644
--- a/lib/angular.dart
+++ b/lib/angular.dart
@@ -37,6 +37,8 @@ part 'scope.dart';
part 'selector.dart';
part 'string_utilities.dart';
+bool useMirrorsInParser = true;
+
ASSERT(condition) {
if (!condition) {
throw new AssertionError();
@@ -170,3 +172,74 @@ bootstrapAngular(modules, [rootElementSelector = '[ng-app]']) {
$rootScope.$digest();
});
}
+
+class TimerStat {
+ int count = 0;
+ double total = 0.0;
+ double avg = 0.0;
+ double min = double.MAX_FINITE;
+ double max = 0.0;
+ List top = [];
+}
+
+Map stats = {};
+
+int _depth = 0;
+
+time(desc, fn) {
+ //_depth++;
+ desc = _prefix(_depth - 1, desc);
+ var stat = stats[desc];
+ if (stat == null) {
+ stat = new TimerStat();
+ stats[desc] = stat;
+ }
+ var start = dom.window.performance.now();
+ try {
+ return fn();
+ } finally {
+ var diff = dom.window.performance.now() - start;
+ stat.count++;
+ stat.total += diff;
+ if (diff < stat.min) stat.min = diff;
+ if (diff > stat.max) stat.max = diff;
+ stat.avg = (stat.avg * (stat.count - 1) + diff) / stat.count;
+ _insertIntoTop20(stat.top, diff);
+ //_depth--;
+ }
+}
+
+String _prefix(int depth, String str) {
+ if (depth > 0) {
+ return ' - ' + _prefix(depth - 1, str);
+ }
+ return str;
+}
+
+_insertIntoTop20(List top, double val) {
+ int i = 0;
+ for (; i < top.length; i++) {
+ if (top[i] >= val) {
+ break;
+ }
+ }
+ top.insert(i, val);
+ while (top.length > 10) {
+ top.removeAt(0);
+ }
+}
+
+clearTimerStats() {
+ stats.clear();
+}
+
+dumpTimerStats() {
+ print('------------------------');
+ double total = 0.0;
+ stats.forEach((desc, stat) {
+ print('$desc; count: ${stat.count} total:${stat.total} avg:${stat.avg} min:${stat.min} max:${stat.max} top:${stat.top.map((v) => v.round()).join(', ')}');
+ total += stat.total;
+ });
+ print('------------------------');
+}
+
diff --git a/lib/block.dart b/lib/block.dart
index b70c1b4dc..d5cc263bb 100644
--- a/lib/block.dart
+++ b/lib/block.dart
@@ -128,7 +128,9 @@ class Block implements ElementWrapper {
nodeModule.factory(BlockFactory, blockFactory);
nodeModule.factory(BoundBlockFactory, boundBlockFactory);
var nodeInjector = parentInjector.createChild([nodeModule]);
- directiveRefs.forEach((ref) => nodeInjector.get(ref.directive.type));
+ time('Block nodeInjector.get', () {
+ directiveRefs.forEach((ref) => nodeInjector.get(ref.directive.type));
+ });
return nodeInjector;
}
@@ -493,10 +495,12 @@ class BlockFactory {
}
Block call(Injector injector, [List elements]) {
- if (elements == null) {
- elements = cloneElements(templateElements);
- }
- return new Block(injector, elements, directivePositions);
+ return time('BlockFactory', () {
+ if (elements == null) {
+ elements = cloneElements(templateElements);
+ }
+ return new Block(injector, elements, directivePositions);
+ });
}
BoundBlockFactory bind(Injector injector) {
diff --git a/lib/compiler.dart b/lib/compiler.dart
index 9b77274db..99077bdf1 100644
--- a/lib/compiler.dart
+++ b/lib/compiler.dart
@@ -122,11 +122,13 @@ class Compiler {
BlockFactory call(List elements) {
List domElements = elements;
List templateElements = cloneElements(domElements);
- var directivePositions = _compileBlock(
- new NodeCursor(domElements), new NodeCursor(templateElements),
- null);
-
- return new BlockFactory(templateElements,
- directivePositions == null ? [] : directivePositions);
+ return time('compiler', () {
+ var directivePositions = _compileBlock(
+ new NodeCursor(domElements), new NodeCursor(templateElements),
+ null);
+
+ return new BlockFactory(templateElements,
+ directivePositions == null ? [] : directivePositions);
+ });
}
}
diff --git a/lib/directives/ng_repeat.dart b/lib/directives/ng_repeat.dart
index 03cb354e9..516a43373 100644
--- a/lib/directives/ng_repeat.dart
+++ b/lib/directives/ng_repeat.dart
@@ -49,95 +49,97 @@ class NgRepeatAttrDirective {
keyIdentifier = match.group(2);
scope.$watchCollection(listExpr, (collection) {
- var previousNode = blockHole.elements[0], // current position of the node
- nextNode,
- arrayLength,
- childScope,
- trackById,
- newRowOrder = [],
- cursor = blockHole;
- // Same as lastBlockMap but it has the current state. It will become the
- // lastBlockMap on the next iteration.
- Map newRows = new Map();
- arrayLength = collection.length;
- // locate existing items
- var length = newRowOrder.length = collection.length;
- for(var index = 0; index < length; index++) {
- var value = collection[index];
- trackById = trackByIdFn(index, value, index);
- if(lastRows.containsKey(trackById)) {
- var row = lastRows[trackById];
- lastRows.remove(trackById);
- newRows[trackById] = row;
- newRowOrder[index] = row;
- } else if (newRows.containsKey(trackById)) {
- // restore lastBlockMap
- newRowOrder.forEach((row) {
- if (row != null && row.startNode != null) {
- lastRows[row.id] = row;
- }
- });
- // This is a duplicate and we need to throw an error
- throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $expression, Duplicate key: $trackById";
- } else {
- // new never before seen row
- newRowOrder[index] = new Row(trackById);
- newRows[trackById] = null;
+ time('NgRepeat.scope.\$watchCollection', () {
+ var previousNode = blockHole.elements[0], // current position of the node
+ nextNode,
+ arrayLength,
+ childScope,
+ trackById,
+ newRowOrder = [],
+ cursor = blockHole;
+ // Same as lastBlockMap but it has the current state. It will become the
+ // lastBlockMap on the next iteration.
+ Map newRows = new Map();
+ arrayLength = collection.length;
+ // locate existing items
+ var length = newRowOrder.length = collection.length;
+ for(var index = 0; index < length; index++) {
+ var value = collection[index];
+ trackById = trackByIdFn(index, value, index);
+ if(lastRows.containsKey(trackById)) {
+ var row = lastRows[trackById];
+ lastRows.remove(trackById);
+ newRows[trackById] = row;
+ newRowOrder[index] = row;
+ } else if (newRows.containsKey(trackById)) {
+ // restore lastBlockMap
+ newRowOrder.forEach((row) {
+ if (row != null && row.startNode != null) {
+ lastRows[row.id] = row;
+ }
+ });
+ // This is a duplicate and we need to throw an error
+ throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $expression, Duplicate key: $trackById";
+ } else {
+ // new never before seen row
+ newRowOrder[index] = new Row(trackById);
+ newRows[trackById] = null;
+ }
}
- }
- // remove existing items
- lastRows.forEach((key, row){
- row.block.remove();
- row.scope.$destroy();
- });
+ // remove existing items
+ lastRows.forEach((key, row){
+ row.block.remove();
+ row.scope.$destroy();
+ });
- for (var index = 0, length = collection.length; index < length; index++) {
- var key = index;
- var value = collection[index];
- Row row = newRowOrder[index];
+ for (var index = 0, length = collection.length; index < length; index++) {
+ var key = index;
+ var value = collection[index];
+ Row row = newRowOrder[index];
- if (row.startNode != null) {
- // if we have already seen this object, then we need to reuse the
- // associated scope/element
- childScope = row.scope;
+ if (row.startNode != null) {
+ // if we have already seen this object, then we need to reuse the
+ // associated scope/element
+ childScope = row.scope;
- nextNode = previousNode;
- do {
- nextNode = nextNode.nextNode;
- } while(nextNode != null);
+ nextNode = previousNode;
+ do {
+ nextNode = nextNode.nextNode;
+ } while(nextNode != null);
- if (row.startNode == nextNode) {
- // do nothing
+ if (row.startNode == nextNode) {
+ // do nothing
+ } else {
+ // existing item which got moved
+ row.block.moveAfter(cursor);
+ }
+ previousNode = row.endNode;
} else {
- // existing item which got moved
- row.block.moveAfter(cursor);
+ // new item which we don't know about
+ childScope = scope.$new();
}
- previousNode = row.endNode;
- } else {
- // new item which we don't know about
- childScope = scope.$new();
- }
- childScope[valueIdentifier] = value;
- childScope.$index = index;
- childScope.$first = (index == 0);
- childScope.$last = (index == (arrayLength - 1));
- childScope.$middle = !(childScope.$first || childScope.$last);
+ childScope[valueIdentifier] = value;
+ childScope.$index = index;
+ childScope.$first = (index == 0);
+ childScope.$last = (index == (arrayLength - 1));
+ childScope.$middle = !(childScope.$first || childScope.$last);
- if (row.startNode == null) {
- newRows[row.id] = row;
- var block = boundBlockFactory(childScope);
- row.block = block;
- row.scope = childScope;
- row.elements = block.elements;
- row.startNode = row.elements[0];
- row.endNode = row.elements[row.elements.length - 1];
- block.insertAfter(cursor);
+ if (row.startNode == null) {
+ newRows[row.id] = row;
+ var block = boundBlockFactory(childScope);
+ row.block = block;
+ row.scope = childScope;
+ row.elements = block.elements;
+ row.startNode = row.elements[0];
+ row.endNode = row.elements[row.elements.length - 1];
+ block.insertAfter(cursor);
+ }
+ cursor = row.block;
}
- cursor = row.block;
- }
- lastRows = newRows;
+ lastRows = newRows;
+ });
});
}
}
diff --git a/lib/interface_typing.dart b/lib/interface_typing.dart
index 9471fad31..237503175 100644
--- a/lib/interface_typing.dart
+++ b/lib/interface_typing.dart
@@ -23,6 +23,48 @@ _isType(obj, type) {
return false;
}
+var OBJECT_MIRROR = fastReflectClass(Object);
+
+bool hasField(Type type, Symbol fieldSymbol) {
+ return _hasField(fastReflectClass(type), fieldSymbol);
+}
+
+bool _hasField(ClassMirror cm, Symbol fieldSymbol) {
+ if (cm.members.containsKey(fieldSymbol)) {
+ return true;
+ }
+ if (cm.superclass != null && cm.superclass != OBJECT_MIRROR && _hasField(cm.superclass, fieldSymbol)) {
+ return true;
+ }
+ /*
+ for (ClassMirror interface in cm.superinterfaces) {
+ if (_hasField(interface, fieldSymbol)) {
+ return true;
+ }
+ }*/
+ return false;
+}
+
+bool hasMethod(Type type, Symbol fieldSymbol) {
+ return _hasMethod(fastReflectClass(type), fieldSymbol);
+}
+
+bool _hasMethod(ClassMirror cm, Symbol fieldSymbol) {
+ if (cm.methods.containsKey(fieldSymbol)) {
+ return true;
+ }
+ if (cm.superclass != null && cm.superclass != OBJECT_MIRROR && _hasMethod(cm.superclass, fieldSymbol)) {
+ return true;
+ }
+ /*
+ for (ClassMirror interface in cm.superinterfaces) {
+ if (_hasMethod(interface, fieldSymbol)) {
+ return true;
+ }
+ }*/
+ return false;
+}
+
isInterface(obj, Type type) {
if (_isType(obj, type)) return true;
diff --git a/lib/parser.dart b/lib/parser.dart
index b355ac652..b9dd9cf7b 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -149,6 +149,26 @@ getterChild(value, childKey) {
}
}
+ if (useMirrorsInParser) {
+ if (hasMethod(value.runtimeType, new Symbol('[]')) &&
+ hasMethod(value.runtimeType, new Symbol('containsKey')) &&
+ value.containsKey(childKey)) {
+ return [true, value[childKey]];
+ } else {
+ InstanceMirror instanceMirror = reflect(value);
+ Symbol curSym = new Symbol(childKey);
+ if (hasMethod(value.runtimeType, curSym)) {
+ return [true, _relaxFnArgs(([a0, a1, a2, a3, a4, a5]) {
+ var args = stripTrailingNulls([a0, a1, a2, a3, a4, a5]);
+ return instanceMirror.invoke(curSym, args).reflectee;
+ })];
+ } else if (hasField(value.runtimeType, curSym)) {
+ return [true, instanceMirror.getField(curSym).reflectee];
+ }
+ }
+ return [false, null];
+ }
+
// TODO: replace with isInterface(value, Getter) when dart:mirrors
// can support mixins.
try {
diff --git a/lib/scope.dart b/lib/scope.dart
index dbc1c7ac7..6e86d7b9f 100644
--- a/lib/scope.dart
+++ b/lib/scope.dart
@@ -174,89 +174,91 @@ class Scope implements Map {
$digest() {
- var value, last,
- asyncQueue = _asyncQueue,
- length,
- dirty, _ttlLeft = _ttl,
- logIdx, logMsg;
- List> watchLog = [];
- List watchers;
- Watch watch;
- Scope next, current, target = this;
-
- _beginPhase('\$digest');
- try {
- do { // "while dirty" loop
- dirty = false;
- current = target;
- //asyncQueue = current._asyncQueue;
- //dump('aQ: ${asyncQueue.length}');
-
- while(asyncQueue.length > 0) {
- try {
- current.$eval(asyncQueue.removeAt(0));
- } catch (e, s) {
- _exceptionHandler(e, s);
+ return time('Scope.\$digest', () {
+ var value, last,
+ asyncQueue = _asyncQueue,
+ length,
+ dirty, _ttlLeft = _ttl,
+ logIdx, logMsg;
+ List> watchLog = [];
+ List watchers;
+ Watch watch;
+ Scope next, current, target = this;
+
+ _beginPhase('\$digest');
+ try {
+ do { // "while dirty" loop
+ dirty = false;
+ current = target;
+ //asyncQueue = current._asyncQueue;
+ //dump('aQ: ${asyncQueue.length}');
+
+ while(asyncQueue.length > 0) {
+ try {
+ current.$eval(asyncQueue.removeAt(0));
+ } catch (e, s) {
+ _exceptionHandler(e, s);
+ }
}
- }
- do { // "traverse the scopes" loop
- if ((watchers = current._watchers) != null) {
- // process our watches
- length = watchers.length;
- while (length-- > 0) {
- try {
- watch = watchers[length];
- if ((value = watch.get(current)) != (last = watch.last) &&
- !(value is num && last is num && value.isNaN && last.isNaN)) {
- dirty = true;
- watch.last = value;
- watch.fn(value, ((last == initWatchVal) ? value : last), current);
- if (_ttlLeft < 5) {
- logIdx = 4 - _ttlLeft;
- while (watchLog.length <= logIdx) {
- watchLog.add([]);
+ do { // "traverse the scopes" loop
+ if ((watchers = current._watchers) != null) {
+ // process our watches
+ length = watchers.length;
+ while (length-- > 0) {
+ try {
+ watch = watchers[length];
+ if ((value = watch.get(current)) != (last = watch.last) &&
+ !(value is num && last is num && value.isNaN && last.isNaN)) {
+ dirty = true;
+ watch.last = value;
+ watch.fn(value, ((last == initWatchVal) ? value : last), current);
+ if (_ttlLeft < 5) {
+ logIdx = 4 - _ttlLeft;
+ while (watchLog.length <= logIdx) {
+ watchLog.add([]);
+ }
+ logMsg = (watch.exp is Function)
+ ? 'fn: ' + (watch.exp.name || watch.exp.toString())
+ : watch.exp;
+ logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
+ watchLog[logIdx].add(logMsg);
}
- logMsg = (watch.exp is Function)
- ? 'fn: ' + (watch.exp.name || watch.exp.toString())
- : watch.exp;
- logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
- watchLog[logIdx].add(logMsg);
}
+ } catch (e, s) {
+ _exceptionHandler(e, s);
}
- } catch (e, s) {
- _exceptionHandler(e, s);
}
}
- }
- // 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 $broadcast
- if (current._childHead == null) {
- if (current == target) {
- next = null;
- } else {
- next = current._nextSibling;
- if (next == null) {
- while(current != target && (next = current._nextSibling) == null) {
- current = current.$parent;
+ // 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 $broadcast
+ if (current._childHead == null) {
+ if (current == target) {
+ next = null;
+ } else {
+ next = current._nextSibling;
+ if (next == null) {
+ while(current != target && (next = current._nextSibling) == null) {
+ current = current.$parent;
+ }
}
}
+ } else {
+ next = current._childHead;
}
- } else {
- next = current._childHead;
- }
- } while ((current = next) != null);
+ } while ((current = next) != null);
- if(dirty && (_ttlLeft--) == 0) {
- throw '$_ttl \$digest() iterations reached. Aborting!\n' +
- 'Watchers fired in the last 5 iterations: ${toJson(watchLog)}';
- }
- } while (dirty || asyncQueue.length > 0);
- } finally {
- _clearPhase();
- }
+ if(dirty && (_ttlLeft--) == 0) {
+ throw '$_ttl \$digest() iterations reached. Aborting!\n' +
+ 'Watchers fired in the last 5 iterations: ${toJson(watchLog)}';
+ }
+ } while (dirty || asyncQueue.length > 0);
+ } finally {
+ _clearPhase();
+ }
+ });
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 3b8f8515a..8f39773f8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,3 +7,5 @@ dependencies:
js: any
di: any
+dev_dependencies:
+ benchmark_harness: any
\ No newline at end of file