| Index: third_party/pkg/angular/lib/core/scope.dart
|
| diff --git a/third_party/pkg/angular/lib/core/scope.dart b/third_party/pkg/angular/lib/core/scope.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..72637b8bf25d16e7127997a7de726ccb6fcd3678
|
| --- /dev/null
|
| +++ b/third_party/pkg/angular/lib/core/scope.dart
|
| @@ -0,0 +1,882 @@
|
| +part of angular.core;
|
| +
|
| +
|
| +/**
|
| + * Used by [Scope.$on] to notify the listeners of events.
|
| + */
|
| +class ScopeEvent {
|
| + String name;
|
| + Scope targetScope;
|
| + Scope currentScope;
|
| + bool propagationStopped = false;
|
| + bool defaultPrevented = false;
|
| +
|
| + ScopeEvent(this.name, this.targetScope);
|
| +
|
| + stopPropagation () => propagationStopped = true;
|
| + preventDefault() => defaultPrevented = true;
|
| +}
|
| +
|
| +/**
|
| + * Allows the configuration of [Scope.$digest] iteration maximum time-to-live
|
| + * value. Digest keeps checking the state of the watcher getters until it
|
| + * can execute one full iteration with no watchers triggering. TTL is used
|
| + * to prevent an infinite loop where watch A triggers watch B which in turn
|
| + * triggers watch A. If the system does not stabilize in TTL iteration then
|
| + * an digest is stop an an exception is thrown.
|
| + */
|
| +@NgInjectableService()
|
| +class ScopeDigestTTL {
|
| + final num ttl;
|
| + ScopeDigestTTL(): ttl = 5;
|
| + ScopeDigestTTL.value(num this.ttl);
|
| +}
|
| +
|
| +/**
|
| + * Scope has two responsibilities. 1) to keep track af watches and 2)
|
| + * to keep references to the model so that they are available for
|
| + * data-binding.
|
| + */
|
| +@proxy
|
| +@NgInjectableService()
|
| +class Scope implements Map {
|
| + final ExceptionHandler _exceptionHandler;
|
| + final Parser _parser;
|
| + final NgZone _zone;
|
| + final num _ttl;
|
| + final Map<String, Object> _properties = {};
|
| + final _WatchList _watchers = new _WatchList();
|
| + final Map<String, List<Function>> _listeners = {};
|
| + final bool _isolate;
|
| + final bool _lazy;
|
| + final Profiler _perf;
|
| + final Scope $parent;
|
| +
|
| + String $id;
|
| + Scope $root;
|
| + num _nextId = 0;
|
| + String _phase;
|
| + List _innerAsyncQueue;
|
| + List _outerAsyncQueue;
|
| + Scope _nextSibling, _prevSibling, _childHead, _childTail;
|
| + bool _skipAutoDigest = false;
|
| + bool _disabled = false;
|
| +
|
| + Scope(this._exceptionHandler, this._parser, ScopeDigestTTL ttl,
|
| + this._zone, this._perf):
|
| + $parent = null, _isolate = false, _lazy = false, _ttl = ttl.ttl {
|
| + _properties[r'this']= this;
|
| + $root = this;
|
| + $id = '_${$root._nextId++}';
|
| + _innerAsyncQueue = [];
|
| + _outerAsyncQueue = [];
|
| +
|
| + // Set up the zone to auto digest this scope.
|
| + _zone.onTurnDone = _autoDigestOnTurnDone;
|
| + _zone.onError = (e, s, ls) => _exceptionHandler(e, s);
|
| + }
|
| +
|
| + Scope._child(Scope parent, bool this._isolate, bool this._lazy, Profiler this._perf):
|
| + $parent = parent, _ttl = parent._ttl, _parser = parent._parser,
|
| + _exceptionHandler = parent._exceptionHandler, _zone = parent._zone {
|
| + _properties[r'this'] = this;
|
| + $root = $parent.$root;
|
| + $id = '_${$root._nextId++}';
|
| + _innerAsyncQueue = $parent._innerAsyncQueue;
|
| + _outerAsyncQueue = $parent._outerAsyncQueue;
|
| +
|
| + _prevSibling = $parent._childTail;
|
| + if ($parent._childHead != null) {
|
| + $parent._childTail._nextSibling = this;
|
| + $parent._childTail = this;
|
| + } else {
|
| + $parent._childHead = $parent._childTail = this;
|
| + }
|
| + }
|
| +
|
| + _autoDigestOnTurnDone() {
|
| + if (_skipAutoDigest) {
|
| + _skipAutoDigest = false;
|
| + } else {
|
| + $digest();
|
| + }
|
| + }
|
| +
|
| + _identical(a, b) =>
|
| + identical(a, b) ||
|
| + (a is String && b is String && a == b) ||
|
| + (a is num && b is num && a.isNaN && b.isNaN);
|
| +
|
| + containsKey(String name) => this[name] != null;
|
| + remove(String name) => this._properties.remove(name);
|
| + operator []=(String name, value) => _properties[name] = value;
|
| + operator [](String name) {
|
| + if (name == r'$id') return this.$id;
|
| + if (name == r'$parent') return this.$parent;
|
| + if (name == r'$root') return this.$root;
|
| + var scope = this;
|
| + do {
|
| + if (scope._properties.containsKey(name)) {
|
| + return scope._properties[name];
|
| + } else if (!scope._isolate) {
|
| + scope = scope.$parent;
|
| + } else {
|
| + return null;
|
| + }
|
| + } while(scope != null);
|
| + return null;
|
| + }
|
| +
|
| + noSuchMethod(Invocation invocation) {
|
| + var name = MirrorSystem.getName(invocation.memberName);
|
| + if (invocation.isGetter) {
|
| + return this[name];
|
| + } else if (invocation.isSetter) {
|
| + var value = invocation.positionalArguments[0];
|
| + name = name.substring(0, name.length - 1);
|
| + this[name] = value;
|
| + return value;
|
| + } else {
|
| + if (this[name] is Function) {
|
| + return this[name]();
|
| + } else {
|
| + super.noSuchMethod(invocation);
|
| + }
|
| + }
|
| + }
|
| +
|
| +
|
| + /**
|
| + * Create a new child [Scope].
|
| + *
|
| + * * [isolate] - If set to true the child scope does not inherit properties from the parent scope.
|
| + * This in essence creates an independent (isolated) view for the users of the scope.
|
| + * * [lazy] - If set to true the scope digest will only run if the scope is marked as [$dirty].
|
| + * This is usefull if we expect that the bindings in the scope are constant and there is no need
|
| + * to check them on each digest. The digest can be forced by marking it [$dirty].
|
| + */
|
| + $new({bool isolate: false, bool lazy: false}) =>
|
| + new Scope._child(this, isolate, lazy, _perf);
|
| +
|
| + /**
|
| + * *EXPERIMENTAL:* This feature is experimental. We reserve the right to change or delete it.
|
| + *
|
| + * A dissabled scope will not be part of the [$digest] cycle until it is re-enabled.
|
| + */
|
| + set $disabled(value) => this._disabled = value;
|
| + get $disabled => this._disabled;
|
| +
|
| + /**
|
| + * Registers a listener callback to be executed whenever the [watchExpression] changes.
|
| + *
|
| + * The watchExpression is called on every call to [$digest] and should return the value that
|
| + * will be watched. (Since [$digest] reruns when it detects changes the watchExpression can
|
| + * execute multiple times per [$digest] and should be idempotent.)
|
| + *
|
| + * The listener is called only when the value from the current [watchExpression] and the
|
| + * previous call to [watchExpression] are not identical (with the exception of the initial run,
|
| + * see below).
|
| + *
|
| + * The watch listener may change the model, which may trigger other listeners to fire. This is
|
| + * achieved by rerunning the watchers until no changes are detected. The rerun iteration limit
|
| + * is 10 to prevent an infinite loop deadlock.
|
| + * If you want to be notified whenever [$digest] is called, you can register a [watchExpression]
|
| + * function with no listener. (Since [watchExpression] can execute multiple times per [$digest]
|
| + * cycle when a change is detected, be prepared for multiple calls to your listener.)
|
| + *
|
| + * After a watcher is registered with the scope, the listener fn is called asynchronously
|
| + * (via [$evalAsync]) to initialize the watcher. In rare cases, this is undesirable because the
|
| + * listener is called when the result of [watchExpression] didn't change. To detect this
|
| + * scenario within the listener fn, you can compare the newVal and oldVal. If these two values
|
| + * are identical then the listener was called due to initialization.
|
| + *
|
| + * * [watchExpression] - can be any one of these: a [Function] - `(Scope scope) => ...;` or a
|
| + * [String] - `expression` which is compiled with [Parser] service into a function
|
| + * * [listener] - A [Function] `(currentValue, previousValue, Scope scope) => ...;`
|
| + * * [watchStr] - Used as a debbuging hint to easier identify which expression is associated with
|
| + * this watcher.
|
| + */
|
| + $watch(watchExpression, [Function listener, String watchStr]) {
|
| + if (watchStr == null) {
|
| + watchStr = watchExpression.toString();
|
| +
|
| + // Keep prod fast
|
| + assert((() {
|
| + watchStr = _source(watchExpression);
|
| + return true;
|
| + })());
|
| + }
|
| + var watcher = new _Watch(_compileToFn(listener), _initWatchVal,
|
| + _compileToFn(watchExpression), watchStr);
|
| + _watchers.addLast(watcher);
|
| + return () => _watchers.remove(watcher);
|
| + }
|
| +
|
| + /**
|
| + * A variant of [$watch] where it watches a collection of [watchExpressios]. If any
|
| + * one expression in the collection changes the [listener] is executed.
|
| + *
|
| + * * [watcherExpressions] - `List<String|(Scope scope){}>`
|
| + * * [Listener] - `(List newValues, List previousValues, Scope scope)`
|
| + */
|
| + $watchSet(List watchExpressions, [Function listener, String watchStr]) {
|
| + if (watchExpressions.length == 0) return () => null;
|
| +
|
| + var lastValues = new List(watchExpressions.length);
|
| + var currentValues = new List(watchExpressions.length);
|
| +
|
| + if (watchExpressions.length == 1) {
|
| + // Special case size of one.
|
| + return $watch(watchExpressions[0], (value, oldValue, scope) {
|
| + currentValues[0] = value;
|
| + lastValues[0] = oldValue;
|
| + listener(currentValues, lastValues, scope);
|
| + });
|
| + }
|
| + var deregesterFns = [];
|
| + var changeCount = 0;
|
| + for(var i = 0, ii = watchExpressions.length; i < ii; i++) {
|
| + deregesterFns.add($watch(watchExpressions[i], (value, oldValue, __) {
|
| + currentValues[i] = value;
|
| + lastValues[i] = oldValue;
|
| + changeCount++;
|
| + }));
|
| + }
|
| + deregesterFns.add($watch((s) => changeCount, (c, o, scope) {
|
| + listener(currentValues, lastValues, scope);
|
| + }));
|
| + return () {
|
| + for(var i = 0, ii = deregesterFns.length; i < ii; i++) {
|
| + deregesterFns[i]();
|
| + }
|
| + };
|
| + }
|
| +
|
| + /**
|
| + * Shallow watches the properties of an object and fires whenever any of the properties change
|
| + * (for arrays, this implies watching the array items; for object maps, this implies watching
|
| + * the properties). If a change is detected, the listener callback is fired.
|
| + *
|
| + * The obj collection is observed via standard [$watch] operation and is examined on every call
|
| + * to [$digest] to see if any items have been added, removed, or moved.
|
| + *
|
| + * The listener is called whenever anything within the obj has changed. Examples include
|
| + * adding, removing, and moving items belonging to an object or array.
|
| + */
|
| + $watchCollection(obj, listener, [String expression, bool shallow=false]) {
|
| + var oldValue;
|
| + var newValue;
|
| + int changeDetected = 0;
|
| + Function objGetter = _compileToFn(obj);
|
| + List internalArray = [];
|
| + Map internalMap = {};
|
| + int oldLength = 0;
|
| + int newLength;
|
| + var key;
|
| + List keysToRemove = [];
|
| + Function detectNewKeys = (key, value) {
|
| + newLength++;
|
| + if (oldValue.containsKey(key)) {
|
| + if (!_identical(oldValue[key], value)) {
|
| + changeDetected++;
|
| + oldValue[key] = value;
|
| + }
|
| + } else {
|
| + oldLength++;
|
| + oldValue[key] = value;
|
| + changeDetected++;
|
| + }
|
| + };
|
| + Function findMissingKeys = (key, _) {
|
| + if (!newValue.containsKey(key)) {
|
| + oldLength--;
|
| + keysToRemove.add(key);
|
| + }
|
| + };
|
| +
|
| + Function removeMissingKeys = (k) => oldValue.remove(k);
|
| +
|
| + var $watchCollectionWatch;
|
| +
|
| + if (shallow) {
|
| + $watchCollectionWatch = (_) {
|
| + newValue = objGetter(this);
|
| + newLength = newValue == null ? 0 : newValue.length;
|
| + if (newLength != oldLength) {
|
| + oldLength = newLength;
|
| + changeDetected++;
|
| + }
|
| + if (!identical(oldValue, newValue)) {
|
| + oldValue = newValue;
|
| + changeDetected++;
|
| + }
|
| + return changeDetected;
|
| + };
|
| + } else {
|
| + $watchCollectionWatch = (_) {
|
| + newValue = objGetter(this);
|
| +
|
| + if (newValue is! Map && newValue is! List) {
|
| + if (!_identical(oldValue, newValue)) {
|
| + oldValue = newValue;
|
| + changeDetected++;
|
| + }
|
| + } else if (newValue is Iterable) {
|
| + if (!_identical(oldValue, internalArray)) {
|
| + // we are transitioning from something which was not an array into array.
|
| + oldValue = internalArray;
|
| + oldLength = oldValue.length = 0;
|
| + changeDetected++;
|
| + }
|
| +
|
| + newLength = newValue.length;
|
| +
|
| + if (oldLength != newLength) {
|
| + // if lengths do not match we need to trigger change notification
|
| + changeDetected++;
|
| + oldValue.length = oldLength = newLength;
|
| + }
|
| + // copy the items to oldValue and look for changes.
|
| + for (var i = 0; i < newLength; i++) {
|
| + if (!_identical(oldValue[i], newValue.elementAt(i))) {
|
| + changeDetected++;
|
| + oldValue[i] = newValue.elementAt(i);
|
| + }
|
| + }
|
| + } else { // Map
|
| + if (!_identical(oldValue, internalMap)) {
|
| + // we are transitioning from something which was not an object into object.
|
| + oldValue = internalMap = {};
|
| + oldLength = 0;
|
| + changeDetected++;
|
| + }
|
| + // copy the items to oldValue and look for changes.
|
| + newLength = 0;
|
| + newValue.forEach(detectNewKeys);
|
| + if (oldLength > newLength) {
|
| + // we used to have more keys, need to find them and destroy them.
|
| + changeDetected++;
|
| + oldValue.forEach(findMissingKeys);
|
| + keysToRemove.forEach(removeMissingKeys);
|
| + keysToRemove.clear();
|
| + }
|
| + }
|
| + return changeDetected;
|
| + };
|
| + }
|
| +
|
| + var $watchCollectionAction = (_, __, ___) {
|
| + relaxFnApply(listener, [newValue, oldValue, this]);
|
| + };
|
| +
|
| + return this.$watch($watchCollectionWatch,
|
| + $watchCollectionAction,
|
| + expression == null ? obj : expression);
|
| + }
|
| +
|
| +
|
| + /**
|
| + * Add this function to your code if you want to add a $digest
|
| + * and want to assert that the digest will be called on this turn.
|
| + * This method will be deleted when we are comfortable with
|
| + * auto-digesting scope.
|
| + */
|
| + $$verifyDigestWillRun() {
|
| + assert(!_skipAutoDigest);
|
| + _zone.assertInTurn();
|
| + }
|
| +
|
| + /**
|
| + * *EXPERIMENTAL:* This feature is experimental. We reserve the right to change or delete it.
|
| + *
|
| + * Marks a scope as dirty. If the scope is lazy (see [$new]) then the scope will be included
|
| + * in the next [$digest].
|
| + *
|
| + * NOTE: This has no effect for non-lazy scopes.
|
| + */
|
| + $dirty() {
|
| + this._disabled = false;
|
| + }
|
| +
|
| + $digest() {
|
| + try {
|
| + _beginPhase('\$digest');
|
| + _digestWhileDirtyLoop();
|
| + } catch (e, s) {
|
| + _exceptionHandler(e, s);
|
| + } finally {
|
| + _clearPhase();
|
| + }
|
| + }
|
| +
|
| +
|
| + _digestWhileDirtyLoop() {
|
| + _digestHandleQueue('ng.innerAsync', _innerAsyncQueue);
|
| +
|
| + int timerId;
|
| + assert((timerId = _perf.startTimer('ng.dirty_check', 0)) != false);
|
| + _Watch lastDirtyWatch = _digestComputeLastDirty();
|
| + assert(_perf.stopTimer(timerId) != false);
|
| +
|
| + if (lastDirtyWatch == null) {
|
| + _digestHandleQueue('ng.outerAsync', _outerAsyncQueue);
|
| + return;
|
| + }
|
| +
|
| + List<List<String>> watchLog = [];
|
| + for (int iteration = 1, ttl = _ttl; iteration < ttl; iteration++) {
|
| + _Watch stopWatch = _digestHandleQueue('ng.innerAsync', _innerAsyncQueue)
|
| + ? null // Evaluating async work requires re-evaluating all watchers.
|
| + : lastDirtyWatch;
|
| + lastDirtyWatch = null;
|
| +
|
| + List<String> expressionLog;
|
| + if (ttl - iteration <= 3) {
|
| + expressionLog = <String>[];
|
| + watchLog.add(expressionLog);
|
| + }
|
| +
|
| + int timerId;
|
| + assert((timerId = _perf.startTimer('ng.dirty_check', iteration)) != false);
|
| + lastDirtyWatch = _digestComputeLastDirtyUntil(stopWatch, expressionLog);
|
| + assert(_perf.stopTimer(timerId) != false);
|
| +
|
| + if (lastDirtyWatch == null) {
|
| + _digestComputePerfCounters();
|
| + _digestHandleQueue('ng.outerAsync', _outerAsyncQueue);
|
| + return;
|
| + }
|
| + }
|
| +
|
| + // I've seen things you people wouldn't believe. Attack ships on fire
|
| + // off the shoulder of Orion. I've watched C-beams glitter in the dark
|
| + // near the Tannhauser Gate. All those moments will be lost in time,
|
| + // like tears in rain. Time to die.
|
| + throw '$_ttl \$digest() iterations reached. Aborting!\n'
|
| + 'Watchers fired in the last ${watchLog.length} iterations: '
|
| + '${_toJson(watchLog)}';
|
| + }
|
| +
|
| +
|
| + bool _digestHandleQueue(String timerName, List queue) {
|
| + if (queue.isEmpty) {
|
| + return false;
|
| + }
|
| + do {
|
| + var timerId;
|
| + try {
|
| + var workFn = queue.removeAt(0);
|
| + assert((timerId = _perf.startTimer(timerName, _source(workFn))) != false);
|
| + $root.$eval(workFn);
|
| + } catch (e, s) {
|
| + _exceptionHandler(e, s);
|
| + } finally {
|
| + assert(_perf.stopTimer(timerId) != false);
|
| + }
|
| + } while (queue.isNotEmpty);
|
| + return true;
|
| + }
|
| +
|
| +
|
| + _Watch _digestComputeLastDirty() {
|
| + int watcherCount = 0;
|
| + int scopeCount = 0;
|
| + Scope scope = this;
|
| + do {
|
| + _WatchList watchers = scope._watchers;
|
| + watcherCount += watchers.length;
|
| + scopeCount++;
|
| + for (_Watch watch = watchers.head; watch != null; watch = watch.next) {
|
| + var last = watch.last;
|
| + var value = watch.get(scope);
|
| + if (!_identical(value, last)) {
|
| + return _digestHandleDirty(scope, watch, last, value, null);
|
| + }
|
| + }
|
| + } while ((scope = _digestComputeNextScope(scope)) != null);
|
| + _digestUpdatePerfCounters(watcherCount, scopeCount);
|
| + return null;
|
| + }
|
| +
|
| +
|
| + _Watch _digestComputeLastDirtyUntil(_Watch stopWatch, List<String> log) {
|
| + int watcherCount = 0;
|
| + int scopeCount = 0;
|
| + Scope scope = this;
|
| + do {
|
| + _WatchList watchers = scope._watchers;
|
| + watcherCount += watchers.length;
|
| + scopeCount++;
|
| + for (_Watch watch = watchers.head; watch != null; watch = watch.next) {
|
| + if (identical(stopWatch, watch)) return null;
|
| + var last = watch.last;
|
| + var value = watch.get(scope);
|
| + if (!_identical(value, last)) {
|
| + return _digestHandleDirty(scope, watch, last, value, log);
|
| + }
|
| + }
|
| + } while ((scope = _digestComputeNextScope(scope)) != null);
|
| + return null;
|
| + }
|
| +
|
| +
|
| + _Watch _digestHandleDirty(Scope scope, _Watch watch, last, value, List<String> log) {
|
| + _Watch lastDirtyWatch;
|
| + while (true) {
|
| + if (!_identical(value, last)) {
|
| + lastDirtyWatch = watch;
|
| + if (log != null) log.add(watch.exp == null ? '[unknown]' : watch.exp);
|
| + watch.last = value;
|
| + var fireTimer;
|
| + assert((fireTimer = _perf.startTimer('ng.fire', watch.exp)) != false);
|
| + watch.fn(value, identical(_initWatchVal, last) ? value : last, scope);
|
| + assert(_perf.stopTimer(fireTimer) != false);
|
| + }
|
| + watch = watch.next;
|
| + while (watch == null) {
|
| + scope = _digestComputeNextScope(scope);
|
| + if (scope == null) return lastDirtyWatch;
|
| + watch = scope._watchers.head;
|
| + }
|
| + last = watch.last;
|
| + value = watch.get(scope);
|
| + }
|
| + }
|
| +
|
| +
|
| + Scope _digestComputeNextScope(Scope scope) {
|
| + // 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
|
| + Scope target = this;
|
| + Scope childHead = scope._childHead;
|
| + while (childHead != null && childHead._disabled) {
|
| + childHead = childHead._nextSibling;
|
| + }
|
| + if (childHead == null) {
|
| + if (scope == target) {
|
| + return null;
|
| + } else {
|
| + Scope next = scope._nextSibling;
|
| + if (next == null) {
|
| + while (scope != target && (next = scope._nextSibling) == null) {
|
| + scope = scope.$parent;
|
| + }
|
| + }
|
| + return next;
|
| + }
|
| + } else {
|
| + if (childHead._lazy) childHead._disabled = true;
|
| + return childHead;
|
| + }
|
| + }
|
| +
|
| +
|
| + void _digestComputePerfCounters() {
|
| + int watcherCount = 0, scopeCount = 0;
|
| + Scope scope = this;
|
| + do {
|
| + scopeCount++;
|
| + watcherCount += scope._watchers.length;
|
| + } while ((scope = _digestComputeNextScope(scope)) != null);
|
| + _digestUpdatePerfCounters(watcherCount, scopeCount);
|
| + }
|
| +
|
| +
|
| + void _digestUpdatePerfCounters(int watcherCount, int scopeCount) {
|
| + _perf.counters['ng.scope.watchers'] = watcherCount;
|
| + _perf.counters['ng.scopes'] = scopeCount;
|
| + }
|
| +
|
| +
|
| + $destroy() {
|
| + if ($root == this) return; // we can't remove the root node;
|
| +
|
| + $broadcast(r'$destroy');
|
| +
|
| + if ($parent._childHead == this) $parent._childHead = _nextSibling;
|
| + if ($parent._childTail == this) $parent._childTail = _prevSibling;
|
| + if (_prevSibling != null) _prevSibling._nextSibling = _nextSibling;
|
| + if (_nextSibling != null) _nextSibling._prevSibling = _prevSibling;
|
| + }
|
| +
|
| +
|
| + $eval(expr, [locals]) {
|
| + return relaxFnArgs(_compileToFn(expr))(locals == null ? this : new ScopeLocals(this, locals));
|
| + }
|
| +
|
| +
|
| + $evalAsync(expr, {outsideDigest: false}) {
|
| + if (outsideDigest) {
|
| + _outerAsyncQueue.add(expr);
|
| + } else {
|
| + _innerAsyncQueue.add(expr);
|
| + }
|
| + }
|
| +
|
| +
|
| + /**
|
| + * Skip running a $digest at the end of this turn.
|
| + * The primary use case is to skip the digest in the current VM turn because
|
| + * you just scheduled or are otherwise certain of an impending VM turn and the
|
| + * digest at the end of that turn is sufficient. You should be able to answer
|
| + * "No" to the question "Is there any other code that is aware that this VM
|
| + * turn occured and therefore expected a digest?". If your answer is "Yes",
|
| + * then you run the risk that the very next VM turn is not for your event and
|
| + * now that other code runs in that turn and sees stale values.
|
| + *
|
| + * You might call this function, for instance, from an event listener where,
|
| + * though the event occured, you need to wait for another event before you can
|
| + * perform something meaningful. You might schedule that other event,
|
| + * set a flag for the handler of the other event to recognize, etc. and then
|
| + * call this method to skip the digest this cycle. Note that you should call
|
| + * this function *after* you have successfully confirmed that the expected VM
|
| + * turn will occur (perhaps by scheduling it) to ensure that the digest
|
| + * actually does take place on that turn.
|
| + */
|
| + $skipAutoDigest() {
|
| + _zone.assertInTurn();
|
| + _skipAutoDigest = true;
|
| + }
|
| +
|
| +
|
| + $apply([expr]) {
|
| + return _zone.run(() {
|
| + var timerId;
|
| + try {
|
| + assert((timerId = _perf.startTimer('ng.\$apply', _source(expr))) != false);
|
| + return $eval(expr);
|
| + } catch (e, s) {
|
| + _exceptionHandler(e, s);
|
| + } finally {
|
| + assert(_perf.stopTimer(timerId) != false);
|
| + }
|
| + });
|
| + }
|
| +
|
| +
|
| + $on(name, listener) {
|
| + var namedListeners = _listeners[name];
|
| + if (!_listeners.containsKey(name)) {
|
| + _listeners[name] = namedListeners = [];
|
| + }
|
| + namedListeners.add(listener);
|
| +
|
| + return () {
|
| + namedListeners.remove(listener);
|
| + };
|
| + }
|
| +
|
| +
|
| + $emit(name, [List args]) {
|
| + var empty = [],
|
| + namedListeners,
|
| + scope = this,
|
| + event = new ScopeEvent(name, this),
|
| + listenerArgs = [event],
|
| + i;
|
| +
|
| + if (args != null) {
|
| + listenerArgs.addAll(args);
|
| + }
|
| +
|
| + do {
|
| + namedListeners = scope._listeners[name];
|
| + if (namedListeners != null) {
|
| + event.currentScope = scope;
|
| + i = 0;
|
| + for (var length = namedListeners.length; i<length; i++) {
|
| + try {
|
| + relaxFnApply(namedListeners[i], listenerArgs);
|
| + if (event.propagationStopped) return event;
|
| + } catch (e, s) {
|
| + _exceptionHandler(e, s);
|
| + }
|
| + }
|
| + }
|
| + //traverse upwards
|
| + scope = scope.$parent;
|
| + } while (scope != null);
|
| +
|
| + return event;
|
| + }
|
| +
|
| +
|
| + $broadcast(String name, [List listenerArgs]) {
|
| + var target = this,
|
| + current = target,
|
| + next = target,
|
| + event = new ScopeEvent(name, this);
|
| +
|
| + //down while you can, then up and next sibling or up and next sibling until back at root
|
| + if (listenerArgs == null) {
|
| + listenerArgs = [];
|
| + }
|
| + listenerArgs.insert(0, event);
|
| + do {
|
| + current = next;
|
| + event.currentScope = current;
|
| + if (current._listeners.containsKey(name)) {
|
| + current._listeners[name].forEach((listener) {
|
| + try {
|
| + relaxFnApply(listener, listenerArgs);
|
| + } 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;
|
| + }
|
| + }
|
| + }
|
| + } else {
|
| + next = current._childHead;
|
| + }
|
| + } while ((current = next) != null);
|
| +
|
| + return event;
|
| + }
|
| +
|
| + _beginPhase(phase) {
|
| + if ($root._phase != null) {
|
| + // TODO(deboer): Remove the []s when dartbug.com/11999 is fixed.
|
| + throw ['${$root._phase} already in progress'];
|
| + }
|
| + assert(_perf.startTimer('ng.phase.${phase}') != false);
|
| +
|
| + $root._phase = phase;
|
| + }
|
| +
|
| + _clearPhase() {
|
| + assert(_perf.stopTimer('ng.phase.${$root._phase}') != false);
|
| + $root._phase = null;
|
| + }
|
| +
|
| + Function _compileToFn(exp) {
|
| + if (exp == null) {
|
| + return () => null;
|
| + } else if (exp is String) {
|
| + return _parser(exp).eval;
|
| + } else if (exp is Function) {
|
| + return exp;
|
| + } else {
|
| + throw 'Expecting String or Function';
|
| + }
|
| + }
|
| +}
|
| +
|
| +@proxy
|
| +class ScopeLocals implements Scope, Map {
|
| + static wrapper(dynamic scope, Map<String, Object> locals) => new ScopeLocals(scope, locals);
|
| +
|
| + dynamic _scope;
|
| + Map<String, Object> _locals;
|
| +
|
| + ScopeLocals(this._scope, this._locals);
|
| +
|
| + operator []=(String name, value) => _scope[name] = value;
|
| + operator [](String name) => (_locals.containsKey(name) ? _locals : _scope)[name];
|
| +
|
| + noSuchMethod(Invocation invocation) => mirror.reflect(_scope).delegate(invocation);
|
| +}
|
| +
|
| +class _InitWatchVal { const _InitWatchVal(); }
|
| +const _initWatchVal = const _InitWatchVal();
|
| +
|
| +class _Watch {
|
| + final Function fn;
|
| + final Function get;
|
| + final String exp;
|
| + var last;
|
| +
|
| + _Watch previous;
|
| + _Watch next;
|
| +
|
| + _Watch(fn, this.last, getFn, this.exp)
|
| + : this.fn = relaxFnArgs3(fn)
|
| + , this.get = relaxFnArgs1(getFn);
|
| +}
|
| +
|
| +class _WatchList {
|
| + int length = 0;
|
| + _Watch head;
|
| + _Watch tail;
|
| +
|
| + void addLast(_Watch watch) {
|
| + assert(watch.previous == null);
|
| + assert(watch.next == null);
|
| + if (tail == null) {
|
| + tail = head = watch;
|
| + } else {
|
| + watch.previous = tail;
|
| + tail.next = watch;
|
| + tail = watch;
|
| + }
|
| + length++;
|
| + }
|
| +
|
| + void remove(_Watch watch) {
|
| + if (watch == head) {
|
| + _Watch next = watch.next;
|
| + if (next == null) tail = null;
|
| + else next.previous = null;
|
| + head = next;
|
| + } else if (watch == tail) {
|
| + _Watch previous = watch.previous;
|
| + previous.next = null;
|
| + tail = previous;
|
| + } else {
|
| + _Watch next = watch.next;
|
| + _Watch previous = watch.previous;
|
| + previous.next = next;
|
| + next.previous = previous;
|
| + }
|
| + length--;
|
| + }
|
| +}
|
| +
|
| +_toJson(obj) {
|
| + try {
|
| + return JSON.encode(obj);
|
| + } catch(e) {
|
| + var ret = "NOT-JSONABLE";
|
| + // Keep prod fast.
|
| + assert((() {
|
| + var mirror = reflect(obj);
|
| + if (mirror is ClosureMirror) {
|
| + // work-around dartbug.com/14130
|
| + try {
|
| + ret = mirror.function.source;
|
| + } on NoSuchMethodError catch (e) {}
|
| + }
|
| + return true;
|
| + })());
|
| + return ret;
|
| + }
|
| +}
|
| +
|
| +String _source(obj) {
|
| + if (obj is Function) {
|
| + var m = reflect(obj);
|
| + if (m is ClosureMirror) {
|
| + // work-around dartbug.com/14130
|
| + try {
|
| + return "FN: ${m.function.source}";
|
| + } on NoSuchMethodError catch (e) {
|
| + } on UnimplementedError catch (e) {
|
| + }
|
| + }
|
| + }
|
| + return '$obj';
|
| +}
|
|
|