'use strict'; /** * DESIGN NOTES * * The design decisions behind the scope ware heavily favored for speed and memory consumption. * * The typical use of scope is to watch the expressions, which most of the time return the same * value as last time so we optimize the operation. * * Closures construction is expensive from speed as well as memory: * - no closures, instead ups prototypical inheritance for API * - Internal state needs to be stored on scope directly, which means that private state is * exposed as $$____ properties * * Loop operations are optimized by using while(count--) { ... } * - this means that in order to keep the same order of execution as addition we have to add * items to the array at the begging (shift) instead of at the end (push) * * Child scopes are created and removed often * - Using array would be slow since inserts in meddle are expensive so we use linked list * * There are few watches then a lot of observers. This is why you don't want the observer to be * implemented in the same way as watch. Watch requires return of initialization function which * are expensive to construct. */ function createScope(providers, instanceCache) { var scope = new Scope(); (scope.$service = createInjector(scope, providers, instanceCache)).eager(); return scope; } /** * @workInProgress * @ngdoc function * @name angular.scope * * @description * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes * are created using the {@link angular.scope.$new $new()} method. * (Most scopes are created automatically when compiled HTML template is executed.) * * Here is a simple scope snippet to show how you can interact with the scope. *
       var scope = angular.scope();
       scope.salutation = 'Hello';
       scope.name = 'World';
       expect(scope.greeting).toEqual(undefined);
       scope.$watch('name', function(){
         this.greeting = this.salutation + ' ' + this.name + '!';
       }); // initialize the watch
       expect(scope.greeting).toEqual(undefined);
       scope.name = 'Misko';
       // still old value, since watches have not been called yet
       expect(scope.greeting).toEqual(undefined);
       scope.$digest(); // fire all  the watches
       expect(scope.greeting).toEqual('Hello Misko!');
 * 
 *
 * # Inheritance
 * A scope can inherit from a parent scope, as in this example:
 * 
     var parent = angular.scope();
     var child = parent.$new();
     parent.salutation = "Hello";
     child.name = "World";
     expect(child.salutation).toEqual('Hello');
     child.salutation = "Welcome";
     expect(child.salutation).toEqual('Welcome');
     expect(parent.salutation).toEqual('Hello');
 * 
 *
 * # Dependency Injection
 * See {@link guide/dev_guide.di dependency injection}.
 *
 *
 * @param {Object.
       var scope = angular.scope();
       scope.name = 'misko';
       scope.counter = 0;
       expect(scope.counter).toEqual(0);
       scope.$watch('name', function(scope, newValue, oldValue) { counter = counter + 1; });
       expect(scope.counter).toEqual(0);
       scope.$digest();
       // no variable change
       expect(scope.counter).toEqual(0);
       scope.name = 'adam';
       scope.$digest();
       expect(scope.counter).toEqual(1);
     
   *
   *
   *
   * @param {(function()|string)} watchExpression Expression that is evaluated on each
   *    {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a
   *    call to the `listener`.
   *
   *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
   *    - `function(scope)`: called with current `scope` as a parameter.
   * @param {(function()|string)=} listener Callback called whenever the return value of
   *   the `watchExpression` changes.
   *
   *    - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
   *    - `function(scope, newValue, oldValue)`: called with current `scope` an previous and
   *       current values as parameters.
   */
  $watch: function(watchExp, listener) {
    var scope = this;
    var get = compileToFn(watchExp, 'watch');
    var listenFn = compileToFn(listener || noop, 'listener');
    var array = scope.$$watchers;
    if (!array) {
      array = scope.$$watchers = [];
    }
    // we use unshift since we use a while loop in $digest for speed.
    // the while loop reads in reverse order.
    array.unshift({
      fn: listenFn,
      last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.
      get: get
    });
  },
  /**
   * @workInProgress
   * @ngdoc function
   * @name angular.scope.$digest
   * @function
   *
   * @description
   * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children.
   * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the
   * `$digest()` keeps calling the {@link angular.scope.$watch watchers} until no more listeners are
   * firing. This means that it is possible to get into an infinite loop. This function will throw
   * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100.
   *
   * Usually you don't call `$digest()` directly in
   * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}.
   * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a
   * {@link angular.directive directive}) will force a `$digest()`.
   *
   * If you want to be notified whenever `$digest()` is called,
   * you can register a `watchExpression` function  with {@link angular.scope.$watch $watch()}
   * with no `listener`.
   *
   * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
   * life-cycle.
   *
   * # Example
     
       var scope = angular.scope();
       scope.name = 'misko';
       scope.counter = 0;
       expect(scope.counter).toEqual(0);
       scope.$digest('name', function(scope, newValue, oldValue) { counter = counter + 1; });
       expect(scope.counter).toEqual(0);
       scope.$digest();
       // no variable change
       expect(scope.counter).toEqual(0);
       scope.name = 'adam';
       scope.$digest();
       expect(scope.counter).toEqual(1);
     
   *
   */
  $digest: function() {
    var watch, value, last, next,
        watchers,
        asyncQueue,
        length,
        dirty, ttl = 100,
        scope;
    if (this.$$phase) {
      throw Error(this.$$phase + ' already in progress');
    }
    do {
      dirty = false;
      scope = this;
      do {
        scope.$$phase = '$digest';
        asyncQueue = scope.$$asyncQueue;
        while(asyncQueue.length) {
          try {
            scope.$eval(asyncQueue.shift());
          } catch (e) {
            scope.$service('$exceptionHandler')(e);
          }
        }
        if ((watchers = scope.$$watchers)) {
          // process our watches
          length = watchers.length;
          while (length--) {
            try {
              watch = watchers[length];
              // Most common watches are on primitives, in which case we can short
              // circuit it with === operator, only when === fails do we use .equals
              if ((value = watch.get(scope)) !== (last = watch.last) && !equals(value, last)) {
                dirty = true;
                watch.fn(scope, watch.last = copy(value), last);
              }
            } catch (e) {
              scope.$service('$exceptionHandler')(e);
            }
          }
        }
        scope.$$phase = null;
        // find the next scope in traversal.
        if (!(next = scope.$$childHead || scope.$$nextSibling) && scope !== this) {
          do {
            scope = scope.$parent;
            if (scope == this || (next = scope.$$nextSibling)) {
              break;
            }
          } while (scope !== this);
        }
      } while ((scope = next));
      if(!(ttl--)) {
        throw Error('100 $digest() iterations reached. Aborting!');
      }
    } while (dirty);
  },
  /**
   * @workInProgress
   * @ngdoc function
   * @name angular.scope.$destroy
   * @function
   *
   * @description
   * Remove the current scope (and all of its children) from the parent scope. Removal implies
   * that calls to {@link angular.scope.$digest $digest()} will no longer propagate to the current
   * scope and its children. Removal also implies that the current scope is eligible for garbage
   * collection.
   *
   * The `$destroy()` is usually used by directives such as
   * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop.
   *
   */
  $destroy: function() {
    if (this.$root == this) return; // we can't remove the root node;
    var parent = this.$parent;
    if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
    if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
    if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
    if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
  },
  /**
   * @workInProgress
   * @ngdoc function
   * @name angular.scope.$eval
   * @function
   *
   * @description
   * Executes the expression on the current scope returning the result. Any exceptions in the
   * expression are propagated (uncaught). This is useful when evaluating engular expressions.
   *
   * # Example
     
       var scope = angular.scope();
       scope.a = 1;
       scope.b = 2;
       expect(scope.$eval('a+b')).toEqual(3);
       expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
     
   *
   * @param {(string|function())=} expression An angular expression to be executed.
   *
   *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}.
   *    - `function(scope)`: execute the function with the current `scope` parameter.
   *
   * @returns {*} The result of evaluating the expression.
   */
  $eval: function(expr) {
    var fn = isString(expr)
      ? expressionCompile(expr)
      : expr || noop;
    return fn(this);
  },
  /**
   * @workInProgress
   * @ngdoc function
   * @name angular.scope.$evalAsync
   * @function
   *
   * @description
   * Executes the expression on the current scope at a later point in time.
   *
   * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
   *
   *   - it will execute in the current script execution context (before any DOM rendering).
   *   - at least one {@link angular.scope.$digest $digest cycle} will be performed after
   *     `expression` execution.
   *
   * Any exceptions from the execution of the expression are forwarded to the
   * {@link angular.service.$exceptionHandler $exceptionHandler} service.
   *
   * @param {(string|function())=} expression An angular expression to be executed.
   *
   *    - `string`: execute using the rules as defined in  {@link guide/dev_guide.expressions expression}.
   *    - `function(scope)`: execute the function with the current `scope` parameter.
   *
   */
  $evalAsync: function(expr) {
    this.$$asyncQueue.push(expr);
  },
  /**
   * @workInProgress
   * @ngdoc function
   * @name angular.scope.$apply
   * @function
   *
   * @description
   * `$apply()` is used to execute an expression in angular from outside of the angular framework.
   * (For example from browser DOM events, setTimeout, XHR or third party libraries).
   * Because we are calling into the angular framework we need to perform proper scope life-cycle
   * of {@link angular.service.$exceptionHandler exception handling},
   * {@link angular.scope.$digest executing watches}.
   *
   * ## Life cycle
   *
   * # Pseudo-Code of `$apply()`
      function $apply(expr) {
        try {
          return $eval(expr);
        } catch (e) {
          $exceptionHandler(e);
        } finally {
          $root.$digest();
        }
      }
   *
   *
   * Scope's `$apply()` method transitions through the following stages:
   *
   * 1. The {@link guide/dev_guide.expressions expression} is executed using the
   *    {@link angular.scope.$eval $eval()} method.
   * 2. Any exceptions from the execution of the expression are forwarded to the
   *    {@link angular.service.$exceptionHandler $exceptionHandler} service.
   * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression
   *    was executed using the {@link angular.scope.$digest $digest()} method.
   *
   *
   * @param {(string|function())=} exp An angular expression to be executed.
   *
   *    - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
   *    - `function(scope)`: execute the function with current `scope` parameter.
   *
   * @returns {*} The result of evaluating the expression.
   */
  $apply: function(expr) {
    try {
      return this.$eval(expr);
    } catch (e) {
      this.$service('$exceptionHandler')(e);
    } finally {
      this.$root.$digest();
    }
  }
};
function compileToFn(exp, name) {
  var fn = isString(exp)
    ? expressionCompile(exp)
    : exp;
  assertArgFn(fn, name);
  return fn;
}