'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 $RootScopeProvider(){ this.$get = ['$injector', '$exceptionHandler', function( $injector, $exceptionHandler){ /** * @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.=} providers Map of service factory which need to be provided * for the current scope. Defaults to {@link angular.service}. * @param {Object.=} instanceCache Provides pre-instantiated services which should * append/override services provided by `providers`. This is handy when unit-testing and having * the need to override a default service. * @returns {Object} Newly created scope. * */ function Scope() { this.$id = nextUid(); this.$$phase = this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this.$destructor = noop; this['this'] = this.$root = this; this.$$asyncQueue = []; this.$$listeners = {}; } /** * @ngdoc property * @name angular.scope.$id * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for * debugging. */ Scope.prototype = { /** * @ngdoc function * @name angular.scope.$new * @function * * @description * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope * hierarchy using {@link angular.scope.$destroy $destroy()}. * * {@link angular.scope.$destroy $destroy()} must be called on a scope when it is desired for * the scope and its child scopes to be permanently detached from the parent and thus stop * participating in model change detection and listener notification by invoking. * * @param {function()=} Class Constructor function which the scope should be applied to the scope. * @param {...*} curryArguments Any additional arguments which are curried into the constructor. * See {@link guide/dev_guide.di dependency injection}. * @returns {Object} The newly created child scope. * */ $new: function(Class, curryArguments) { var Child = function() {}; // should be anonymous; This is so that when the minifier munges // the name it does not become random set of chars. These will then show up as class // name in the debugger. var child; Child.prototype = this; child = new Child(); child['this'] = child; child.$$listeners = {}; child.$parent = this; child.$id = nextUid(); child.$$asyncQueue = []; child.$$phase = child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; child.$$prevSibling = this.$$childTail; if (this.$$childHead) { this.$$childTail.$$nextSibling = child; this.$$childTail = child; } else { this.$$childHead = this.$$childTail = child; } // short circuit if we have no class if (Class) { // can't use forEach, we need speed! var ClassPrototype = Class.prototype; for(var key in ClassPrototype) { child[key] = bind(child, ClassPrototype[key]); } $injector.invoke(child, Class, curryArguments); } return child; }, /** * @ngdoc function * @name angular.scope.$watch * @function * * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. * * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and * should return the value which will be watched. (Since {@link angular.scope.$digest $digest()} * reruns when it detects changes the `watchExpression` can execute multiple times per * {@link angular.scope.$digest $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 equal. The inequality is determined according to * {@link angular.equals} function. To save the value of the object for later comparison * {@link angular.copy} function is used. It also means that watching complex options will * have adverse memory and performance implications. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This * is achieved by rerunning the watchers until no changes are detected. The rerun iteration * limit is 100 to prevent infinity loop deadlock. * * * If you want to be notified whenever {@link angular.scope.$digest $digest} is called, * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is * detected, be prepared for multiple calls to your listener.) * * * # Example
           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. * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener) { var scope = this, get = compileToFn(watchExp, 'watch'), listenFn = compileToFn(listener || noop, 'listener'), array = scope.$$watchers, watcher = { fn: listenFn, last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. get: get, exp: watchExp }; 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(watcher); return function() { angularArray.remove(array, watcher); }; }, /** * @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, watchers, asyncQueue, length, dirty, ttl = 100, next, current, target = this, watchLog = []; if (target.$$phase) { throw Error(target.$$phase + ' already in progress'); } do { dirty = false; current = target; do { current.$$phase = '$digest'; asyncQueue = current.$$asyncQueue; while(asyncQueue.length) { try { current.$eval(asyncQueue.shift()); } catch (e) { $exceptionHandler(e); } } if ((watchers = current.$$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(current)) !== (last = watch.last) && !equals(value, last)) { dirty = true; watch.last = copy(value); watch.fn(current, value, last); if (ttl < 5) { if (!watchLog[4-ttl]) watchLog[4-ttl] = []; if (isFunction(watch.exp)) { watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); } else { watchLog[4-ttl].push(watch.exp); } } } } catch (e) { $exceptionHandler(e); } } } current.$$phase = null; // 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 (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { while(current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next)); if(!(ttl--)) { throw Error('100 $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); } } while (dirty); }, /** * @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 destructing scope emits an `$destroy` {@link angular.scope.$emit event}. * * 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; this.$emit('$destroy'); 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; }, /** * @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); }, /** * @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); }, /** * @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) { $exceptionHandler(e); } finally { this.$root.$digest(); } }, /** * @ngdoc function * @name angular.scope.$on * @function * * @description * Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of * event life cycle. * * @param {string} name Event name to listen on. * @param {function(event)} listener Function to call when the event is emitted. * @returns {function()} Returns a deregistration function for this listener. * * The event listener function format is: `function(event)`. The `event` object passed into the * listener has the following attributes * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed. * - `currentScope` - {Scope}: the current scope which is handling the event. * - `name` - {string}: Name of the event. * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation * (available only for events that were `$emit`-ed). */ $on: function(name, listener) { var namedListeners = this.$$listeners[name]; if (!namedListeners) { this.$$listeners[name] = namedListeners = []; } namedListeners.push(listener); return function() { angularArray.remove(namedListeners, listener); }; }, /** * @ngdoc function * @name angular.scope.$emit * @function * * @description * Dispatches an event `name` upwards through the scope hierarchy notifying the * registered {@link angular.scope.$on} listeners. * * The event life cycle starts at the scope on which `$emit` was called. All * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. * Afterwards, the event traverses upwards toward the root scope and calls all registered * listeners along the way. The event will stop propagating if one of the listeners cancels it. * * Any exception emmited from the {@link angular.scope.$on listeners} will be passed * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to emit. * @param {...*} args Optional set of arguments which will be passed onto the event listeners. */ $emit: function(name, args) { var empty = [], namedListeners, canceled = false, scope = this, event = { name: name, targetScope: scope, cancel: function() {canceled = true;} }, listenerArgs = concat([event], arguments, 1), i, length; do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; for (i=0, length=namedListeners.length; i