angularjs-rails 1.2.25 → 1.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -72,7 +72,7 @@ function minErr(module, ErrorConstructor) {
72
72
  return match;
73
73
  });
74
74
 
75
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.3/' +
75
+ message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' +
76
76
  (module ? module + '/' : '') + code;
77
77
  for (i = 2; i < arguments.length; i++) {
78
78
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -53,9 +53,10 @@ angular.mock.$Browser = function() {
53
53
  self.onUrlChange = function(listener) {
54
54
  self.pollFns.push(
55
55
  function() {
56
- if (self.$$lastUrl != self.$$url) {
56
+ if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) {
57
57
  self.$$lastUrl = self.$$url;
58
- listener(self.$$url);
58
+ self.$$lastState = self.$$state;
59
+ listener(self.$$url, self.$$state);
59
60
  }
60
61
  }
61
62
  );
@@ -151,15 +152,24 @@ angular.mock.$Browser.prototype = {
151
152
  return pollFn;
152
153
  },
153
154
 
154
- url: function(url, replace) {
155
+ url: function(url, replace, state) {
156
+ if (angular.isUndefined(state)) {
157
+ state = null;
158
+ }
155
159
  if (url) {
156
160
  this.$$url = url;
161
+ // Native pushState serializes & copies the object; simulate it.
162
+ this.$$state = angular.copy(state);
157
163
  return this;
158
164
  }
159
165
 
160
166
  return this.$$url;
161
167
  },
162
168
 
169
+ state: function() {
170
+ return this.$$state;
171
+ },
172
+
163
173
  cookies: function(name, value) {
164
174
  if (name) {
165
175
  if (angular.isUndefined(value)) {
@@ -806,6 +816,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
806
816
  animate.queue.push({
807
817
  event : method,
808
818
  element : arguments[0],
819
+ options : arguments[arguments.length-1],
809
820
  args : arguments
810
821
  });
811
822
  return $delegate[method].apply($delegate, arguments);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -380,6 +380,10 @@ function $RouteProvider(){
380
380
  * defined in `resolve` route property. Once all of the dependencies are resolved
381
381
  * `$routeChangeSuccess` is fired.
382
382
  *
383
+ * The route change (and the `$location` change that triggered it) can be prevented
384
+ * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on}
385
+ * for more details about event object.
386
+ *
383
387
  * @param {Object} angularEvent Synthetic event object.
384
388
  * @param {Route} next Future route information.
385
389
  * @param {Route} current Current route information.
@@ -424,6 +428,8 @@ function $RouteProvider(){
424
428
  */
425
429
 
426
430
  var forceReload = false,
431
+ preparedRoute,
432
+ preparedRouteIsUpdateOnly,
427
433
  $route = {
428
434
  routes: routes,
429
435
 
@@ -440,7 +446,11 @@ function $RouteProvider(){
440
446
  */
441
447
  reload: function() {
442
448
  forceReload = true;
443
- $rootScope.$evalAsync(updateRoute);
449
+ $rootScope.$evalAsync(function() {
450
+ // Don't support cancellation of a reload for now...
451
+ prepareRoute();
452
+ commitRoute();
453
+ });
444
454
  },
445
455
 
446
456
  /**
@@ -474,7 +484,8 @@ function $RouteProvider(){
474
484
  }
475
485
  };
476
486
 
477
- $rootScope.$on('$locationChangeSuccess', updateRoute);
487
+ $rootScope.$on('$locationChangeStart', prepareRoute);
488
+ $rootScope.$on('$locationChangeSuccess', commitRoute);
478
489
 
479
490
  return $route;
480
491
 
@@ -512,36 +523,50 @@ function $RouteProvider(){
512
523
  return params;
513
524
  }
514
525
 
515
- function updateRoute() {
516
- var next = parseRoute(),
517
- last = $route.current;
518
-
519
- if (next && last && next.$$route === last.$$route
520
- && angular.equals(next.pathParams, last.pathParams)
521
- && !next.reloadOnSearch && !forceReload) {
522
- last.params = next.params;
523
- angular.copy(last.params, $routeParams);
524
- $rootScope.$broadcast('$routeUpdate', last);
525
- } else if (next || last) {
526
+ function prepareRoute($locationEvent) {
527
+ var lastRoute = $route.current;
528
+
529
+ preparedRoute = parseRoute();
530
+ preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route
531
+ && angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
532
+ && !preparedRoute.reloadOnSearch && !forceReload;
533
+
534
+ if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
535
+ if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
536
+ if ($locationEvent) {
537
+ $locationEvent.preventDefault();
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ function commitRoute() {
544
+ var lastRoute = $route.current;
545
+ var nextRoute = preparedRoute;
546
+
547
+ if (preparedRouteIsUpdateOnly) {
548
+ lastRoute.params = nextRoute.params;
549
+ angular.copy(lastRoute.params, $routeParams);
550
+ $rootScope.$broadcast('$routeUpdate', lastRoute);
551
+ } else if (nextRoute || lastRoute) {
526
552
  forceReload = false;
527
- $rootScope.$broadcast('$routeChangeStart', next, last);
528
- $route.current = next;
529
- if (next) {
530
- if (next.redirectTo) {
531
- if (angular.isString(next.redirectTo)) {
532
- $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
553
+ $route.current = nextRoute;
554
+ if (nextRoute) {
555
+ if (nextRoute.redirectTo) {
556
+ if (angular.isString(nextRoute.redirectTo)) {
557
+ $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params)
533
558
  .replace();
534
559
  } else {
535
- $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
560
+ $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search()))
536
561
  .replace();
537
562
  }
538
563
  }
539
564
  }
540
565
 
541
- $q.when(next).
566
+ $q.when(nextRoute).
542
567
  then(function() {
543
- if (next) {
544
- var locals = angular.extend({}, next.resolve),
568
+ if (nextRoute) {
569
+ var locals = angular.extend({}, nextRoute.resolve),
545
570
  template, templateUrl;
546
571
 
547
572
  angular.forEach(locals, function(value, key) {
@@ -549,17 +574,17 @@ function $RouteProvider(){
549
574
  $injector.get(value) : $injector.invoke(value, null, null, key);
550
575
  });
551
576
 
552
- if (angular.isDefined(template = next.template)) {
577
+ if (angular.isDefined(template = nextRoute.template)) {
553
578
  if (angular.isFunction(template)) {
554
- template = template(next.params);
579
+ template = template(nextRoute.params);
555
580
  }
556
- } else if (angular.isDefined(templateUrl = next.templateUrl)) {
581
+ } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) {
557
582
  if (angular.isFunction(templateUrl)) {
558
- templateUrl = templateUrl(next.params);
583
+ templateUrl = templateUrl(nextRoute.params);
559
584
  }
560
585
  templateUrl = $sce.getTrustedResourceUrl(templateUrl);
561
586
  if (angular.isDefined(templateUrl)) {
562
- next.loadedTemplateUrl = templateUrl;
587
+ nextRoute.loadedTemplateUrl = templateUrl;
563
588
  template = $templateRequest(templateUrl);
564
589
  }
565
590
  }
@@ -571,16 +596,16 @@ function $RouteProvider(){
571
596
  }).
572
597
  // after route change
573
598
  then(function(locals) {
574
- if (next == $route.current) {
575
- if (next) {
576
- next.locals = locals;
577
- angular.copy(next.params, $routeParams);
599
+ if (nextRoute == $route.current) {
600
+ if (nextRoute) {
601
+ nextRoute.locals = locals;
602
+ angular.copy(nextRoute.params, $routeParams);
578
603
  }
579
- $rootScope.$broadcast('$routeChangeSuccess', next, last);
604
+ $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
580
605
  }
581
606
  }, function(error) {
582
- if (next == $route.current) {
583
- $rootScope.$broadcast('$routeChangeError', next, last, error);
607
+ if (nextRoute == $route.current) {
608
+ $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
584
609
  }
585
610
  });
586
611
  }
@@ -854,7 +879,7 @@ function ngViewFactory( $route, $anchorScroll, $animate) {
854
879
  link: function(scope, $element, attr, ctrl, $transclude) {
855
880
  var currentScope,
856
881
  currentElement,
857
- previousElement,
882
+ previousLeaveAnimation,
858
883
  autoScrollExp = attr.autoscroll,
859
884
  onloadExp = attr.onload || '';
860
885
 
@@ -862,19 +887,20 @@ function ngViewFactory( $route, $anchorScroll, $animate) {
862
887
  update();
863
888
 
864
889
  function cleanupLastView() {
865
- if(previousElement) {
866
- previousElement.remove();
867
- previousElement = null;
890
+ if(previousLeaveAnimation) {
891
+ $animate.cancel(previousLeaveAnimation);
892
+ previousLeaveAnimation = null;
868
893
  }
894
+
869
895
  if(currentScope) {
870
896
  currentScope.$destroy();
871
897
  currentScope = null;
872
898
  }
873
899
  if(currentElement) {
874
- $animate.leave(currentElement).then(function() {
875
- previousElement = null;
900
+ previousLeaveAnimation = $animate.leave(currentElement);
901
+ previousLeaveAnimation.then(function() {
902
+ previousLeaveAnimation = null;
876
903
  });
877
- previousElement = currentElement;
878
904
  currentElement = null;
879
905
  }
880
906
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-rc.3
2
+ * @license AngularJS v1.3.0-rc.5
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -9190,7 +9190,7 @@ return jQuery;
9190
9190
  }));
9191
9191
 
9192
9192
  /**
9193
- * @license AngularJS v1.3.0-rc.3
9193
+ * @license AngularJS v1.3.0-rc.5
9194
9194
  * (c) 2010-2014 Google, Inc. http://angularjs.org
9195
9195
  * License: MIT
9196
9196
  */
@@ -9263,7 +9263,7 @@ function minErr(module, ErrorConstructor) {
9263
9263
  return match;
9264
9264
  });
9265
9265
 
9266
- message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.3/' +
9266
+ message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' +
9267
9267
  (module ? module + '/' : '') + code;
9268
9268
  for (i = 2; i < arguments.length; i++) {
9269
9269
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -9279,6 +9279,7 @@ function minErr(module, ErrorConstructor) {
9279
9279
  jqLite: true,
9280
9280
  jQuery: true,
9281
9281
  slice: true,
9282
+ splice: true,
9282
9283
  push: true,
9283
9284
  toString: true,
9284
9285
  ngMinErr: true,
@@ -9355,6 +9356,12 @@ function minErr(module, ErrorConstructor) {
9355
9356
  getBlockNodes: true,
9356
9357
  hasOwnProperty: true,
9357
9358
  createMap: true,
9359
+
9360
+ NODE_TYPE_ELEMENT: true,
9361
+ NODE_TYPE_TEXT: true,
9362
+ NODE_TYPE_COMMENT: true,
9363
+ NODE_TYPE_DOCUMENT: true,
9364
+ NODE_TYPE_DOCUMENT_FRAGMENT: true,
9358
9365
  */
9359
9366
 
9360
9367
  ////////////////////////////////////
@@ -9434,6 +9441,7 @@ var /** holds major version number for IE or NaN for real browsers */
9434
9441
  jqLite, // delay binding since jQuery could be loaded after us.
9435
9442
  jQuery, // delay binding
9436
9443
  slice = [].slice,
9444
+ splice = [].splice,
9437
9445
  push = [].push,
9438
9446
  toString = Object.prototype.toString,
9439
9447
  ngMinErr = minErr('ng'),
@@ -9444,13 +9452,10 @@ var /** holds major version number for IE or NaN for real browsers */
9444
9452
  uid = 0;
9445
9453
 
9446
9454
  /**
9447
- * IE 11 changed the format of the UserAgent string.
9448
- * See http://msdn.microsoft.com/en-us/library/ms537503.aspx
9455
+ * documentMode is an IE-only property
9456
+ * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
9449
9457
  */
9450
- msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
9451
- if (isNaN(msie)) {
9452
- msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
9453
- }
9458
+ msie = document.documentMode;
9454
9459
 
9455
9460
 
9456
9461
  /**
@@ -9466,7 +9471,7 @@ function isArrayLike(obj) {
9466
9471
 
9467
9472
  var length = obj.length;
9468
9473
 
9469
- if (obj.nodeType === 1 && length) {
9474
+ if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
9470
9475
  return true;
9471
9476
  }
9472
9477
 
@@ -10302,11 +10307,9 @@ function startingTag(element) {
10302
10307
  // are not allowed to have children. So we just ignore it.
10303
10308
  element.empty();
10304
10309
  } catch(e) {}
10305
- // As Per DOM Standards
10306
- var TEXT_NODE = 3;
10307
10310
  var elemHtml = jqLite('<div>').append(element).html();
10308
10311
  try {
10309
- return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
10312
+ return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
10310
10313
  elemHtml.
10311
10314
  match(/^(<[^>]+>)/)[1].
10312
10315
  replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
@@ -10885,6 +10888,12 @@ function createMap() {
10885
10888
  return Object.create(null);
10886
10889
  }
10887
10890
 
10891
+ var NODE_TYPE_ELEMENT = 1;
10892
+ var NODE_TYPE_TEXT = 3;
10893
+ var NODE_TYPE_COMMENT = 8;
10894
+ var NODE_TYPE_DOCUMENT = 9;
10895
+ var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
10896
+
10888
10897
  /**
10889
10898
  * @ngdoc type
10890
10899
  * @name angular.Module
@@ -11304,11 +11313,11 @@ function setupModuleLoader(window) {
11304
11313
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
11305
11314
  */
11306
11315
  var version = {
11307
- full: '1.3.0-rc.3', // all of these placeholder strings will be replaced by grunt's
11316
+ full: '1.3.0-rc.5', // all of these placeholder strings will be replaced by grunt's
11308
11317
  major: 1, // package task
11309
11318
  minor: 3,
11310
11319
  dot: 0,
11311
- codeName: 'aggressive-pacifism'
11320
+ codeName: 'impossible-choreography'
11312
11321
  };
11313
11322
 
11314
11323
 
@@ -11342,8 +11351,7 @@ function publishExternalAPI(angular){
11342
11351
  'getTestability': getTestability,
11343
11352
  '$$minErr': minErr,
11344
11353
  '$$csp': csp,
11345
- 'reloadWithDebugInfo': reloadWithDebugInfo,
11346
- '$$hasClass': jqLiteHasClass
11354
+ 'reloadWithDebugInfo': reloadWithDebugInfo
11347
11355
  });
11348
11356
 
11349
11357
  angularModule = setupModuleLoader(window);
@@ -11489,7 +11497,7 @@ function publishExternalAPI(angular){
11489
11497
  * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
11490
11498
  * - [`clone()`](http://api.jquery.com/clone/)
11491
11499
  * - [`contents()`](http://api.jquery.com/contents/)
11492
- * - [`css()`](http://api.jquery.com/css/)
11500
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
11493
11501
  * - [`data()`](http://api.jquery.com/data/)
11494
11502
  * - [`detach()`](http://api.jquery.com/detach/)
11495
11503
  * - [`empty()`](http://api.jquery.com/empty/)
@@ -11611,7 +11619,7 @@ function jqLiteAcceptsData(node) {
11611
11619
  // The window object can accept data but has no nodeType
11612
11620
  // Otherwise we are only interested in elements (1) and documents (9)
11613
11621
  var nodeType = node.nodeType;
11614
- return nodeType === 1 || !nodeType || nodeType === 9;
11622
+ return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
11615
11623
  }
11616
11624
 
11617
11625
  function jqLiteBuildFragment(html, context) {
@@ -11864,7 +11872,7 @@ function jqLiteController(element, name) {
11864
11872
  function jqLiteInheritedData(element, name, value) {
11865
11873
  // if element is the document object work with the html element instead
11866
11874
  // this makes $(document).scope() possible
11867
- if(element.nodeType == 9) {
11875
+ if(element.nodeType == NODE_TYPE_DOCUMENT) {
11868
11876
  element = element.documentElement;
11869
11877
  }
11870
11878
  var names = isArray(name) ? name : [name];
@@ -11877,7 +11885,7 @@ function jqLiteInheritedData(element, name, value) {
11877
11885
  // If dealing with a document fragment node with a host element, and no parent, use the host
11878
11886
  // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
11879
11887
  // to lookup parent controllers.
11880
- element = element.parentNode || (element.nodeType === 11 && element.host);
11888
+ element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
11881
11889
  }
11882
11890
  }
11883
11891
 
@@ -12055,7 +12063,7 @@ forEach({
12055
12063
  function getText(element, value) {
12056
12064
  if (isUndefined(value)) {
12057
12065
  var nodeType = element.nodeType;
12058
- return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
12066
+ return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
12059
12067
  }
12060
12068
  element.textContent = value;
12061
12069
  }
@@ -12277,7 +12285,7 @@ forEach({
12277
12285
  children: function(element) {
12278
12286
  var children = [];
12279
12287
  forEach(element.childNodes, function(element){
12280
- if (element.nodeType === 1)
12288
+ if (element.nodeType === NODE_TYPE_ELEMENT)
12281
12289
  children.push(element);
12282
12290
  });
12283
12291
  return children;
@@ -12289,7 +12297,7 @@ forEach({
12289
12297
 
12290
12298
  append: function(element, node) {
12291
12299
  var nodeType = element.nodeType;
12292
- if (nodeType !== 1 && nodeType !== 11) return;
12300
+ if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
12293
12301
 
12294
12302
  node = new JQLite(node);
12295
12303
 
@@ -12300,7 +12308,7 @@ forEach({
12300
12308
  },
12301
12309
 
12302
12310
  prepend: function(element, node) {
12303
- if (element.nodeType === 1) {
12311
+ if (element.nodeType === NODE_TYPE_ELEMENT) {
12304
12312
  var index = element.firstChild;
12305
12313
  forEach(new JQLite(node), function(child){
12306
12314
  element.insertBefore(child, index);
@@ -12351,7 +12359,7 @@ forEach({
12351
12359
 
12352
12360
  parent: function(element) {
12353
12361
  var parent = element.parentNode;
12354
- return parent && parent.nodeType !== 11 ? parent : null;
12362
+ return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
12355
12363
  },
12356
12364
 
12357
12365
  next: function(element) {
@@ -12510,13 +12518,13 @@ HashMap.prototype = {
12510
12518
  * @kind function
12511
12519
  *
12512
12520
  * @description
12513
- * Creates an injector function that can be used for retrieving services as well as for
12521
+ * Creates an injector object that can be used for retrieving services as well as for
12514
12522
  * dependency injection (see {@link guide/di dependency injection}).
12515
12523
  *
12516
12524
 
12517
12525
  * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
12518
12526
  * {@link angular.module}. The `ng` module must be explicitly added.
12519
- * @returns {function()} Injector function. See {@link auto.$injector $injector}.
12527
+ * @returns {function()} Injector object. See {@link auto.$injector $injector}.
12520
12528
  *
12521
12529
  * @example
12522
12530
  * Typical usage
@@ -12623,7 +12631,6 @@ function annotate(fn, strictDi, name) {
12623
12631
  /**
12624
12632
  * @ngdoc service
12625
12633
  * @name $injector
12626
- * @kind function
12627
12634
  *
12628
12635
  * @description
12629
12636
  *
@@ -12638,7 +12645,7 @@ function annotate(fn, strictDi, name) {
12638
12645
  * expect($injector.get('$injector')).toBe($injector);
12639
12646
  * expect($injector.invoke(function($injector) {
12640
12647
  * return $injector;
12641
- * }).toBe($injector);
12648
+ * })).toBe($injector);
12642
12649
  * ```
12643
12650
  *
12644
12651
  * # Injection Function Annotation
@@ -12705,8 +12712,8 @@ function annotate(fn, strictDi, name) {
12705
12712
  * @description
12706
12713
  * Allows the user to query if the particular service exists.
12707
12714
  *
12708
- * @param {string} Name of the service to query.
12709
- * @returns {boolean} returns true if injector has given service.
12715
+ * @param {string} name Name of the service to query.
12716
+ * @returns {boolean} `true` if injector has given service.
12710
12717
  */
12711
12718
 
12712
12719
  /**
@@ -13166,7 +13173,21 @@ function createInjector(modulesToLoad, strictDi) {
13166
13173
  return providerCache[name + providerSuffix] = provider_;
13167
13174
  }
13168
13175
 
13169
- function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
13176
+ function enforceReturnValue(name, factory) {
13177
+ return function enforcedReturnValue() {
13178
+ var result = instanceInjector.invoke(factory);
13179
+ if (isUndefined(result)) {
13180
+ throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
13181
+ }
13182
+ return result;
13183
+ };
13184
+ }
13185
+
13186
+ function factory(name, factoryFn, enforce) {
13187
+ return provider(name, {
13188
+ $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
13189
+ });
13190
+ }
13170
13191
 
13171
13192
  function service(name, constructor) {
13172
13193
  return factory(name, ['$injector', function($injector) {
@@ -13174,7 +13195,7 @@ function createInjector(modulesToLoad, strictDi) {
13174
13195
  }]);
13175
13196
  }
13176
13197
 
13177
- function value(name, val) { return factory(name, valueFn(val)); }
13198
+ function value(name, val) { return factory(name, valueFn(val), false); }
13178
13199
 
13179
13200
  function constant(name, value) {
13180
13201
  assertNotHasOwnProperty(name, 'constant');
@@ -13425,7 +13446,10 @@ function $AnchorScrollProvider() {
13425
13446
  // (no url change, no $location.hash() change), browser native does scroll
13426
13447
  if (autoScrollingEnabled) {
13427
13448
  $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
13428
- function autoScrollWatchAction() {
13449
+ function autoScrollWatchAction(newVal, oldVal) {
13450
+ // skip the initial scroll if $location.hash is empty
13451
+ if (newVal === oldVal && newVal === '') return;
13452
+
13429
13453
  $rootScope.$evalAsync(scroll);
13430
13454
  });
13431
13455
  }
@@ -13515,9 +13539,57 @@ var $AnimateProvider = ['$provide', function($provide) {
13515
13539
  return this.$$classNameFilter;
13516
13540
  };
13517
13541
 
13518
- this.$get = ['$$q', '$$asyncCallback', function($$q, $$asyncCallback) {
13542
+ this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
13519
13543
 
13520
13544
  var currentDefer;
13545
+
13546
+ function runAnimationPostDigest(fn) {
13547
+ var cancelFn, defer = $$q.defer();
13548
+ defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
13549
+ cancelFn && cancelFn();
13550
+ };
13551
+
13552
+ $rootScope.$$postDigest(function ngAnimatePostDigest() {
13553
+ cancelFn = fn(function ngAnimateNotifyComplete() {
13554
+ defer.resolve();
13555
+ });
13556
+ });
13557
+
13558
+ return defer.promise;
13559
+ }
13560
+
13561
+ function resolveElementClasses(element, cache) {
13562
+ var toAdd = [], toRemove = [];
13563
+
13564
+ var hasClasses = createMap();
13565
+ forEach((element.attr('class') || '').split(/\s+/), function(className) {
13566
+ hasClasses[className] = true;
13567
+ });
13568
+
13569
+ forEach(cache.classes, function(status, className) {
13570
+ var hasClass = hasClasses[className];
13571
+
13572
+ // If the most recent class manipulation (via $animate) was to remove the class, and the
13573
+ // element currently has the class, the class is scheduled for removal. Otherwise, if
13574
+ // the most recent class manipulation (via $animate) was to add the class, and the
13575
+ // element does not currently have the class, the class is scheduled to be added.
13576
+ if (status === false && hasClass) {
13577
+ toRemove.push(className);
13578
+ } else if (status === true && !hasClass) {
13579
+ toAdd.push(className);
13580
+ }
13581
+ });
13582
+
13583
+ return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove];
13584
+ }
13585
+
13586
+ function cachedClassManipulation(cache, classes, op) {
13587
+ for (var i=0, ii = classes.length; i < ii; ++i) {
13588
+ var className = classes[i];
13589
+ cache[className] = op;
13590
+ }
13591
+ }
13592
+
13521
13593
  function asyncPromise() {
13522
13594
  // only serve one instance of a promise in order to save CPU cycles
13523
13595
  if (!currentDefer) {
@@ -13621,13 +13693,17 @@ var $AnimateProvider = ['$provide', function($provide) {
13621
13693
  * @return {Promise} the animation callback promise
13622
13694
  */
13623
13695
  addClass : function(element, className) {
13696
+ return this.setClass(element, className, []);
13697
+ },
13698
+
13699
+ $$addClassImmediately : function addClassImmediately(element, className) {
13700
+ element = jqLite(element);
13624
13701
  className = !isString(className)
13625
13702
  ? (isArray(className) ? className.join(' ') : '')
13626
13703
  : className;
13627
13704
  forEach(element, function (element) {
13628
13705
  jqLiteAddClass(element, className);
13629
13706
  });
13630
- return asyncPromise();
13631
13707
  },
13632
13708
 
13633
13709
  /**
@@ -13643,6 +13719,11 @@ var $AnimateProvider = ['$provide', function($provide) {
13643
13719
  * @return {Promise} the animation callback promise
13644
13720
  */
13645
13721
  removeClass : function(element, className) {
13722
+ return this.setClass(element, [], className);
13723
+ },
13724
+
13725
+ $$removeClassImmediately : function removeClassImmediately(element, className) {
13726
+ element = jqLite(element);
13646
13727
  className = !isString(className)
13647
13728
  ? (isArray(className) ? className.join(' ') : '')
13648
13729
  : className;
@@ -13665,10 +13746,53 @@ var $AnimateProvider = ['$provide', function($provide) {
13665
13746
  * @param {string} remove the CSS class which will be removed from the element
13666
13747
  * @return {Promise} the animation callback promise
13667
13748
  */
13668
- setClass : function(element, add, remove) {
13669
- this.addClass(element, add);
13670
- this.removeClass(element, remove);
13671
- return asyncPromise();
13749
+ setClass : function(element, add, remove, runSynchronously) {
13750
+ var self = this;
13751
+ var STORAGE_KEY = '$$animateClasses';
13752
+ var createdCache = false;
13753
+ element = jqLite(element);
13754
+
13755
+ if (runSynchronously) {
13756
+ // TODO(@caitp/@matsko): Remove undocumented `runSynchronously` parameter, and always
13757
+ // perform DOM manipulation asynchronously or in postDigest.
13758
+ self.$$addClassImmediately(element, add);
13759
+ self.$$removeClassImmediately(element, remove);
13760
+ return asyncPromise();
13761
+ }
13762
+
13763
+ var cache = element.data(STORAGE_KEY);
13764
+ if (!cache) {
13765
+ cache = {
13766
+ classes: {}
13767
+ };
13768
+ createdCache = true;
13769
+ }
13770
+
13771
+ var classes = cache.classes;
13772
+
13773
+ add = isArray(add) ? add : add.split(' ');
13774
+ remove = isArray(remove) ? remove : remove.split(' ');
13775
+ cachedClassManipulation(classes, add, true);
13776
+ cachedClassManipulation(classes, remove, false);
13777
+
13778
+ if (createdCache) {
13779
+ cache.promise = runAnimationPostDigest(function(done) {
13780
+ var cache = element.data(STORAGE_KEY);
13781
+ element.removeData(STORAGE_KEY);
13782
+
13783
+ var classes = cache && resolveElementClasses(element, cache);
13784
+
13785
+ if (classes) {
13786
+ if (classes[0]) self.$$addClassImmediately(element, classes[0]);
13787
+ if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
13788
+ }
13789
+
13790
+ done();
13791
+ });
13792
+ element.data(STORAGE_KEY, cache);
13793
+ }
13794
+
13795
+ return cache.promise;
13672
13796
  },
13673
13797
 
13674
13798
  enabled : noop,
@@ -13687,6 +13811,8 @@ function $$AsyncCallbackProvider(){
13687
13811
  }];
13688
13812
  }
13689
13813
 
13814
+ /* global stripHash: true */
13815
+
13690
13816
  /**
13691
13817
  * ! This is a private undocumented service !
13692
13818
  *
@@ -13810,8 +13936,9 @@ function Browser(window, document, $log, $sniffer) {
13810
13936
  //////////////////////////////////////////////////////////////
13811
13937
 
13812
13938
  var lastBrowserUrl = location.href,
13939
+ lastHistoryState = history.state,
13813
13940
  baseElement = document.find('base'),
13814
- newLocation = null;
13941
+ reloadLocation = null;
13815
13942
 
13816
13943
  /**
13817
13944
  * @name $browser#url
@@ -13830,26 +13957,42 @@ function Browser(window, document, $log, $sniffer) {
13830
13957
  * {@link ng.$location $location service} to change url.
13831
13958
  *
13832
13959
  * @param {string} url New url (when used as setter)
13833
- * @param {boolean=} replace Should new url replace current history record ?
13960
+ * @param {boolean=} replace Should new url replace current history record?
13961
+ * @param {object=} state object to use with pushState/replaceState
13834
13962
  */
13835
- self.url = function(url, replace) {
13963
+ self.url = function(url, replace, state) {
13964
+ // In modern browsers `history.state` is `null` by default; treating it separately
13965
+ // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
13966
+ // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
13967
+ if (isUndefined(state)) {
13968
+ state = null;
13969
+ }
13970
+
13836
13971
  // Android Browser BFCache causes location, history reference to become stale.
13837
13972
  if (location !== window.location) location = window.location;
13838
13973
  if (history !== window.history) history = window.history;
13839
13974
 
13840
13975
  // setter
13841
13976
  if (url) {
13842
- if (lastBrowserUrl == url) return;
13977
+ // Don't change anything if previous and current URLs and states match. This also prevents
13978
+ // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
13979
+ // See https://github.com/angular/angular.js/commit/ffb2701
13980
+ if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
13981
+ return;
13982
+ }
13983
+ var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
13843
13984
  lastBrowserUrl = url;
13844
- if ($sniffer.history) {
13845
- if (replace) history.replaceState(null, '', url);
13846
- else {
13847
- history.pushState(null, '', url);
13848
- // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
13849
- baseElement.attr('href', baseElement.attr('href'));
13850
- }
13985
+ // Don't use history API if only the hash changed
13986
+ // due to a bug in IE10/IE11 which leads
13987
+ // to not firing a `hashchange` nor `popstate` event
13988
+ // in some cases (see #9143).
13989
+ if ($sniffer.history && (!sameBase || history.state !== state)) {
13990
+ history[replace ? 'replaceState' : 'pushState'](state, '', url);
13991
+ lastHistoryState = history.state;
13851
13992
  } else {
13852
- newLocation = url;
13993
+ if (!sameBase) {
13994
+ reloadLocation = url;
13995
+ }
13853
13996
  if (replace) {
13854
13997
  location.replace(url);
13855
13998
  } else {
@@ -13859,23 +14002,38 @@ function Browser(window, document, $log, $sniffer) {
13859
14002
  return self;
13860
14003
  // getter
13861
14004
  } else {
13862
- // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
13863
- // methods not updating location.href synchronously.
14005
+ // - reloadLocation is needed as browsers don't allow to read out
14006
+ // the new location.href if a reload happened.
13864
14007
  // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
13865
- return newLocation || location.href.replace(/%27/g,"'");
14008
+ return reloadLocation || location.href.replace(/%27/g,"'");
13866
14009
  }
13867
14010
  };
13868
14011
 
14012
+ /**
14013
+ * @name $browser#state
14014
+ *
14015
+ * @description
14016
+ * This method is a getter.
14017
+ *
14018
+ * Return history.state or null if history.state is undefined.
14019
+ *
14020
+ * @returns {object} state
14021
+ */
14022
+ self.state = function() {
14023
+ return isUndefined(history.state) ? null : history.state;
14024
+ };
14025
+
13869
14026
  var urlChangeListeners = [],
13870
14027
  urlChangeInit = false;
13871
14028
 
13872
14029
  function fireUrlChange() {
13873
- newLocation = null;
13874
- if (lastBrowserUrl == self.url()) return;
14030
+ if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
14031
+ return;
14032
+ }
13875
14033
 
13876
14034
  lastBrowserUrl = self.url();
13877
14035
  forEach(urlChangeListeners, function(listener) {
13878
- listener(self.url());
14036
+ listener(self.url(), history.state);
13879
14037
  });
13880
14038
  }
13881
14039
 
@@ -13910,9 +14068,7 @@ function Browser(window, document, $log, $sniffer) {
13910
14068
  // html5 history api - popstate event
13911
14069
  if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
13912
14070
  // hashchange event
13913
- if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
13914
- // polling
13915
- else self.addPollFn(fireUrlChange);
14071
+ jqLite(window).on('hashchange', fireUrlChange);
13916
14072
 
13917
14073
  urlChangeInit = true;
13918
14074
  }
@@ -14687,8 +14843,11 @@ function $TemplateCacheProvider() {
14687
14843
  * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
14688
14844
  * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
14689
14845
  * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
14846
+ * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
14690
14847
  * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
14691
14848
  * `null` to the `link` fn if not found.
14849
+ * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
14850
+ * `null` to the `link` fn if not found.
14692
14851
  *
14693
14852
  *
14694
14853
  * #### `controllerAs`
@@ -14766,22 +14925,18 @@ function $TemplateCacheProvider() {
14766
14925
  * (because SVG doesn't work with custom elements in the DOM tree).
14767
14926
  *
14768
14927
  * #### `transclude`
14769
- * compile the content of the element and make it available to the directive.
14770
- * Typically used with {@link ng.directive:ngTransclude
14771
- * ngTransclude}. The advantage of transclusion is that the linking function receives a
14772
- * transclusion function which is pre-bound to the correct scope. In a typical setup the widget
14773
- * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
14774
- * scope. This makes it possible for the widget to have private state, and the transclusion to
14775
- * be bound to the parent (pre-`isolate`) scope.
14928
+ * Extract the contents of the element where the directive appears and make it available to the directive.
14929
+ * The contents are compiled and provided to the directive as a **transclusion function**. See the
14930
+ * {@link $compile#transclusion Transclusion} section below.
14776
14931
  *
14777
- * * `true` - transclude the content of the directive.
14778
- * * `'element'` - transclude the whole element including any directives defined at lower priority.
14932
+ * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
14933
+ * directive's element or the entire element:
14934
+ *
14935
+ * * `true` - transclude the content (i.e. the child nodes) of the directive's element.
14936
+ * * `'element'` - transclude the whole of the directive's element including any directives on this
14937
+ * element that defined at a lower priority than this directive. When used, the `template`
14938
+ * property is ignored.
14779
14939
  *
14780
- * <div class="alert alert-warning">
14781
- * **Note:** When testing an element transclude directive you must not place the directive at the root of the
14782
- * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
14783
- * Testing Transclusion Directives}.
14784
- * </div>
14785
14940
  *
14786
14941
  * #### `compile`
14787
14942
  *
@@ -14879,7 +15034,121 @@ function $TemplateCacheProvider() {
14879
15034
  * It is safe to do DOM transformation in the post-linking function on elements that are not waiting
14880
15035
  * for their async templates to be resolved.
14881
15036
  *
14882
- * <a name="Attributes"></a>
15037
+ *
15038
+ * ### Transclusion
15039
+ *
15040
+ * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
15041
+ * copying them to another part of the DOM, while maintaining their connection to the original AngularJS
15042
+ * scope from where they were taken.
15043
+ *
15044
+ * Transclusion is used (often with {@link ngTransclude}) to insert the
15045
+ * original contents of a directive's element into a specified place in the template of the directive.
15046
+ * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
15047
+ * content has access to the properties on the scope from which it was taken, even if the directive
15048
+ * has isolated scope.
15049
+ * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
15050
+ *
15051
+ * This makes it possible for the widget to have private state for its template, while the transcluded
15052
+ * content has access to its originating scope.
15053
+ *
15054
+ * <div class="alert alert-warning">
15055
+ * **Note:** When testing an element transclude directive you must not place the directive at the root of the
15056
+ * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
15057
+ * Testing Transclusion Directives}.
15058
+ * </div>
15059
+ *
15060
+ * #### Transclusion Functions
15061
+ *
15062
+ * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
15063
+ * function** to the directive's `link` function and `controller`. This transclusion function is a special
15064
+ * **linking function** that will return the compiled contents linked to a new transclusion scope.
15065
+ *
15066
+ * <div class="alert alert-info">
15067
+ * If you are just using {@link ngTransclude} then you don't need to worry about this function, since
15068
+ * ngTransclude will deal with it for us.
15069
+ * </div>
15070
+ *
15071
+ * If you want to manually control the insertion and removal of the transcluded content in your directive
15072
+ * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
15073
+ * object that contains the compiled DOM, which is linked to the correct transclusion scope.
15074
+ *
15075
+ * When you call a transclusion function you can pass in a **clone attach function**. This function is accepts
15076
+ * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
15077
+ * content and the `scope` is the newly created transclusion scope, to which the clone is bound.
15078
+ *
15079
+ * <div class="alert alert-info">
15080
+ * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
15081
+ * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
15082
+ * </div>
15083
+ *
15084
+ * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
15085
+ * attach function**:
15086
+ *
15087
+ * ```js
15088
+ * var transcludedContent, transclusionScope;
15089
+ *
15090
+ * $transclude(function(clone, scope) {
15091
+ * element.append(clone);
15092
+ * transcludedContent = clone;
15093
+ * transclusionScope = scope;
15094
+ * });
15095
+ * ```
15096
+ *
15097
+ * Later, if you want to remove the transcluded content from your DOM then you should also destroy the
15098
+ * associated transclusion scope:
15099
+ *
15100
+ * ```js
15101
+ * transcludedContent.remove();
15102
+ * transclusionScope.$destroy();
15103
+ * ```
15104
+ *
15105
+ * <div class="alert alert-info">
15106
+ * **Best Practice**: if you intend to add and remove transcluded content manually in your directive
15107
+ * (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
15108
+ * then you are also responsible for calling `$destroy` on the transclusion scope.
15109
+ * </div>
15110
+ *
15111
+ * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
15112
+ * automatically destroy their transluded clones as necessary so you do not need to worry about this if
15113
+ * you are simply using {@link ngTransclude} to inject the transclusion into your directive.
15114
+ *
15115
+ *
15116
+ * #### Transclusion Scopes
15117
+ *
15118
+ * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
15119
+ * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
15120
+ * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
15121
+ * was taken.
15122
+ *
15123
+ * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
15124
+ * like this:
15125
+ *
15126
+ * ```html
15127
+ * <div ng-app>
15128
+ * <div isolate>
15129
+ * <div transclusion>
15130
+ * </div>
15131
+ * </div>
15132
+ * </div>
15133
+ * ```
15134
+ *
15135
+ * The `$parent` scope hierarchy will look like this:
15136
+ *
15137
+ * ```
15138
+ * - $rootScope
15139
+ * - isolate
15140
+ * - transclusion
15141
+ * ```
15142
+ *
15143
+ * but the scopes will inherit prototypically from different scopes to their `$parent`.
15144
+ *
15145
+ * ```
15146
+ * - $rootScope
15147
+ * - transclusion
15148
+ * - isolate
15149
+ * ```
15150
+ *
15151
+ *
14883
15152
  * ### Attributes
14884
15153
  *
14885
15154
  * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
@@ -14917,7 +15186,7 @@ function $TemplateCacheProvider() {
14917
15186
  * }
14918
15187
  * ```
14919
15188
  *
14920
- * Below is an example using `$compileProvider`.
15189
+ * ## Example
14921
15190
  *
14922
15191
  * <div class="alert alert-warning">
14923
15192
  * **Note**: Typically directives are registered with `module.directive`. The example below is
@@ -15042,7 +15311,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15042
15311
  Suffix = 'Directive',
15043
15312
  COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
15044
15313
  CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
15045
- ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
15314
+ ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
15315
+ REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
15046
15316
 
15047
15317
  // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
15048
15318
  // The assumption is that future DOM event attribute names will begin with
@@ -15347,10 +15617,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15347
15617
 
15348
15618
  nodeName = nodeName_(this.$$element);
15349
15619
 
15350
- // sanitize a[href] and img[src] values
15351
15620
  if ((nodeName === 'a' && key === 'href') ||
15352
15621
  (nodeName === 'img' && key === 'src')) {
15622
+ // sanitize a[href] and img[src] values
15353
15623
  this[key] = value = $$sanitizeUri(value, key === 'src');
15624
+ } else if (nodeName === 'img' && key === 'srcset') {
15625
+ // sanitize img[srcset] values
15626
+ var result = "";
15627
+
15628
+ // first check if there are spaces because it's not the same pattern
15629
+ var trimmedSrcset = trim(value);
15630
+ // ( 999x ,| 999w ,| ,|, )
15631
+ var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
15632
+ var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
15633
+
15634
+ // split srcset into tuple of uri and descriptor except for the last item
15635
+ var rawUris = trimmedSrcset.split(pattern);
15636
+
15637
+ // for each tuples
15638
+ var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
15639
+ for (var i=0; i<nbrUrisWith2parts; i++) {
15640
+ var innerIdx = i*2;
15641
+ // sanitize the uri
15642
+ result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
15643
+ // add the descriptor
15644
+ result += ( " " + trim(rawUris[innerIdx+1]));
15645
+ }
15646
+
15647
+ // split the last item into uri and descriptor
15648
+ var lastTuple = trim(rawUris[i*2]).split(/\s/);
15649
+
15650
+ // sanitize the last uri
15651
+ result += $$sanitizeUri(trim(lastTuple[0]), true);
15652
+
15653
+ // and add the last descriptor if any
15654
+ if( lastTuple.length === 2) {
15655
+ result += (" " + trim(lastTuple[1]));
15656
+ }
15657
+ this[key] = value = result;
15354
15658
  }
15355
15659
 
15356
15660
  if (writeAttr !== false) {
@@ -15388,12 +15692,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15388
15692
  * @param {string} key Normalized key. (ie ngAttribute) .
15389
15693
  * @param {function(interpolatedValue)} fn Function that will be called whenever
15390
15694
  the interpolated value of the attribute changes.
15391
- * See the {@link guide/directive#Attributes Directives} guide for more info.
15695
+ * See {@link ng.$compile#attributes $compile} for more info.
15392
15696
  * @returns {function()} Returns a deregistration function for this observer.
15393
15697
  */
15394
15698
  $observe: function(key, fn) {
15395
15699
  var attrs = this,
15396
- $$observers = (attrs.$$observers || (attrs.$$observers = {})),
15700
+ $$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
15397
15701
  listeners = ($$observers[key] || ($$observers[key] = []));
15398
15702
 
15399
15703
  listeners.push(fn);
@@ -15469,7 +15773,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15469
15773
  // We can not compile top level text elements since text nodes can be merged and we will
15470
15774
  // not be able to attach scope data to them, so we will wrap them in <span>
15471
15775
  forEach($compileNodes, function(node, index){
15472
- if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
15776
+ if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
15473
15777
  $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
15474
15778
  }
15475
15779
  });
@@ -15478,27 +15782,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15478
15782
  maxPriority, ignoreDirective, previousCompileContext);
15479
15783
  compile.$$addScopeClass($compileNodes);
15480
15784
  var namespace = null;
15481
- var namespaceAdaptedCompileNodes = $compileNodes;
15482
- var lastCompileNode;
15483
15785
  return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
15484
15786
  assertArg(scope, 'scope');
15485
15787
  if (!namespace) {
15486
15788
  namespace = detectNamespaceForChildElements(futureParentElement);
15487
15789
  }
15488
- if (namespace !== 'html' && $compileNodes[0] !== lastCompileNode) {
15489
- namespaceAdaptedCompileNodes = jqLite(
15790
+ var $linkNode;
15791
+ if (namespace !== 'html') {
15792
+ // When using a directive with replace:true and templateUrl the $compileNodes
15793
+ // (or a child element inside of them)
15794
+ // might change, so we need to recreate the namespace adapted compileNodes
15795
+ // for call to the link function.
15796
+ // Note: This will already clone the nodes...
15797
+ $linkNode = jqLite(
15490
15798
  wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
15491
15799
  );
15800
+ } else if (cloneConnectFn) {
15801
+ // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
15802
+ // and sometimes changes the structure of the DOM.
15803
+ $linkNode = JQLitePrototype.clone.call($compileNodes);
15804
+ } else {
15805
+ $linkNode = $compileNodes;
15492
15806
  }
15493
- // When using a directive with replace:true and templateUrl the $compileNodes
15494
- // might change, so we need to recreate the namespace adapted compileNodes.
15495
- lastCompileNode = $compileNodes[0];
15496
-
15497
- // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
15498
- // and sometimes changes the structure of the DOM.
15499
- var $linkNode = cloneConnectFn
15500
- ? JQLitePrototype.clone.call(namespaceAdaptedCompileNodes) // IMPORTANT!!!
15501
- : namespaceAdaptedCompileNodes;
15502
15807
 
15503
15808
  if (transcludeControllers) {
15504
15809
  for (var controllerName in transcludeControllers) {
@@ -15641,20 +15946,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15641
15946
 
15642
15947
  function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) {
15643
15948
 
15644
- var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement) {
15645
- var scopeCreated = false;
15949
+ var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
15646
15950
 
15647
15951
  if (!transcludedScope) {
15648
- transcludedScope = scope.$new();
15952
+ transcludedScope = scope.$new(false, containingScope);
15649
15953
  transcludedScope.$$transcluded = true;
15650
- scopeCreated = true;
15651
15954
  }
15652
15955
 
15653
- var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
15654
- if (scopeCreated && !elementTransclusion) {
15655
- clone.on('$destroy', function() { transcludedScope.$destroy(); });
15656
- }
15657
- return clone;
15956
+ return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
15658
15957
  };
15659
15958
 
15660
15959
  return boundTranscludeFn;
@@ -15677,7 +15976,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15677
15976
  className;
15678
15977
 
15679
15978
  switch(nodeType) {
15680
- case 1: /* Element */
15979
+ case NODE_TYPE_ELEMENT: /* Element */
15681
15980
  // use the node name: <directive>
15682
15981
  addDirective(directives,
15683
15982
  directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
@@ -15689,37 +15988,35 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15689
15988
  var attrEndName = false;
15690
15989
 
15691
15990
  attr = nAttrs[j];
15692
- if (!msie || msie >= 8 || attr.specified) {
15693
- name = attr.name;
15694
- value = trim(attr.value);
15695
-
15696
- // support ngAttr attribute binding
15697
- ngAttrName = directiveNormalize(name);
15698
- if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
15699
- name = snake_case(ngAttrName.substr(6), '-');
15700
- }
15991
+ name = attr.name;
15992
+ value = trim(attr.value);
15701
15993
 
15702
- var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
15703
- if (directiveIsMultiElement(directiveNName)) {
15704
- if (ngAttrName === directiveNName + 'Start') {
15705
- attrStartName = name;
15706
- attrEndName = name.substr(0, name.length - 5) + 'end';
15707
- name = name.substr(0, name.length - 6);
15708
- }
15709
- }
15994
+ // support ngAttr attribute binding
15995
+ ngAttrName = directiveNormalize(name);
15996
+ if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
15997
+ name = snake_case(ngAttrName.substr(6), '-');
15998
+ }
15710
15999
 
15711
- nName = directiveNormalize(name.toLowerCase());
15712
- attrsMap[nName] = name;
15713
- if (isNgAttr || !attrs.hasOwnProperty(nName)) {
15714
- attrs[nName] = value;
15715
- if (getBooleanAttrName(node, nName)) {
15716
- attrs[nName] = true; // presence means true
15717
- }
16000
+ var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
16001
+ if (directiveIsMultiElement(directiveNName)) {
16002
+ if (ngAttrName === directiveNName + 'Start') {
16003
+ attrStartName = name;
16004
+ attrEndName = name.substr(0, name.length - 5) + 'end';
16005
+ name = name.substr(0, name.length - 6);
15718
16006
  }
15719
- addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
15720
- addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
15721
- attrEndName);
15722
16007
  }
16008
+
16009
+ nName = directiveNormalize(name.toLowerCase());
16010
+ attrsMap[nName] = name;
16011
+ if (isNgAttr || !attrs.hasOwnProperty(nName)) {
16012
+ attrs[nName] = value;
16013
+ if (getBooleanAttrName(node, nName)) {
16014
+ attrs[nName] = true; // presence means true
16015
+ }
16016
+ }
16017
+ addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
16018
+ addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
16019
+ attrEndName);
15723
16020
  }
15724
16021
 
15725
16022
  // use class as directive
@@ -15734,10 +16031,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15734
16031
  }
15735
16032
  }
15736
16033
  break;
15737
- case 3: /* Text Node */
16034
+ case NODE_TYPE_TEXT: /* Text Node */
15738
16035
  addTextInterpolateDirective(directives, node.nodeValue);
15739
16036
  break;
15740
- case 8: /* Comment */
16037
+ case NODE_TYPE_COMMENT: /* Comment */
15741
16038
  try {
15742
16039
  match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
15743
16040
  if (match) {
@@ -15777,7 +16074,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15777
16074
  "Unterminated attribute, found '{0}' but no matching '{1}' found.",
15778
16075
  attrStart, attrEnd);
15779
16076
  }
15780
- if (node.nodeType == 1 /** Element **/) {
16077
+ if (node.nodeType == NODE_TYPE_ELEMENT) {
15781
16078
  if (node.hasAttribute(attrStart)) depth++;
15782
16079
  if (node.hasAttribute(attrEnd)) depth--;
15783
16080
  }
@@ -15956,11 +16253,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15956
16253
  if (jqLiteIsTextNode(directiveValue)) {
15957
16254
  $template = [];
15958
16255
  } else {
15959
- $template = jqLite(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
16256
+ $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
15960
16257
  }
15961
16258
  compileNode = $template[0];
15962
16259
 
15963
- if ($template.length != 1 || compileNode.nodeType !== 1) {
16260
+ if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
15964
16261
  throw $compileMinErr('tplrt',
15965
16262
  "Template for directive '{0}' must have exactly one root element. {1}",
15966
16263
  directiveName, '');
@@ -16064,14 +16361,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16064
16361
 
16065
16362
  function getControllers(directiveName, require, $element, elementControllers) {
16066
16363
  var value, retrievalMethod = 'data', optional = false;
16364
+ var $searchElement = $element;
16365
+ var match;
16067
16366
  if (isString(require)) {
16068
- while((value = require.charAt(0)) == '^' || value == '?') {
16069
- require = require.substr(1);
16070
- if (value == '^') {
16071
- retrievalMethod = 'inheritedData';
16072
- }
16073
- optional = optional || value == '?';
16367
+ match = require.match(REQUIRE_PREFIX_REGEXP);
16368
+ require = require.substring(match[0].length);
16369
+
16370
+ if (match[3]) {
16371
+ if (match[1]) match[3] = null;
16372
+ else match[1] = match[3];
16074
16373
  }
16374
+ if (match[1] === '^') {
16375
+ retrievalMethod = 'inheritedData';
16376
+ } else if (match[1] === '^^') {
16377
+ retrievalMethod = 'inheritedData';
16378
+ $searchElement = $element.parent();
16379
+ }
16380
+ if (match[2] === '?') {
16381
+ optional = true;
16382
+ }
16383
+
16075
16384
  value = null;
16076
16385
 
16077
16386
  if (elementControllers && retrievalMethod === 'data') {
@@ -16079,7 +16388,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16079
16388
  value = value.instance;
16080
16389
  }
16081
16390
  }
16082
- value = value || $element[retrievalMethod]('$' + require + 'Controller');
16391
+ value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
16083
16392
 
16084
16393
  if (!value && !optional) {
16085
16394
  throw $compileMinErr('ctreq',
@@ -16285,7 +16594,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16285
16594
  if (!futureParentElement) {
16286
16595
  futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
16287
16596
  }
16288
- return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement);
16597
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
16289
16598
  }
16290
16599
  }
16291
16600
  }
@@ -16426,11 +16735,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16426
16735
  if (jqLiteIsTextNode(content)) {
16427
16736
  $template = [];
16428
16737
  } else {
16429
- $template = jqLite(wrapTemplate(templateNamespace, trim(content)));
16738
+ $template = removeComments(wrapTemplate(templateNamespace, trim(content)));
16430
16739
  }
16431
16740
  compileNode = $template[0];
16432
16741
 
16433
- if ($template.length != 1 || compileNode.nodeType !== 1) {
16742
+ if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
16434
16743
  throw $compileMinErr('tplrt',
16435
16744
  "Template for directive '{0}' must have exactly one root element. {1}",
16436
16745
  origAsyncDirective.name, templateUrl);
@@ -16469,6 +16778,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16469
16778
  boundTranscludeFn = linkQueue.shift(),
16470
16779
  linkNode = $compileNode[0];
16471
16780
 
16781
+ if (scope.$$destroyed) continue;
16782
+
16472
16783
  if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
16473
16784
  var oldClasses = beforeTemplateLinkNode.className;
16474
16785
 
@@ -16495,6 +16806,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16495
16806
 
16496
16807
  return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
16497
16808
  var childBoundTranscludeFn = boundTranscludeFn;
16809
+ if (scope.$$destroyed) return;
16498
16810
  if (linkQueue) {
16499
16811
  linkQueue.push(scope);
16500
16812
  linkQueue.push(node);
@@ -16611,6 +16923,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16611
16923
  "ng- versions (such as ng-click instead of onclick) instead.");
16612
16924
  }
16613
16925
 
16926
+ // If the attribute was removed, then we are done
16927
+ if (!attr[name]) {
16928
+ return;
16929
+ }
16930
+
16614
16931
  // we need to interpolate again, in case the attribute value has been updated
16615
16932
  // (e.g. by another directive's compile function)
16616
16933
  interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name),
@@ -16838,6 +17155,23 @@ function tokenDifference(str1, str2) {
16838
17155
  return values;
16839
17156
  }
16840
17157
 
17158
+ function removeComments(jqNodes) {
17159
+ jqNodes = jqLite(jqNodes);
17160
+ var i = jqNodes.length;
17161
+
17162
+ if (i <= 1) {
17163
+ return jqNodes;
17164
+ }
17165
+
17166
+ while (i--) {
17167
+ var node = jqNodes[i];
17168
+ if (node.nodeType === NODE_TYPE_COMMENT) {
17169
+ splice.call(jqNodes, i, 1);
17170
+ }
17171
+ }
17172
+ return jqNodes;
17173
+ }
17174
+
16841
17175
  /**
16842
17176
  * @ngdoc provider
16843
17177
  * @name $controllerProvider
@@ -17141,7 +17475,8 @@ function $HttpProvider() {
17141
17475
  var JSON_START = /^\s*(\[|\{[^\{])/,
17142
17476
  JSON_END = /[\}\]]\s*$/,
17143
17477
  PROTECTION_PREFIX = /^\)\]\}',?\n/,
17144
- CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
17478
+ APPLICATION_JSON = 'application/json',
17479
+ CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
17145
17480
 
17146
17481
  /**
17147
17482
  * @ngdoc property
@@ -17166,12 +17501,15 @@ function $HttpProvider() {
17166
17501
  **/
17167
17502
  var defaults = this.defaults = {
17168
17503
  // transform incoming response data
17169
- transformResponse: [function(data) {
17504
+ transformResponse: [function defaultHttpResponseTransform(data, headers) {
17170
17505
  if (isString(data)) {
17171
17506
  // strip json vulnerability protection prefix
17172
17507
  data = data.replace(PROTECTION_PREFIX, '');
17173
- if (JSON_START.test(data) && JSON_END.test(data))
17508
+ var contentType = headers('Content-Type');
17509
+ if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
17510
+ (JSON_START.test(data) && JSON_END.test(data))) {
17174
17511
  data = fromJson(data);
17512
+ }
17175
17513
  }
17176
17514
  return data;
17177
17515
  }],
@@ -18129,18 +18467,8 @@ function $HttpProvider() {
18129
18467
  }];
18130
18468
  }
18131
18469
 
18132
- function createXhr(method) {
18133
- //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
18134
- //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
18135
- //if it is available
18136
- if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
18137
- !window.XMLHttpRequest)) {
18138
- return new window.ActiveXObject("Microsoft.XMLHTTP");
18139
- } else if (window.XMLHttpRequest) {
18140
- return new window.XMLHttpRequest();
18141
- }
18142
-
18143
- throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
18470
+ function createXhr() {
18471
+ return new window.XMLHttpRequest();
18144
18472
  }
18145
18473
 
18146
18474
  /**
@@ -18166,11 +18494,8 @@ function $HttpBackendProvider() {
18166
18494
  }
18167
18495
 
18168
18496
  function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
18169
- var ABORTED = -1;
18170
-
18171
18497
  // TODO(vojta): fix the signature
18172
18498
  return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
18173
- var status;
18174
18499
  $browser.$$incOutstandingRequestCount();
18175
18500
  url = url || $browser.url();
18176
18501
 
@@ -18188,7 +18513,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
18188
18513
  });
18189
18514
  } else {
18190
18515
 
18191
- var xhr = createXhr(method);
18516
+ var xhr = createXhr();
18192
18517
 
18193
18518
  xhr.open(method, url, true);
18194
18519
  forEach(headers, function(value, key) {
@@ -18197,44 +18522,39 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
18197
18522
  }
18198
18523
  });
18199
18524
 
18200
- // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
18201
- // response is in the cache. the promise api will ensure that to the app code the api is
18202
- // always async
18203
- xhr.onreadystatechange = function() {
18204
- // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
18205
- // xhrs that are resolved while the app is in the background (see #5426).
18206
- // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
18207
- // continuing
18208
- //
18209
- // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
18210
- // Safari respectively.
18211
- if (xhr && xhr.readyState == 4) {
18212
- var responseHeaders = null,
18213
- response = null,
18214
- statusText = '';
18215
-
18216
- if(status !== ABORTED) {
18217
- responseHeaders = xhr.getAllResponseHeaders();
18218
-
18219
- // responseText is the old-school way of retrieving response (supported by IE8 & 9)
18220
- // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
18221
- response = ('response' in xhr) ? xhr.response : xhr.responseText;
18222
- }
18525
+ xhr.onload = function requestLoaded() {
18526
+ var statusText = xhr.statusText || '';
18223
18527
 
18224
- // Accessing statusText on an aborted xhr object will
18225
- // throw an 'c00c023f error' in IE9 and lower, don't touch it.
18226
- if (!(status === ABORTED && msie < 10)) {
18227
- statusText = xhr.statusText;
18228
- }
18528
+ // responseText is the old-school way of retrieving response (supported by IE8 & 9)
18529
+ // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
18530
+ var response = ('response' in xhr) ? xhr.response : xhr.responseText;
18531
+
18532
+ // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
18533
+ var status = xhr.status === 1223 ? 204 : xhr.status;
18229
18534
 
18230
- completeRequest(callback,
18231
- status || xhr.status,
18232
- response,
18233
- responseHeaders,
18234
- statusText);
18535
+ // fix status code when it is 0 (0 status is undocumented).
18536
+ // Occurs when accessing file resources or on Android 4.1 stock browser
18537
+ // while retrieving files from application cache.
18538
+ if (status === 0) {
18539
+ status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
18235
18540
  }
18541
+
18542
+ completeRequest(callback,
18543
+ status,
18544
+ response,
18545
+ xhr.getAllResponseHeaders(),
18546
+ statusText);
18547
+ };
18548
+
18549
+ var requestError = function () {
18550
+ // The response is always empty
18551
+ // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
18552
+ completeRequest(callback, -1, null, null, '');
18236
18553
  };
18237
18554
 
18555
+ xhr.onerror = requestError;
18556
+ xhr.onabort = requestError;
18557
+
18238
18558
  if (withCredentials) {
18239
18559
  xhr.withCredentials = true;
18240
18560
  }
@@ -18267,7 +18587,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
18267
18587
 
18268
18588
 
18269
18589
  function timeoutRequest() {
18270
- status = ABORTED;
18271
18590
  jsonpDone && jsonpDone();
18272
18591
  xhr && xhr.abort();
18273
18592
  }
@@ -18277,17 +18596,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
18277
18596
  timeoutId && $browserDefer.cancel(timeoutId);
18278
18597
  jsonpDone = xhr = null;
18279
18598
 
18280
- // fix status code when it is 0 (0 status is undocumented).
18281
- // Occurs when accessing file resources or on Android 4.1 stock browser
18282
- // while retrieving files from application cache.
18283
- if (status === 0) {
18284
- status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
18285
- }
18286
-
18287
- // normalize IE bug (http://bugs.jquery.com/ticket/1450)
18288
- status = status === 1223 ? 204 : status;
18289
- statusText = statusText || '';
18290
-
18291
18599
  callback(status, response, headersString, statusText);
18292
18600
  $browser.$$completeOutstandingRequest(noop);
18293
18601
  }
@@ -19239,9 +19547,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
19239
19547
  }
19240
19548
 
19241
19549
 
19242
- LocationHashbangInHtml5Url.prototype =
19243
- LocationHashbangUrl.prototype =
19244
- LocationHtml5Url.prototype = {
19550
+ var locationPrototype = {
19245
19551
 
19246
19552
  /**
19247
19553
  * Are we in html5 mode?
@@ -19250,7 +19556,7 @@ LocationHashbangInHtml5Url.prototype =
19250
19556
  $$html5: false,
19251
19557
 
19252
19558
  /**
19253
- * Has any change been replacing ?
19559
+ * Has any change been replacing?
19254
19560
  * @private
19255
19561
  */
19256
19562
  $$replace: false,
@@ -19352,7 +19658,7 @@ LocationHashbangInHtml5Url.prototype =
19352
19658
  * @return {string} path
19353
19659
  */
19354
19660
  path: locationGetterSetter('$$path', function(path) {
19355
- path = path ? path.toString() : '';
19661
+ path = path !== null ? path.toString() : '';
19356
19662
  return path.charAt(0) == '/' ? path : '/' + path;
19357
19663
  }),
19358
19664
 
@@ -19449,7 +19755,7 @@ LocationHashbangInHtml5Url.prototype =
19449
19755
  * @return {string} hash
19450
19756
  */
19451
19757
  hash: locationGetterSetter('$$hash', function(hash) {
19452
- return hash ? hash.toString() : '';
19758
+ return hash !== null ? hash.toString() : '';
19453
19759
  }),
19454
19760
 
19455
19761
  /**
@@ -19466,6 +19772,46 @@ LocationHashbangInHtml5Url.prototype =
19466
19772
  }
19467
19773
  };
19468
19774
 
19775
+ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) {
19776
+ Location.prototype = Object.create(locationPrototype);
19777
+
19778
+ /**
19779
+ * @ngdoc method
19780
+ * @name $location#state
19781
+ *
19782
+ * @description
19783
+ * This method is getter / setter.
19784
+ *
19785
+ * Return the history state object when called without any parameter.
19786
+ *
19787
+ * Change the history state object when called with one parameter and return `$location`.
19788
+ * The state object is later passed to `pushState` or `replaceState`.
19789
+ *
19790
+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
19791
+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
19792
+ * older browsers (like IE9 or Android < 4.0), don't use this method.
19793
+ *
19794
+ * @param {object=} state State object for pushState or replaceState
19795
+ * @return {object} state
19796
+ */
19797
+ Location.prototype.state = function(state) {
19798
+ if (!arguments.length)
19799
+ return this.$$state;
19800
+
19801
+ if (Location !== LocationHtml5Url || !this.$$html5) {
19802
+ throw $locationMinErr('nostate', 'History API state support is available only ' +
19803
+ 'in HTML5 mode and only in browsers supporting HTML5 History API');
19804
+ }
19805
+ // The user might modify `stateObject` after invoking `$location.state(stateObject)`
19806
+ // but we're changing the $$state reference to $browser.state() during the $digest
19807
+ // so the modification window is narrow.
19808
+ this.$$state = isUndefined(state) ? null : state;
19809
+
19810
+ return this;
19811
+ };
19812
+ });
19813
+
19814
+
19469
19815
  function locationGetter(property) {
19470
19816
  return function() {
19471
19817
  return this[property];
@@ -19522,7 +19868,8 @@ function $LocationProvider(){
19522
19868
  var hashPrefix = '',
19523
19869
  html5Mode = {
19524
19870
  enabled: false,
19525
- requireBase: true
19871
+ requireBase: true,
19872
+ rewriteLinks: true
19526
19873
  };
19527
19874
 
19528
19875
  /**
@@ -19546,15 +19893,17 @@ function $LocationProvider(){
19546
19893
  * @name $locationProvider#html5Mode
19547
19894
  * @description
19548
19895
  * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
19549
- * If object, sets `enabled` and `requireBase` to respective values.
19550
- * - **enabled** – `{boolean}` – Sets `html5Mode.enabled`. If true, will rely on
19551
- * `history.pushState` to change urls where supported. Will fall back to hash-prefixed paths
19552
- * in browsers that do not support `pushState`.
19553
- * - **requireBase** - `{boolean}` - Sets `html5Mode.requireBase` (default: `true`). When
19554
- * html5Mode is enabled, specifies whether or not a <base> tag is required to be present. If
19555
- * `enabled` and `requireBase` are true, and a base tag is not present, an error will be
19556
- * thrown when `$location` is injected. See the
19557
- * {@link guide/$location $location guide for more information}
19896
+ * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
19897
+ * properties:
19898
+ * - **enabled** – `{boolean}` (default: false) If true, will rely on `history.pushState` to
19899
+ * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
19900
+ * support `pushState`.
19901
+ * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
19902
+ * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
19903
+ * true, and a base tag is not present, an error will be thrown when `$location` is injected.
19904
+ * See the {@link guide/$location $location guide for more information}
19905
+ * - **rewriteLinks** - `{boolean}` - (default: `false`) When html5Mode is enabled, disables
19906
+ * url rewriting for relative linksTurns off url rewriting for relative links.
19558
19907
  *
19559
19908
  * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
19560
19909
  */
@@ -19563,12 +19912,19 @@ function $LocationProvider(){
19563
19912
  html5Mode.enabled = mode;
19564
19913
  return this;
19565
19914
  } else if (isObject(mode)) {
19566
- html5Mode.enabled = isBoolean(mode.enabled) ?
19567
- mode.enabled :
19568
- html5Mode.enabled;
19569
- html5Mode.requireBase = isBoolean(mode.requireBase) ?
19570
- mode.requireBase :
19571
- html5Mode.requireBase;
19915
+
19916
+ if (isBoolean(mode.enabled)) {
19917
+ html5Mode.enabled = mode.enabled;
19918
+ }
19919
+
19920
+ if (isBoolean(mode.requireBase)) {
19921
+ html5Mode.requireBase = mode.requireBase;
19922
+ }
19923
+
19924
+ if (isBoolean(mode.rewriteLinks)) {
19925
+ html5Mode.rewriteLinks = mode.rewriteLinks;
19926
+ }
19927
+
19572
19928
  return this;
19573
19929
  } else {
19574
19930
  return html5Mode;
@@ -19580,14 +19936,21 @@ function $LocationProvider(){
19580
19936
  * @name $location#$locationChangeStart
19581
19937
  * @eventType broadcast on root scope
19582
19938
  * @description
19583
- * Broadcasted before a URL will change. This change can be prevented by calling
19939
+ * Broadcasted before a URL will change.
19940
+ *
19941
+ * This change can be prevented by calling
19584
19942
  * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
19585
19943
  * details about event object. Upon successful change
19586
19944
  * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
19587
19945
  *
19946
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
19947
+ * the browser supports the HTML5 History API.
19948
+ *
19588
19949
  * @param {Object} angularEvent Synthetic event object.
19589
19950
  * @param {string} newUrl New URL
19590
19951
  * @param {string=} oldUrl URL that was before it was changed.
19952
+ * @param {string=} newState New history state object
19953
+ * @param {string=} oldState History state object that was before it was changed.
19591
19954
  */
19592
19955
 
19593
19956
  /**
@@ -19597,9 +19960,14 @@ function $LocationProvider(){
19597
19960
  * @description
19598
19961
  * Broadcasted after a URL was changed.
19599
19962
  *
19963
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
19964
+ * the browser supports the HTML5 History API.
19965
+ *
19600
19966
  * @param {Object} angularEvent Synthetic event object.
19601
19967
  * @param {string} newUrl New URL
19602
19968
  * @param {string=} oldUrl URL that was before it was changed.
19969
+ * @param {string=} newState New history state object
19970
+ * @param {string=} oldState History state object that was before it was changed.
19603
19971
  */
19604
19972
 
19605
19973
  this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
@@ -19624,13 +19992,34 @@ function $LocationProvider(){
19624
19992
  $location = new LocationMode(appBase, '#' + hashPrefix);
19625
19993
  $location.$$parseLinkUrl(initialUrl, initialUrl);
19626
19994
 
19995
+ $location.$$state = $browser.state();
19996
+
19627
19997
  var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
19628
19998
 
19999
+ function setBrowserUrlWithFallback(url, replace, state) {
20000
+ var oldUrl = $location.url();
20001
+ var oldState = $location.$$state;
20002
+ try {
20003
+ $browser.url(url, replace, state);
20004
+
20005
+ // Make sure $location.state() returns referentially identical (not just deeply equal)
20006
+ // state object; this makes possible quick checking if the state changed in the digest
20007
+ // loop. Checking deep equality would be too expensive.
20008
+ $location.$$state = $browser.state();
20009
+ } catch (e) {
20010
+ // Restore old values if pushState fails
20011
+ $location.url(oldUrl);
20012
+ $location.$$state = oldState;
20013
+
20014
+ throw e;
20015
+ }
20016
+ }
20017
+
19629
20018
  $rootElement.on('click', function(event) {
19630
20019
  // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
19631
20020
  // currently we open nice url link and redirect then
19632
20021
 
19633
- if (event.ctrlKey || event.metaKey || event.which == 2) return;
20022
+ if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
19634
20023
 
19635
20024
  var elm = jqLite(event.target);
19636
20025
 
@@ -19656,6 +20045,9 @@ function $LocationProvider(){
19656
20045
 
19657
20046
  if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
19658
20047
  if ($location.$$parseLinkUrl(absHref, relHref)) {
20048
+ // We do a preventDefault for all urls that are part of the angular application,
20049
+ // in html5mode and also without, so that we are able to abort navigation without
20050
+ // getting double entries in the location history.
19659
20051
  event.preventDefault();
19660
20052
  // update location manually
19661
20053
  if ($location.absUrl() != $browser.url()) {
@@ -19673,52 +20065,63 @@ function $LocationProvider(){
19673
20065
  $browser.url($location.absUrl(), true);
19674
20066
  }
19675
20067
 
19676
- // update $location when $browser url changes
19677
- $browser.onUrlChange(function(newUrl) {
19678
- if ($location.absUrl() != newUrl) {
19679
- $rootScope.$evalAsync(function() {
19680
- var oldUrl = $location.absUrl();
20068
+ var initializing = true;
19681
20069
 
19682
- $location.$$parse(newUrl);
19683
- if ($rootScope.$broadcast('$locationChangeStart', newUrl,
19684
- oldUrl).defaultPrevented) {
19685
- $location.$$parse(oldUrl);
19686
- $browser.url(oldUrl);
19687
- } else {
19688
- afterLocationChange(oldUrl);
19689
- }
19690
- });
19691
- if (!$rootScope.$$phase) $rootScope.$digest();
19692
- }
20070
+ // update $location when $browser url changes
20071
+ $browser.onUrlChange(function(newUrl, newState) {
20072
+ $rootScope.$evalAsync(function() {
20073
+ var oldUrl = $location.absUrl();
20074
+ var oldState = $location.$$state;
20075
+
20076
+ $location.$$parse(newUrl);
20077
+ $location.$$state = newState;
20078
+ if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
20079
+ newState, oldState).defaultPrevented) {
20080
+ $location.$$parse(oldUrl);
20081
+ $location.$$state = oldState;
20082
+ setBrowserUrlWithFallback(oldUrl, false, oldState);
20083
+ } else {
20084
+ initializing = false;
20085
+ afterLocationChange(oldUrl, oldState);
20086
+ }
20087
+ });
20088
+ if (!$rootScope.$$phase) $rootScope.$digest();
19693
20089
  });
19694
20090
 
19695
20091
  // update browser
19696
- var changeCounter = 0;
19697
20092
  $rootScope.$watch(function $locationWatch() {
19698
20093
  var oldUrl = $browser.url();
20094
+ var oldState = $browser.state();
19699
20095
  var currentReplace = $location.$$replace;
19700
20096
 
19701
- if (!changeCounter || oldUrl != $location.absUrl()) {
19702
- changeCounter++;
20097
+ if (initializing || oldUrl !== $location.absUrl() ||
20098
+ ($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
20099
+ initializing = false;
20100
+
19703
20101
  $rootScope.$evalAsync(function() {
19704
- if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
19705
- defaultPrevented) {
20102
+ if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
20103
+ $location.$$state, oldState).defaultPrevented) {
19706
20104
  $location.$$parse(oldUrl);
20105
+ $location.$$state = oldState;
19707
20106
  } else {
19708
- $browser.url($location.absUrl(), currentReplace);
19709
- afterLocationChange(oldUrl);
20107
+ setBrowserUrlWithFallback($location.absUrl(), currentReplace,
20108
+ oldState === $location.$$state ? null : $location.$$state);
20109
+ afterLocationChange(oldUrl, oldState);
19710
20110
  }
19711
20111
  });
19712
20112
  }
20113
+
19713
20114
  $location.$$replace = false;
19714
20115
 
19715
- return changeCounter;
20116
+ // we don't need to return anything because $evalAsync will make the digest loop dirty when
20117
+ // there is a change
19716
20118
  });
19717
20119
 
19718
20120
  return $location;
19719
20121
 
19720
- function afterLocationChange(oldUrl) {
19721
- $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
20122
+ function afterLocationChange(oldUrl, oldState) {
20123
+ $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
20124
+ $location.$$state, oldState);
19722
20125
  }
19723
20126
  }];
19724
20127
  }
@@ -19977,6 +20380,11 @@ forEach({
19977
20380
  CONSTANTS[name] = constantGetter;
19978
20381
  });
19979
20382
 
20383
+ //Not quite a constant, but can be lex/parsed the same
20384
+ CONSTANTS['this'] = function(self) { return self; };
20385
+ CONSTANTS['this'].sharedGetter = true;
20386
+
20387
+
19980
20388
  //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
19981
20389
  var OPERATORS = extend(createMap(), {
19982
20390
  /* jshint bitwise : false */
@@ -21840,14 +22248,11 @@ function $RootScopeProvider(){
21840
22248
  this.$$phase = this.$parent = this.$$watchers =
21841
22249
  this.$$nextSibling = this.$$prevSibling =
21842
22250
  this.$$childHead = this.$$childTail = null;
21843
- this['this'] = this.$root = this;
22251
+ this.$root = this;
21844
22252
  this.$$destroyed = false;
21845
- this.$$asyncQueue = [];
21846
- this.$$postDigestQueue = [];
21847
22253
  this.$$listeners = {};
21848
22254
  this.$$listenerCount = {};
21849
22255
  this.$$isolateBindings = null;
21850
- this.$$applyAsyncQueue = [];
21851
22256
  }
21852
22257
 
21853
22258
  /**
@@ -21896,18 +22301,23 @@ function $RootScopeProvider(){
21896
22301
  * When creating widgets, it is useful for the widget to not accidentally read parent
21897
22302
  * state.
21898
22303
  *
22304
+ * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
22305
+ * of the newly created scope. Defaults to `this` scope if not provided.
22306
+ * This is used when creating a transclude scope to correctly place it
22307
+ * in the scope hierarchy while maintaining the correct prototypical
22308
+ * inheritance.
22309
+ *
21899
22310
  * @returns {Object} The newly created child scope.
21900
22311
  *
21901
22312
  */
21902
- $new: function(isolate) {
22313
+ $new: function(isolate, parent) {
21903
22314
  var child;
21904
22315
 
22316
+ parent = parent || this;
22317
+
21905
22318
  if (isolate) {
21906
22319
  child = new Scope();
21907
22320
  child.$root = this.$root;
21908
- // ensure that there is just one async queue per $rootScope and its children
21909
- child.$$asyncQueue = this.$$asyncQueue;
21910
- child.$$postDigestQueue = this.$$postDigestQueue;
21911
22321
  } else {
21912
22322
  // Only create a child scope class if somebody asks for one,
21913
22323
  // but cache it to allow the VM to optimize lookups.
@@ -21924,16 +22334,27 @@ function $RootScopeProvider(){
21924
22334
  }
21925
22335
  child = new this.$$ChildScope();
21926
22336
  }
21927
- child['this'] = child;
21928
- child.$parent = this;
21929
- child.$$prevSibling = this.$$childTail;
21930
- if (this.$$childHead) {
21931
- this.$$childTail.$$nextSibling = child;
21932
- this.$$childTail = child;
22337
+ child.$parent = parent;
22338
+ child.$$prevSibling = parent.$$childTail;
22339
+ if (parent.$$childHead) {
22340
+ parent.$$childTail.$$nextSibling = child;
22341
+ parent.$$childTail = child;
21933
22342
  } else {
21934
- this.$$childHead = this.$$childTail = child;
22343
+ parent.$$childHead = parent.$$childTail = child;
21935
22344
  }
22345
+
22346
+ // When the new scope is not isolated or we inherit from `this`, and
22347
+ // the parent scope is destroyed, the property `$$destroyed` is inherited
22348
+ // prototypically. In all other cases, this property needs to be set
22349
+ // when the parent scope is destroyed.
22350
+ // The listener needs to be added after the parent is set
22351
+ if (isolate || parent != this) child.$on('$destroy', destroyChild);
22352
+
21936
22353
  return child;
22354
+
22355
+ function destroyChild() {
22356
+ child.$$destroyed = true;
22357
+ }
21937
22358
  },
21938
22359
 
21939
22360
  /**
@@ -22409,8 +22830,6 @@ function $RootScopeProvider(){
22409
22830
  $digest: function() {
22410
22831
  var watch, value, last,
22411
22832
  watchers,
22412
- asyncQueue = this.$$asyncQueue,
22413
- postDigestQueue = this.$$postDigestQueue,
22414
22833
  length,
22415
22834
  dirty, ttl = TTL,
22416
22835
  next, current, target = this,
@@ -22575,6 +22994,10 @@ function $RootScopeProvider(){
22575
22994
  if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
22576
22995
  if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
22577
22996
 
22997
+ // Disable listeners, watchers and apply/digest methods
22998
+ this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
22999
+ this.$on = this.$watch = this.$watchGroup = function() { return noop; };
23000
+ this.$$listeners = {};
22578
23001
 
22579
23002
  // All of the code below is bogus code that works around V8's memory leak via optimized code
22580
23003
  // and inline caches.
@@ -22585,15 +23008,7 @@ function $RootScopeProvider(){
22585
23008
  // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
22586
23009
 
22587
23010
  this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
22588
- this.$$childTail = this.$root = null;
22589
-
22590
- // don't reset these to null in case some async task tries to register a listener/watch/task
22591
- this.$$listeners = {};
22592
- this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
22593
-
22594
- // prevent NPEs since these methods have references to properties we nulled out
22595
- this.$destroy = this.$digest = this.$apply = noop;
22596
- this.$on = this.$watch = this.$watchGroup = function() { return noop; };
23011
+ this.$$childTail = this.$root = this.$$watchers = null;
22597
23012
  },
22598
23013
 
22599
23014
  /**
@@ -22660,19 +23075,19 @@ function $RootScopeProvider(){
22660
23075
  $evalAsync: function(expr) {
22661
23076
  // if we are outside of an $digest loop and this is the first time we are scheduling async
22662
23077
  // task also schedule async auto-flush
22663
- if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
23078
+ if (!$rootScope.$$phase && !asyncQueue.length) {
22664
23079
  $browser.defer(function() {
22665
- if ($rootScope.$$asyncQueue.length) {
23080
+ if (asyncQueue.length) {
22666
23081
  $rootScope.$digest();
22667
23082
  }
22668
23083
  });
22669
23084
  }
22670
23085
 
22671
- this.$$asyncQueue.push({scope: this, expression: expr});
23086
+ asyncQueue.push({scope: this, expression: expr});
22672
23087
  },
22673
23088
 
22674
23089
  $$postDigest : function(fn) {
22675
- this.$$postDigestQueue.push(fn);
23090
+ postDigestQueue.push(fn);
22676
23091
  },
22677
23092
 
22678
23093
  /**
@@ -22756,7 +23171,7 @@ function $RootScopeProvider(){
22756
23171
  */
22757
23172
  $applyAsync: function(expr) {
22758
23173
  var scope = this;
22759
- expr && $rootScope.$$applyAsyncQueue.push($applyAsyncExpression);
23174
+ expr && applyAsyncQueue.push($applyAsyncExpression);
22760
23175
  scheduleApplyAsync();
22761
23176
 
22762
23177
  function $applyAsyncExpression() {
@@ -22965,6 +23380,11 @@ function $RootScopeProvider(){
22965
23380
 
22966
23381
  var $rootScope = new Scope();
22967
23382
 
23383
+ //The internal queues. Expose them on the $rootScope for debugging/testing purposes.
23384
+ var asyncQueue = $rootScope.$$asyncQueue = [];
23385
+ var postDigestQueue = $rootScope.$$postDigestQueue = [];
23386
+ var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
23387
+
22968
23388
  return $rootScope;
22969
23389
 
22970
23390
 
@@ -22998,10 +23418,9 @@ function $RootScopeProvider(){
22998
23418
  function initWatchVal() {}
22999
23419
 
23000
23420
  function flushApplyAsync() {
23001
- var queue = $rootScope.$$applyAsyncQueue;
23002
- while (queue.length) {
23421
+ while (applyAsyncQueue.length) {
23003
23422
  try {
23004
- queue.shift()();
23423
+ applyAsyncQueue.shift()();
23005
23424
  } catch(e) {
23006
23425
  $exceptionHandler(e);
23007
23426
  }
@@ -23080,12 +23499,9 @@ function $$SanitizeUriProvider() {
23080
23499
  return function sanitizeUri(uri, isImage) {
23081
23500
  var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
23082
23501
  var normalizedVal;
23083
- // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
23084
- if (!msie || msie >= 8 ) {
23085
- normalizedVal = urlResolve(uri).href;
23086
- if (normalizedVal !== '' && !normalizedVal.match(regex)) {
23087
- return 'unsafe:'+normalizedVal;
23088
- }
23502
+ normalizedVal = urlResolve(uri).href;
23503
+ if (normalizedVal !== '' && !normalizedVal.match(regex)) {
23504
+ return 'unsafe:'+normalizedVal;
23089
23505
  }
23090
23506
  return uri;
23091
23507
  };
@@ -24157,7 +24573,6 @@ function $SceProvider() {
24157
24573
  * @requires $document
24158
24574
  *
24159
24575
  * @property {boolean} history Does the browser support html5 history api ?
24160
- * @property {boolean} hashchange Does the browser support hashchange event ?
24161
24576
  * @property {boolean} transitions Does the browser support CSS transition events ?
24162
24577
  * @property {boolean} animations Does the browser support CSS animation events ?
24163
24578
  *
@@ -24214,9 +24629,6 @@ function $SnifferProvider() {
24214
24629
  // jshint -W018
24215
24630
  history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
24216
24631
  // jshint +W018
24217
- hashchange: 'onhashchange' in $window &&
24218
- // IE8 compatible mode lies
24219
- (!documentMode || documentMode > 7),
24220
24632
  hasEvent: function(event) {
24221
24633
  // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
24222
24634
  // it. In particular the event is not fired when backspace or delete key are pressed or
@@ -25686,7 +26098,7 @@ function limitToFilter(){
25686
26098
  * correctly, make sure they are actually being saved as numbers and not strings.
25687
26099
  *
25688
26100
  * @param {Array} array The array to sort.
25689
- * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
26101
+ * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
25690
26102
  * used by the comparator to determine the order of elements.
25691
26103
  *
25692
26104
  * Can be one of:
@@ -25699,10 +26111,13 @@ function limitToFilter(){
25699
26111
  * is interpreted as a property name to be used in comparisons (for example `"special name"`
25700
26112
  * to sort object by the value of their `special name` property). An expression can be
25701
26113
  * optionally prefixed with `+` or `-` to control ascending or descending sort order
25702
- * (for example, `+name` or `-name`).
26114
+ * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
26115
+ * element itself is used to compare where sorting.
25703
26116
  * - `Array`: An array of function or string predicates. The first predicate in the array
25704
26117
  * is used for sorting, but when two items are equivalent, the next predicate is used.
25705
26118
  *
26119
+ * If the predicate is missing or empty then it defaults to `'+'`.
26120
+ *
25706
26121
  * @param {boolean=} reverse Reverse the order of the array.
25707
26122
  * @returns {Array} Sorted copy of the source array.
25708
26123
  *
@@ -25791,8 +26206,8 @@ orderByFilter.$inject = ['$parse'];
25791
26206
  function orderByFilter($parse){
25792
26207
  return function(array, sortPredicate, reverseOrder) {
25793
26208
  if (!(isArrayLike(array))) return array;
25794
- if (!sortPredicate) return array;
25795
26209
  sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
26210
+ if (sortPredicate.length === 0) { sortPredicate = ['+']; }
25796
26211
  sortPredicate = sortPredicate.map(function(predicate){
25797
26212
  var descending = false, get = predicate || identity;
25798
26213
  if (isString(predicate)) {
@@ -25800,6 +26215,12 @@ function orderByFilter($parse){
25800
26215
  descending = predicate.charAt(0) == '-';
25801
26216
  predicate = predicate.substring(1);
25802
26217
  }
26218
+ if ( predicate === '' ) {
26219
+ // Effectively no predicate was passed so we compare identity
26220
+ return reverseComparator(function(a,b) {
26221
+ return compare(a, b);
26222
+ }, descending);
26223
+ }
25803
26224
  get = $parse(predicate);
25804
26225
  if (get.constant) {
25805
26226
  var key = get();
@@ -25875,22 +26296,6 @@ function ngDirective(directive) {
25875
26296
  var htmlAnchorDirective = valueFn({
25876
26297
  restrict: 'E',
25877
26298
  compile: function(element, attr) {
25878
-
25879
- if (msie <= 8) {
25880
-
25881
- // turn <a href ng-click="..">link</a> into a stylable link in IE
25882
- // but only if it doesn't have name attribute, in which case it's an anchor
25883
- if (!attr.href && !attr.name) {
25884
- attr.$set('href', '');
25885
- }
25886
-
25887
- // add a comment node to anchors to workaround IE bug that causes element content to be reset
25888
- // to new attribute content if attribute is updated with value containing @ and element also
25889
- // contains value with @
25890
- // see issue #1949
25891
- element.append(document.createComment('IE fix'));
25892
- }
25893
-
25894
26299
  if (!attr.href && !attr.xlinkHref && !attr.name) {
25895
26300
  return function(scope, element) {
25896
26301
  // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
@@ -26338,11 +26743,9 @@ var nullFormCtrl = {
26338
26743
  $$renameControl: nullFormRenameControl,
26339
26744
  $removeControl: noop,
26340
26745
  $setValidity: noop,
26341
- $$setPending: noop,
26342
26746
  $setDirty: noop,
26343
26747
  $setPristine: noop,
26344
- $setSubmitted: noop,
26345
- $$clearControlValidity: noop
26748
+ $setSubmitted: noop
26346
26749
  },
26347
26750
  SUBMITTED_CLASS = 'ng-submitted';
26348
26751
 
@@ -26407,9 +26810,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
26407
26810
 
26408
26811
  parentForm.$addControl(form);
26409
26812
 
26410
- // Setup initial state of the control
26411
- element.addClass(PRISTINE_CLASS);
26412
-
26413
26813
  /**
26414
26814
  * @ngdoc method
26415
26815
  * @name form.FormController#$rollbackViewValue
@@ -26782,10 +27182,14 @@ var formDirectiveFactory = function(isNgForm) {
26782
27182
  name: 'form',
26783
27183
  restrict: isNgForm ? 'EAC' : 'E',
26784
27184
  controller: FormController,
26785
- compile: function() {
27185
+ compile: function ngFormCompile(formElement) {
27186
+ // Setup initial state of the control
27187
+ formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
27188
+
26786
27189
  return {
26787
- pre: function(scope, formElement, attr, controller) {
26788
- if (!attr.action) {
27190
+ pre: function ngFormPreLink(scope, formElement, attr, controller) {
27191
+ // if `action` attr is not present on the form, prevent the default action (submission)
27192
+ if (!('action' in attr)) {
26789
27193
  // we can't use jq events because if a form is destroyed during submission the default
26790
27194
  // action is not prevented. see #1238
26791
27195
  //
@@ -27939,16 +28343,15 @@ function createDateInputType(type, regexp, parseDate, format) {
27939
28343
  badInputChecker(scope, element, attr, ctrl);
27940
28344
  baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
27941
28345
  var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
28346
+ var previousDate;
27942
28347
 
27943
28348
  ctrl.$$parserName = type;
27944
28349
  ctrl.$parsers.push(function(value) {
27945
28350
  if (ctrl.$isEmpty(value)) return null;
27946
28351
  if (regexp.test(value)) {
27947
- var previousDate = ctrl.$modelValue;
27948
- if (previousDate && timezone === 'UTC') {
27949
- var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
27950
- previousDate = new Date(previousDate.getTime() + timezoneOffset);
27951
- }
28352
+ // Note: We cannot read ctrl.$modelValue, as there might be a different
28353
+ // parser/formatter in the processing chain so that the model
28354
+ // contains some different data format!
27952
28355
  var parsedDate = parseDate(value, previousDate);
27953
28356
  if (timezone === 'UTC') {
27954
28357
  parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
@@ -27959,8 +28362,18 @@ function createDateInputType(type, regexp, parseDate, format) {
27959
28362
  });
27960
28363
 
27961
28364
  ctrl.$formatters.push(function(value) {
27962
- if (isDate(value)) {
28365
+ if (!ctrl.$isEmpty(value)) {
28366
+ if (!isDate(value)) {
28367
+ throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
28368
+ }
28369
+ previousDate = value;
28370
+ if (previousDate && timezone === 'UTC') {
28371
+ var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
28372
+ previousDate = new Date(previousDate.getTime() + timezoneOffset);
28373
+ }
27963
28374
  return $filter('date')(value, format, timezone);
28375
+ } else {
28376
+ previousDate = null;
27964
28377
  }
27965
28378
  return '';
27966
28379
  });
@@ -27986,6 +28399,11 @@ function createDateInputType(type, regexp, parseDate, format) {
27986
28399
  ctrl.$validate();
27987
28400
  });
27988
28401
  }
28402
+ // Override the standard $isEmpty to detect invalid dates as well
28403
+ ctrl.$isEmpty = function(value) {
28404
+ // Invalid Date: getTime() returns NaN
28405
+ return !value || (value.getTime && value.getTime() !== value.getTime());
28406
+ };
27989
28407
 
27990
28408
  function parseObservedDateValue(val) {
27991
28409
  return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
@@ -28300,10 +28718,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
28300
28718
  return {
28301
28719
  restrict: 'E',
28302
28720
  require: ['?ngModel'],
28303
- link: function(scope, element, attr, ctrls) {
28304
- if (ctrls[0]) {
28305
- (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
28306
- $browser, $filter, $parse);
28721
+ link: {
28722
+ pre: function(scope, element, attr, ctrls) {
28723
+ if (ctrls[0]) {
28724
+ (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
28725
+ $browser, $filter, $parse);
28726
+ }
28307
28727
  }
28308
28728
  }
28309
28729
  };
@@ -28604,11 +29024,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
28604
29024
  var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
28605
29025
  currentValidationRunId = 0;
28606
29026
 
28607
- // Setup initial state of the control
28608
- $element
28609
- .addClass(PRISTINE_CLASS)
28610
- .addClass(UNTOUCHED_CLASS);
28611
-
28612
29027
  /**
28613
29028
  * @ngdoc method
28614
29029
  * @name ngModel.NgModelController#$setValidity
@@ -28901,14 +29316,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
28901
29316
  };
28902
29317
 
28903
29318
  this.$$parseAndValidate = function() {
28904
- var parserValid = true,
28905
- viewValue = ctrl.$$lastCommittedViewValue,
28906
- modelValue = viewValue;
28907
- for(var i = 0; i < ctrl.$parsers.length; i++) {
28908
- modelValue = ctrl.$parsers[i](modelValue);
28909
- if (isUndefined(modelValue)) {
28910
- parserValid = false;
28911
- break;
29319
+ var viewValue = ctrl.$$lastCommittedViewValue;
29320
+ var modelValue = viewValue;
29321
+ var parserValid = isUndefined(modelValue) ? undefined : true;
29322
+
29323
+ if (parserValid) {
29324
+ for(var i = 0; i < ctrl.$parsers.length; i++) {
29325
+ modelValue = ctrl.$parsers[i](modelValue);
29326
+ if (isUndefined(modelValue)) {
29327
+ parserValid = false;
29328
+ break;
29329
+ }
28912
29330
  }
28913
29331
  }
28914
29332
  if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
@@ -29225,42 +29643,51 @@ var ngModelDirective = function() {
29225
29643
  restrict: 'A',
29226
29644
  require: ['ngModel', '^?form', '^?ngModelOptions'],
29227
29645
  controller: NgModelController,
29228
- link: {
29229
- pre: function(scope, element, attr, ctrls) {
29230
- var modelCtrl = ctrls[0],
29231
- formCtrl = ctrls[1] || nullFormCtrl;
29646
+ // Prelink needs to run before any input directive
29647
+ // so that we can set the NgModelOptions in NgModelController
29648
+ // before anyone else uses it.
29649
+ priority: 1,
29650
+ compile: function ngModelCompile(element) {
29651
+ // Setup initial state of the control
29652
+ element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
29232
29653
 
29233
- modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
29654
+ return {
29655
+ pre: function ngModelPreLink(scope, element, attr, ctrls) {
29656
+ var modelCtrl = ctrls[0],
29657
+ formCtrl = ctrls[1] || nullFormCtrl;
29234
29658
 
29235
- // notify others, especially parent forms
29236
- formCtrl.$addControl(modelCtrl);
29659
+ modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
29237
29660
 
29238
- attr.$observe('name', function(newValue) {
29239
- if (modelCtrl.$name !== newValue) {
29240
- formCtrl.$$renameControl(modelCtrl, newValue);
29241
- }
29242
- });
29661
+ // notify others, especially parent forms
29662
+ formCtrl.$addControl(modelCtrl);
29243
29663
 
29244
- scope.$on('$destroy', function() {
29245
- formCtrl.$removeControl(modelCtrl);
29246
- });
29247
- },
29248
- post: function(scope, element, attr, ctrls) {
29249
- var modelCtrl = ctrls[0];
29250
- if (modelCtrl.$options && modelCtrl.$options.updateOn) {
29251
- element.on(modelCtrl.$options.updateOn, function(ev) {
29252
- modelCtrl.$$debounceViewValueCommit(ev && ev.type);
29664
+ attr.$observe('name', function(newValue) {
29665
+ if (modelCtrl.$name !== newValue) {
29666
+ formCtrl.$$renameControl(modelCtrl, newValue);
29667
+ }
29253
29668
  });
29254
- }
29255
29669
 
29256
- element.on('blur', function(ev) {
29257
- if (modelCtrl.$touched) return;
29670
+ scope.$on('$destroy', function() {
29671
+ formCtrl.$removeControl(modelCtrl);
29672
+ });
29673
+ },
29674
+ post: function ngModelPostLink(scope, element, attr, ctrls) {
29675
+ var modelCtrl = ctrls[0];
29676
+ if (modelCtrl.$options && modelCtrl.$options.updateOn) {
29677
+ element.on(modelCtrl.$options.updateOn, function(ev) {
29678
+ modelCtrl.$$debounceViewValueCommit(ev && ev.type);
29679
+ });
29680
+ }
29681
+
29682
+ element.on('blur', function(ev) {
29683
+ if (modelCtrl.$touched) return;
29258
29684
 
29259
- scope.$apply(function() {
29260
- modelCtrl.$setTouched();
29685
+ scope.$apply(function() {
29686
+ modelCtrl.$setTouched();
29687
+ });
29261
29688
  });
29262
- });
29263
- }
29689
+ }
29690
+ };
29264
29691
  }
29265
29692
  };
29266
29693
  };
@@ -29815,8 +30242,9 @@ function addSetValidityMethod(context) {
29815
30242
  parentForm = context.parentForm,
29816
30243
  $animate = context.$animate;
29817
30244
 
30245
+ classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
30246
+
29818
30247
  ctrl.$setValidity = setValidity;
29819
- toggleValidationCss('', true);
29820
30248
 
29821
30249
  function setValidity(validationErrorKey, state, options) {
29822
30250
  if (state === undefined) {
@@ -29966,11 +30394,9 @@ var ngBindDirective = ['$compile', function($compile) {
29966
30394
  $compile.$$addBindingClass(templateElement);
29967
30395
  return function ngBindLink(scope, element, attr) {
29968
30396
  $compile.$$addBindingInfo(element, attr.ngBind);
30397
+ element = element[0];
29969
30398
  scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
29970
- // We are purposefully using == here rather than === because we want to
29971
- // catch when value is "null or undefined"
29972
- // jshint -W041
29973
- element.text(value == undefined ? '' : value);
30399
+ element.textContent = value === undefined ? '' : value;
29974
30400
  });
29975
30401
  };
29976
30402
  }
@@ -30036,8 +30462,9 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
30036
30462
  return function ngBindTemplateLink(scope, element, attr) {
30037
30463
  var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
30038
30464
  $compile.$$addBindingInfo(element, interpolateFn.expressions);
30465
+ element = element[0];
30039
30466
  attr.$observe('ngBindTemplate', function(value) {
30040
- element.text(value);
30467
+ element.textContent = value === undefined ? '' : value;
30041
30468
  });
30042
30469
  };
30043
30470
  }
@@ -30054,7 +30481,10 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
30054
30481
  * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
30055
30482
  * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
30056
30483
  * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
30057
- * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to
30484
+ * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
30485
+ * include "angular-sanitize.js" in your application.
30486
+ *
30487
+ * You may also bypass sanitization for values you know are safe. To do so, bind to
30058
30488
  * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
30059
30489
  * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
30060
30490
  *
@@ -30816,7 +31246,125 @@ var ngControllerDirective = [function() {
30816
31246
  ...
30817
31247
  </html>
30818
31248
  ```
30819
- */
31249
+ * @example
31250
+ // Note: the suffix `.csp` in the example name triggers
31251
+ // csp mode in our http server!
31252
+ <example name="example.csp" module="cspExample" ng-csp="true">
31253
+ <file name="index.html">
31254
+ <div ng-controller="MainController as ctrl">
31255
+ <div>
31256
+ <button ng-click="ctrl.inc()" id="inc">Increment</button>
31257
+ <span id="counter">
31258
+ {{ctrl.counter}}
31259
+ </span>
31260
+ </div>
31261
+
31262
+ <div>
31263
+ <button ng-click="ctrl.evil()" id="evil">Evil</button>
31264
+ <span id="evilError">
31265
+ {{ctrl.evilError}}
31266
+ </span>
31267
+ </div>
31268
+ </div>
31269
+ </file>
31270
+ <file name="script.js">
31271
+ angular.module('cspExample', [])
31272
+ .controller('MainController', function() {
31273
+ this.counter = 0;
31274
+ this.inc = function() {
31275
+ this.counter++;
31276
+ };
31277
+ this.evil = function() {
31278
+ // jshint evil:true
31279
+ try {
31280
+ eval('1+2');
31281
+ } catch (e) {
31282
+ this.evilError = e.message;
31283
+ }
31284
+ };
31285
+ });
31286
+ </file>
31287
+ <file name="protractor.js" type="protractor">
31288
+ var util, webdriver;
31289
+
31290
+ var incBtn = element(by.id('inc'));
31291
+ var counter = element(by.id('counter'));
31292
+ var evilBtn = element(by.id('evil'));
31293
+ var evilError = element(by.id('evilError'));
31294
+
31295
+ function getAndClearSevereErrors() {
31296
+ return browser.manage().logs().get('browser').then(function(browserLog) {
31297
+ return browserLog.filter(function(logEntry) {
31298
+ return logEntry.level.value > webdriver.logging.Level.WARNING.value;
31299
+ });
31300
+ });
31301
+ }
31302
+
31303
+ function clearErrors() {
31304
+ getAndClearSevereErrors();
31305
+ }
31306
+
31307
+ function expectNoErrors() {
31308
+ getAndClearSevereErrors().then(function(filteredLog) {
31309
+ expect(filteredLog.length).toEqual(0);
31310
+ if (filteredLog.length) {
31311
+ console.log('browser console errors: ' + util.inspect(filteredLog));
31312
+ }
31313
+ });
31314
+ }
31315
+
31316
+ function expectError(regex) {
31317
+ getAndClearSevereErrors().then(function(filteredLog) {
31318
+ var found = false;
31319
+ filteredLog.forEach(function(log) {
31320
+ if (log.message.match(regex)) {
31321
+ found = true;
31322
+ }
31323
+ });
31324
+ if (!found) {
31325
+ throw new Error('expected an error that matches ' + regex);
31326
+ }
31327
+ });
31328
+ }
31329
+
31330
+ beforeEach(function() {
31331
+ util = require('util');
31332
+ webdriver = require('protractor/node_modules/selenium-webdriver');
31333
+ });
31334
+
31335
+ // For now, we only test on Chrome,
31336
+ // as Safari does not load the page with Protractor's injected scripts,
31337
+ // and Firefox webdriver always disables content security policy (#6358)
31338
+ if (browser.params.browser !== 'chrome') {
31339
+ return;
31340
+ }
31341
+
31342
+ it('should not report errors when the page is loaded', function() {
31343
+ // clear errors so we are not dependent on previous tests
31344
+ clearErrors();
31345
+ // Need to reload the page as the page is already loaded when
31346
+ // we come here
31347
+ browser.driver.getCurrentUrl().then(function(url) {
31348
+ browser.get(url);
31349
+ });
31350
+ expectNoErrors();
31351
+ });
31352
+
31353
+ it('should evaluate expressions', function() {
31354
+ expect(counter.getText()).toEqual('0');
31355
+ incBtn.click();
31356
+ expect(counter.getText()).toEqual('1');
31357
+ expectNoErrors();
31358
+ });
31359
+
31360
+ it('should throw and report an error when using "eval"', function() {
31361
+ evilBtn.click();
31362
+ expect(evilError.getText()).toMatch(/Content Security Policy/);
31363
+ expectError(/Content Security Policy/);
31364
+ });
31365
+ </file>
31366
+ </example>
31367
+ */
30820
31368
 
30821
31369
  // ngCsp is not implemented as a proper directive any more, because we need it be processed while we
30822
31370
  // bootstrap the system (before $parse is instantiated), for this reason we just have
@@ -31323,7 +31871,7 @@ forEach(
31323
31871
  * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
31324
31872
  * is created when the element is restored. The scope created within `ngIf` inherits from
31325
31873
  * its parent scope using
31326
- * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance).
31874
+ * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
31327
31875
  * An important implication of this is if `ngModel` is used within `ngIf` to bind to
31328
31876
  * a javascript primitive defined in the parent scope. In this case any modifications made to the
31329
31877
  * variable within the child scope will override (hide) the value in the parent scope.
@@ -31337,8 +31885,8 @@ forEach(
31337
31885
  * and `leave` effects.
31338
31886
  *
31339
31887
  * @animations
31340
- * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container
31341
- * leave - happens just before the ngIf contents are removed from the DOM
31888
+ * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
31889
+ * leave - happens just before the `ngIf` contents are removed from the DOM
31342
31890
  *
31343
31891
  * @element ANY
31344
31892
  * @scope
@@ -32478,6 +33026,8 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
32478
33026
  };
32479
33027
  }];
32480
33028
 
33029
+ var NG_HIDE_CLASS = 'ng-hide';
33030
+ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
32481
33031
  /**
32482
33032
  * @ngdoc directive
32483
33033
  * @name ngShow
@@ -32639,7 +33189,11 @@ var ngShowDirective = ['$animate', function($animate) {
32639
33189
  multiElement: true,
32640
33190
  link: function(scope, element, attr) {
32641
33191
  scope.$watch(attr.ngShow, function ngShowWatchAction(value){
32642
- $animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide');
33192
+ // we're adding a temporary, animation-specific class for ng-hide since this way
33193
+ // we can control when the element is actually displayed on screen without having
33194
+ // to have a global/greedy CSS selector that breaks when other animations are run.
33195
+ // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
33196
+ $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
32643
33197
  });
32644
33198
  }
32645
33199
  };
@@ -32794,7 +33348,9 @@ var ngHideDirective = ['$animate', function($animate) {
32794
33348
  multiElement: true,
32795
33349
  link: function(scope, element, attr) {
32796
33350
  scope.$watch(attr.ngHide, function ngHideWatchAction(value){
32797
- $animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide');
33351
+ // The comment inside of the ngShowDirective explains why we add and
33352
+ // remove a temporary class for the show/hide animation
33353
+ $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
32798
33354
  });
32799
33355
  }
32800
33356
  };
@@ -33216,6 +33772,12 @@ var ngOptionsMinErr = minErr('ngOptions');
33216
33772
  * be bound to string values at present.
33217
33773
  * </div>
33218
33774
  *
33775
+ * <div class="alert alert-info">
33776
+ * **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
33777
+ * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
33778
+ * or property name (for object data sources) of the value within the collection.
33779
+ * </div>
33780
+ *
33219
33781
  * @param {string} ngModel Assignable angular expression to data-bind to.
33220
33782
  * @param {string=} name Property name of the form under which the control is published.
33221
33783
  * @param {string=} required The control is considered valid only if value is entered.
@@ -33250,7 +33812,25 @@ var ngOptionsMinErr = minErr('ngOptions');
33250
33812
  * DOM element.
33251
33813
  * * `trackexpr`: Used when working with an array of objects. The result of this expression will be
33252
33814
  * used to identify the objects in the array. The `trackexpr` will most likely refer to the
33253
- * `value` variable (e.g. `value.propertyName`).
33815
+ * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
33816
+ * even when the options are recreated (e.g. reloaded from the server).
33817
+
33818
+ * <div class="alert alert-info">
33819
+ * **Note:** Using `select as` together with `trackexpr` is not possible (and will throw).
33820
+ * Reasoning:
33821
+ * - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
33822
+ * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItemß'}}],
33823
+ * $scope.selected = {name: 'aSubItem'};
33824
+ * - track by is always applied to `value`, with purpose to preserve the selection,
33825
+ * (to `item` in this case)
33826
+ * - to calculate whether an item is selected we do the following:
33827
+ * 1. apply `track by` to the values in the array, e.g.
33828
+ * In the example: [1,2]
33829
+ * 2. apply `track by` to the already selected value in `ngModel`:
33830
+ * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
33831
+ * value from `ngModel` is `{name: aSubItem}`.
33832
+ *
33833
+ * </div>
33254
33834
  *
33255
33835
  * @example
33256
33836
  <example module="selectExample">
@@ -33507,6 +34087,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33507
34087
 
33508
34088
  var displayFn = $parse(match[2] || match[1]),
33509
34089
  valueName = match[4] || match[6],
34090
+ selectAs = / as /.test(match[0]) && match[1],
34091
+ selectAsFn = selectAs ? $parse(selectAs) : null,
33510
34092
  keyName = match[5],
33511
34093
  groupByFn = $parse(match[3] || ''),
33512
34094
  valueFn = $parse(match[2] ? match[1] : valueName),
@@ -33517,7 +34099,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33517
34099
  // We try to reuse these if possible
33518
34100
  // - optionGroupsCache[0] is the options with no option group
33519
34101
  // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
33520
- optionGroupsCache = [[{element: selectElement, label:''}]];
34102
+ optionGroupsCache = [[{element: selectElement, label:''}]],
34103
+ //re-usable object to represent option's locals
34104
+ locals = {};
34105
+
34106
+ if (trackFn && selectAsFn) {
34107
+ throw ngOptionsMinErr('trkslct',
34108
+ "Comprehension expression cannot contain both selectAs '{0}' " +
34109
+ "and trackBy '{1}' expressions.",
34110
+ selectAs, track);
34111
+ }
33521
34112
 
33522
34113
  if (nullOption) {
33523
34114
  // compile the element since there might be bindings in it
@@ -33535,103 +34126,110 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33535
34126
  // clear contents, we'll add what's needed based on the model
33536
34127
  selectElement.empty();
33537
34128
 
33538
- selectElement.on('change', function() {
34129
+ selectElement.on('change', selectionChanged);
34130
+
34131
+ ctrl.$render = render;
34132
+
34133
+ scope.$watchCollection(valuesFn, scheduleRendering);
34134
+ scope.$watchCollection(getLabels, scheduleRendering);
34135
+
34136
+ if (multiple) {
34137
+ scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
34138
+ }
34139
+
34140
+ // ------------------------------------------------------------------ //
34141
+
34142
+ function callExpression(exprFn, key, value) {
34143
+ locals[valueName] = value;
34144
+ if (keyName) locals[keyName] = key;
34145
+ return exprFn(scope, locals);
34146
+ }
34147
+
34148
+ function selectionChanged() {
33539
34149
  scope.$apply(function() {
33540
34150
  var optionGroup,
33541
34151
  collection = valuesFn(scope) || [],
33542
- locals = {},
33543
34152
  key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
33544
-
34153
+ var viewValue;
33545
34154
  if (multiple) {
33546
- value = [];
33547
- for (groupIndex = 0, groupLength = optionGroupsCache.length;
33548
- groupIndex < groupLength;
33549
- groupIndex++) {
33550
- // list of options for that group. (first item has the parent)
33551
- optionGroup = optionGroupsCache[groupIndex];
33552
-
33553
- for(index = 1, length = optionGroup.length; index < length; index++) {
33554
- if ((optionElement = optionGroup[index].element)[0].selected) {
33555
- key = optionElement.val();
33556
- if (keyName) locals[keyName] = key;
33557
- if (trackFn) {
33558
- for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
33559
- locals[valueName] = collection[trackIndex];
33560
- if (trackFn(scope, locals) == key) break;
33561
- }
33562
- } else {
33563
- locals[valueName] = collection[key];
33564
- }
33565
- value.push(valueFn(scope, locals));
33566
- }
33567
- }
33568
- }
34155
+ viewValue = [];
34156
+ forEach(selectElement.val(), function(selectedKey) {
34157
+ viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
34158
+ });
33569
34159
  } else {
33570
- key = selectElement.val();
33571
- if (key == '?') {
33572
- value = undefined;
33573
- } else if (key === ''){
33574
- value = null;
33575
- } else {
33576
- if (trackFn) {
33577
- for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
33578
- locals[valueName] = collection[trackIndex];
33579
- if (trackFn(scope, locals) == key) {
33580
- value = valueFn(scope, locals);
33581
- break;
33582
- }
33583
- }
33584
- } else {
33585
- locals[valueName] = collection[key];
33586
- if (keyName) locals[keyName] = key;
33587
- value = valueFn(scope, locals);
33588
- }
33589
- }
34160
+ var selectedKey = selectElement.val();
34161
+ viewValue = getViewValue(selectedKey, collection[selectedKey]);
33590
34162
  }
33591
- ctrl.$setViewValue(value);
34163
+ ctrl.$setViewValue(viewValue);
33592
34164
  render();
33593
34165
  });
33594
- });
34166
+ }
33595
34167
 
33596
- ctrl.$render = render;
34168
+ function getViewValue(key, value) {
34169
+ if (key === '?') {
34170
+ return undefined;
34171
+ } else if (key === '') {
34172
+ return null;
34173
+ } else {
34174
+ var viewValueFn = selectAsFn ? selectAsFn : valueFn;
34175
+ return callExpression(viewValueFn, key, value);
34176
+ }
34177
+ }
33597
34178
 
33598
- scope.$watchCollection(valuesFn, scheduleRendering);
33599
- scope.$watchCollection(function () {
33600
- var locals = {},
33601
- values = valuesFn(scope);
33602
- if (values) {
33603
- var toDisplay = new Array(values.length);
34179
+ function getLabels() {
34180
+ var values = valuesFn(scope);
34181
+ var toDisplay;
34182
+ if (values && isArray(values)) {
34183
+ toDisplay = new Array(values.length);
33604
34184
  for (var i = 0, ii = values.length; i < ii; i++) {
33605
- locals[valueName] = values[i];
33606
- toDisplay[i] = displayFn(scope, locals);
34185
+ toDisplay[i] = callExpression(displayFn, i, values[i]);
33607
34186
  }
33608
34187
  return toDisplay;
34188
+ } else if (values) {
34189
+ // TODO: Add a test for this case
34190
+ toDisplay = {};
34191
+ for (var prop in values) {
34192
+ if (values.hasOwnProperty(prop)) {
34193
+ toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
34194
+ }
34195
+ }
33609
34196
  }
33610
- }, scheduleRendering);
33611
-
33612
- if (multiple) {
33613
- scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
34197
+ return toDisplay;
33614
34198
  }
33615
34199
 
33616
-
33617
- function getSelectedSet() {
33618
- var selectedSet = false;
34200
+ function createIsSelectedFn(viewValue) {
34201
+ var selectedSet;
33619
34202
  if (multiple) {
33620
- var modelValue = ctrl.$modelValue;
33621
- if (trackFn && isArray(modelValue)) {
34203
+ if (!selectAs && trackFn && isArray(viewValue)) {
34204
+
33622
34205
  selectedSet = new HashMap([]);
33623
- var locals = {};
33624
- for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) {
33625
- locals[valueName] = modelValue[trackIndex];
33626
- selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]);
34206
+ for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
34207
+ // tracking by key
34208
+ selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
33627
34209
  }
33628
34210
  } else {
33629
- selectedSet = new HashMap(modelValue);
34211
+ selectedSet = new HashMap(viewValue);
33630
34212
  }
34213
+ } else if (!selectAsFn && trackFn) {
34214
+ viewValue = callExpression(trackFn, null, viewValue);
33631
34215
  }
33632
- return selectedSet;
33633
- }
34216
+ return function isSelected(key, value) {
34217
+ var compareValueFn;
34218
+ if (selectAsFn) {
34219
+ compareValueFn = selectAsFn;
34220
+ } else if (trackFn) {
34221
+ compareValueFn = trackFn;
34222
+ } else {
34223
+ compareValueFn = valueFn;
34224
+ }
33634
34225
 
34226
+ if (multiple) {
34227
+ return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
34228
+ } else {
34229
+ return viewValue == callExpression(compareValueFn, key, value);
34230
+ }
34231
+ };
34232
+ }
33635
34233
 
33636
34234
  function scheduleRendering() {
33637
34235
  if (!renderScheduled) {
@@ -33640,78 +34238,64 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33640
34238
  }
33641
34239
  }
33642
34240
 
33643
-
33644
34241
  function render() {
33645
34242
  renderScheduled = false;
33646
34243
 
33647
- // Temporary location for the option groups before we render them
34244
+ // Temporary location for the option groups before we render them
33648
34245
  var optionGroups = {'':[]},
33649
34246
  optionGroupNames = [''],
33650
34247
  optionGroupName,
33651
34248
  optionGroup,
33652
34249
  option,
33653
34250
  existingParent, existingOptions, existingOption,
33654
- modelValue = ctrl.$modelValue,
34251
+ viewValue = ctrl.$viewValue,
33655
34252
  values = valuesFn(scope) || [],
33656
34253
  keys = keyName ? sortedKeys(values) : values,
33657
34254
  key,
34255
+ value,
33658
34256
  groupLength, length,
33659
34257
  groupIndex, index,
33660
- locals = {},
33661
34258
  selected,
33662
- selectedSet = getSelectedSet(),
34259
+ isSelected = createIsSelectedFn(viewValue),
34260
+ anySelected = false,
33663
34261
  lastElement,
33664
34262
  element,
33665
34263
  label;
33666
34264
 
33667
-
33668
34265
  // We now build up the list of options we need (we merge later)
33669
34266
  for (index = 0; length = keys.length, index < length; index++) {
33670
-
33671
34267
  key = index;
33672
34268
  if (keyName) {
33673
34269
  key = keys[index];
33674
34270
  if ( key.charAt(0) === '$' ) continue;
33675
- locals[keyName] = key;
33676
34271
  }
34272
+ value = values[key];
33677
34273
 
33678
- locals[valueName] = values[key];
33679
-
33680
- optionGroupName = groupByFn(scope, locals) || '';
34274
+ optionGroupName = callExpression(groupByFn, key, value) || '';
33681
34275
  if (!(optionGroup = optionGroups[optionGroupName])) {
33682
34276
  optionGroup = optionGroups[optionGroupName] = [];
33683
34277
  optionGroupNames.push(optionGroupName);
33684
34278
  }
33685
- if (multiple) {
33686
- selected = isDefined(
33687
- selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals))
33688
- );
33689
- } else {
33690
- if (trackFn) {
33691
- var modelCast = {};
33692
- modelCast[valueName] = modelValue;
33693
- selected = trackFn(scope, modelCast) === trackFn(scope, locals);
33694
- } else {
33695
- selected = modelValue === valueFn(scope, locals);
33696
- }
33697
- selectedSet = selectedSet || selected; // see if at least one item is selected
33698
- }
33699
- label = displayFn(scope, locals); // what will be seen by the user
34279
+
34280
+ selected = isSelected(key, value);
34281
+ anySelected = anySelected || selected;
34282
+
34283
+ label = callExpression(displayFn, key, value); // what will be seen by the user
33700
34284
 
33701
34285
  // doing displayFn(scope, locals) || '' overwrites zero values
33702
34286
  label = isDefined(label) ? label : '';
33703
34287
  optionGroup.push({
33704
34288
  // either the index into array or key from object
33705
- id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index),
34289
+ id: (keyName ? keys[index] : index),
33706
34290
  label: label,
33707
34291
  selected: selected // determine if we should be selected
33708
34292
  });
33709
34293
  }
33710
34294
  if (!multiple) {
33711
- if (nullOption || modelValue === null) {
34295
+ if (nullOption || viewValue === null) {
33712
34296
  // insert null option if we have a placeholder, or the model is null
33713
- optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
33714
- } else if (!selectedSet) {
34297
+ optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
34298
+ } else if (!anySelected) {
33715
34299
  // option could not be found, we have to insert the undefined item
33716
34300
  optionGroups[''].unshift({id:'?', label:'', selected:true});
33717
34301
  }
@@ -33792,6 +34376,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33792
34376
  id: option.id,
33793
34377
  selected: option.selected
33794
34378
  });
34379
+ selectCtrl.addOption(option.label, element);
33795
34380
  if (lastElement) {
33796
34381
  lastElement.after(element);
33797
34382
  } else {
@@ -33803,7 +34388,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
33803
34388
  // remove any excessive OPTIONs in a group
33804
34389
  index++; // increment since the existingOptions[0] is parent element not OPTION
33805
34390
  while(existingOptions.length > index) {
33806
- existingOptions.pop().element.remove();
34391
+ option = existingOptions.pop();
34392
+ selectCtrl.removeOption(option.label);
34393
+ option.element.remove();
33807
34394
  }
33808
34395
  }
33809
34396
  // remove any excessive OPTGROUPs from select
@@ -33839,11 +34426,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
33839
34426
  selectCtrl = parent.data(selectCtrlName) ||
33840
34427
  parent.parent().data(selectCtrlName); // in case we are in optgroup
33841
34428
 
33842
- if (selectCtrl && selectCtrl.databound) {
33843
- // For some reason Opera defaults to true and if not overridden this messes up the repeater.
33844
- // We don't want the view to drive the initialization of the model anyway.
33845
- element.prop('selected', false);
33846
- } else {
34429
+ if (!selectCtrl || !selectCtrl.databound) {
33847
34430
  selectCtrl = nullSelectCtrl;
33848
34431
  }
33849
34432
 
@@ -34197,7 +34780,11 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
34197
34780
  };
34198
34781
 
34199
34782
  (function() {
34200
- var msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1], 10);
34783
+ /**
34784
+ * documentMode is an IE-only property
34785
+ * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
34786
+ */
34787
+ var msie = document.documentMode;
34201
34788
 
34202
34789
  /**
34203
34790
  * Triggers a browser event. Attempts to choose the right event if one is
@@ -34248,97 +34835,71 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
34248
34835
  return keys.indexOf(key) !== -1;
34249
34836
  }
34250
34837
 
34251
- if (msie < 9) {
34252
- if (inputType == 'radio' || inputType == 'checkbox') {
34253
- element.checked = !element.checked;
34838
+ var evnt;
34839
+ if(/transitionend/.test(eventType)) {
34840
+ if(window.WebKitTransitionEvent) {
34841
+ evnt = new WebKitTransitionEvent(eventType, eventData);
34842
+ evnt.initEvent(eventType, false, true);
34254
34843
  }
34255
-
34256
- // WTF!!! Error: Unspecified error.
34257
- // Don't know why, but some elements when detached seem to be in inconsistent state and
34258
- // calling .fireEvent() on them will result in very unhelpful error (Error: Unspecified error)
34259
- // forcing the browser to compute the element position (by reading its CSS)
34260
- // puts the element in consistent state.
34261
- element.style.posLeft;
34262
-
34263
- // TODO(vojta): create event objects with pressed keys to get it working on IE<9
34264
- var ret = element.fireEvent('on' + eventType);
34265
- if (inputType == 'submit') {
34266
- while(element) {
34267
- if (element.nodeName.toLowerCase() == 'form') {
34268
- element.fireEvent('onsubmit');
34269
- break;
34270
- }
34271
- element = element.parentNode;
34272
- }
34273
- }
34274
- return ret;
34275
- } else {
34276
- var evnt;
34277
- if(/transitionend/.test(eventType)) {
34278
- if(window.WebKitTransitionEvent) {
34279
- evnt = new WebKitTransitionEvent(eventType, eventData);
34280
- evnt.initEvent(eventType, false, true);
34844
+ else {
34845
+ try {
34846
+ evnt = new TransitionEvent(eventType, eventData);
34281
34847
  }
34282
- else {
34283
- try {
34284
- evnt = new TransitionEvent(eventType, eventData);
34285
- }
34286
- catch(e) {
34287
- evnt = document.createEvent('TransitionEvent');
34288
- evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
34289
- }
34848
+ catch(e) {
34849
+ evnt = document.createEvent('TransitionEvent');
34850
+ evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
34290
34851
  }
34291
34852
  }
34292
- else if(/animationend/.test(eventType)) {
34293
- if(window.WebKitAnimationEvent) {
34294
- evnt = new WebKitAnimationEvent(eventType, eventData);
34295
- evnt.initEvent(eventType, false, true);
34853
+ }
34854
+ else if(/animationend/.test(eventType)) {
34855
+ if(window.WebKitAnimationEvent) {
34856
+ evnt = new WebKitAnimationEvent(eventType, eventData);
34857
+ evnt.initEvent(eventType, false, true);
34858
+ }
34859
+ else {
34860
+ try {
34861
+ evnt = new AnimationEvent(eventType, eventData);
34296
34862
  }
34297
- else {
34298
- try {
34299
- evnt = new AnimationEvent(eventType, eventData);
34300
- }
34301
- catch(e) {
34302
- evnt = document.createEvent('AnimationEvent');
34303
- evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
34304
- }
34863
+ catch(e) {
34864
+ evnt = document.createEvent('AnimationEvent');
34865
+ evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
34305
34866
  }
34306
34867
  }
34307
- else {
34308
- evnt = document.createEvent('MouseEvents');
34309
- x = x || 0;
34310
- y = y || 0;
34311
- evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
34312
- pressed('alt'), pressed('shift'), pressed('meta'), 0, element);
34313
- }
34314
-
34315
- /* we're unable to change the timeStamp value directly so this
34316
- * is only here to allow for testing where the timeStamp value is
34317
- * read */
34318
- evnt.$manualTimeStamp = eventData.timeStamp;
34319
-
34320
- if(!evnt) return;
34321
-
34322
- var originalPreventDefault = evnt.preventDefault,
34323
- appWindow = element.ownerDocument.defaultView,
34324
- fakeProcessDefault = true,
34325
- finalProcessDefault,
34326
- angular = appWindow.angular || {};
34327
-
34328
- // igor: temporary fix for https://bugzilla.mozilla.org/show_bug.cgi?id=684208
34329
- angular['ff-684208-preventDefault'] = false;
34330
- evnt.preventDefault = function() {
34331
- fakeProcessDefault = false;
34332
- return originalPreventDefault.apply(evnt, arguments);
34333
- };
34868
+ }
34869
+ else {
34870
+ evnt = document.createEvent('MouseEvents');
34871
+ x = x || 0;
34872
+ y = y || 0;
34873
+ evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
34874
+ pressed('alt'), pressed('shift'), pressed('meta'), 0, element);
34875
+ }
34334
34876
 
34335
- element.dispatchEvent(evnt);
34336
- finalProcessDefault = !(angular['ff-684208-preventDefault'] || !fakeProcessDefault);
34877
+ /* we're unable to change the timeStamp value directly so this
34878
+ * is only here to allow for testing where the timeStamp value is
34879
+ * read */
34880
+ evnt.$manualTimeStamp = eventData.timeStamp;
34337
34881
 
34338
- delete angular['ff-684208-preventDefault'];
34882
+ if(!evnt) return;
34339
34883
 
34340
- return finalProcessDefault;
34341
- }
34884
+ var originalPreventDefault = evnt.preventDefault,
34885
+ appWindow = element.ownerDocument.defaultView,
34886
+ fakeProcessDefault = true,
34887
+ finalProcessDefault,
34888
+ angular = appWindow.angular || {};
34889
+
34890
+ // igor: temporary fix for https://bugzilla.mozilla.org/show_bug.cgi?id=684208
34891
+ angular['ff-684208-preventDefault'] = false;
34892
+ evnt.preventDefault = function() {
34893
+ fakeProcessDefault = false;
34894
+ return originalPreventDefault.apply(evnt, arguments);
34895
+ };
34896
+
34897
+ element.dispatchEvent(evnt);
34898
+ finalProcessDefault = !(angular['ff-684208-preventDefault'] || !fakeProcessDefault);
34899
+
34900
+ delete angular['ff-684208-preventDefault'];
34901
+
34902
+ return finalProcessDefault;
34342
34903
  };
34343
34904
  }());
34344
34905
 
@@ -36079,5 +36640,5 @@ if (config.autotest) {
36079
36640
  })(window, document);
36080
36641
 
36081
36642
 
36082
- !window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n\n[ng\\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],\n.ng-cloak, .x-ng-cloak,\n.ng-hide:not(.ng-animate) {\n display: none !important;\n}\n\nng\\:form {\n display: block;\n}\n</style>');
36643
+ !window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n\n[ng\\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],\n.ng-cloak, .x-ng-cloak,\n.ng-hide:not(.ng-hide-animate) {\n display: none !important;\n}\n\nng\\:form {\n display: block;\n}\n</style>');
36083
36644
  !window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n/* CSS Document */\n\n/** Structure */\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n font-size: 14px;\n}\n\n#system-error {\n font-size: 1.5em;\n text-align: center;\n}\n\n#json, #xml {\n display: none;\n}\n\n#header {\n position: fixed;\n width: 100%;\n}\n\n#specs {\n padding-top: 50px;\n}\n\n#header .angular {\n font-family: Courier New, monospace;\n font-weight: bold;\n}\n\n#header h1 {\n font-weight: normal;\n float: left;\n font-size: 30px;\n line-height: 30px;\n margin: 0;\n padding: 10px 10px;\n height: 30px;\n}\n\n#application h2,\n#specs h2 {\n margin: 0;\n padding: 0.5em;\n font-size: 1.1em;\n}\n\n#status-legend {\n margin-top: 10px;\n margin-right: 10px;\n}\n\n#header,\n#application,\n.test-info,\n.test-actions li {\n overflow: hidden;\n}\n\n#application {\n margin: 10px;\n}\n\n#application iframe {\n width: 100%;\n height: 758px;\n}\n\n#application .popout {\n float: right;\n}\n\n#application iframe {\n border: none;\n}\n\n.tests li,\n.test-actions li,\n.test-it li,\n.test-it ol,\n.status-display {\n list-style-type: none;\n}\n\n.tests,\n.test-it ol,\n.status-display {\n margin: 0;\n padding: 0;\n}\n\n.test-info {\n margin-left: 1em;\n margin-top: 0.5em;\n border-radius: 8px 0 0 8px;\n -webkit-border-radius: 8px 0 0 8px;\n -moz-border-radius: 8px 0 0 8px;\n cursor: pointer;\n}\n\n.test-info:hover .test-name {\n text-decoration: underline;\n}\n\n.test-info .closed:before {\n content: \'\\25b8\\00A0\';\n}\n\n.test-info .open:before {\n content: \'\\25be\\00A0\';\n font-weight: bold;\n}\n\n.test-it ol {\n margin-left: 2.5em;\n}\n\n.status-display,\n.status-display li {\n float: right;\n}\n\n.status-display li {\n padding: 5px 10px;\n}\n\n.timer-result,\n.test-title {\n display: inline-block;\n margin: 0;\n padding: 4px;\n}\n\n.test-actions .test-title,\n.test-actions .test-result {\n display: table-cell;\n padding-left: 0.5em;\n padding-right: 0.5em;\n}\n\n.test-actions {\n display: table;\n}\n\n.test-actions li {\n display: table-row;\n}\n\n.timer-result {\n width: 4em;\n padding: 0 10px;\n text-align: right;\n font-family: monospace;\n}\n\n.test-it pre,\n.test-actions pre {\n clear: left;\n color: black;\n margin-left: 6em;\n}\n\n.test-describe {\n padding-bottom: 0.5em;\n}\n\n.test-describe .test-describe {\n margin: 5px 5px 10px 2em;\n}\n\n.test-actions .status-pending .test-title:before {\n content: \'\\00bb\\00A0\';\n}\n\n.scrollpane {\n max-height: 20em;\n overflow: auto;\n}\n\n/** Colors */\n\n#header {\n background-color: #F2C200;\n}\n\n#specs h2 {\n border-top: 2px solid #BABAD1;\n}\n\n#specs h2,\n#application h2 {\n background-color: #efefef;\n}\n\n#application {\n border: 1px solid #BABAD1;\n}\n\n.test-describe .test-describe {\n border-left: 1px solid #BABAD1;\n border-right: 1px solid #BABAD1;\n border-bottom: 1px solid #BABAD1;\n}\n\n.status-display {\n border: 1px solid #777;\n}\n\n.status-display .status-pending,\n.status-pending .test-info {\n background-color: #F9EEBC;\n}\n\n.status-display .status-success,\n.status-success .test-info {\n background-color: #B1D7A1;\n}\n\n.status-display .status-failure,\n.status-failure .test-info {\n background-color: #FF8286;\n}\n\n.status-display .status-error,\n.status-error .test-info {\n background-color: black;\n color: white;\n}\n\n.test-actions .status-success .test-title {\n color: #30B30A;\n}\n\n.test-actions .status-failure .test-title {\n color: #DF0000;\n}\n\n.test-actions .status-error .test-title {\n color: black;\n}\n\n.test-actions .timer-result {\n color: #888;\n}\n</style>');