Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(423)

Side by Side Diff: third_party/pkg/angular/lib/core/scope.dart

Issue 124053002: Adding Angular and dependent packages for testing (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 part of angular.core;
2
3
4 /**
5 * Used by [Scope.$on] to notify the listeners of events.
6 */
7 class ScopeEvent {
8 String name;
9 Scope targetScope;
10 Scope currentScope;
11 bool propagationStopped = false;
12 bool defaultPrevented = false;
13
14 ScopeEvent(this.name, this.targetScope);
15
16 stopPropagation () => propagationStopped = true;
17 preventDefault() => defaultPrevented = true;
18 }
19
20 /**
21 * Allows the configuration of [Scope.$digest] iteration maximum time-to-live
22 * value. Digest keeps checking the state of the watcher getters until it
23 * can execute one full iteration with no watchers triggering. TTL is used
24 * to prevent an infinite loop where watch A triggers watch B which in turn
25 * triggers watch A. If the system does not stabilize in TTL iteration then
26 * an digest is stop an an exception is thrown.
27 */
28 @NgInjectableService()
29 class ScopeDigestTTL {
30 final num ttl;
31 ScopeDigestTTL(): ttl = 5;
32 ScopeDigestTTL.value(num this.ttl);
33 }
34
35 /**
36 * Scope has two responsibilities. 1) to keep track af watches and 2)
37 * to keep references to the model so that they are available for
38 * data-binding.
39 */
40 @proxy
41 @NgInjectableService()
42 class Scope implements Map {
43 final ExceptionHandler _exceptionHandler;
44 final Parser _parser;
45 final NgZone _zone;
46 final num _ttl;
47 final Map<String, Object> _properties = {};
48 final _WatchList _watchers = new _WatchList();
49 final Map<String, List<Function>> _listeners = {};
50 final bool _isolate;
51 final bool _lazy;
52 final Profiler _perf;
53 final Scope $parent;
54
55 String $id;
56 Scope $root;
57 num _nextId = 0;
58 String _phase;
59 List _innerAsyncQueue;
60 List _outerAsyncQueue;
61 Scope _nextSibling, _prevSibling, _childHead, _childTail;
62 bool _skipAutoDigest = false;
63 bool _disabled = false;
64
65 Scope(this._exceptionHandler, this._parser, ScopeDigestTTL ttl,
66 this._zone, this._perf):
67 $parent = null, _isolate = false, _lazy = false, _ttl = ttl.ttl {
68 _properties[r'this']= this;
69 $root = this;
70 $id = '_${$root._nextId++}';
71 _innerAsyncQueue = [];
72 _outerAsyncQueue = [];
73
74 // Set up the zone to auto digest this scope.
75 _zone.onTurnDone = _autoDigestOnTurnDone;
76 _zone.onError = (e, s, ls) => _exceptionHandler(e, s);
77 }
78
79 Scope._child(Scope parent, bool this._isolate, bool this._lazy, Profiler this. _perf):
80 $parent = parent, _ttl = parent._ttl, _parser = parent._parser,
81 _exceptionHandler = parent._exceptionHandler, _zone = parent._zone {
82 _properties[r'this'] = this;
83 $root = $parent.$root;
84 $id = '_${$root._nextId++}';
85 _innerAsyncQueue = $parent._innerAsyncQueue;
86 _outerAsyncQueue = $parent._outerAsyncQueue;
87
88 _prevSibling = $parent._childTail;
89 if ($parent._childHead != null) {
90 $parent._childTail._nextSibling = this;
91 $parent._childTail = this;
92 } else {
93 $parent._childHead = $parent._childTail = this;
94 }
95 }
96
97 _autoDigestOnTurnDone() {
98 if (_skipAutoDigest) {
99 _skipAutoDigest = false;
100 } else {
101 $digest();
102 }
103 }
104
105 _identical(a, b) =>
106 identical(a, b) ||
107 (a is String && b is String && a == b) ||
108 (a is num && b is num && a.isNaN && b.isNaN);
109
110 containsKey(String name) => this[name] != null;
111 remove(String name) => this._properties.remove(name);
112 operator []=(String name, value) => _properties[name] = value;
113 operator [](String name) {
114 if (name == r'$id') return this.$id;
115 if (name == r'$parent') return this.$parent;
116 if (name == r'$root') return this.$root;
117 var scope = this;
118 do {
119 if (scope._properties.containsKey(name)) {
120 return scope._properties[name];
121 } else if (!scope._isolate) {
122 scope = scope.$parent;
123 } else {
124 return null;
125 }
126 } while(scope != null);
127 return null;
128 }
129
130 noSuchMethod(Invocation invocation) {
131 var name = MirrorSystem.getName(invocation.memberName);
132 if (invocation.isGetter) {
133 return this[name];
134 } else if (invocation.isSetter) {
135 var value = invocation.positionalArguments[0];
136 name = name.substring(0, name.length - 1);
137 this[name] = value;
138 return value;
139 } else {
140 if (this[name] is Function) {
141 return this[name]();
142 } else {
143 super.noSuchMethod(invocation);
144 }
145 }
146 }
147
148
149 /**
150 * Create a new child [Scope].
151 *
152 * * [isolate] - If set to true the child scope does not inherit properties fr om the parent scope.
153 * This in essence creates an independent (isolated) view for the users of t he scope.
154 * * [lazy] - If set to true the scope digest will only run if the scope is ma rked as [$dirty].
155 * This is usefull if we expect that the bindings in the scope are constant and there is no need
156 * to check them on each digest. The digest can be forced by marking it [$di rty].
157 */
158 $new({bool isolate: false, bool lazy: false}) =>
159 new Scope._child(this, isolate, lazy, _perf);
160
161 /**
162 * *EXPERIMENTAL:* This feature is experimental. We reserve the right to chang e or delete it.
163 *
164 * A dissabled scope will not be part of the [$digest] cycle until it is re-en abled.
165 */
166 set $disabled(value) => this._disabled = value;
167 get $disabled => this._disabled;
168
169 /**
170 * Registers a listener callback to be executed whenever the [watchExpression] changes.
171 *
172 * The watchExpression is called on every call to [$digest] and should return the value that
173 * will be watched. (Since [$digest] reruns when it detects changes the watchE xpression can
174 * execute multiple times per [$digest] and should be idempotent.)
175 *
176 * The listener is called only when the value from the current [watchExpressio n] and the
177 * previous call to [watchExpression] are not identical (with the exception of the initial run,
178 * see below).
179 *
180 * The watch listener may change the model, which may trigger other listeners to fire. This is
181 * achieved by rerunning the watchers until no changes are detected. The rerun iteration limit
182 * is 10 to prevent an infinite loop deadlock.
183 * If you want to be notified whenever [$digest] is called, you can register a [watchExpression]
184 * function with no listener. (Since [watchExpression] can execute multiple ti mes per [$digest]
185 * cycle when a change is detected, be prepared for multiple calls to your lis tener.)
186 *
187 * After a watcher is registered with the scope, the listener fn is called asy nchronously
188 * (via [$evalAsync]) to initialize the watcher. In rare cases, this is undesi rable because the
189 * listener is called when the result of [watchExpression] didn't change. To d etect this
190 * scenario within the listener fn, you can compare the newVal and oldVal. If these two values
191 * are identical then the listener was called due to initialization.
192 *
193 * * [watchExpression] - can be any one of these: a [Function] - `(Scope scope ) => ...;` or a
194 * [String] - `expression` which is compiled with [Parser] service into a f unction
195 * * [listener] - A [Function] `(currentValue, previousValue, Scope scope) => ...;`
196 * * [watchStr] - Used as a debbuging hint to easier identify which expression is associated with
197 * this watcher.
198 */
199 $watch(watchExpression, [Function listener, String watchStr]) {
200 if (watchStr == null) {
201 watchStr = watchExpression.toString();
202
203 // Keep prod fast
204 assert((() {
205 watchStr = _source(watchExpression);
206 return true;
207 })());
208 }
209 var watcher = new _Watch(_compileToFn(listener), _initWatchVal,
210 _compileToFn(watchExpression), watchStr);
211 _watchers.addLast(watcher);
212 return () => _watchers.remove(watcher);
213 }
214
215 /**
216 * A variant of [$watch] where it watches a collection of [watchExpressios]. I f any
217 * one expression in the collection changes the [listener] is executed.
218 *
219 * * [watcherExpressions] - `List<String|(Scope scope){}>`
220 * * [Listener] - `(List newValues, List previousValues, Scope scope)`
221 */
222 $watchSet(List watchExpressions, [Function listener, String watchStr]) {
223 if (watchExpressions.length == 0) return () => null;
224
225 var lastValues = new List(watchExpressions.length);
226 var currentValues = new List(watchExpressions.length);
227
228 if (watchExpressions.length == 1) {
229 // Special case size of one.
230 return $watch(watchExpressions[0], (value, oldValue, scope) {
231 currentValues[0] = value;
232 lastValues[0] = oldValue;
233 listener(currentValues, lastValues, scope);
234 });
235 }
236 var deregesterFns = [];
237 var changeCount = 0;
238 for(var i = 0, ii = watchExpressions.length; i < ii; i++) {
239 deregesterFns.add($watch(watchExpressions[i], (value, oldValue, __) {
240 currentValues[i] = value;
241 lastValues[i] = oldValue;
242 changeCount++;
243 }));
244 }
245 deregesterFns.add($watch((s) => changeCount, (c, o, scope) {
246 listener(currentValues, lastValues, scope);
247 }));
248 return () {
249 for(var i = 0, ii = deregesterFns.length; i < ii; i++) {
250 deregesterFns[i]();
251 }
252 };
253 }
254
255 /**
256 * Shallow watches the properties of an object and fires whenever any of the p roperties change
257 * (for arrays, this implies watching the array items; for object maps, this i mplies watching
258 * the properties). If a change is detected, the listener callback is fired.
259 *
260 * The obj collection is observed via standard [$watch] operation and is exam ined on every call
261 * to [$digest] to see if any items have been added, removed, or moved.
262 *
263 * The listener is called whenever anything within the obj has changed. Examp les include
264 * adding, removing, and moving items belonging to an object or array.
265 */
266 $watchCollection(obj, listener, [String expression, bool shallow=false]) {
267 var oldValue;
268 var newValue;
269 int changeDetected = 0;
270 Function objGetter = _compileToFn(obj);
271 List internalArray = [];
272 Map internalMap = {};
273 int oldLength = 0;
274 int newLength;
275 var key;
276 List keysToRemove = [];
277 Function detectNewKeys = (key, value) {
278 newLength++;
279 if (oldValue.containsKey(key)) {
280 if (!_identical(oldValue[key], value)) {
281 changeDetected++;
282 oldValue[key] = value;
283 }
284 } else {
285 oldLength++;
286 oldValue[key] = value;
287 changeDetected++;
288 }
289 };
290 Function findMissingKeys = (key, _) {
291 if (!newValue.containsKey(key)) {
292 oldLength--;
293 keysToRemove.add(key);
294 }
295 };
296
297 Function removeMissingKeys = (k) => oldValue.remove(k);
298
299 var $watchCollectionWatch;
300
301 if (shallow) {
302 $watchCollectionWatch = (_) {
303 newValue = objGetter(this);
304 newLength = newValue == null ? 0 : newValue.length;
305 if (newLength != oldLength) {
306 oldLength = newLength;
307 changeDetected++;
308 }
309 if (!identical(oldValue, newValue)) {
310 oldValue = newValue;
311 changeDetected++;
312 }
313 return changeDetected;
314 };
315 } else {
316 $watchCollectionWatch = (_) {
317 newValue = objGetter(this);
318
319 if (newValue is! Map && newValue is! List) {
320 if (!_identical(oldValue, newValue)) {
321 oldValue = newValue;
322 changeDetected++;
323 }
324 } else if (newValue is Iterable) {
325 if (!_identical(oldValue, internalArray)) {
326 // we are transitioning from something which was not an array into a rray.
327 oldValue = internalArray;
328 oldLength = oldValue.length = 0;
329 changeDetected++;
330 }
331
332 newLength = newValue.length;
333
334 if (oldLength != newLength) {
335 // if lengths do not match we need to trigger change notification
336 changeDetected++;
337 oldValue.length = oldLength = newLength;
338 }
339 // copy the items to oldValue and look for changes.
340 for (var i = 0; i < newLength; i++) {
341 if (!_identical(oldValue[i], newValue.elementAt(i))) {
342 changeDetected++;
343 oldValue[i] = newValue.elementAt(i);
344 }
345 }
346 } else { // Map
347 if (!_identical(oldValue, internalMap)) {
348 // we are transitioning from something which was not an object into object.
349 oldValue = internalMap = {};
350 oldLength = 0;
351 changeDetected++;
352 }
353 // copy the items to oldValue and look for changes.
354 newLength = 0;
355 newValue.forEach(detectNewKeys);
356 if (oldLength > newLength) {
357 // we used to have more keys, need to find them and destroy them.
358 changeDetected++;
359 oldValue.forEach(findMissingKeys);
360 keysToRemove.forEach(removeMissingKeys);
361 keysToRemove.clear();
362 }
363 }
364 return changeDetected;
365 };
366 }
367
368 var $watchCollectionAction = (_, __, ___) {
369 relaxFnApply(listener, [newValue, oldValue, this]);
370 };
371
372 return this.$watch($watchCollectionWatch,
373 $watchCollectionAction,
374 expression == null ? obj : expression);
375 }
376
377
378 /**
379 * Add this function to your code if you want to add a $digest
380 * and want to assert that the digest will be called on this turn.
381 * This method will be deleted when we are comfortable with
382 * auto-digesting scope.
383 */
384 $$verifyDigestWillRun() {
385 assert(!_skipAutoDigest);
386 _zone.assertInTurn();
387 }
388
389 /**
390 * *EXPERIMENTAL:* This feature is experimental. We reserve the right to chang e or delete it.
391 *
392 * Marks a scope as dirty. If the scope is lazy (see [$new]) then the scope wi ll be included
393 * in the next [$digest].
394 *
395 * NOTE: This has no effect for non-lazy scopes.
396 */
397 $dirty() {
398 this._disabled = false;
399 }
400
401 $digest() {
402 try {
403 _beginPhase('\$digest');
404 _digestWhileDirtyLoop();
405 } catch (e, s) {
406 _exceptionHandler(e, s);
407 } finally {
408 _clearPhase();
409 }
410 }
411
412
413 _digestWhileDirtyLoop() {
414 _digestHandleQueue('ng.innerAsync', _innerAsyncQueue);
415
416 int timerId;
417 assert((timerId = _perf.startTimer('ng.dirty_check', 0)) != false);
418 _Watch lastDirtyWatch = _digestComputeLastDirty();
419 assert(_perf.stopTimer(timerId) != false);
420
421 if (lastDirtyWatch == null) {
422 _digestHandleQueue('ng.outerAsync', _outerAsyncQueue);
423 return;
424 }
425
426 List<List<String>> watchLog = [];
427 for (int iteration = 1, ttl = _ttl; iteration < ttl; iteration++) {
428 _Watch stopWatch = _digestHandleQueue('ng.innerAsync', _innerAsyncQueue)
429 ? null // Evaluating async work requires re-evaluating all watchers.
430 : lastDirtyWatch;
431 lastDirtyWatch = null;
432
433 List<String> expressionLog;
434 if (ttl - iteration <= 3) {
435 expressionLog = <String>[];
436 watchLog.add(expressionLog);
437 }
438
439 int timerId;
440 assert((timerId = _perf.startTimer('ng.dirty_check', iteration)) != false) ;
441 lastDirtyWatch = _digestComputeLastDirtyUntil(stopWatch, expressionLog);
442 assert(_perf.stopTimer(timerId) != false);
443
444 if (lastDirtyWatch == null) {
445 _digestComputePerfCounters();
446 _digestHandleQueue('ng.outerAsync', _outerAsyncQueue);
447 return;
448 }
449 }
450
451 // I've seen things you people wouldn't believe. Attack ships on fire
452 // off the shoulder of Orion. I've watched C-beams glitter in the dark
453 // near the Tannhauser Gate. All those moments will be lost in time,
454 // like tears in rain. Time to die.
455 throw '$_ttl \$digest() iterations reached. Aborting!\n'
456 'Watchers fired in the last ${watchLog.length} iterations: '
457 '${_toJson(watchLog)}';
458 }
459
460
461 bool _digestHandleQueue(String timerName, List queue) {
462 if (queue.isEmpty) {
463 return false;
464 }
465 do {
466 var timerId;
467 try {
468 var workFn = queue.removeAt(0);
469 assert((timerId = _perf.startTimer(timerName, _source(workFn))) != false );
470 $root.$eval(workFn);
471 } catch (e, s) {
472 _exceptionHandler(e, s);
473 } finally {
474 assert(_perf.stopTimer(timerId) != false);
475 }
476 } while (queue.isNotEmpty);
477 return true;
478 }
479
480
481 _Watch _digestComputeLastDirty() {
482 int watcherCount = 0;
483 int scopeCount = 0;
484 Scope scope = this;
485 do {
486 _WatchList watchers = scope._watchers;
487 watcherCount += watchers.length;
488 scopeCount++;
489 for (_Watch watch = watchers.head; watch != null; watch = watch.next) {
490 var last = watch.last;
491 var value = watch.get(scope);
492 if (!_identical(value, last)) {
493 return _digestHandleDirty(scope, watch, last, value, null);
494 }
495 }
496 } while ((scope = _digestComputeNextScope(scope)) != null);
497 _digestUpdatePerfCounters(watcherCount, scopeCount);
498 return null;
499 }
500
501
502 _Watch _digestComputeLastDirtyUntil(_Watch stopWatch, List<String> log) {
503 int watcherCount = 0;
504 int scopeCount = 0;
505 Scope scope = this;
506 do {
507 _WatchList watchers = scope._watchers;
508 watcherCount += watchers.length;
509 scopeCount++;
510 for (_Watch watch = watchers.head; watch != null; watch = watch.next) {
511 if (identical(stopWatch, watch)) return null;
512 var last = watch.last;
513 var value = watch.get(scope);
514 if (!_identical(value, last)) {
515 return _digestHandleDirty(scope, watch, last, value, log);
516 }
517 }
518 } while ((scope = _digestComputeNextScope(scope)) != null);
519 return null;
520 }
521
522
523 _Watch _digestHandleDirty(Scope scope, _Watch watch, last, value, List<String> log) {
524 _Watch lastDirtyWatch;
525 while (true) {
526 if (!_identical(value, last)) {
527 lastDirtyWatch = watch;
528 if (log != null) log.add(watch.exp == null ? '[unknown]' : watch.exp);
529 watch.last = value;
530 var fireTimer;
531 assert((fireTimer = _perf.startTimer('ng.fire', watch.exp)) != false);
532 watch.fn(value, identical(_initWatchVal, last) ? value : last, scope);
533 assert(_perf.stopTimer(fireTimer) != false);
534 }
535 watch = watch.next;
536 while (watch == null) {
537 scope = _digestComputeNextScope(scope);
538 if (scope == null) return lastDirtyWatch;
539 watch = scope._watchers.head;
540 }
541 last = watch.last;
542 value = watch.get(scope);
543 }
544 }
545
546
547 Scope _digestComputeNextScope(Scope scope) {
548 // Insanity Warning: scope depth-first traversal
549 // yes, this code is a bit crazy, but it works and we have tests to prove it !
550 // this piece should be kept in sync with the traversal in $broadcast
551 Scope target = this;
552 Scope childHead = scope._childHead;
553 while (childHead != null && childHead._disabled) {
554 childHead = childHead._nextSibling;
555 }
556 if (childHead == null) {
557 if (scope == target) {
558 return null;
559 } else {
560 Scope next = scope._nextSibling;
561 if (next == null) {
562 while (scope != target && (next = scope._nextSibling) == null) {
563 scope = scope.$parent;
564 }
565 }
566 return next;
567 }
568 } else {
569 if (childHead._lazy) childHead._disabled = true;
570 return childHead;
571 }
572 }
573
574
575 void _digestComputePerfCounters() {
576 int watcherCount = 0, scopeCount = 0;
577 Scope scope = this;
578 do {
579 scopeCount++;
580 watcherCount += scope._watchers.length;
581 } while ((scope = _digestComputeNextScope(scope)) != null);
582 _digestUpdatePerfCounters(watcherCount, scopeCount);
583 }
584
585
586 void _digestUpdatePerfCounters(int watcherCount, int scopeCount) {
587 _perf.counters['ng.scope.watchers'] = watcherCount;
588 _perf.counters['ng.scopes'] = scopeCount;
589 }
590
591
592 $destroy() {
593 if ($root == this) return; // we can't remove the root node;
594
595 $broadcast(r'$destroy');
596
597 if ($parent._childHead == this) $parent._childHead = _nextSibling;
598 if ($parent._childTail == this) $parent._childTail = _prevSibling;
599 if (_prevSibling != null) _prevSibling._nextSibling = _nextSibling;
600 if (_nextSibling != null) _nextSibling._prevSibling = _prevSibling;
601 }
602
603
604 $eval(expr, [locals]) {
605 return relaxFnArgs(_compileToFn(expr))(locals == null ? this : new ScopeLoca ls(this, locals));
606 }
607
608
609 $evalAsync(expr, {outsideDigest: false}) {
610 if (outsideDigest) {
611 _outerAsyncQueue.add(expr);
612 } else {
613 _innerAsyncQueue.add(expr);
614 }
615 }
616
617
618 /**
619 * Skip running a $digest at the end of this turn.
620 * The primary use case is to skip the digest in the current VM turn because
621 * you just scheduled or are otherwise certain of an impending VM turn and the
622 * digest at the end of that turn is sufficient. You should be able to answer
623 * "No" to the question "Is there any other code that is aware that this VM
624 * turn occured and therefore expected a digest?". If your answer is "Yes",
625 * then you run the risk that the very next VM turn is not for your event and
626 * now that other code runs in that turn and sees stale values.
627 *
628 * You might call this function, for instance, from an event listener where,
629 * though the event occured, you need to wait for another event before you can
630 * perform something meaningful. You might schedule that other event,
631 * set a flag for the handler of the other event to recognize, etc. and then
632 * call this method to skip the digest this cycle. Note that you should call
633 * this function *after* you have successfully confirmed that the expected VM
634 * turn will occur (perhaps by scheduling it) to ensure that the digest
635 * actually does take place on that turn.
636 */
637 $skipAutoDigest() {
638 _zone.assertInTurn();
639 _skipAutoDigest = true;
640 }
641
642
643 $apply([expr]) {
644 return _zone.run(() {
645 var timerId;
646 try {
647 assert((timerId = _perf.startTimer('ng.\$apply', _source(expr))) != fals e);
648 return $eval(expr);
649 } catch (e, s) {
650 _exceptionHandler(e, s);
651 } finally {
652 assert(_perf.stopTimer(timerId) != false);
653 }
654 });
655 }
656
657
658 $on(name, listener) {
659 var namedListeners = _listeners[name];
660 if (!_listeners.containsKey(name)) {
661 _listeners[name] = namedListeners = [];
662 }
663 namedListeners.add(listener);
664
665 return () {
666 namedListeners.remove(listener);
667 };
668 }
669
670
671 $emit(name, [List args]) {
672 var empty = [],
673 namedListeners,
674 scope = this,
675 event = new ScopeEvent(name, this),
676 listenerArgs = [event],
677 i;
678
679 if (args != null) {
680 listenerArgs.addAll(args);
681 }
682
683 do {
684 namedListeners = scope._listeners[name];
685 if (namedListeners != null) {
686 event.currentScope = scope;
687 i = 0;
688 for (var length = namedListeners.length; i<length; i++) {
689 try {
690 relaxFnApply(namedListeners[i], listenerArgs);
691 if (event.propagationStopped) return event;
692 } catch (e, s) {
693 _exceptionHandler(e, s);
694 }
695 }
696 }
697 //traverse upwards
698 scope = scope.$parent;
699 } while (scope != null);
700
701 return event;
702 }
703
704
705 $broadcast(String name, [List listenerArgs]) {
706 var target = this,
707 current = target,
708 next = target,
709 event = new ScopeEvent(name, this);
710
711 //down while you can, then up and next sibling or up and next sibling until back at root
712 if (listenerArgs == null) {
713 listenerArgs = [];
714 }
715 listenerArgs.insert(0, event);
716 do {
717 current = next;
718 event.currentScope = current;
719 if (current._listeners.containsKey(name)) {
720 current._listeners[name].forEach((listener) {
721 try {
722 relaxFnApply(listener, listenerArgs);
723 } catch(e, s) {
724 _exceptionHandler(e, s);
725 }
726 });
727 }
728
729 // Insanity Warning: scope depth-first traversal
730 // yes, this code is a bit crazy, but it works and we have tests to prove it!
731 // this piece should be kept in sync with the traversal in $broadcast
732 if (current._childHead == null) {
733 if (current == target) {
734 next = null;
735 } else {
736 next = current._nextSibling;
737 if (next == null) {
738 while(current != target && (next = current._nextSibling) == null) {
739 current = current.$parent;
740 }
741 }
742 }
743 } else {
744 next = current._childHead;
745 }
746 } while ((current = next) != null);
747
748 return event;
749 }
750
751 _beginPhase(phase) {
752 if ($root._phase != null) {
753 // TODO(deboer): Remove the []s when dartbug.com/11999 is fixed.
754 throw ['${$root._phase} already in progress'];
755 }
756 assert(_perf.startTimer('ng.phase.${phase}') != false);
757
758 $root._phase = phase;
759 }
760
761 _clearPhase() {
762 assert(_perf.stopTimer('ng.phase.${$root._phase}') != false);
763 $root._phase = null;
764 }
765
766 Function _compileToFn(exp) {
767 if (exp == null) {
768 return () => null;
769 } else if (exp is String) {
770 return _parser(exp).eval;
771 } else if (exp is Function) {
772 return exp;
773 } else {
774 throw 'Expecting String or Function';
775 }
776 }
777 }
778
779 @proxy
780 class ScopeLocals implements Scope, Map {
781 static wrapper(dynamic scope, Map<String, Object> locals) => new ScopeLocals(s cope, locals);
782
783 dynamic _scope;
784 Map<String, Object> _locals;
785
786 ScopeLocals(this._scope, this._locals);
787
788 operator []=(String name, value) => _scope[name] = value;
789 operator [](String name) => (_locals.containsKey(name) ? _locals : _scope)[nam e];
790
791 noSuchMethod(Invocation invocation) => mirror.reflect(_scope).delegate(invocat ion);
792 }
793
794 class _InitWatchVal { const _InitWatchVal(); }
795 const _initWatchVal = const _InitWatchVal();
796
797 class _Watch {
798 final Function fn;
799 final Function get;
800 final String exp;
801 var last;
802
803 _Watch previous;
804 _Watch next;
805
806 _Watch(fn, this.last, getFn, this.exp)
807 : this.fn = relaxFnArgs3(fn)
808 , this.get = relaxFnArgs1(getFn);
809 }
810
811 class _WatchList {
812 int length = 0;
813 _Watch head;
814 _Watch tail;
815
816 void addLast(_Watch watch) {
817 assert(watch.previous == null);
818 assert(watch.next == null);
819 if (tail == null) {
820 tail = head = watch;
821 } else {
822 watch.previous = tail;
823 tail.next = watch;
824 tail = watch;
825 }
826 length++;
827 }
828
829 void remove(_Watch watch) {
830 if (watch == head) {
831 _Watch next = watch.next;
832 if (next == null) tail = null;
833 else next.previous = null;
834 head = next;
835 } else if (watch == tail) {
836 _Watch previous = watch.previous;
837 previous.next = null;
838 tail = previous;
839 } else {
840 _Watch next = watch.next;
841 _Watch previous = watch.previous;
842 previous.next = next;
843 next.previous = previous;
844 }
845 length--;
846 }
847 }
848
849 _toJson(obj) {
850 try {
851 return JSON.encode(obj);
852 } catch(e) {
853 var ret = "NOT-JSONABLE";
854 // Keep prod fast.
855 assert((() {
856 var mirror = reflect(obj);
857 if (mirror is ClosureMirror) {
858 // work-around dartbug.com/14130
859 try {
860 ret = mirror.function.source;
861 } on NoSuchMethodError catch (e) {}
862 }
863 return true;
864 })());
865 return ret;
866 }
867 }
868
869 String _source(obj) {
870 if (obj is Function) {
871 var m = reflect(obj);
872 if (m is ClosureMirror) {
873 // work-around dartbug.com/14130
874 try {
875 return "FN: ${m.function.source}";
876 } on NoSuchMethodError catch (e) {
877 } on UnimplementedError catch (e) {
878 }
879 }
880 }
881 return '$obj';
882 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698