From 70aab54d6349c2945bae585a814011f1db8dcc2e Mon Sep 17 00:00:00 2001 From: Daniel Alvarez Date: Thu, 21 Apr 2022 15:11:28 -0400 Subject: [PATCH 1/6] Update FileSaver package dependency --- bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 23014ac..6c19887 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-file-saver", - "version": "1.1.3", + "version": "1.1.4", "main": "dist/angular-file-saver.bundle.js", "authors": [ "Philipp Alferov " @@ -24,6 +24,6 @@ ], "dependencies": { "blob-polyfill": "~1.0.20150320", - "file-saver.js": "~1.20150507.2" + "file-saver": "^2.0.4" } } From cfef37ed0ee6e88d8d43b227c614203a12a77dff Mon Sep 17 00:00:00 2001 From: Daniel Alvarez Date: Thu, 21 Apr 2022 15:12:26 -0400 Subject: [PATCH 2/6] Update bundle --- dist/angular-file-saver.bundle.js | 36 +- dist/angular-file-saver.js | 20 +- docs/dist/examples.js | 8920 ++++++++++++++++++++--------- docs/dist/examples.min.js | 13 +- docs/index.html | 6 +- 5 files changed, 6307 insertions(+), 2688 deletions(-) diff --git a/dist/angular-file-saver.bundle.js b/dist/angular-file-saver.bundle.js index f0ecd84..1d5b14d 100644 --- a/dist/angular-file-saver.bundle.js +++ b/dist/angular-file-saver.bundle.js @@ -52,7 +52,7 @@ return /******/ (function(modules) { // webpackBootstrap /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { 'use strict'; /* @@ -74,9 +74,9 @@ return /******/ (function(modules) { // webpackBootstrap .factory('SaveAs', [__webpack_require__(5)]); -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { 'use strict'; @@ -122,9 +122,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 2 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { 'use strict'; @@ -146,9 +146,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 3 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { 'use strict'; @@ -159,9 +159,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 4 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { /* Blob.js * A Blob implementation. @@ -376,9 +376,9 @@ return /******/ (function(modules) { // webpackBootstrap }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); -/***/ }, +/***/ }), /* 5 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { 'use strict'; @@ -387,9 +387,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 6 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_RESULT__;/* FileSaver.js * A saveAs() FileSaver implementation. @@ -581,22 +581,22 @@ return /******/ (function(modules) { // webpackBootstrap } -/***/ }, +/***/ }), /* 7 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { module.exports = function() { throw new Error("define cannot be used indirect"); }; -/***/ }, +/***/ }), /* 8 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; /* WEBPACK VAR INJECTION */}.call(exports, {})) -/***/ } +/***/ }) /******/ ]) }); ; \ No newline at end of file diff --git a/dist/angular-file-saver.js b/dist/angular-file-saver.js index 9317049..2e798e7 100644 --- a/dist/angular-file-saver.js +++ b/dist/angular-file-saver.js @@ -52,7 +52,7 @@ return /******/ (function(modules) { // webpackBootstrap /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { 'use strict'; @@ -75,9 +75,9 @@ return /******/ (function(modules) { // webpackBootstrap .factory('SaveAs', ['$window', 'FileSaverUtils', __webpack_require__(4)]); -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { 'use strict'; @@ -123,9 +123,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 2 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { 'use strict'; @@ -147,9 +147,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 3 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { 'use strict'; @@ -164,9 +164,9 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ }, +/***/ }), /* 4 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { 'use strict'; @@ -181,7 +181,7 @@ return /******/ (function(modules) { // webpackBootstrap }; -/***/ } +/***/ }) /******/ ]) }); ; \ No newline at end of file diff --git a/docs/dist/examples.js b/docs/dist/examples.js index aae8d4c..56fd2da 100644 --- a/docs/dist/examples.js +++ b/docs/dist/examples.js @@ -52,7 +52,7 @@ return /******/ (function(modules) { // webpackBootstrap /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { 'use strict'; @@ -77,30 +77,91 @@ return /******/ (function(modules) { // webpackBootstrap .controller('DownloadText', ['FileSaver', 'Blob', '$timeout', DownloadText]); -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, exports, __webpack_require__) { __webpack_require__(2); module.exports = angular; -/***/ }, +/***/ }), /* 2 */ -/***/ function(module, exports) { +/***/ (function(module, exports) { /** - * @license AngularJS v1.6.1 - * (c) 2010-2016 Google, Inc. http://angularjs.org + * @license AngularJS v1.8.3 + * (c) 2010-2020 Google LLC. http://angularjs.org * License: MIT */ (function(window) {'use strict'; + /* exported + minErrConfig, + errorHandlingConfig, + isValidObjectMaxDepth + */ + + var minErrConfig = { + objectMaxDepth: 5, + urlErrorParamsEnabled: true + }; + + /** + * @ngdoc function + * @name angular.errorHandlingConfig + * @module ng + * @kind function + * + * @description + * Configure several aspects of error handling in AngularJS if used as a setter or return the + * current configuration if used as a getter. The following options are supported: + * + * - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages. + * + * Omitted or undefined options will leave the corresponding configuration values unchanged. + * + * @param {Object=} config - The configuration object. May only contain the options that need to be + * updated. Supported keys: + * + * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a + * non-positive or non-numeric value, removes the max depth limit. + * Default: 5 + * + * * `urlErrorParamsEnabled` **{Boolean}** - Specifies whether the generated error url will + * contain the parameters of the thrown error. Disabling the parameters can be useful if the + * generated error url is very long. + * + * Default: true. When used without argument, it returns the current value. + */ + function errorHandlingConfig(config) { + if (isObject(config)) { + if (isDefined(config.objectMaxDepth)) { + minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; + } + if (isDefined(config.urlErrorParamsEnabled) && isBoolean(config.urlErrorParamsEnabled)) { + minErrConfig.urlErrorParamsEnabled = config.urlErrorParamsEnabled; + } + } else { + return minErrConfig; + } + } + + /** + * @private + * @param {Number} maxDepth + * @return {boolean} + */ + function isValidObjectMaxDepth(maxDepth) { + return isNumber(maxDepth) && maxDepth > 0; + } + + /** * @description * * This object provides a utility for producing rich Error messages within - * Angular. It can be called as follows: + * AngularJS. It can be called as follows: * * var exampleMinErr = minErr('example'); * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); @@ -117,7 +178,7 @@ return /******/ (function(modules) { // webpackBootstrap * Since data will be parsed statically during a build step, some restrictions * are applied with respect to how minErr instances are created and called. * Instances should have names of the form namespaceMinErr for a minErr created - * using minErr('namespace') . Error codes, namespaces and template strings + * using minErr('namespace'). Error codes, namespaces and template strings * should all be static strings, not variables or general expressions. * * @param {string} module The namespace to use for the new minErr instance. @@ -128,32 +189,41 @@ return /******/ (function(modules) { // webpackBootstrap function minErr(module, ErrorConstructor) { ErrorConstructor = ErrorConstructor || Error; - return function() { - var SKIP_INDEXES = 2; - var templateArgs = arguments, - code = templateArgs[0], + var url = 'https://errors.angularjs.org/1.8.3/'; + var regex = url.replace('.', '\\.') + '[\\s\\S]*'; + var errRegExp = new RegExp(regex, 'g'); + + return function() { + var code = arguments[0], + template = arguments[1], message = '[' + (module ? module + ':' : '') + code + '] ', - template = templateArgs[1], + templateArgs = sliceArgs(arguments, 2).map(function(arg) { + return toDebugString(arg, minErrConfig.objectMaxDepth); + }), paramPrefix, i; + // A minErr message has two parts: the message itself and the url that contains the + // encoded message. + // The message's parameters can contain other error messages which also include error urls. + // To prevent the messages from getting too long, we strip the error urls from the parameters. + message += template.replace(/\{\d+\}/g, function(match) { - var index = +match.slice(1, -1), - shiftedIndex = index + SKIP_INDEXES; + var index = +match.slice(1, -1); - if (shiftedIndex < templateArgs.length) { - return toDebugString(templateArgs[shiftedIndex]); + if (index < templateArgs.length) { + return templateArgs[index].replace(errRegExp, ''); } return match; }); - message += '\nhttp://errors.angularjs.org/1.6.1/' + - (module ? module + '/' : '') + code; + message += '\n' + url + (module ? module + '/' : '') + code; - for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { - message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + - encodeURIComponent(toDebugString(templateArgs[i])); + if (minErrConfig.urlErrorParamsEnabled) { + for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { + message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); + } } return new ErrorConstructor(message); @@ -170,6 +240,9 @@ return /******/ (function(modules) { // webpackBootstrap splice, push, toString, + minErrConfig, + errorHandlingConfig, + isValidObjectMaxDepth, ngMinErr, angularModule, uid, @@ -178,8 +251,6 @@ return /******/ (function(modules) { // webpackBootstrap lowercase, uppercase, - manualLowercase, - manualUppercase, nodeName_, isArrayLike, forEach, @@ -202,6 +273,7 @@ return /******/ (function(modules) { // webpackBootstrap isNumber, isNumberNaN, isDate, + isError, isArray, isFunction, isRegExp, @@ -219,6 +291,7 @@ return /******/ (function(modules) { // webpackBootstrap includes, arrayRemove, copy, + simpleCompare, equals, csp, jq, @@ -230,6 +303,7 @@ return /******/ (function(modules) { // webpackBootstrap fromJson, convertTimezoneToLocal, timezoneToOffset, + addDateMinutes, startingTag, tryDecodeURIComponent, parseKeyValue, @@ -249,6 +323,7 @@ return /******/ (function(modules) { // webpackBootstrap hasOwnProperty, createMap, stringify, + UNSAFE_restoreLegacyJqLiteXHTMLReplacement, NODE_TYPE_ELEMENT, NODE_TYPE_ATTRIBUTE, @@ -267,13 +342,11 @@ return /******/ (function(modules) { // webpackBootstrap * @installation * @description * - * # ng (core module) * The ng module is loaded by default when an AngularJS application is started. The module itself * contains the essential components for an AngularJS application to function. The table below * lists a high level breakdown of each of the services/factories, filters, directives and testing * components available within this core module. * - *
*/ var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; @@ -286,15 +359,7 @@ return /******/ (function(modules) { // webpackBootstrap var hasOwnProperty = Object.prototype.hasOwnProperty; /** - * @ngdoc function - * @name angular.lowercase - * @module ng - * @kind function - * - * @deprecated - * sinceVersion="1.5.0" - * removeVersion="1.7.0" - * Use [String.prototype.toLowerCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase) instead. + * @private * * @description Converts the specified string to lowercase. * @param {string} string String to be converted to lowercase. @@ -303,15 +368,7 @@ return /******/ (function(modules) { // webpackBootstrap var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; /** - * @ngdoc function - * @name angular.uppercase - * @module ng - * @kind function - * - * @deprecated - * sinceVersion="1.5.0" - * removeVersion="1.7.0" - * Use [String.prototype.toUpperCase](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) instead. + * @private * * @description Converts the specified string to uppercase. * @param {string} string String to be converted to uppercase. @@ -320,31 +377,6 @@ return /******/ (function(modules) { // webpackBootstrap var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; - var manualLowercase = function(s) { - /* eslint-disable no-bitwise */ - return isString(s) - ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) - : s; - /* eslint-enable */ - }; - var manualUppercase = function(s) { - /* eslint-disable no-bitwise */ - return isString(s) - ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) - : s; - /* eslint-enable */ - }; - - - // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish - // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods - // with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387 - if ('i' !== 'I'.toLowerCase()) { - lowercase = manualLowercase; - uppercase = manualUppercase; - } - - var msie, // holds major version number for IE, or NaN if UA is not IE. jqLite, // delay binding since jQuery could be loaded after us. @@ -392,8 +424,7 @@ return /******/ (function(modules) { // webpackBootstrap // NodeList objects (with `item` method) and // other objects with suitable length characteristics are array-like - return isNumber(length) && - (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function'); + return isNumber(length) && (length >= 0 && (length - 1) in obj || typeof obj.item === 'function'); } @@ -542,8 +573,10 @@ return /******/ (function(modules) { // webpackBootstrap } else if (isElement(src)) { dst[key] = src.clone(); } else { - if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; - baseExtend(dst[key], [src], true); + if (key !== '__proto__') { + if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; + baseExtend(dst[key], [src], true); + } } } else { dst[key] = src; @@ -592,6 +625,22 @@ return /******/ (function(modules) { // webpackBootstrap * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source * objects, performing a deep copy. * + * @deprecated + * sinceVersion="1.6.5" + * This function is deprecated, but will not be removed in the 1.x lifecycle. + * There are edge cases (see {@link angular.merge#known-issues known issues}) that are not + * supported by this function. We suggest using another, similar library for all-purpose merging, + * such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge). + * + * @knownIssue + * This is a list of (known) object types that are not handled correctly by this function: + * - [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) + * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream) + * - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient) + * - AngularJS {@link $rootScope.Scope scopes}; + * + * `angular.merge` also does not support merging objects with circular references. + * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). * @returns {Object} Reference to `dst`. @@ -794,12 +843,32 @@ return /******/ (function(modules) { // webpackBootstrap * @kind function * * @description - * Determines if a reference is an `Array`. Alias of Array.isArray. + * Determines if a reference is an `Array`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Array`. */ - var isArray = Array.isArray; + function isArray(arr) { + return Array.isArray(arr) || arr instanceof Array; + } + + /** + * @description + * Determines if a reference is an `Error`. + * Loosely based on https://www.npmjs.com/package/iserror + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Error`. + */ + function isError(value) { + var tag = toString.call(value); + switch (tag) { + case '[object Error]': return true; + case '[object Exception]': return true; + case '[object DOMException]': return true; + default: return value instanceof Error; + } + } /** * @ngdoc function @@ -949,7 +1018,9 @@ return /******/ (function(modules) { // webpackBootstrap * @kind function * * @description - * Creates a deep copy of `source`, which should be an object or an array. + * Creates a deep copy of `source`, which should be an object or an array. This functions is used + * internally, mostly in the change-detection code. It is not intended as an all-purpose copy + * function, and has several limitations (see below). * * * If no destination is supplied, a copy of the object or array is created. * * If a destination is provided, all of its elements (for arrays) or properties (for objects) @@ -958,15 +1029,35 @@ return /******/ (function(modules) { // webpackBootstrap * * If `source` is identical to `destination` an exception will be thrown. * *
+ * *
* Only enumerable properties are taken into account. Non-enumerable properties (both on `source` * and on `destination`) will be ignored. *
* - * @param {*} source The source that will be used to make a copy. - * Can be any type, including primitives, `null`, and `undefined`. - * @param {(Object|Array)=} destination Destination into which the source is copied. If - * provided, must be of the same type as `source`. + *
+ * `angular.copy` does not check if destination and source are of the same type. It's the + * developer's responsibility to make sure they are compatible. + *
+ * + * @knownIssue + * This is a non-exhaustive list of object types / features that are not handled correctly by + * `angular.copy`. Note that since this functions is used by the change detection code, this + * means binding or watching objects of these types (or that include these types) might not work + * correctly. + * - [`File`](https://developer.mozilla.org/docs/Web/API/File) + * - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) + * - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData) + * - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream) + * - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set) + * - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) + * - [`getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/ + * [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + * + * @param {*} source The source that will be used to make a copy. Can be any type, including + * primitives, `null`, and `undefined`. + * @param {(Object|Array)=} destination Destination into which the source is copied. If provided, + * must be of the same type as `source`. * @returns {*} The copy or updated `destination`, if `destination` was specified. * * @example @@ -982,7 +1073,7 @@ return /******/ (function(modules) { // webpackBootstrap
form = {{user | json}}
-
master = {{master | json}}
+
leader = {{leader | json}}
@@ -990,16 +1081,16 @@ return /******/ (function(modules) { // webpackBootstrap angular. module('copyExample', []). controller('ExampleController', ['$scope', function($scope) { - $scope.master = {}; + $scope.leader = {}; $scope.reset = function() { // Example with 1 argument - $scope.user = angular.copy($scope.master); + $scope.user = angular.copy($scope.leader); }; $scope.update = function(user) { // Example with 2 arguments - angular.copy(user, $scope.master); + angular.copy(user, $scope.leader); }; $scope.reset(); @@ -1007,9 +1098,10 @@ return /******/ (function(modules) { // webpackBootstrap */ - function copy(source, destination) { + function copy(source, destination, maxDepth) { var stackSource = []; var stackDest = []; + maxDepth = isValidObjectMaxDepth(maxDepth) ? maxDepth : NaN; if (destination) { if (isTypedArray(destination) || isArrayBuffer(destination)) { @@ -1032,35 +1124,39 @@ return /******/ (function(modules) { // webpackBootstrap stackSource.push(source); stackDest.push(destination); - return copyRecurse(source, destination); + return copyRecurse(source, destination, maxDepth); } - return copyElement(source); + return copyElement(source, maxDepth); - function copyRecurse(source, destination) { + function copyRecurse(source, destination, maxDepth) { + maxDepth--; + if (maxDepth < 0) { + return '...'; + } var h = destination.$$hashKey; var key; if (isArray(source)) { for (var i = 0, ii = source.length; i < ii; i++) { - destination.push(copyElement(source[i])); + destination.push(copyElement(source[i], maxDepth)); } } else if (isBlankObject(source)) { // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty for (key in source) { - destination[key] = copyElement(source[key]); + destination[key] = copyElement(source[key], maxDepth); } } else if (source && typeof source.hasOwnProperty === 'function') { // Slow path, which must rely on hasOwnProperty for (key in source) { if (source.hasOwnProperty(key)) { - destination[key] = copyElement(source[key]); + destination[key] = copyElement(source[key], maxDepth); } } } else { // Slowest path --- hasOwnProperty can't be called as a method for (key in source) { if (hasOwnProperty.call(source, key)) { - destination[key] = copyElement(source[key]); + destination[key] = copyElement(source[key], maxDepth); } } } @@ -1068,7 +1164,7 @@ return /******/ (function(modules) { // webpackBootstrap return destination; } - function copyElement(source) { + function copyElement(source, maxDepth) { // Simple values if (!isObject(source)) { return source; @@ -1097,7 +1193,7 @@ return /******/ (function(modules) { // webpackBootstrap stackDest.push(destination); return needsRecurse - ? copyRecurse(source, destination) + ? copyRecurse(source, destination, maxDepth) : destination; } @@ -1148,6 +1244,10 @@ return /******/ (function(modules) { // webpackBootstrap } + // eslint-disable-next-line no-self-compare + function simpleCompare(a, b) { return a === b || (a !== a && b !== b); } + + /** * @ngdoc function * @name angular.equals @@ -1228,7 +1328,7 @@ return /******/ (function(modules) { // webpackBootstrap } } else if (isDate(o1)) { if (!isDate(o2)) return false; - return equals(o1.getTime(), o2.getTime()); + return simpleCompare(o1.getTime(), o2.getTime()); } else if (isRegExp(o1)) { if (!isRegExp(o2)) return false; return o1.toString() === o2.toString(); @@ -1301,7 +1401,7 @@ return /******/ (function(modules) { // webpackBootstrap * used to force either jqLite by leaving ng-jq blank or setting the name of * the jquery variable under window (eg. jQuery). * - * Since angular looks for this directive when it is loaded (doesn't wait for the + * Since AngularJS looks for this directive when it is loaded (doesn't wait for the * DOMContentLoaded event), it must be placed on an element that comes before the script * which loads angular. Also, only the first instance of `ng-jq` will be used and all * others ignored. @@ -1414,7 +1514,7 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be - * stripped since angular uses this notation internally. + * stripped since AngularJS uses this notation internally. * * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON. * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. @@ -1472,7 +1572,7 @@ return /******/ (function(modules) { // webpackBootstrap var ALL_COLONS = /:/g; function timezoneToOffset(timezone, fallback) { - // Support: IE 9-11 only, Edge 13-14+ + // Support: IE 9-11 only, Edge 13-15+ // IE/Edge do not "understand" colon (`:`) in timezone timezone = timezone.replace(ALL_COLONS, ''); var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; @@ -1499,13 +1599,8 @@ return /******/ (function(modules) { // webpackBootstrap * @returns {string} Returns the string representation of the element. */ function startingTag(element) { - element = jqLite(element).clone(); - try { - // turns out IE does not let you set .html() on elements which - // are not allowed to have children. So we just ignore it. - element.empty(); - } catch (e) { /* empty */ } - var elemHtml = jqLite('
').append(element).html(); + element = jqLite(element).clone().empty(); + var elemHtml = jqLite('
').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : elemHtml. @@ -1639,30 +1734,52 @@ return /******/ (function(modules) { // webpackBootstrap } function allowAutoBootstrap(document) { - if (!document.currentScript) { + var script = document.currentScript; + + if (!script) { + // Support: IE 9-11 only + // IE does not have `document.currentScript` return true; } - var src = document.currentScript.getAttribute('src'); - var link = document.createElement('a'); - link.href = src; - if (document.location.origin === link.origin) { - // Same-origin resources are always allowed, even for non-whitelisted schemes. - return true; + + // If the `currentScript` property has been clobbered just return false, since this indicates a probable attack + if (!(script instanceof window.HTMLScriptElement || script instanceof window.SVGScriptElement)) { + return false; } - // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. - // This is to prevent angular.js bundled with browser extensions from being used to bypass the - // content security policy in web pages and other browser extensions. - switch (link.protocol) { - case 'http:': - case 'https:': - case 'ftp:': - case 'blob:': - case 'file:': - case 'data:': + + var attributes = script.attributes; + var srcs = [attributes.getNamedItem('src'), attributes.getNamedItem('href'), attributes.getNamedItem('xlink:href')]; + + return srcs.every(function(src) { + if (!src) { return true; - default: + } + if (!src.value) { return false; - } + } + + var link = document.createElement('a'); + link.href = src.value; + + if (document.location.origin === link.origin) { + // Same-origin resources are always allowed, even for banned URL schemes. + return true; + } + // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. + // This is to prevent angular.js bundled with browser extensions from being used to bypass the + // content security policy in web pages and other browser extensions. + switch (link.protocol) { + case 'http:': + case 'https:': + case 'ftp:': + case 'blob:': + case 'file:': + case 'data:': + return true; + default: + return false; + } + }); } // Cached as it has to run during loading so that document.currentScript is available. @@ -1709,6 +1826,10 @@ return /******/ (function(modules) { // webpackBootstrap * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` * would not be resolved to `3`. * + * @example + * + * ### Simple Usage + * * `ngApp` is the easiest, and most common way to bootstrap an application. * @@ -1725,6 +1846,10 @@ return /******/ (function(modules) { // webpackBootstrap * + * @example + * + * ### With `ngStrictDi` + * * Using `ngStrictDi`, you would see something like this: * @@ -1827,7 +1952,7 @@ return /******/ (function(modules) { // webpackBootstrap }); if (appElement) { if (!isAutoBootstrapAllowed) { - window.console.error('Angular: disabling automatic bootstrap. @@ -7779,11 +8307,11 @@ return /******/ (function(modules) { // webpackBootstrap it('should auto compile', function() { var textarea = $('textarea'); var output = $('div[compile]'); - // The initial state reads 'Hello Angular'. - expect(output.getText()).toBe('Hello Angular'); + // The initial state reads 'Hello AngularJS'. + expect(output.getText()).toBe('Hello AngularJS'); textarea.clear(); textarea.sendKeys('{{name}}!'); - expect(output.getText()).toBe('Angular!'); + expect(output.getText()).toBe('AngularJS!'); }); @@ -7830,40 +8358,42 @@ return /******/ (function(modules) { // webpackBootstrap * } * ``` * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add - * the cloned elements; only needed for transcludes that are allowed to contain non html - * elements (e.g. SVG elements). See also the directive.controller property. + * the cloned elements; only needed for transcludes that are allowed to contain non HTML + * elements (e.g. SVG elements). See also the `directive.controller` property. * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * - * After linking the view is not updated until after a call to $digest which typically is done by - * Angular automatically. + * After linking the view is not updated until after a call to `$digest`, which typically is done by + * AngularJS automatically. * * If you need access to the bound view, there are two ways to do it: * * - If you are not asking the linking function to clone the template, create the DOM element(s) * before you send them to the compiler and keep this reference around. * ```js - * var element = $compile('

{{total}}

')(scope); + * var element = angular.element('

{{total}}

'); + * $compile(element)(scope); * ``` * * - if on the other hand, you need the element to be cloned, the view reference from the original * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: + * this case, you can access the clone either via the `cloneAttachFn` or the value returned by the + * linking function: * ```js - * var templateElement = angular.element('

{{total}}

'), - * scope = ....; - * + * var templateElement = angular.element('

{{total}}

'); * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { - * //attach the clone to DOM document at the right place + * // Attach the clone to DOM document at the right place. * }); * - * //now we have reference to the cloned DOM via `clonedElement` + * // Now we have reference to the cloned DOM via `clonedElement`. + * // NOTE: The `clonedElement` returned by the linking function is the same as the + * // `clonedElement` passed to `cloneAttachFn`. * ``` * * * For information on how the compiler works, see the - * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. + * {@link guide/compiler AngularJS HTML Compiler} section of the Developer Guide. * * @knownIssue * @@ -7873,65 +8403,438 @@ return /******/ (function(modules) { // webpackBootstrap compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it section on double compilation} for an in-depth explanation and ways to avoid it. - * - */ - var $compileMinErr = minErr('$compile'); + * @knownIssue - function UNINITIALIZED_VALUE() {} - var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); + ### Issues with `replace: true` + * + *
+ * **Note**: {@link $compile#-replace- `replace: true`} is deprecated and not recommended to use, + * mainly due to the issues listed here. It has been completely removed in the new Angular. + *
+ * + * #### Attribute values are not merged + * + * When a `replace` directive encounters the same attribute on the original and the replace node, + * it will simply deduplicate the attribute and join the values with a space or with a `;` in case of + * the `style` attribute. + * ```html + * Original Node: + * Replace Template: + * Result: + * ``` + * + * That means attributes that contain AngularJS expressions will not be merged correctly, e.g. + * {@link ngShow} or {@link ngClass} will cause a {@link $parse} error: + * + * ```html + * Original Node: + * Replace Template: + * Result: + * ``` + * + * See issue [#5695](https://github.com/angular/angular.js/issues/5695). + * + * #### Directives are not deduplicated before compilation + * + * When the original node and the replace template declare the same directive(s), they will be + * {@link guide/compiler#double-compilation-and-how-to-avoid-it compiled twice} because the compiler + * does not deduplicate them. In many cases, this is not noticeable, but e.g. {@link ngModel} will + * attach `$formatters` and `$parsers` twice. + * + * See issue [#2573](https://github.com/angular/angular.js/issues/2573). + * + * #### `transclude: element` in the replace template root can have unexpected effects + * + * When the replace template has a directive at the root node that uses + * {@link $compile#-transclude- `transclude: element`}, e.g. + * {@link ngIf} or {@link ngRepeat}, the DOM structure or scope inheritance can be incorrect. + * See the following issues: + * + * - Incorrect scope on replaced element: + * [#9837](https://github.com/angular/angular.js/issues/9837) + * - Different DOM between `template` and `templateUrl`: + * [#10612](https://github.com/angular/angular.js/issues/14326) + * + */ /** - * @ngdoc provider - * @name $compileProvider + * @ngdoc directive + * @name ngProp + * @restrict A + * @element ANY + * + * @usage + * + * ```html + * + * + * ``` + * + * or with uppercase letters in property (e.g. "propName"): + * + * + * ```html + * + * + * ``` + * * * @description + * The `ngProp` directive binds an expression to a DOM element property. + * `ngProp` allows writing to arbitrary properties by including + * the property name in the attribute, e.g. `ng-prop-value="'my value'"` binds 'my value' to + * the `value` property. + * + * Usually, it's not necessary to write to properties in AngularJS, as the built-in directives + * handle the most common use cases (instead of the above example, you would use {@link ngValue}). + * + * However, [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements) + * often use custom properties to hold data, and `ngProp` can be used to provide input to these + * custom elements. + * + * ## Binding to camelCase properties + * + * Since HTML attributes are case-insensitive, camelCase properties like `innerHTML` must be escaped. + * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so + * `innerHTML` must be written as `ng-prop-inner_h_t_m_l="expression"` (Note that this is just an + * example, and for binding HTML {@link ngBindHtml} should be used. + * + * ## Security + * + * Binding expressions to arbitrary properties poses a security risk, as properties like `innerHTML` + * can insert potentially dangerous HTML into the application, e.g. script tags that execute + * malicious code. + * For this reason, `ngProp` applies Strict Contextual Escaping with the {@link ng.$sce $sce service}. + * This means vulnerable properties require their content to be "trusted", based on the + * context of the property. For example, the `innerHTML` is in the `HTML` context, and the + * `iframe.src` property is in the `RESOURCE_URL` context, which requires that values written to + * this property are trusted as a `RESOURCE_URL`. + * + * This can be set explicitly by calling $sce.trustAs(type, value) on the value that is + * trusted before passing it to the `ng-prop-*` directive. There are exist shorthand methods for + * each context type in the form of {@link ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl()} et al. + * + * In some cases you can also rely upon automatic sanitization of untrusted values - see below. + * + * Based on the context, other options may exist to mark a value as trusted / configure the behavior + * of {@link ng.$sce}. For example, to restrict the `RESOURCE_URL` context to specific origins, use + * the {@link $sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList()} + * and {@link $sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList()}. + * + * {@link ng.$sce#what-trusted-context-types-are-supported- Find out more about the different context types}. + * + * ### HTML Sanitization + * + * By default, `$sce` will throw an error if it detects untrusted HTML content, and will not bind the + * content. + * However, if you include the {@link ngSanitize ngSanitize module}, it will try to sanitize the + * potentially dangerous HTML, e.g. strip non-trusted tags and attributes when binding to + * `innerHTML`. + * + * @example + * ### Binding to different contexts + * + * + * + * angular.module('exampleNgProp', []) + * .component('main', { + * templateUrl: 'main.html', + * controller: function($sce) { + * this.safeContent = 'Safe content'; + * this.unsafeContent = ''; + * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); + * } + * }); + * + * + *
+ *
+ * Binding to a property without security context: + *
+ * innerText (safeContent) + *
+ * + *
+ * "Safe" content that requires a security context will throw because the contents could potentially be dangerous ... + *
+ * innerHTML (safeContent) + *
+ * + *
+ * ... so that actually dangerous content cannot be executed: + *
+ * innerHTML (unsafeContent) + *
+ * + *
+ * ... but unsafe Content that has been trusted explicitly works - only do this if you are 100% sure! + *
+ * innerHTML (trustedUnsafeContent) + *
+ *
+ *
+ * + *
+ *
+ * + * .prop-unit { + * margin-bottom: 10px; + * } + * + * .prop-binding { + * min-height: 30px; + * border: 1px solid blue; + * } + * + * .prop-note { + * font-family: Monospace; + * } + * + *
+ * + * + * @example + * ### Binding to innerHTML with ngSanitize + * + * + * + * angular.module('exampleNgProp', ['ngSanitize']) + * .component('main', { + * templateUrl: 'main.html', + * controller: function($sce) { + * this.safeContent = 'Safe content'; + * this.unsafeContent = ''; + * this.trustedUnsafeContent = $sce.trustAsHtml(this.unsafeContent); + * } + * }); + * + * + *
+ *
+ * "Safe" content will be sanitized ... + *
+ * innerHTML (safeContent) + *
+ * + *
+ * ... as will dangerous content: + *
+ * innerHTML (unsafeContent) + *
+ * + *
+ * ... and content that has been trusted explicitly works the same as without ngSanitize: + *
+ * innerHTML (trustedUnsafeContent) + *
+ *
+ *
+ * + *
+ *
+ * + * .prop-unit { + * margin-bottom: 10px; + * } + * + * .prop-binding { + * min-height: 30px; + * border: 1px solid blue; + * } + * + * .prop-note { + * font-family: Monospace; + * } + * + *
+ * */ - $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; - /** @this */ - function $CompileProvider($provide, $$sanitizeUriProvider) { - var hasDirectives = {}, - Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, - ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), - REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; - - // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes - // The assumption is that future DOM event attribute names will begin with - // 'on' and be composed of only English letters. - var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; - var bindingCache = createMap(); - - function parseIsolateBindings(scope, directiveName, isController) { - var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/; - - var bindings = createMap(); - - forEach(scope, function(definition, scopeName) { - if (definition in bindingCache) { - bindings[scopeName] = bindingCache[definition]; - return; - } - var match = definition.match(LOCAL_REGEXP); - - if (!match) { - throw $compileMinErr('iscp', - 'Invalid {3} for directive \'{0}\'.' + - ' Definition: {... {1}: \'{2}\' ...}', - directiveName, scopeName, definition, - (isController ? 'controller bindings definition' : - 'isolate scope definition')); - } - bindings[scopeName] = { - mode: match[1][0], - collection: match[2] === '*', - optional: match[3] === '?', - attrName: match[4] || scopeName - }; - if (match[4]) { - bindingCache[definition] = bindings[scopeName]; + /** @ngdoc directive + * @name ngOn + * @restrict A + * @element ANY + * + * @usage + * + * ```html + * + * + * ``` + * + * or with uppercase letters in property (e.g. "eventName"): + * + * + * ```html + * + * + * ``` + * + * @description + * The `ngOn` directive adds an event listener to a DOM element via + * {@link angular.element angular.element().on()}, and evaluates an expression when the event is + * fired. + * `ngOn` allows adding listeners for arbitrary events by including + * the event name in the attribute, e.g. `ng-on-drop="onDrop()"` executes the 'onDrop()' expression + * when the `drop` event is fired. + * + * AngularJS provides specific directives for many events, such as {@link ngClick}, so in most + * cases it is not necessary to use `ngOn`. However, AngularJS does not support all events + * (e.g. the `drop` event in the example above), and new events might be introduced in later DOM + * standards. + * + * Another use-case for `ngOn` is listening to + * [custom events](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) + * fired by + * [custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements). + * + * ## Binding to camelCase properties + * + * Since HTML attributes are case-insensitive, camelCase properties like `myEvent` must be escaped. + * AngularJS uses the underscore (_) in front of a character to indicate that it is uppercase, so + * `myEvent` must be written as `ng-on-my_event="expression"`. + * + * @example + * ### Bind to built-in DOM events + * + * + * + * angular.module('exampleNgOn', []) + * .component('main', { + * templateUrl: 'main.html', + * controller: function() { + * this.clickCount = 0; + * this.mouseoverCount = 0; + * + * this.loadingState = 0; + * } + * }); + * + * + *
+ * This is equivalent to `ngClick` and `ngMouseover`:
+ *
+ * clickCount: {{$ctrl.clickCount}}
+ * mouseover: {{$ctrl.mouseoverCount}} + * + *
+ * + * For the `error` and `load` event on images no built-in AngularJS directives exist:
+ *
+ *
+ * Image is loading + * Image load error + * Image loaded successfully + *
+ *
+ *
+ * + *
+ *
+ *
+ * + * + * @example + * ### Bind to custom DOM events + * + * + * + * angular.module('exampleNgOn', []) + * .component('main', { + * templateUrl: 'main.html', + * controller: function() { + * this.eventLog = ''; + * + * this.listener = function($event) { + * this.eventLog = 'Event with type "' + $event.type + '" fired at ' + $event.detail; + * }; + * } + * }) + * .component('childComponent', { + * templateUrl: 'child.html', + * controller: function($element) { + * this.fireEvent = function() { + * var event = new CustomEvent('customtype', { detail: new Date()}); + * + * $element[0].dispatchEvent(event); + * }; + * } + * }); + * + * + *
+ * Event log: {{$ctrl.eventLog}} + *
+ * + + * + * + *
+ *
+ *
+ */ + + var $compileMinErr = minErr('$compile'); + + function UNINITIALIZED_VALUE() {} + var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); + + /** + * @ngdoc provider + * @name $compileProvider + * + * @description + */ + $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; + /** @this */ + function $CompileProvider($provide, $$sanitizeUriProvider) { + var hasDirectives = {}, + Suffix = 'Directive', + COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, + ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), + REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; + + // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes + // The assumption is that future DOM event attribute names will begin with + // 'on' and be composed of only English letters. + var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + var bindingCache = createMap(); + + function parseIsolateBindings(scope, directiveName, isController) { + var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/; + + var bindings = createMap(); + + forEach(scope, function(definition, scopeName) { + definition = definition.trim(); + + if (definition in bindingCache) { + bindings[scopeName] = bindingCache[definition]; + return; + } + var match = definition.match(LOCAL_REGEXP); + + if (!match) { + throw $compileMinErr('iscp', + 'Invalid {3} for directive \'{0}\'.' + + ' Definition: {... {1}: \'{2}\' ...}', + directiveName, scopeName, definition, + (isController ? 'controller bindings definition' : + 'isolate scope definition')); + } + + bindings[scopeName] = { + mode: match[1][0], + collection: match[2] === '*', + optional: match[3] === '?', + attrName: match[4] || scopeName + }; + if (match[4]) { + bindingCache[definition] = bindings[scopeName]; } }); @@ -8011,9 +8914,9 @@ return /******/ (function(modules) { // webpackBootstrap * @description * Register a new directive with the compiler. * - * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which - * will match as ng-bind), or an object map of directives where the keys are the - * names and the values are the factories. + * @param {string|Object} name Name of the directive in camel-case (i.e. `ngBind` which will match + * as `ng-bind`), or an object map of directives where the keys are the names and the values + * are the factories. * @param {Function|Array} directiveFactory An injectable directive factory function. See the * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. * @returns {ng.$compileProvider} Self for chaining. @@ -8062,7 +8965,8 @@ return /******/ (function(modules) { // webpackBootstrap * @ngdoc method * @name $compileProvider#component * @module ng - * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match ``) + * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match ``), + * or an object map of components where the keys are the names and the values are the component definition objects. * @param {Object} options Component definition object (a simplified * {@link ng.$compile#directive-definition-object directive definition object}), * with the following properties (all optional): @@ -8145,6 +9049,11 @@ return /******/ (function(modules) { // webpackBootstrap * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. */ this.component = function registerComponent(name, options) { + if (!isString(name)) { + forEach(name, reverseParams(bind(this, registerComponent))); + return this; + } + var controller = options.controller || function() {}; function factory($injector) { @@ -8181,7 +9090,7 @@ return /******/ (function(modules) { // webpackBootstrap // TODO(pete) remove the following `forEach` before we release 1.6.0 // The component-router@0.2.0 looks for the annotations on the controller constructor - // Nothing in Angular looks for annotations on the factory function but we can't remove + // Nothing in AngularJS looks for annotations on the factory function but we can't remove // it from 1.5.x yet. // Copy any annotation properties (starting with $) over to the factory and controller constructor functions @@ -8202,63 +9111,105 @@ return /******/ (function(modules) { // webpackBootstrap /** * @ngdoc method - * @name $compileProvider#aHrefSanitizationWhitelist + * @name $compileProvider#aHrefSanitizationTrustedUrlList * @kind function * * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * Retrieves or overrides the default regular expression that is used for determining trusted safe * urls during a[href] sanitization. * * The sanitization is a security measure aimed at preventing XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationTrustedUrlList` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * - * @param {RegExp=} regexp New regexp to whitelist urls with. + * @param {RegExp=} regexp New regexp to trust urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.aHrefSanitizationWhitelist = function(regexp) { + this.aHrefSanitizationTrustedUrlList = function(regexp) { if (isDefined(regexp)) { - $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); + $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(regexp); return this; } else { - return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); + return $$sanitizeUriProvider.aHrefSanitizationTrustedUrlList(); } }; /** * @ngdoc method - * @name $compileProvider#imgSrcSanitizationWhitelist + * @name $compileProvider#aHrefSanitizationWhitelist + * @kind function + * + * @deprecated + * sinceVersion="1.8.1" + * + * This method is deprecated. Use {@link $compileProvider#aHrefSanitizationTrustedUrlList + * aHrefSanitizationTrustedUrlList} instead. + */ + Object.defineProperty(this, 'aHrefSanitizationWhitelist', { + get: function() { + return this.aHrefSanitizationTrustedUrlList; + }, + set: function(value) { + this.aHrefSanitizationTrustedUrlList = value; + } + }); + + + /** + * @ngdoc method + * @name $compileProvider#imgSrcSanitizationTrustedUrlList * @kind function * * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * Retrieves or overrides the default regular expression that is used for determining trusted safe * urls during img[src] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` + * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationTrustedUrlList` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * - * @param {RegExp=} regexp New regexp to whitelist urls with. + * @param {RegExp=} regexp New regexp to trust urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.imgSrcSanitizationWhitelist = function(regexp) { + this.imgSrcSanitizationTrustedUrlList = function(regexp) { if (isDefined(regexp)) { - $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); + $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(regexp); return this; } else { - return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); + return $$sanitizeUriProvider.imgSrcSanitizationTrustedUrlList(); } }; + + /** + * @ngdoc method + * @name $compileProvider#imgSrcSanitizationWhitelist + * @kind function + * + * @deprecated + * sinceVersion="1.8.1" + * + * This method is deprecated. Use {@link $compileProvider#imgSrcSanitizationTrustedUrlList + * imgSrcSanitizationTrustedUrlList} instead. + */ + Object.defineProperty(this, 'imgSrcSanitizationWhitelist', { + get: function() { + return this.imgSrcSanitizationTrustedUrlList; + }, + set: function(value) { + this.imgSrcSanitizationTrustedUrlList = value; + } + }); + /** * @ngdoc method * @name $compileProvider#debugInfoEnabled @@ -8274,7 +9225,12 @@ return /******/ (function(modules) { // webpackBootstrap * binding information and a reference to the current scope on to DOM elements. * If enabled, the compiler will add the following to DOM elements that have been bound to the scope * * `ng-binding` CSS class + * * `ng-scope` and `ng-isolated-scope` CSS classes * * `$binding` data property containing an array of the binding expressions + * * Data properties used by the {@link angular.element#methods `scope()`/`isolateScope()` methods} to return + * the element's scope. + * * Placeholder comments will contain information about what directive and binding caused the placeholder. + * E.g. ``. * * You may want to disable this in production for a significant performance boost. See * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. @@ -8292,34 +9248,33 @@ return /******/ (function(modules) { // webpackBootstrap /** * @ngdoc method - * @name $compileProvider#preAssignBindingsEnabled + * @name $compileProvider#strictComponentBindingsEnabled * - * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the - * current preAssignBindingsEnabled state + * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, + * otherwise return the current strictComponentBindingsEnabled state. * @returns {*} current value if used as getter or itself (chaining) if used as setter * * @kind function * * @description - * Call this method to enable/disable whether directive controllers are assigned bindings before - * calling the controller's constructor. - * If enabled (true), the compiler assigns the value of each of the bindings to the - * properties of the controller object before the constructor of this object is called. - * - * If disabled (false), the compiler calls the constructor first before assigning bindings. - * - * The default value is true in Angular 1.5.x but will switch to false in Angular 1.6.x. + * Call this method to enable / disable the strict component bindings check. If enabled, the + * compiler will enforce that all scope / controller bindings of a + * {@link $compileProvider#directive directive} / {@link $compileProvider#component component} + * that are not set as optional with `?`, must be provided when the directive is instantiated. + * If not provided, the compiler will throw the + * {@link error/$compile/missingattr $compile:missingattr error}. + * + * The default value is false. */ - var preAssignBindingsEnabled = false; - this.preAssignBindingsEnabled = function(enabled) { + var strictComponentBindingsEnabled = false; + this.strictComponentBindingsEnabled = function(enabled) { if (isDefined(enabled)) { - preAssignBindingsEnabled = enabled; + strictComponentBindingsEnabled = enabled; return this; } - return preAssignBindingsEnabled; + return strictComponentBindingsEnabled; }; - var TTL = 10; /** * @ngdoc method @@ -8406,11 +9361,96 @@ return /******/ (function(modules) { // webpackBootstrap return cssClassDirectivesEnabledConfig; }; + + /** + * The security context of DOM Properties. + * @private + */ + var PROP_CONTEXTS = createMap(); + + /** + * @ngdoc method + * @name $compileProvider#addPropertySecurityContext + * @description + * + * Defines the security context for DOM properties bound by ng-prop-*. + * + * @param {string} elementName The element name or '*' to match any element. + * @param {string} propertyName The DOM property name. + * @param {string} ctx The {@link $sce} security context in which this value is safe for use, e.g. `$sce.URL` + * @returns {object} `this` for chaining + */ + this.addPropertySecurityContext = function(elementName, propertyName, ctx) { + var key = (elementName.toLowerCase() + '|' + propertyName.toLowerCase()); + + if (key in PROP_CONTEXTS && PROP_CONTEXTS[key] !== ctx) { + throw $compileMinErr('ctxoverride', 'Property context \'{0}.{1}\' already set to \'{2}\', cannot override to \'{3}\'.', elementName, propertyName, PROP_CONTEXTS[key], ctx); + } + + PROP_CONTEXTS[key] = ctx; + return this; + }; + + /* Default property contexts. + * + * Copy of https://github.com/angular/angular/blob/6.0.6/packages/compiler/src/schema/dom_security_schema.ts#L31-L58 + * Changing: + * - SecurityContext.* => SCE_CONTEXTS/$sce.* + * - STYLE => CSS + * - various URL => MEDIA_URL + * - *|formAction, form|action URL => RESOURCE_URL (like the attribute) + */ + (function registerNativePropertyContexts() { + function registerContext(ctx, values) { + forEach(values, function(v) { PROP_CONTEXTS[v.toLowerCase()] = ctx; }); + } + + registerContext(SCE_CONTEXTS.HTML, [ + 'iframe|srcdoc', + '*|innerHTML', + '*|outerHTML' + ]); + registerContext(SCE_CONTEXTS.CSS, ['*|style']); + registerContext(SCE_CONTEXTS.URL, [ + 'area|href', 'area|ping', + 'a|href', 'a|ping', + 'blockquote|cite', + 'body|background', + 'del|cite', + 'input|src', + 'ins|cite', + 'q|cite' + ]); + registerContext(SCE_CONTEXTS.MEDIA_URL, [ + 'audio|src', + 'img|src', 'img|srcset', + 'source|src', 'source|srcset', + 'track|src', + 'video|src', 'video|poster' + ]); + registerContext(SCE_CONTEXTS.RESOURCE_URL, [ + '*|formAction', + 'applet|code', 'applet|codebase', + 'base|href', + 'embed|src', + 'frame|src', + 'form|action', + 'head|profile', + 'html|manifest', + 'iframe|src', + 'link|href', + 'media|src', + 'object|codebase', 'object|data', + 'script|src' + ]); + })(); + + this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', - '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', + '$controller', '$rootScope', '$sce', '$animate', function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, - $controller, $rootScope, $sce, $animate, $$sanitizeUri) { + $controller, $rootScope, $sce, $animate) { var SIMPLE_ATTR_NAME = /^\w/; var specialAttrHolder = window.document.createElement('div'); @@ -8435,19 +9475,15 @@ return /******/ (function(modules) { // webpackBootstrap } // We must run this hook in an apply since the $$postDigest runs outside apply $rootScope.$apply(function() { - var errors = []; for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { try { onChangesQueue[i](); } catch (e) { - errors.push(e); + $exceptionHandler(e); } } // Reset the queue to trigger a new schedule next time there is a change onChangesQueue = undefined; - if (errors.length) { - throw errors; - } }); } finally { onChangesTtl++; @@ -8455,6 +9491,57 @@ return /******/ (function(modules) { // webpackBootstrap } + function sanitizeSrcset(value, invokeType) { + if (!value) { + return value; + } + if (!isString(value)) { + throw $compileMinErr('srcset', 'Can\'t pass trusted values to `{0}`: "{1}"', invokeType, value.toString()); + } + + // Such values are a bit too complex to handle automatically inside $sce. + // Instead, we sanitize each of the URIs individually, which works, even dynamically. + + // It's not possible to work around this using `$sce.trustAsMediaUrl`. + // If you want to programmatically set explicitly trusted unsafe URLs, you should use + // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the + // `ng-bind-html` directive. + + var result = ''; + + // first check if there are spaces because it's not the same pattern + var trimmedSrcset = trim(value); + // ( 999x ,| 999w ,| ,|, ) + var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; + var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; + + // split srcset into tuple of uri and descriptor except for the last item + var rawUris = trimmedSrcset.split(pattern); + + // for each tuples + var nbrUrisWith2parts = Math.floor(rawUris.length / 2); + for (var i = 0; i < nbrUrisWith2parts; i++) { + var innerIdx = i * 2; + // sanitize the uri + result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx])); + // add the descriptor + result += ' ' + trim(rawUris[innerIdx + 1]); + } + + // split the last item into uri and descriptor + var lastTuple = trim(rawUris[i * 2]).split(/\s/); + + // sanitize the last uri + result += $sce.getTrustedMediaUrl(trim(lastTuple[0])); + + // and add the last descriptor if any + if (lastTuple.length === 2) { + result += (' ' + trim(lastTuple[1])); + } + return result; + } + + function Attributes(element, attributesToCopy) { if (attributesToCopy) { var keys = Object.keys(attributesToCopy); @@ -8559,8 +9646,8 @@ return /******/ (function(modules) { // webpackBootstrap */ $set: function(key, value, writeAttr, attrName) { // TODO: decide whether or not to throw an error if "class" - //is set through this function since it may cause $updateClass to - //become unstable. + // is set through this function since it may cause $updateClass to + // become unstable. var node = this.$$element[0], booleanKey = getBooleanAttrName(node, key), @@ -8590,44 +9677,9 @@ return /******/ (function(modules) { // webpackBootstrap nodeName = nodeName_(this.$$element); - if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || - (nodeName === 'img' && key === 'src')) { - // sanitize a[href] and img[src] values - this[key] = value = $$sanitizeUri(value, key === 'src'); - } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) { - // sanitize img[srcset] values - var result = ''; - - // first check if there are spaces because it's not the same pattern - var trimmedSrcset = trim(value); - // ( 999x ,| 999w ,| ,|, ) - var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; - var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; - - // split srcset into tuple of uri and descriptor except for the last item - var rawUris = trimmedSrcset.split(pattern); - - // for each tuples - var nbrUrisWith2parts = Math.floor(rawUris.length / 2); - for (var i = 0; i < nbrUrisWith2parts; i++) { - var innerIdx = i * 2; - // sanitize the uri - result += $$sanitizeUri(trim(rawUris[innerIdx]), true); - // add the descriptor - result += (' ' + trim(rawUris[innerIdx + 1])); - } - - // split the last item into uri and descriptor - var lastTuple = trim(rawUris[i * 2]).split(/\s/); - - // sanitize the last uri - result += $$sanitizeUri(trim(lastTuple[0]), true); - - // and add the last descriptor if any - if (lastTuple.length === 2) { - result += (' ' + trim(lastTuple[1])); - } - this[key] = value = result; + // Sanitize img[srcset] values. + if (nodeName === 'img' && key === 'srcset') { + this[key] = value = sanitizeSrcset(value, '$set(\'srcset\', value)'); } if (writeAttr !== false) { @@ -8635,7 +9687,16 @@ return /******/ (function(modules) { // webpackBootstrap this.$$element.removeAttr(attrName); } else { if (SIMPLE_ATTR_NAME.test(attrName)) { - this.$$element.attr(attrName, value); + // jQuery skips special boolean attrs treatment in XML nodes for + // historical reasons and hence AngularJS cannot freely call + // `.attr(attrName, false) with such attributes. To avoid issues + // in XHTML, call `removeAttr` in such cases instead. + // See https://github.com/jquery/jquery/issues/4249 + if (booleanKey && value === false) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } } else { setSpecialAttr(this.$$element[0], attrName, value); } @@ -8724,7 +9785,7 @@ return /******/ (function(modules) { // webpackBootstrap : function denormalizeTemplate(template) { return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); }, - NG_ATTR_BINDING = /^ngAttr[A-Z]/; + NG_PREFIX_BINDING = /^ng(Attr|Prop|On)([A-Z].*)$/; var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { @@ -8815,7 +9876,7 @@ return /******/ (function(modules) { // webpackBootstrap // for call to the link function. // Note: This will already clone the nodes... $linkNode = jqLite( - wrapTemplate(namespace, jqLite('
').append($compileNodes).html()) + wrapTemplate(namespace, jqLite('
').append($compileNodes).html()) ); } else if (cloneConnectFn) { // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart @@ -9060,43 +10121,66 @@ return /******/ (function(modules) { // webpackBootstrap directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); // iterate over the attributes - for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, + for (var attr, name, nName, value, ngPrefixMatch, nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; + var isNgAttr = false, isNgProp = false, isNgEvent = false; + var multiElementMatch; + attr = nAttrs[j]; name = attr.name; value = attr.value; - // support ngAttr attribute binding - ngAttrName = directiveNormalize(name); - isNgAttr = NG_ATTR_BINDING.test(ngAttrName); - if (isNgAttr) { + nName = directiveNormalize(name.toLowerCase()); + + // Support ng-attr-*, ng-prop-* and ng-on-* + if ((ngPrefixMatch = nName.match(NG_PREFIX_BINDING))) { + isNgAttr = ngPrefixMatch[1] === 'Attr'; + isNgProp = ngPrefixMatch[1] === 'Prop'; + isNgEvent = ngPrefixMatch[1] === 'On'; + + // Normalize the non-prefixed name name = name.replace(PREFIX_REGEXP, '') - .substr(8).replace(/_(.)/g, function(match, letter) { + .toLowerCase() + .substr(4 + ngPrefixMatch[1].length).replace(/_(.)/g, function(match, letter) { return letter.toUpperCase(); }); - } - var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); - if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { + // Support *-start / *-end multi element directives + } else if ((multiElementMatch = nName.match(MULTI_ELEMENT_DIR_RE)) && directiveIsMultiElement(multiElementMatch[1])) { attrStartName = name; attrEndName = name.substr(0, name.length - 5) + 'end'; name = name.substr(0, name.length - 6); } - nName = directiveNormalize(name.toLowerCase()); - attrsMap[nName] = name; - if (isNgAttr || !attrs.hasOwnProperty(nName)) { + if (isNgProp || isNgEvent) { + attrs[nName] = value; + attrsMap[nName] = attr.name; + + if (isNgProp) { + addPropertyDirective(node, directives, nName, name); + } else { + addEventDirective(directives, nName, name); + } + } else { + // Update nName for cases where a prefix was removed + // NOTE: the .toLowerCase() is unnecessary and causes https://github.com/angular/angular.js/issues/16624 for ng-attr-* + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + + if (isNgAttr || !attrs.hasOwnProperty(nName)) { attrs[nName] = value; if (getBooleanAttrName(node, nName)) { attrs[nName] = true; // presence means true } + } + + addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); + addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, + attrEndName); } - addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); - addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, - attrEndName); } if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { @@ -9369,17 +10453,6 @@ return /******/ (function(modules) { // webpackBootstrap compileNode = $compileNode[0]; replaceWith(jqCollection, sliceArgs($template), compileNode); - // Support: Chrome < 50 - // https://github.com/angular/angular.js/issues/14041 - - // In the versions of V8 prior to Chrome 50, the document fragment that is created - // in the `replaceWith` function is improperly garbage collected despite still - // being referenced by the `parentNode` property of all of the child nodes. By adding - // a reference to the fragment via a different property, we can avoid that incorrect - // behavior. - // TODO: remove this line after Chrome 50 has been released - $template[0].$$parentNode = $template[0].parentNode; - childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { // Don't pass in: @@ -9401,7 +10474,7 @@ return /******/ (function(modules) { // webpackBootstrap // We have transclusion slots, // collect them up, compile them and store their transclusion functions - $template = []; + $template = window.document.createDocumentFragment(); var slotMap = createMap(); var filledSlots = createMap(); @@ -9429,10 +10502,10 @@ return /******/ (function(modules) { // webpackBootstrap var slotName = slotMap[directiveNormalize(nodeName_(node))]; if (slotName) { filledSlots[slotName] = true; - slots[slotName] = slots[slotName] || []; - slots[slotName].push(node); + slots[slotName] = slots[slotName] || window.document.createDocumentFragment(); + slots[slotName].appendChild(node); } else { - $template.push(node); + $template.appendChild(node); } }); @@ -9446,9 +10519,12 @@ return /******/ (function(modules) { // webpackBootstrap for (var slotName in slots) { if (slots[slotName]) { // Only define a transclusion function if the slot was filled - slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); + var slotCompileNodes = jqLite(slots[slotName].childNodes); + slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slotCompileNodes, transcludeFn); } } + + $template = jqLite($template.childNodes); } $compileNode.empty(); // clear contents @@ -9639,33 +10715,11 @@ return /******/ (function(modules) { // webpackBootstrap var controller = elementControllers[name]; var bindings = controllerDirective.$$bindings.bindToController; - if (preAssignBindingsEnabled) { - if (bindings) { - controller.bindingInfo = - initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); - } else { - controller.bindingInfo = {}; - } - - var controllerResult = controller(); - if (controllerResult !== controller.instance) { - // If the controller constructor has a return value, overwrite the instance - // from setupControllers - controller.instance = controllerResult; - $element.data('$' + controllerDirective.name + 'Controller', controllerResult); - if (controller.bindingInfo.removeWatches) { - controller.bindingInfo.removeWatches(); - } - controller.bindingInfo = - initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); - } - } else { - controller.instance = controller(); - $element.data('$' + controllerDirective.name + 'Controller', controller.instance); - controller.bindingInfo = - initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); + controller.instance = controller(); + $element.data('$' + controllerDirective.name + 'Controller', controller.instance); + controller.bindingInfo = + initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); } - } // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy forEach(controllerDirectives, function(controllerDirective, name) { @@ -9806,7 +10860,14 @@ return /******/ (function(modules) { // webpackBootstrap if (!value) { var dataName = '$' + name + 'Controller'; - value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); + + if (inheritType === '^^' && $element[0] && $element[0].nodeType === NODE_TYPE_DOCUMENT) { + // inheritedData() uses the documentElement when it finds the document, so we would + // require from the element itself. + value = null; + } else { + value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); + } } if (!value && !optional) { @@ -10073,10 +11134,10 @@ return /******/ (function(modules) { // webpackBootstrap } linkQueue = null; }).catch(function(error) { - if (error instanceof Error) { + if (isError(error)) { $exceptionHandler(error); } - }).catch(noop); + }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { var childBoundTranscludeFn = boundTranscludeFn; @@ -10163,30 +11224,95 @@ return /******/ (function(modules) { // webpackBootstrap } - function getTrustedContext(node, attrNormalizedName) { + function getTrustedAttrContext(nodeName, attrNormalizedName) { if (attrNormalizedName === 'srcdoc') { return $sce.HTML; } - var tag = nodeName_(node); - // All tags with src attributes require a RESOURCE_URL value, except for - // img and various html5 media tags. + // All nodes with src attributes require a RESOURCE_URL value, except for + // img and various html5 media nodes, which require the MEDIA_URL context. if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { - if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) { + if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) { return $sce.RESOURCE_URL; } - // maction[xlink:href] can source SVG. It's not limited to . - } else if (attrNormalizedName === 'xlinkHref' || - (tag === 'form' && attrNormalizedName === 'action') || + return $sce.MEDIA_URL; + } else if (attrNormalizedName === 'xlinkHref') { + // Some xlink:href are okay, most aren't + if (nodeName === 'image') return $sce.MEDIA_URL; + if (nodeName === 'a') return $sce.URL; + return $sce.RESOURCE_URL; + } else if ( + // Formaction + (nodeName === 'form' && attrNormalizedName === 'action') || + // If relative URLs can go where they are not expected to, then + // all sorts of trust issues can arise. + (nodeName === 'base' && attrNormalizedName === 'href') || // links can be stylesheets or imports, which can run script in the current origin - (tag === 'link' && attrNormalizedName === 'href') + (nodeName === 'link' && attrNormalizedName === 'href') ) { return $sce.RESOURCE_URL; + } else if (nodeName === 'a' && (attrNormalizedName === 'href' || + attrNormalizedName === 'ngHref')) { + return $sce.URL; } } + function getTrustedPropContext(nodeName, propNormalizedName) { + var prop = propNormalizedName.toLowerCase(); + return PROP_CONTEXTS[nodeName + '|' + prop] || PROP_CONTEXTS['*|' + prop]; + } + + function sanitizeSrcsetPropertyValue(value) { + return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset'); + } + function addPropertyDirective(node, directives, attrName, propName) { + if (EVENT_HANDLER_ATTR_REGEXP.test(propName)) { + throw $compileMinErr('nodomevents', 'Property bindings for HTML DOM event properties are disallowed'); + } + + var nodeName = nodeName_(node); + var trustedContext = getTrustedPropContext(nodeName, propName); + + var sanitizer = identity; + // Sanitize img[srcset] + source[srcset] values. + if (propName === 'srcset' && (nodeName === 'img' || nodeName === 'source')) { + sanitizer = sanitizeSrcsetPropertyValue; + } else if (trustedContext) { + sanitizer = $sce.getTrusted.bind($sce, trustedContext); + } + + directives.push({ + priority: 100, + compile: function ngPropCompileFn(_, attr) { + var ngPropGetter = $parse(attr[attrName]); + var ngPropWatch = $parse(attr[attrName], function sceValueOf(val) { + // Unwrap the value to compare the actual inner safe value, not the wrapper object. + return $sce.valueOf(val); + }); + + return { + pre: function ngPropPreLinkFn(scope, $element) { + function applyPropValue() { + var propValue = ngPropGetter(scope); + $element[0][propName] = sanitizer(propValue); + } + + applyPropValue(); + scope.$watch(ngPropWatch, applyPropValue); + } + }; + } + }); + } + + function addEventDirective(directives, attrName, eventName) { + directives.push( + createEventDirective($parse, $rootScope, $exceptionHandler, attrName, eventName, /*forceAsync=*/false) + ); + } function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { - var trustedContext = getTrustedContext(node, name); + var nodeName = nodeName_(node); + var trustedContext = getTrustedAttrContext(nodeName, name); var mustHaveExpression = !isNgAttr; var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; @@ -10195,16 +11321,14 @@ return /******/ (function(modules) { // webpackBootstrap // no interpolation found -> ignore if (!interpolateFn) return; - if (name === 'multiple' && nodeName_(node) === 'select') { + if (name === 'multiple' && nodeName === 'select') { throw $compileMinErr('selmulti', 'Binding to the \'multiple\' attribute is not supported. Element: {0}', startingTag(node)); } if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { - throw $compileMinErr('nodomevents', - 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' + - 'ng- versions (such as ng-click instead of onclick) instead.'); + throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed'); } directives.push({ @@ -10311,7 +11435,7 @@ return /******/ (function(modules) { // webpackBootstrap } if (jqLite.hasData(firstElementToRemove)) { - // Copy over user data (that includes Angular's $scope etc.). Don't copy private + // Copy over user data (that includes AngularJS's $scope etc.). Don't copy private // data here because there's no public interface in jQuery to do that and copying over // event listeners (which is the main use of private data) wouldn't work anyway. jqLite.data(newNode, jqLite.data(firstElementToRemove)); @@ -10346,12 +11470,20 @@ return /******/ (function(modules) { // webpackBootstrap } } + function strictBindingsCheck(attrName, directiveName) { + if (strictComponentBindingsEnabled) { + throw $compileMinErr('missingattr', + 'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!', + attrName, directiveName); + } + } // Set up $watches for isolate scope and controller bindings. function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { var removeWatchCollection = []; var initialChanges = {}; var changes; + forEach(bindings, function initializeBinding(definition, scopeName) { var attrName = definition.attrName, optional = definition.optional, @@ -10363,7 +11495,9 @@ return /******/ (function(modules) { // webpackBootstrap case '@': if (!optional && !hasOwnProperty.call(attrs, attrName)) { + strictBindingsCheck(attrName, directive.name); destination[scopeName] = attrs[attrName] = undefined; + } removeWatch = attrs.$observe(attrName, function(value) { if (isString(value) || isBoolean(value)) { @@ -10379,7 +11513,7 @@ return /******/ (function(modules) { // webpackBootstrap // the value is there for use in the link fn destination[scopeName] = $interpolate(lastValue)(scope); } else if (isBoolean(lastValue)) { - // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted + // If the attributes is one of the BOOLEAN_ATTR then AngularJS will have converted // the value to boolean rather than a string, so we special case this situation destination[scopeName] = lastValue; } @@ -10390,6 +11524,7 @@ return /******/ (function(modules) { // webpackBootstrap case '=': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; + strictBindingsCheck(attrName, directive.name); attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; @@ -10398,8 +11533,7 @@ return /******/ (function(modules) { // webpackBootstrap if (parentGet.literal) { compare = equals; } else { - // eslint-disable-next-line no-self-compare - compare = function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }; + compare = simpleCompare; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest @@ -10435,31 +11569,35 @@ return /******/ (function(modules) { // webpackBootstrap case '<': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; + strictBindingsCheck(attrName, directive.name); attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; parentGet = $parse(attrs[attrName]); - var deepWatch = parentGet.literal; + var isLiteral = parentGet.literal; var initialValue = destination[scopeName] = parentGet(scope); initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); - removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) { + removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) { if (oldValue === newValue) { - if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) { + if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) { return; } oldValue = initialValue; } recordChanges(scopeName, newValue, oldValue); destination[scopeName] = newValue; - }, deepWatch); + }); removeWatchCollection.push(removeWatch); break; case '&': + if (!optional && !hasOwnProperty.call(attrs, attrName)) { + strictBindingsCheck(attrName, directive.name); + } // Don't assign Object.prototype method to scope parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; @@ -10474,9 +11612,7 @@ return /******/ (function(modules) { // webpackBootstrap }); function recordChanges(key, currentValue, previousValue) { - if (isFunction(destination.$onChanges) && currentValue !== previousValue && - // eslint-disable-next-line no-self-compare - (currentValue === currentValue || previousValue === previousValue)) { + if (isFunction(destination.$onChanges) && !simpleCompare(currentValue, previousValue)) { // If we have not already scheduled the top level onChangesQueue handler then do so now if (!onChangesQueue) { scope.$$postDigest(flushOnChangesQueue); @@ -10531,7 +11667,9 @@ return /******/ (function(modules) { // webpackBootstrap function directiveNormalize(name) { return name .replace(PREFIX_REGEXP, '') - .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace); + .replace(SPECIAL_CHARS_REGEXP, function(_, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }); } /** @@ -10541,7 +11679,7 @@ return /******/ (function(modules) { // webpackBootstrap * @description * A shared object between directive compile / linking functions which contains normalized DOM * element attributes. The values reflect current binding state `{{ }}`. The normalization is - * needed since all of these are treated as equivalent in Angular: + * needed since all of these are treated as equivalent in AngularJS: * * ``` * @@ -10647,15 +11785,14 @@ return /******/ (function(modules) { // webpackBootstrap * @this * * @description - * The {@link ng.$controller $controller service} is used by Angular to create new + * The {@link ng.$controller $controller service} is used by AngularJS to create new * controllers. * * This provider allows controller registration via the * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { - var controllers = {}, - globals = false; + var controllers = {}; /** * @ngdoc method @@ -10683,22 +11820,7 @@ return /******/ (function(modules) { // webpackBootstrap } }; - /** - * @ngdoc method - * @name $controllerProvider#allowGlobals - * @description If called, allows `$controller` to find controller constructors on `window` - * - * @deprecated - * sinceVersion="v1.3.0" - * removeVersion="v1.7.0" - * This method of finding controllers has been deprecated. - */ - this.allowGlobals = function() { - globals = true; - }; - - - this.$get = ['$injector', '$window', function($injector, $window) { + this.$get = ['$injector', function($injector) { /** * @ngdoc service @@ -10711,8 +11833,6 @@ return /******/ (function(modules) { // webpackBootstrap * * * check if a controller with given name is registered via `$controllerProvider` * * check if evaluating the string on the current scope returns a constructor - * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global - * `window` object (deprecated, not recommended) * * The string can use the `controller as property` syntax, where the controller instance is published * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this @@ -10752,8 +11872,7 @@ return /******/ (function(modules) { // webpackBootstrap identifier = identifier || match[3]; expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] - : getter(locals.$scope, constructor, true) || - (globals ? getter($window, constructor, true) : undefined); + : getter(locals.$scope, constructor, true); if (!expression) { throw $controllerMinErr('ctrlreg', @@ -10885,7 +12004,7 @@ return /******/ (function(modules) { // webpackBootstrap * @this * * @description - * Any uncaught exception in angular expressions is delegated to this service. + * Any uncaught exception in AngularJS expressions is delegated to this service. * The default implementation simply delegates to `$log.error` which logs it into * the browser console. * @@ -10987,14 +12106,14 @@ return /******/ (function(modules) { // webpackBootstrap * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object) * * Note that serializer will sort the request parameters alphabetically. - * */ + */ this.$get = function() { return function ngParamSerializer(params) { if (!params) return ''; var parts = []; forEachSorted(params, function(value, key) { - if (value === null || isUndefined(value)) return; + if (value === null || isUndefined(value) || isFunction(value)) return; if (isArray(value)) { forEach(value, function(v) { parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); @@ -11054,7 +12173,7 @@ return /******/ (function(modules) { // webpackBootstrap * }); * ``` * - * */ + */ this.$get = function() { return function jQueryLikeParamSerializer(params) { if (!params) return ''; @@ -11063,7 +12182,6 @@ return /******/ (function(modules) { // webpackBootstrap return parts.join('&'); function serialize(toSerialize, prefix, topLevel) { - if (toSerialize === null || isUndefined(toSerialize)) return; if (isArray(toSerialize)) { forEach(toSerialize, function(value, index) { serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); @@ -11076,7 +12194,11 @@ return /******/ (function(modules) { // webpackBootstrap (topLevel ? '' : ']')); }); } else { - parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); + if (isFunction(toSerialize)) { + toSerialize = toSerialize(); + } + parts.push(encodeUriQuery(prefix) + '=' + + (toSerialize == null ? '' : encodeUriQuery(serializeValue(toSerialize)))); } } }; @@ -11090,8 +12212,18 @@ return /******/ (function(modules) { // webpackBootstrap if (tempData) { var contentType = headers('Content-Type'); - if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { - data = fromJson(tempData); + var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0); + + if (hasJsonContentType || isJsonLike(tempData)) { + try { + data = fromJson(tempData); + } catch (e) { + if (!hasJsonContentType) { + return data; + } + throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' + + 'Parse error: "{1}"', data, e); + } } } } @@ -11201,7 +12333,7 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. - * */ + */ function $HttpProvider() { /** * @ngdoc property @@ -11214,12 +12346,6 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses * by default. See {@link $http#caching $http Caching} for more information. * - * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. - * Defaults value is `'XSRF-TOKEN'`. - * - * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the - * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. - * * - **`defaults.headers`** - {Object} - Default headers for all $http requests. * Refer to {@link ng.$http#setting-http-headers $http} for documentation on * setting default headers. @@ -11228,17 +12354,40 @@ return /******/ (function(modules) { // webpackBootstrap * - **`defaults.headers.put`** * - **`defaults.headers.patch`** * + * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the + * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the + * {@link $jsonpCallbacks} service. Defaults to `'callback'`. * * - **`defaults.paramSerializer`** - `{string|function(Object):string}` - A function * used to the prepare string representation of request parameters (specified as an object). * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. * - * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the - * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the - * {@link $jsonpCallbacks} service. Defaults to `'callback'`. + * - **`defaults.transformRequest`** - + * `{Array|function(data, headersGetter)}` - + * An array of functions (or a single function) which are applied to the request data. + * By default, this is an array with one request transformation function: * - **/ + * - If the `data` property of the request configuration object contains an object, serialize it + * into JSON format. + * + * - **`defaults.transformResponse`** - + * `{Array|function(data, headersGetter, status)}` - + * An array of functions (or a single function) which are applied to the response data. By default, + * this is an array which applies one response transformation function that does two things: + * + * - If XSRF prefix is detected, strip it + * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}). + * - If the `Content-Type` is `application/json` or the response looks like JSON, + * deserialize it using a JSON parser. + * + * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. + * Defaults value is `'XSRF-TOKEN'`. + * + * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the + * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. + * + */ var defaults = this.defaults = { // transform incoming response data transformResponse: [defaultHttpResponseTransform], @@ -11285,7 +12434,7 @@ return /******/ (function(modules) { // webpackBootstrap * * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. * otherwise, returns the current configured value. - **/ + */ this.useApplyAsync = function(value) { if (isDefined(value)) { useApplyAsync = !!value; @@ -11306,9 +12455,71 @@ return /******/ (function(modules) { // webpackBootstrap * array, on request, but reverse order, on response. * * {@link ng.$http#interceptors Interceptors detailed info} - **/ + */ var interceptorFactories = this.interceptors = []; + /** + * @ngdoc property + * @name $httpProvider#xsrfTrustedOrigins + * @description + * + * Array containing URLs whose origins are trusted to receive the XSRF token. See the + * {@link ng.$http#security-considerations Security Considerations} sections for more details on + * XSRF. + * + * **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme), + * the [hostname](https://en.wikipedia.org/wiki/Hostname) and the + * [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and + * `https:`, the port number can be omitted if using th default ports (80 and 443 respectively). + * Examples: `http://example.com`, `https://api.example.com:9876` + * + *
+ * It is not possible to trust specific URLs/paths. The `path`, `query` and `fragment` parts + * of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be + * treated as `https://foo.com`, meaning that **all** requests to URLs starting with + * `https://foo.com/` will include the XSRF token. + *
+ * + * @example + * + * ```js + * // App served from `https://example.com/`. + * angular. + * module('xsrfTrustedOriginsExample', []). + * config(['$httpProvider', function($httpProvider) { + * $httpProvider.xsrfTrustedOrigins.push('https://api.example.com'); + * }]). + * run(['$http', function($http) { + * // The XSRF token will be sent. + * $http.get('https://api.example.com/preferences').then(...); + * + * // The XSRF token will NOT be sent. + * $http.get('https://stats.example.com/activity').then(...); + * }]); + * ``` + */ + var xsrfTrustedOrigins = this.xsrfTrustedOrigins = []; + + /** + * @ngdoc property + * @name $httpProvider#xsrfWhitelistedOrigins + * @description + * + * @deprecated + * sinceVersion="1.8.1" + * + * This property is deprecated. Use {@link $httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins} + * instead. + */ + Object.defineProperty(this, 'xsrfWhitelistedOrigins', { + get: function() { + return this.xsrfTrustedOrigins; + }, + set: function(origins) { + this.xsrfTrustedOrigins = origins; + } + }); + this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce', function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) { @@ -11332,6 +12543,11 @@ return /******/ (function(modules) { // webpackBootstrap ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); }); + /** + * A function to check request URLs against a list of allowed origins. + */ + var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfTrustedOrigins); + /** * @ngdoc service * @kind function @@ -11343,7 +12559,7 @@ return /******/ (function(modules) { // webpackBootstrap * @requires $injector * * @description - * The `$http` service is a core Angular service that facilitates communication with the remote + * The `$http` service is a core AngularJS service that facilitates communication with the remote * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). * @@ -11360,7 +12576,9 @@ return /******/ (function(modules) { // webpackBootstrap * * ## General usage * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} — - * that is used to generate an HTTP request and returns a {@link ng.$q promise}. + * that is used to generate an HTTP request and returns a {@link ng.$q promise} that is + * resolved (request success) or rejected (request failure) with a + * {@link ng.$http#$http-returns response} object. * * ```js * // Simple GET request example: @@ -11376,23 +12594,6 @@ return /******/ (function(modules) { // webpackBootstrap * }); * ``` * - * The response object has these properties: - * - * - **data** – `{string|Object}` – The response body transformed with the transform - * functions. - * - **status** – `{number}` – HTTP status code of the response. - * - **headers** – `{function([headerName])}` – Header getter function. - * - **config** – `{Object}` – The configuration object that was used to generate the request. - * - **statusText** – `{string}` – HTTP status text of the response. - * - * A response status code between 200 and 299 is considered a success status and will result in - * the success callback being called. Any response status code outside of that range is - * considered an error status and will result in the error callback being called. - * Also, status codes less than -1 are normalized to zero. -1 usually means the request was - * aborted, e.g. using a `config.timeout`. - * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning - * that the outcome (success or error) will be determined by the final response status code. - * * * ## Shortcut methods * @@ -11481,7 +12682,7 @@ return /******/ (function(modules) { // webpackBootstrap * which allows you to `push` or `unshift` a new transformation function into the transformation chain. * *
- * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. + * **Note:** AngularJS does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest * function will be reflected on the scope and in any templates where the object is data-bound. @@ -11498,17 +12699,20 @@ return /******/ (function(modules) { // webpackBootstrap * You can augment or replace the default transformations by modifying these properties by adding to or * replacing the array. * - * Angular provides the following default transformations: + * AngularJS provides the following default transformations: * - * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): + * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is + * an array with one function that does the following: * * - If the `data` property of the request configuration object contains an object, serialize it * into JSON format. * - * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): + * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is + * an array with one function that does the following: * * - If XSRF prefix is detected, strip it (see Security Considerations section below). - * - If JSON response is detected, deserialize it using a JSON parser. + * - If the `Content-Type` is `application/json` or the response looks like JSON, + * deserialize it using a JSON parser. * * * ### Overriding the Default Transformations Per Request @@ -11668,7 +12872,7 @@ return /******/ (function(modules) { // webpackBootstrap * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) * - * Both server and the client must cooperate in order to eliminate these threats. Angular comes + * Both server and the client must cooperate in order to eliminate these threats. AngularJS comes * pre-configured with strategies that address these issues, but for this to work backend server * cooperation is required. * @@ -11678,7 +12882,7 @@ return /******/ (function(modules) { // webpackBootstrap * allows third party website to turn your JSON resource URL into * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To * counter this your server can prefix all JSON requests with following string `")]}',\n"`. - * Angular will automatically strip the prefix before processing it as JSON. + * AngularJS will automatically strip the prefix before processing it as JSON. * * For example if your server needs to return: * ```js @@ -11691,34 +12895,51 @@ return /******/ (function(modules) { // webpackBootstrap * ['one','two'] * ``` * - * Angular will strip the prefix, before processing the JSON. + * AngularJS will strip the prefix, before processing the JSON. * * * ### Cross Site Request Forgery (XSRF) Protection * * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by * which the attacker can trick an authenticated user into unknowingly executing actions on your - * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the + * website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP - * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the - * cookie, your server can be assured that the XHR came from JavaScript running on your domain. - * The header will not be set for cross-domain requests. + * header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read + * the cookie, your server can be assured that the XHR came from JavaScript running on your + * domain. * * To take advantage of this, your server needs to set a token in a JavaScript readable session * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the - * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure - * that only JavaScript running on your domain could have sent the request. The token must be - * unique for each user and must be verifiable by the server (to prevent the JavaScript from + * server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be + * sure that only JavaScript running on your domain could have sent the request. The token must + * be unique for each user and must be verifiable by the server (to prevent the JavaScript from * making up its own tokens). We recommend that the token is a digest of your site's * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) * for added security. * - * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName - * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, - * or the per-request config object. + * The header will — by default — **not** be set for cross-domain requests. This + * prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining + * access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you + * want to, you can trust additional origins to also receive the XSRF token, by adding them + * to {@link ng.$httpProvider#xsrfTrustedOrigins xsrfTrustedOrigins}. This might be + * useful, for example, if your application, served from `example.com`, needs to access your API + * at `api.example.com`. + * See {@link ng.$httpProvider#xsrfTrustedOrigins $httpProvider.xsrfTrustedOrigins} for + * more details. + * + *
+ * **Warning**
+ * Only trusted origins that you have control over and make sure you understand the + * implications of doing so. + *
+ * + * The name of the cookie and the header can be specified using the `xsrfCookieName` and + * `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time, + * `$http.defaults` at run-time, or the per-request config object. + * + * In order to prevent collisions in environments where multiple AngularJS apps share the + * same domain or subdomain, we recommend that each application uses a unique cookie name. * - * In order to prevent collisions in environments where multiple Angular apps share the - * same domain or subdomain, we recommend that each application uses unique cookie name. * * @param {object} config Object describing the request to be made and how it should be * processed. The object has following properties: @@ -11764,14 +12985,44 @@ return /******/ (function(modules) { // webpackBootstrap * See {@link $http#caching $http Caching} for more information. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} * that should abort the request when resolved. + * + * A numerical timeout or a promise returned from {@link ng.$timeout $timeout}, will set + * the `xhrStatus` in the {@link $http#$http-returns response} to "timeout", and any other + * resolved promise will set it to "abort", following standard XMLHttpRequest behavior. + * * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) * for more information. * - **responseType** - `{string}` - see * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). * - * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object - * when the request succeeds or fails. + * @returns {HttpPromise} A {@link ng.$q `Promise}` that will be resolved (request success) + * or rejected (request failure) with a response object. + * + * The response object has these properties: + * + * - **data** – `{string|Object}` – The response body transformed with + * the transform functions. + * - **status** – `{number}` – HTTP status code of the response. + * - **headers** – `{function([headerName])}` – Header getter function. + * - **config** – `{Object}` – The configuration object that was used + * to generate the request. + * - **statusText** – `{string}` – HTTP status text of the response. + * - **xhrStatus** – `{string}` – Status of the XMLHttpRequest + * (`complete`, `error`, `timeout` or `abort`). + * + * + * A response status code between 200 and 299 is considered a success status + * and will result in the success callback being called. Any response status + * code outside of that range is considered an error status and will result + * in the error callback being called. + * Also, status codes less than -1 are normalized to zero. -1 usually means + * the request was aborted, e.g. using a `config.timeout`. More information + * about the status might be available in the `xhrStatus` property. + * + * Note that if the response is a redirect, XMLHttpRequest will transparently + * follow it, meaning that the outcome (success or error) will be determined + * by the final response status code. * * * @property {Array.} pendingRequests Array of config objects for currently pending @@ -11805,8 +13056,8 @@ return /******/ (function(modules) { // webpackBootstrap angular.module('httpExample', []) .config(['$sceDelegateProvider', function($sceDelegateProvider) { - // We must whitelist the JSONP endpoint that we are using to show that we trust it - $sceDelegateProvider.resourceUrlWhitelist([ + // We must add the JSONP endpoint that we are using to the trusted list to show that we trust it + $sceDelegateProvider.trustedResourceUrlList([ 'self', 'https://angularjs.org/**' ]); @@ -11895,7 +13146,7 @@ return /******/ (function(modules) { // webpackBootstrap config.paramSerializer = isString(config.paramSerializer) ? $injector.get(config.paramSerializer) : config.paramSerializer; - $browser.$$incOutstandingRequestCount(); + $browser.$$incOutstandingRequestCount('$http'); var requestInterceptors = []; var responseInterceptors = []; @@ -11933,7 +13184,7 @@ return /******/ (function(modules) { // webpackBootstrap } function completeOutstandingRequest() { - $browser.$$completeOutstandingRequest(noop); + $browser.$$completeOutstandingRequest(noop, '$http'); } function executeHeaderFns(headers, config) { @@ -12021,8 +13272,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; * or an object created by a call to `$sce.trustAsResourceUrl(url)`. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ /** @@ -12034,8 +13286,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; * or an object created by a call to `$sce.trustAsResourceUrl(url)`. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ /** @@ -12047,8 +13300,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; * or an object created by a call to `$sce.trustAsResourceUrl(url)`. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ /** @@ -12060,10 +13314,14 @@ return /******/ (function(modules) { // webpackBootstrap * * Note that, since JSONP requests are sensitive because the response is given full access to the browser, * the url must be declared, via {@link $sce} as a trusted resource URL. - * You can trust a URL by adding it to the whitelist via - * {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or + * You can trust a URL by adding it to the trusted resource URL list via + * {@link $sceDelegateProvider#trustedResourceUrlList `$sceDelegateProvider.trustedResourceUrlList`} or * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}. * + * You should avoid generating the URL for the JSONP request from user provided data. + * Provide additional query parameters via `params` property of the `config` parameter, rather than + * modifying the URL itself. + * * JSONP requests must specify a callback to be used in the response from the server. This callback * is passed as a query parameter in the request. You must specify the name of this parameter by * setting the `jsonpCallbackParam` property on the request config object. @@ -12085,8 +13343,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; * or an object created by a call to `$sce.trustAsResourceUrl(url)`. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ createShortMethods('get', 'delete', 'head', 'jsonp'); @@ -12099,8 +13358,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ /** @@ -12112,8 +13372,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ /** @@ -12125,8 +13386,9 @@ return /******/ (function(modules) { // webpackBootstrap * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object + * @param {Object=} config Optional configuration object. See {@link ng.$http#$http-arguments `$http()` arguments}. + * @returns {HttpPromise} A Promise that will be resolved or rejected with a response object. + * See {@link ng.$http#$http-returns `$http()` return value}. */ createShortMethodsWithData('post', 'put', 'patch'); @@ -12208,7 +13470,8 @@ return /******/ (function(modules) { // webpackBootstrap if ((config.cache || defaults.cache) && config.cache !== false && (config.method === 'GET' || config.method === 'JSONP')) { cache = isObject(config.cache) ? config.cache - : isObject(defaults.cache) ? defaults.cache + : isObject(/** @type {?} */ (defaults).cache) + ? /** @type {?} */ (defaults).cache : defaultCache; } @@ -12221,9 +13484,9 @@ return /******/ (function(modules) { // webpackBootstrap } else { // serving from cache if (isArray(cachedResp)) { - resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); + resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]); } else { - resolvePromise(cachedResp, 200, {}, 'OK'); + resolvePromise(cachedResp, 200, {}, 'OK', 'complete'); } } } else { @@ -12236,7 +13499,7 @@ return /******/ (function(modules) { // webpackBootstrap // if we won't have the response in cache, set the xsrf headers and // send the request to the backend if (isUndefined(cachedResp)) { - var xsrfValue = urlIsSameOrigin(config.url) + var xsrfValue = urlIsAllowedOrigin(config.url) ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] : undefined; if (xsrfValue) { @@ -12280,10 +13543,10 @@ return /******/ (function(modules) { // webpackBootstrap * - resolves the raw $http promise * - calls $apply */ - function done(status, response, headersString, statusText) { + function done(status, response, headersString, statusText, xhrStatus) { if (cache) { if (isSuccess(status)) { - cache.put(url, [status, response, parseHeaders(headersString), statusText]); + cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]); } else { // remove promise from the cache cache.remove(url); @@ -12291,7 +13554,7 @@ return /******/ (function(modules) { // webpackBootstrap } function resolveHttpPromise() { - resolvePromise(response, status, headersString, statusText); + resolvePromise(response, status, headersString, statusText, xhrStatus); } if (useApplyAsync) { @@ -12306,7 +13569,7 @@ return /******/ (function(modules) { // webpackBootstrap /** * Resolves the raw $http promise. */ - function resolvePromise(response, status, headers, statusText) { + function resolvePromise(response, status, headers, statusText, xhrStatus) { //status: HTTP response status code, 0, -1 (aborted by timeout / promise) status = status >= -1 ? status : 0; @@ -12315,12 +13578,13 @@ return /******/ (function(modules) { // webpackBootstrap status: status, headers: headersGetter(headers), config: config, - statusText: statusText + statusText: statusText, + xhrStatus: xhrStatus }); } function resolvePromiseWithResult(result) { - resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus); } function removePendingReq() { @@ -12337,20 +13601,26 @@ return /******/ (function(modules) { // webpackBootstrap return url; } - function sanitizeJsonpCallbackParam(url, key) { - if (/[&?][^=]+=JSON_CALLBACK/.test(url)) { - // Throw if the url already contains a reference to JSON_CALLBACK - throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url); - } - - var callbackParamRegex = new RegExp('[&?]' + key + '='); - if (callbackParamRegex.test(url)) { - // Throw if the callback param was already provided - throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', key, url); + function sanitizeJsonpCallbackParam(url, cbKey) { + var parts = url.split('?'); + if (parts.length > 2) { + // Throw if the url contains more than one `?` query indicator + throw $httpMinErr('badjsonp', 'Illegal use more than one "?", in url, "{1}"', url); } + var params = parseKeyValue(parts[1]); + forEach(params, function(value, key) { + if (value === 'JSON_CALLBACK') { + // Throw if the url already contains a reference to JSON_CALLBACK + throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url); + } + if (key === cbKey) { + // Throw if the callback param was already provided + throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', cbKey, url); + } + }); // Add in the JSON_CALLBACK callback param value - url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK'; + url += ((url.indexOf('?') === -1) ? '?' : '&') + cbKey + '=JSON_CALLBACK'; return url; } @@ -12421,12 +13691,13 @@ return /******/ (function(modules) { // webpackBootstrap var jsonpDone = jsonpReq(url, callbackPath, function(status, text) { // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING) var response = (status === 200) && callbacks.getResponse(callbackPath); - completeRequest(callback, status, response, '', text); + completeRequest(callback, status, response, '', text, 'complete'); callbacks.removeCallback(callbackPath); }); } else { var xhr = createXhr(method, url); + var abortedByTimeout = false; xhr.open(method, url, true); forEach(headers, function(value, key) { @@ -12456,21 +13727,32 @@ return /******/ (function(modules) { // webpackBootstrap status, response, xhr.getAllResponseHeaders(), - statusText); + statusText, + 'complete'); }; var requestError = function() { // The response is always empty // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error - completeRequest(callback, -1, null, null, ''); + completeRequest(callback, -1, null, null, '', 'error'); + }; + + var requestAborted = function() { + completeRequest(callback, -1, null, null, '', abortedByTimeout ? 'timeout' : 'abort'); + }; + + var requestTimeout = function() { + // The response is always empty + // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error + completeRequest(callback, -1, null, null, '', 'timeout'); }; xhr.onerror = requestError; - xhr.onabort = requestError; - xhr.ontimeout = requestError; + xhr.ontimeout = requestTimeout; + xhr.onabort = requestAborted; forEach(eventHandlers, function(value, key) { - xhr.addEventListener(key, value); + xhr.addEventListener(key, value); }); forEach(uploadEventHandlers, function(value, key) { @@ -12501,14 +13783,26 @@ return /******/ (function(modules) { // webpackBootstrap xhr.send(isUndefined(post) ? null : post); } + // Since we are using xhr.abort() when a request times out, we have to set a flag that + // indicates to requestAborted if the request timed out or was aborted. + // + // http.timeout = numerical timeout timeout + // http.timeout = $timeout timeout + // http.timeout = promise abort + // xhr.abort() abort (The xhr object is normally inaccessible, but + // can be exposed with the xhrFactory) if (timeout > 0) { - var timeoutId = $browserDefer(timeoutRequest, timeout); + var timeoutId = $browserDefer(function() { + timeoutRequest('timeout'); + }, timeout); } else if (isPromiseLike(timeout)) { - timeout.then(timeoutRequest); + timeout.then(function() { + timeoutRequest(isDefined(timeout.$$timeoutId) ? 'timeout' : 'abort'); + }); } - - function timeoutRequest() { + function timeoutRequest(reason) { + abortedByTimeout = reason === 'timeout'; if (jsonpDone) { jsonpDone(); } @@ -12517,14 +13811,14 @@ return /******/ (function(modules) { // webpackBootstrap } } - function completeRequest(callback, status, response, headersString, statusText) { + function completeRequest(callback, status, response, headersString, statusText, xhrStatus) { // cancel timeout and subsequent timeout promise resolution if (isDefined(timeoutId)) { $browserDefer.cancel(timeoutId); } jsonpDone = xhr = null; - callback(status, response, headersString, statusText); + callback(status, response, headersString, statusText, xhrStatus); } }; @@ -12588,9 +13882,9 @@ return /******/ (function(modules) { // webpackBootstrap * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. * *
- * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular + * This feature is sometimes used to mix different markup languages, e.g. to wrap an AngularJS * template within a Python Jinja template (or any other template language). Mixing templating - * languages is **very dangerous**. The embedding template language will not safely escape Angular + * languages is **very dangerous**. The embedding template language will not safely escape AngularJS * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) * security bugs! *
@@ -12639,9 +13933,8 @@ return /******/ (function(modules) { // webpackBootstrap if (value) { startSymbol = value; return this; - } else { - return startSymbol; } + return startSymbol; }; /** @@ -12657,9 +13950,8 @@ return /******/ (function(modules) { // webpackBootstrap if (value) { endSymbol = value; return this; - } else { - return endSymbol; } + return endSymbol; }; @@ -12706,7 +13998,7 @@ return /******/ (function(modules) { // webpackBootstrap * ```js * var $interpolate = ...; // injected * var exp = $interpolate('Hello {{name | uppercase}}!'); - * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); + * expect(exp({name:'AngularJS'})).toEqual('Hello ANGULARJS!'); * ``` * * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is @@ -12724,8 +14016,8 @@ return /******/ (function(modules) { // webpackBootstrap * // "allOrNothing" mode * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); * expect(exp(context)).toBeUndefined(); - * context.name = 'Angular'; - * expect(exp(context)).toEqual('Hello Angular!'); + * context.name = 'AngularJS'; + * expect(exp(context)).toEqual('Hello AngularJS!'); * ``` * * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. @@ -12806,16 +14098,21 @@ return /******/ (function(modules) { // webpackBootstrap * - `context`: evaluation context for all expressions embedded in the interpolated text */ function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { + var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL; + // Provide a quick exit and simplified result function for text with no interpolation if (!text.length || text.indexOf(startSymbol) === -1) { - var constantInterp; - if (!mustHaveExpression) { - var unescapedText = unescapeText(text); - constantInterp = valueFn(unescapedText); - constantInterp.exp = text; - constantInterp.expressions = []; - constantInterp.$$watchDelegate = constantWatchDelegate; + if (mustHaveExpression) return; + + var unescapedText = unescapeText(text); + if (contextAllowsConcatenation) { + unescapedText = $sce.getTrusted(trustedContext, unescapedText); } + var constantInterp = valueFn(unescapedText); + constantInterp.exp = text; + constantInterp.expressions = []; + constantInterp.$$watchDelegate = constantWatchDelegate; + return constantInterp; } @@ -12824,11 +14121,13 @@ return /******/ (function(modules) { // webpackBootstrap endIndex, index = 0, expressions = [], - parseFns = [], + parseFns, textLength = text.length, exp, concat = [], - expressionPositions = []; + expressionPositions = [], + singleExpression; + while (index < textLength) { if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && @@ -12838,10 +14137,9 @@ return /******/ (function(modules) { // webpackBootstrap } exp = text.substring(startIndex + startSymbolLength, endIndex); expressions.push(exp); - parseFns.push($parse(exp, parseStringifyInterceptor)); index = endIndex + endSymbolLength; expressionPositions.push(concat.length); - concat.push(''); + concat.push(''); // Placeholder that will get replaced with the evaluated expression. } else { // we did not find an interpolation, so we have to add the remainder to the separators array if (index !== textLength) { @@ -12851,15 +14149,25 @@ return /******/ (function(modules) { // webpackBootstrap } } + singleExpression = concat.length === 1 && expressionPositions.length === 1; + // Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted + // objects rather than simple strings + // (we don't modify the expression if the input consists of only a single trusted input) + var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor; + parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); }); + // Concatenating expressions makes it hard to reason about whether some combination of // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a - // single expression be used for iframe[src], object[src], etc., we ensure that the value - // that's used is assigned or constructed by some JS code somewhere that is more testable or - // make it obvious that you bound the value to some user controlled value. This helps reduce - // the load when auditing for XSS issues. - if (trustedContext && concat.length > 1) { - $interpolateMinErr.throwNoconcat(text); - } + // single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly), + // we ensure that the value that's used is assigned or constructed by some JS code somewhere + // that is more testable or make it obvious that you bound the value to some user controlled + // value. This helps reduce the load when auditing for XSS issues. + + // Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values + // passed to it. In that case, `$sce.getTrusted` will be called on either the single expression + // or on the overall concatenated string (losing trusted types used in the mix, by design). + // Both these methods will sanitize plain strings. Also, HTML could be included, but since it's + // only used in srcdoc attributes, this would not be very useful. if (!mustHaveExpression || expressions.length) { var compute = function(values) { @@ -12867,13 +14175,16 @@ return /******/ (function(modules) { // webpackBootstrap if (allOrNothing && isUndefined(values[i])) return; concat[expressionPositions[i]] = values[i]; } - return concat.join(''); - }; - var getValue = function(value) { - return trustedContext ? - $sce.getTrusted(trustedContext, value) : - $sce.valueOf(value); + if (contextAllowsConcatenation) { + // If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string + return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join('')); + } else if (trustedContext && concat.length > 1) { + // This context does not allow more than one part, e.g. expr + string or exp + exp. + $interpolateMinErr.throwNoconcat(text); + } + // In an unprivileged context or only one part: just concatenate and return. + return concat.join(''); }; return extend(function interpolationFn(context) { @@ -12899,9 +14210,7 @@ return /******/ (function(modules) { // webpackBootstrap var lastValue; return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) { var currValue = compute(values); - if (isFunction(listener)) { - listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); - } + listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); lastValue = currValue; }); } @@ -12910,7 +14219,13 @@ return /******/ (function(modules) { // webpackBootstrap function parseStringifyInterceptor(value) { try { - value = getValue(value); + // In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual + // parts of a full URL. We don't care about losing the trustedness here. + // In non-concatenable contexts, where there is only one expression, this interceptor is + // not applied to the expression. + value = (trustedContext && !contextAllowsConcatenation) ? + $sce.getTrusted(trustedContext, value) : + $sce.valueOf(value); return allOrNothing && !isDefined(value) ? value : stringify(value); } catch (err) { $exceptionHandler($interpolateMinErr.interr(text, err)); @@ -12954,206 +14269,230 @@ return /******/ (function(modules) { // webpackBootstrap }]; } + var $intervalMinErr = minErr('$interval'); + /** @this */ function $IntervalProvider() { - this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', - function($rootScope, $window, $q, $$q, $browser) { + this.$get = ['$$intervalFactory', '$window', + function($$intervalFactory, $window) { var intervals = {}; + var setIntervalFn = function(tick, delay, deferred) { + var id = $window.setInterval(tick, delay); + intervals[id] = deferred; + return id; + }; + var clearIntervalFn = function(id) { + $window.clearInterval(id); + delete intervals[id]; + }; + /** + * @ngdoc service + * @name $interval + * + * @description + * AngularJS's wrapper for `window.setInterval`. The `fn` function is executed every `delay` + * milliseconds. + * + * The return value of registering an interval function is a promise. This promise will be + * notified upon each tick of the interval, and will be resolved after `count` iterations, or + * run indefinitely if `count` is not defined. The value of the notification will be the + * number of iterations that have run. + * To cancel an interval, call `$interval.cancel(promise)`. + * + * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + *
+ * **Note**: Intervals created by this service must be explicitly destroyed when you are finished + * with them. In particular they are not automatically destroyed when a controller's scope or a + * directive's element are destroyed. + * You should take this into consideration and make sure to always cancel the interval at the + * appropriate moment. See the example below for more details on how and when to do this. + *
+ * + * @param {function()} fn A function that should be called repeatedly. If no additional arguments + * are passed (see below), the function is called with the current iteration count. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {promise} A promise which will be notified on each iteration. It will resolve once all iterations of the interval complete. + * + * @example + * + * + * + * + *
+ *
+ *
+ * Current time is: + *
+ * Blood 1 : {{blood_1}} + * Blood 2 : {{blood_2}} + * + * + * + *
+ *
+ * + *
+ *
+ */ + var interval = $$intervalFactory(setIntervalFn, clearIntervalFn); - /** - * @ngdoc service - * @name $interval - * - * @description - * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` - * milliseconds. - * - * The return value of registering an interval function is a promise. This promise will be - * notified upon each tick of the interval, and will be resolved after `count` iterations, or - * run indefinitely if `count` is not defined. The value of the notification will be the - * number of iterations that have run. - * To cancel an interval, call `$interval.cancel(promise)`. - * - * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to - * move forward by `millis` milliseconds and trigger any functions scheduled to run in that - * time. - * - *
- * **Note**: Intervals created by this service must be explicitly destroyed when you are finished - * with them. In particular they are not automatically destroyed when a controller's scope or a - * directive's element are destroyed. - * You should take this into consideration and make sure to always cancel the interval at the - * appropriate moment. See the example below for more details on how and when to do this. - *
- * - * @param {function()} fn A function that should be called repeatedly. If no additional arguments - * are passed (see below), the function is called with the current iteration count. - * @param {number} delay Number of milliseconds between each function call. - * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat - * indefinitely. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @param {...*=} Pass additional parameters to the executed function. - * @returns {promise} A promise which will be notified on each iteration. - * - * @example - * - * - * - * - *
- *
- *
- * Current time is: - *
- * Blood 1 : {{blood_1}} - * Blood 2 : {{blood_2}} - * - * - * - *
- *
- * - *
- *
- */ - function interval(fn, delay, count, invokeApply) { - var hasParams = arguments.length > 4, - args = hasParams ? sliceArgs(arguments, 4) : [], - setInterval = $window.setInterval, - clearInterval = $window.clearInterval, - iteration = 0, - skipApply = (isDefined(invokeApply) && !invokeApply), - deferred = (skipApply ? $$q : $q).defer(), - promise = deferred.promise; + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {Promise=} promise returned by the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled. + */ + interval.cancel = function(promise) { + if (!promise) return false; - count = isDefined(count) ? count : 0; + if (!promise.hasOwnProperty('$$intervalId')) { + throw $intervalMinErr('badprom', + '`$interval.cancel()` called with a promise that was not generated by `$interval()`.'); + } - promise.$$intervalId = setInterval(function tick() { - if (skipApply) { - $browser.defer(callback); - } else { - $rootScope.$evalAsync(callback); - } - deferred.notify(iteration++); + if (!intervals.hasOwnProperty(promise.$$intervalId)) return false; - if (count > 0 && iteration >= count) { - deferred.resolve(iteration); - clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - } + var id = promise.$$intervalId; + var deferred = intervals[id]; - if (!skipApply) $rootScope.$apply(); + // Interval cancels should not report an unhandled promise. + markQExceptionHandled(deferred.promise); + deferred.reject('canceled'); + clearIntervalFn(id); + + return true; + }; + + return interval; + }]; + } - }, delay); + /** @this */ + function $$IntervalFactoryProvider() { + this.$get = ['$browser', '$q', '$$q', '$rootScope', + function($browser, $q, $$q, $rootScope) { + return function intervalFactory(setIntervalFn, clearIntervalFn) { + return function intervalFn(fn, delay, count, invokeApply) { + var hasParams = arguments.length > 4, + args = hasParams ? sliceArgs(arguments, 4) : [], + iteration = 0, + skipApply = isDefined(invokeApply) && !invokeApply, + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; + + count = isDefined(count) ? count : 0; + + function callback() { + if (!hasParams) { + fn(iteration); + } else { + fn.apply(null, args); + } + } - intervals[promise.$$intervalId] = deferred; + function tick() { + if (skipApply) { + $browser.defer(callback); + } else { + $rootScope.$evalAsync(callback); + } + deferred.notify(iteration++); - return promise; + if (count > 0 && iteration >= count) { + deferred.resolve(iteration); + clearIntervalFn(promise.$$intervalId); + } - function callback() { - if (!hasParams) { - fn(iteration); - } else { - fn.apply(null, args); + if (!skipApply) $rootScope.$apply(); } - } - } + promise.$$intervalId = setIntervalFn(tick, delay, deferred, skipApply); - /** - * @ngdoc method - * @name $interval#cancel - * - * @description - * Cancels a task associated with the `promise`. - * - * @param {Promise=} promise returned by the `$interval` function. - * @returns {boolean} Returns `true` if the task was successfully canceled. - */ - interval.cancel = function(promise) { - if (promise && promise.$$intervalId in intervals) { - // Interval cancels should not report as unhandled promise. - intervals[promise.$$intervalId].promise.catch(noop); - intervals[promise.$$intervalId].reject('canceled'); - $window.clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - return true; - } - return false; + return promise; + }; }; - - return interval; }]; } @@ -13167,8 +14506,8 @@ return /******/ (function(modules) { // webpackBootstrap * how they vary compared to the requested url. */ var $jsonpCallbacksProvider = /** @this */ function() { - this.$get = ['$window', function($window) { - var callbacks = $window.angular.callbacks; + this.$get = function() { + var callbacks = angular.callbacks; var callbackMap = {}; function createCallback(callbackId) { @@ -13235,7 +14574,7 @@ return /******/ (function(modules) { // webpackBootstrap delete callbackMap[callbackPath]; } }; - }]; + }; }; /** @@ -13243,12 +14582,14 @@ return /******/ (function(modules) { // webpackBootstrap * @name $locale * * @description - * $locale service provides localization rules for various Angular components. As of right now the + * $locale service provides localization rules for various AngularJS components. As of right now the * only public api is: * * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ + /* global stripHash: true */ + var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; var $locationMinErr = minErr('$location'); @@ -13265,12 +14606,36 @@ return /******/ (function(modules) { // webpackBootstrap i = segments.length; while (i--) { - segments[i] = encodeUriSegment(segments[i]); + // decode forward slashes to prevent them from being double encoded + segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/')); + } + + return segments.join('/'); + } + + function decodePath(path, html5Mode) { + var segments = path.split('/'), + i = segments.length; + + while (i--) { + segments[i] = decodeURIComponent(segments[i]); + if (html5Mode) { + // encode forward slashes to prevent them from being mistaken for path separators + segments[i] = segments[i].replace(/\//g, '%2F'); + } } return segments.join('/'); } + function normalizePath(pathValue, searchValue, hashValue) { + var search = toKeyValue(searchValue), + hash = hashValue ? '#' + encodeUriSegment(hashValue) : '', + path = encodePath(pathValue); + + return path + (search ? '?' + search : '') + hash; + } + function parseAbsoluteUrl(absoluteUrl, locationObj) { var parsedUrl = urlResolve(absoluteUrl); @@ -13280,7 +14645,7 @@ return /******/ (function(modules) { // webpackBootstrap } var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/; - function parseAppUrl(url, locationObj) { + function parseAppUrl(url, locationObj, html5Mode) { if (DOUBLE_SLASH_REGEX.test(url)) { throw $locationMinErr('badpath', 'Invalid url "{0}".', url); @@ -13291,8 +14656,8 @@ return /******/ (function(modules) { // webpackBootstrap url = '/' + url; } var match = urlResolve(url); - locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? - match.pathname.substring(1) : match.pathname); + var path = prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname; + locationObj.$$path = decodePath(path, html5Mode); locationObj.$$search = parseKeyValue(match.search); locationObj.$$hash = decodeURIComponent(match.hash); @@ -13319,17 +14684,11 @@ return /******/ (function(modules) { // webpackBootstrap } } - function stripHash(url) { var index = url.indexOf('#'); return index === -1 ? url : url.substr(0, index); } - function trimEmptyHash(url) { - return url.replace(/(#.+)|#$/, '$1'); - } - - function stripFile(url) { return url.substr(0, stripHash(url).lastIndexOf('/') + 1); } @@ -13367,7 +14726,7 @@ return /******/ (function(modules) { // webpackBootstrap appBaseNoFile); } - parseAppUrl(pathUrl, this); + parseAppUrl(pathUrl, this, true); if (!this.$$path) { this.$$path = '/'; @@ -13376,16 +14735,8 @@ return /******/ (function(modules) { // webpackBootstrap this.$$compose(); }; - /** - * Compose url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' + this.$$normalizeUrl = function(url) { + return appBaseNoFile + url.substr(1); // first char is always '/' }; this.$$parseLinkUrl = function(url, relHref) { @@ -13463,12 +14814,12 @@ return /******/ (function(modules) { // webpackBootstrap withoutHashUrl = ''; if (isUndefined(withoutBaseUrl)) { appBase = url; - this.replace(); + /** @type {?} */ (this).replace(); } } } - parseAppUrl(withoutHashUrl, this); + parseAppUrl(withoutHashUrl, this, false); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); @@ -13482,7 +14833,7 @@ return /******/ (function(modules) { // webpackBootstrap * * a.setAttribute('href', '/foo') * * a.pathname === '/C:/foo' //true * - * Inside of Angular, we're always using pathnames that + * Inside of AngularJS, we're always using pathnames that * do not include drive names for routing. */ function removeWindowsDriveName(path, url, base) { @@ -13509,16 +14860,8 @@ return /******/ (function(modules) { // webpackBootstrap } }; - /** - * Compose hashbang URL and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); + this.$$normalizeUrl = function(url) { + return appBase + (url ? hashPrefix + url : ''); }; this.$$parseLinkUrl = function(url, relHref) { @@ -13569,15 +14912,10 @@ return /******/ (function(modules) { // webpackBootstrap return !!rewrittenUrl; }; - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$normalizeUrl = function(url) { // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' - this.$$absUrl = appBase + hashPrefix + this.$$url; + return appBase + hashPrefix + url; }; - } @@ -13602,9 +14940,19 @@ return /******/ (function(modules) { // webpackBootstrap $$replace: false, /** - * @ngdoc method - * @name $location#absUrl - * + * Compose url and update `url` and `absUrl` property + * @private + */ + $$compose: function() { + this.$$url = normalizePath(this.$$path, this.$$search, this.$$hash); + this.$$absUrl = this.$$normalizeUrl(this.$$url); + this.$$urlUpdatedByLocation = true; + }, + + /** + * @ngdoc method + * @name $location#absUrl + * * @description * This method is getter only. * @@ -13685,7 +15033,7 @@ return /******/ (function(modules) { // webpackBootstrap * * Return host of current URL. * - * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. + * Note: compared to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. * * * ```js @@ -13905,6 +15253,7 @@ return /******/ (function(modules) { // webpackBootstrap // but we're changing the $$state reference to $browser.state() during the $digest // so the modification window is narrow. this.$$state = isUndefined(state) ? null : state; + this.$$urlUpdatedByLocation = true; return this; }; @@ -14105,6 +15454,13 @@ return /******/ (function(modules) { // webpackBootstrap var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; + // Determine if two URLs are equal despite potentially having different encoding/normalizing + // such as $location.absUrl() vs $browser.url() + // See https://github.com/angular/angular.js/issues/16592 + function urlsEqual(a, b) { + return a === b || urlResolve(a).href === urlResolve(b).href; + } + function setBrowserUrlWithFallback(url, replace, state) { var oldUrl = $location.url(); var oldState = $location.$$state; @@ -14157,15 +15513,13 @@ return /******/ (function(modules) { // webpackBootstrap if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { if ($location.$$parseLinkUrl(absHref, relHref)) { - // We do a preventDefault for all urls that are part of the angular application, + // We do a preventDefault for all urls that are part of the AngularJS application, // in html5mode and also without, so that we are able to abort navigation without // getting double entries in the location history. event.preventDefault(); // update location manually if ($location.absUrl() !== $browser.url()) { $rootScope.$apply(); - // hack to work around FF6 bug 684208 when scenario runner clicks on links - $window.angular['ff-684208-preventDefault'] = true; } } } @@ -14173,7 +15527,7 @@ return /******/ (function(modules) { // webpackBootstrap // rewrite hashbang url <> html5 url - if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) { + if ($location.absUrl() !== initialUrl) { $browser.url($location.absUrl(), true); } @@ -14192,7 +15546,6 @@ return /******/ (function(modules) { // webpackBootstrap var oldUrl = $location.absUrl(); var oldState = $location.$$state; var defaultPrevented; - newUrl = trimEmptyHash(newUrl); $location.$$parse(newUrl); $location.$$state = newState; @@ -14217,36 +15570,40 @@ return /******/ (function(modules) { // webpackBootstrap // update browser $rootScope.$watch(function $locationWatch() { - var oldUrl = trimEmptyHash($browser.url()); - var newUrl = trimEmptyHash($location.absUrl()); - var oldState = $browser.state(); - var currentReplace = $location.$$replace; - var urlOrStateChanged = oldUrl !== newUrl || - ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); + if (initializing || $location.$$urlUpdatedByLocation) { + $location.$$urlUpdatedByLocation = false; - if (initializing || urlOrStateChanged) { - initializing = false; + var oldUrl = $browser.url(); + var newUrl = $location.absUrl(); + var oldState = $browser.state(); + var currentReplace = $location.$$replace; + var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) || + ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); - $rootScope.$evalAsync(function() { - var newUrl = $location.absUrl(); - var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - $location.$$state, oldState).defaultPrevented; + if (initializing || urlOrStateChanged) { + initializing = false; - // if the location was changed by a `$locationChangeStart` handler then stop - // processing this location change - if ($location.absUrl() !== newUrl) return; + $rootScope.$evalAsync(function() { + var newUrl = $location.absUrl(); + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + $location.$$state, oldState).defaultPrevented; - if (defaultPrevented) { - $location.$$parse(oldUrl); - $location.$$state = oldState; - } else { - if (urlOrStateChanged) { - setBrowserUrlWithFallback(newUrl, currentReplace, - oldState === $location.$$state ? null : $location.$$state); + // if the location was changed by a `$locationChangeStart` handler then stop + // processing this location change + if ($location.absUrl() !== newUrl) return; + + if (defaultPrevented) { + $location.$$parse(oldUrl); + $location.$$state = oldState; + } else { + if (urlOrStateChanged) { + setBrowserUrlWithFallback(newUrl, currentReplace, + oldState === $location.$$state ? null : $location.$$state); + } + afterLocationChange(oldUrl, oldState); } - afterLocationChange(oldUrl, oldState); - } - }); + }); + } } $location.$$replace = false; @@ -14275,6 +15632,14 @@ return /******/ (function(modules) { // webpackBootstrap * * The main purpose of this service is to simplify debugging and troubleshooting. * + * To reveal the location of the calls to `$log` in the JavaScript console, + * you can "blackbox" the AngularJS source in your browser: + * + * [Mozilla description of blackboxing](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Black_box_a_source). + * [Chrome description of blackboxing](https://developer.chrome.com/devtools/docs/blackboxing). + * + * Note: Not all browsers support blackboxing. + * * The default is to log `debug` messages. You can use * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. * @@ -14324,13 +15689,22 @@ return /******/ (function(modules) { // webpackBootstrap this.debugEnabled = function(flag) { if (isDefined(flag)) { debug = flag; - return this; + return this; } else { return debug; } }; this.$get = ['$window', function($window) { + // Support: IE 9-11, Edge 12-14+ + // IE/Edge display errors in such a way that it requires the user to click in 4 places + // to see the stack trace. There is no way to feature-detect it so there's a chance + // of the user agent sniffing to go wrong but since it's only about logging, this shouldn't + // break apps. Other browsers display errors in a sensible way and some of them map stack + // traces along source maps if available so it makes sense to let browsers display it + // as they want. + var formatStackTrace = msie || /\bEdge\//.test($window.navigator && $window.navigator.userAgent); + return { /** * @ngdoc method @@ -14387,8 +15761,8 @@ return /******/ (function(modules) { // webpackBootstrap }; function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { + if (isError(arg)) { + if (arg.stack && formatStackTrace) { arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ? 'Error: ' + arg.message + '\n' + arg.stack : arg.stack; @@ -14401,29 +15775,17 @@ return /******/ (function(modules) { // webpackBootstrap function consoleLog(type) { var console = $window.console || {}, - logFn = console[type] || console.log || noop, - hasApply = false; - - // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. - // The reason behind this is that console.log has type "object" in IE8... - try { - hasApply = !!logFn.apply; - } catch (e) { /* empty */ } - - if (hasApply) { - return function() { - var args = []; - forEach(arguments, function(arg) { - args.push(formatError(arg)); - }); - return logFn.apply(console, args); - }; - } + logFn = console[type] || console.log || noop; - // we are IE which either doesn't have window.console => this is noop and we do nothing, - // or we are IE where console.log doesn't have apply so we log at least first 2 args - return function(arg1, arg2) { - logFn(arg1, arg2 == null ? '' : arg2); + return function() { + var args = []; + forEach(arguments, function(arg) { + args.push(formatError(arg)); + }); + // Support: IE 9 only + // console methods don't inherit from Function.prototype in IE 9 so we can't + // call `logFn.apply(console, args)` directly. + return Function.prototype.apply.call(logFn, console, args); }; } }]; @@ -14444,12 +15806,12 @@ return /******/ (function(modules) { // webpackBootstrap var objectValueOf = {}.constructor.prototype.valueOf; - // Sandboxing Angular Expressions + // Sandboxing AngularJS Expressions // ------------------------------ - // Angular expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by + // AngularJS expressions are no longer sandboxed. So it is now even easier to access arbitrary JS code by // various means such as obtaining a reference to native JS functions like the Function constructor. // - // As an example, consider the following Angular expression: + // As an example, consider the following AngularJS expression: // // {}.toString.constructor('alert("evil JS code")') // @@ -15051,15 +16413,47 @@ return /******/ (function(modules) { // webpackBootstrap return !fn.$stateful; } - function findConstantAndWatchExpressions(ast, $filter) { + var PURITY_ABSOLUTE = 1; + var PURITY_RELATIVE = 2; + + // Detect nodes which could depend on non-shallow state of objects + function isPure(node, parentIsPure) { + switch (node.type) { + // Computed members might invoke a stateful toString() + case AST.MemberExpression: + if (node.computed) { + return false; + } + break; + + // Unary always convert to primative + case AST.UnaryExpression: + return PURITY_ABSOLUTE; + + // The binary + operator can invoke a stateful toString(). + case AST.BinaryExpression: + return node.operator !== '+' ? PURITY_ABSOLUTE : false; + + // Functions / filters probably read state from within objects + case AST.CallExpression: + return false; + } + + return (undefined === parentIsPure) ? PURITY_RELATIVE : parentIsPure; + } + + function findConstantAndWatchExpressions(ast, $filter, parentIsPure) { var allConstants; var argsToWatch; var isStatelessFilter; + + var astIsPure = ast.isPure = isPure(ast, parentIsPure); + switch (ast.type) { case AST.Program: allConstants = true; forEach(ast.body, function(expr) { - findConstantAndWatchExpressions(expr.expression, $filter); + findConstantAndWatchExpressions(expr.expression, $filter, astIsPure); allConstants = allConstants && expr.expression.constant; }); ast.constant = allConstants; @@ -15069,26 +16463,26 @@ return /******/ (function(modules) { // webpackBootstrap ast.toWatch = []; break; case AST.UnaryExpression: - findConstantAndWatchExpressions(ast.argument, $filter); + findConstantAndWatchExpressions(ast.argument, $filter, astIsPure); ast.constant = ast.argument.constant; ast.toWatch = ast.argument.toWatch; break; case AST.BinaryExpression: - findConstantAndWatchExpressions(ast.left, $filter); - findConstantAndWatchExpressions(ast.right, $filter); + findConstantAndWatchExpressions(ast.left, $filter, astIsPure); + findConstantAndWatchExpressions(ast.right, $filter, astIsPure); ast.constant = ast.left.constant && ast.right.constant; ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); break; case AST.LogicalExpression: - findConstantAndWatchExpressions(ast.left, $filter); - findConstantAndWatchExpressions(ast.right, $filter); + findConstantAndWatchExpressions(ast.left, $filter, astIsPure); + findConstantAndWatchExpressions(ast.right, $filter, astIsPure); ast.constant = ast.left.constant && ast.right.constant; ast.toWatch = ast.constant ? [] : [ast]; break; case AST.ConditionalExpression: - findConstantAndWatchExpressions(ast.test, $filter); - findConstantAndWatchExpressions(ast.alternate, $filter); - findConstantAndWatchExpressions(ast.consequent, $filter); + findConstantAndWatchExpressions(ast.test, $filter, astIsPure); + findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure); + findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure); ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; ast.toWatch = ast.constant ? [] : [ast]; break; @@ -15097,30 +16491,28 @@ return /******/ (function(modules) { // webpackBootstrap ast.toWatch = [ast]; break; case AST.MemberExpression: - findConstantAndWatchExpressions(ast.object, $filter); + findConstantAndWatchExpressions(ast.object, $filter, astIsPure); if (ast.computed) { - findConstantAndWatchExpressions(ast.property, $filter); + findConstantAndWatchExpressions(ast.property, $filter, astIsPure); } ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); - ast.toWatch = [ast]; + ast.toWatch = ast.constant ? [] : [ast]; break; case AST.CallExpression: isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false; allConstants = isStatelessFilter; argsToWatch = []; forEach(ast.arguments, function(expr) { - findConstantAndWatchExpressions(expr, $filter); + findConstantAndWatchExpressions(expr, $filter, astIsPure); allConstants = allConstants && expr.constant; - if (!expr.constant) { - argsToWatch.push.apply(argsToWatch, expr.toWatch); - } + argsToWatch.push.apply(argsToWatch, expr.toWatch); }); ast.constant = allConstants; ast.toWatch = isStatelessFilter ? argsToWatch : [ast]; break; case AST.AssignmentExpression: - findConstantAndWatchExpressions(ast.left, $filter); - findConstantAndWatchExpressions(ast.right, $filter); + findConstantAndWatchExpressions(ast.left, $filter, astIsPure); + findConstantAndWatchExpressions(ast.right, $filter, astIsPure); ast.constant = ast.left.constant && ast.right.constant; ast.toWatch = [ast]; break; @@ -15128,11 +16520,9 @@ return /******/ (function(modules) { // webpackBootstrap allConstants = true; argsToWatch = []; forEach(ast.elements, function(expr) { - findConstantAndWatchExpressions(expr, $filter); + findConstantAndWatchExpressions(expr, $filter, astIsPure); allConstants = allConstants && expr.constant; - if (!expr.constant) { - argsToWatch.push.apply(argsToWatch, expr.toWatch); - } + argsToWatch.push.apply(argsToWatch, expr.toWatch); }); ast.constant = allConstants; ast.toWatch = argsToWatch; @@ -15141,10 +16531,14 @@ return /******/ (function(modules) { // webpackBootstrap allConstants = true; argsToWatch = []; forEach(ast.properties, function(property) { - findConstantAndWatchExpressions(property.value, $filter); - allConstants = allConstants && property.value.constant && !property.computed; - if (!property.value.constant) { - argsToWatch.push.apply(argsToWatch, property.value.toWatch); + findConstantAndWatchExpressions(property.value, $filter, astIsPure); + allConstants = allConstants && property.value.constant; + argsToWatch.push.apply(argsToWatch, property.value.toWatch); + if (property.computed) { + //`{[key]: value}` implicitly does `key.toString()` which may be non-pure + findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false); + allConstants = allConstants && property.key.constant; + argsToWatch.push.apply(argsToWatch, property.key.toWatch); } }); ast.constant = allConstants; @@ -15191,15 +16585,13 @@ return /******/ (function(modules) { // webpackBootstrap return ast.constant; } - function ASTCompiler(astBuilder, $filter) { - this.astBuilder = astBuilder; + function ASTCompiler($filter) { this.$filter = $filter; } ASTCompiler.prototype = { - compile: function(expression) { + compile: function(ast) { var self = this; - var ast = this.astBuilder.ast(expression); this.state = { nextId: 0, filters: {}, @@ -15227,7 +16619,7 @@ return /******/ (function(modules) { // webpackBootstrap var intoId = self.nextId(); self.recurse(watch, intoId); self.return_(intoId); - self.state.inputs.push(fnKey); + self.state.inputs.push({name: fnKey, isPure: watch.isPure}); watch.watchId = key; }); this.state.computing = 'fn'; @@ -15254,8 +16646,6 @@ return /******/ (function(modules) { // webpackBootstrap ifDefined, plusFn); this.state = this.stage = undefined; - fn.literal = isLiteral(ast); - fn.constant = isConstant(ast); return fn; }, @@ -15265,13 +16655,16 @@ return /******/ (function(modules) { // webpackBootstrap watchFns: function() { var result = []; - var fns = this.state.inputs; + var inputs = this.state.inputs; var self = this; - forEach(fns, function(name) { - result.push('var ' + name + '=' + self.generateFunction(name, 's')); + forEach(inputs, function(input) { + result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's')); + if (input.isPure) { + result.push(input.name, '.isPure=' + JSON.stringify(input.isPure) + ';'); + } }); - if (fns.length) { - result.push('fn.inputs=[' + fns.join(',') + '];'); + if (inputs.length) { + result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];'); } return result.join(''); }, @@ -15658,15 +17051,13 @@ return /******/ (function(modules) { // webpackBootstrap }; - function ASTInterpreter(astBuilder, $filter) { - this.astBuilder = astBuilder; + function ASTInterpreter($filter) { this.$filter = $filter; } ASTInterpreter.prototype = { - compile: function(expression) { + compile: function(ast) { var self = this; - var ast = this.astBuilder.ast(expression); findConstantAndWatchExpressions(ast, self.$filter); var assignable; var assign; @@ -15679,6 +17070,7 @@ return /******/ (function(modules) { // webpackBootstrap inputs = []; forEach(toWatch, function(watch, key) { var input = self.recurse(watch); + input.isPure = watch.isPure; watch.input = input; inputs.push(input); watch.watchId = key; @@ -15705,8 +17097,6 @@ return /******/ (function(modules) { // webpackBootstrap if (inputs) { fn.inputs = inputs; } - fn.literal = isLiteral(ast); - fn.constant = isConstant(ast); return fn; }, @@ -16035,20 +17425,36 @@ return /******/ (function(modules) { // webpackBootstrap /** * @constructor */ - var Parser = function Parser(lexer, $filter, options) { - this.lexer = lexer; - this.$filter = $filter; - this.options = options; + function Parser(lexer, $filter, options) { this.ast = new AST(lexer, options); - this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : - new ASTCompiler(this.ast, $filter); - }; + this.astCompiler = options.csp ? new ASTInterpreter($filter) : + new ASTCompiler($filter); + } Parser.prototype = { constructor: Parser, parse: function(text) { - return this.astCompiler.compile(text); + var ast = this.getAst(text); + var fn = this.astCompiler.compile(ast.ast); + fn.literal = isLiteral(ast.ast); + fn.constant = isConstant(ast.ast); + fn.oneTime = ast.oneTime; + return fn; + }, + + getAst: function(exp) { + var oneTime = false; + exp = exp.trim(); + + if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { + oneTime = true; + exp = exp.substring(2); + } + return { + ast: this.ast.ast(exp), + oneTime: oneTime + }; } }; @@ -16065,15 +17471,15 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * - * Converts Angular {@link guide/expression expression} into a function. + * Converts AngularJS {@link guide/expression expression} into a function. * * ```js * var getter = $parse('user.name'); * var setter = getter.assign; - * var context = {user:{name:'angular'}}; + * var context = {user:{name:'AngularJS'}}; * var locals = {user:{name:'local'}}; * - * expect(getter(context)).toEqual('angular'); + * expect(getter(context)).toEqual('AngularJS'); * setter(context, 'newValue'); * expect(context.user.name).toEqual('newValue'); * expect(getter(context, locals)).toEqual('local'); @@ -16139,7 +17545,7 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * - * Allows defining the set of characters that are allowed in Angular expressions. The function + * Allows defining the set of characters that are allowed in AngularJS expressions. The function * `identifierStart` will get called to know if a given character is a valid character to be the * first character for an identifier. The function `identifierContinue` will get called to know if * a given character is a valid character to be a follow-up identifier character. The functions @@ -16171,10 +17577,11 @@ return /******/ (function(modules) { // webpackBootstrap isIdentifierStart: isFunction(identStart) && identStart, isIdentifierContinue: isFunction(identContinue) && identContinue }; + $parse.$$getAst = $$getAst; return $parse; function $parse(exp, interceptorFn) { - var parsedExpression, oneTime, cacheKey; + var parsedExpression, cacheKey; switch (typeof exp) { case 'string': @@ -16184,22 +17591,11 @@ return /******/ (function(modules) { // webpackBootstrap parsedExpression = cache[cacheKey]; if (!parsedExpression) { - if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { - oneTime = true; - exp = exp.substring(2); - } var lexer = new Lexer($parseOptions); var parser = new Parser(lexer, $filter, $parseOptions); parsedExpression = parser.parse(exp); - if (parsedExpression.constant) { - parsedExpression.$$watchDelegate = constantWatchDelegate; - } else if (oneTime) { - parsedExpression.$$watchDelegate = parsedExpression.literal ? - oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; - } else if (parsedExpression.inputs) { - parsedExpression.$$watchDelegate = inputsWatchDelegate; - } - cache[cacheKey] = parsedExpression; + + cache[cacheKey] = addWatchDelegate(parsedExpression); } return addInterceptor(parsedExpression, interceptorFn); @@ -16211,7 +17607,13 @@ return /******/ (function(modules) { // webpackBootstrap } } - function expressionInputDirtyCheck(newValue, oldValueOfValue) { + function $$getAst(exp) { + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + return parser.getAst(exp).ast; + } + + function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) { if (newValue == null || oldValueOfValue == null) { // null/undefined return newValue === oldValueOfValue; @@ -16224,7 +17626,7 @@ return /******/ (function(modules) { // webpackBootstrap // be cheaply dirty-checked newValue = getValueOf(newValue); - if (typeof newValue === 'object') { + if (typeof newValue === 'object' && !compareObjectIdentity) { // objects/arrays are not supported - deep-watching them would be too expensive return false; } @@ -16246,7 +17648,7 @@ return /******/ (function(modules) { // webpackBootstrap inputExpressions = inputExpressions[0]; return scope.$watch(function expressionInputWatch(scope) { var newInputValue = inputExpressions(scope); - if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { + if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) { lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); oldInputValueOf = newInputValue && getValueOf(newInputValue); } @@ -16266,7 +17668,7 @@ return /******/ (function(modules) { // webpackBootstrap for (var i = 0, ii = inputExpressions.length; i < ii; i++) { var newInputValue = inputExpressions[i](scope); - if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { + if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) { oldInputValues[i] = newInputValue; oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); } @@ -16281,59 +17683,50 @@ return /******/ (function(modules) { // webpackBootstrap } function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { + var isDone = parsedExpression.literal ? isAllDefined : isDefined; var unwatch, lastValue; - if (parsedExpression.inputs) { - unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression); - } else { - unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality); - } + + var exp = parsedExpression.$$intercepted || parsedExpression; + var post = parsedExpression.$$interceptor || identity; + + var useInputs = parsedExpression.inputs && !exp.inputs; + + // Propagate the literal/inputs/constant attributes + // ... but not oneTime since we are handling it + oneTimeWatch.literal = parsedExpression.literal; + oneTimeWatch.constant = parsedExpression.constant; + oneTimeWatch.inputs = parsedExpression.inputs; + + // Allow other delegates to run on this wrapped expression + addWatchDelegate(oneTimeWatch); + + unwatch = scope.$watch(oneTimeWatch, listener, objectEquality, prettyPrintExpression); + return unwatch; - function oneTimeWatch(scope) { - return parsedExpression(scope); - } - function oneTimeListener(value, old, scope) { - lastValue = value; - if (isFunction(listener)) { - listener(value, old, scope); - } - if (isDefined(value)) { - scope.$$postDigest(function() { - if (isDefined(lastValue)) { - unwatch(); - } - }); + function unwatchIfDone() { + if (isDone(lastValue)) { + unwatch(); } } - } - function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch, lastValue; - unwatch = scope.$watch(function oneTimeWatch(scope) { - return parsedExpression(scope); - }, function oneTimeListener(value, old, scope) { - lastValue = value; - if (isFunction(listener)) { - listener(value, old, scope); - } - if (isAllDefined(value)) { - scope.$$postDigest(function() { - if (isAllDefined(lastValue)) unwatch(); - }); + function oneTimeWatch(scope, locals, assign, inputs) { + lastValue = useInputs && inputs ? inputs[0] : exp(scope, locals, assign, inputs); + if (isDone(lastValue)) { + scope.$$postDigest(unwatchIfDone); } - }, objectEquality); - - return unwatch; - - function isAllDefined(value) { - var allDefined = true; - forEach(value, function(val) { - if (!isDefined(val)) allDefined = false; - }); - return allDefined; + return post(lastValue); } } + function isAllDefined(value) { + var allDefined = true; + forEach(value, function(val) { + if (!isDefined(val)) allDefined = false; + }); + return allDefined; + } + function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch = scope.$watch(function constantWatch(scope) { unwatch(); @@ -16342,40 +17735,74 @@ return /******/ (function(modules) { // webpackBootstrap return unwatch; } + function addWatchDelegate(parsedExpression) { + if (parsedExpression.constant) { + parsedExpression.$$watchDelegate = constantWatchDelegate; + } else if (parsedExpression.oneTime) { + parsedExpression.$$watchDelegate = oneTimeWatchDelegate; + } else if (parsedExpression.inputs) { + parsedExpression.$$watchDelegate = inputsWatchDelegate; + } + + return parsedExpression; + } + + function chainInterceptors(first, second) { + function chainedInterceptor(value) { + return second(first(value)); + } + chainedInterceptor.$stateful = first.$stateful || second.$stateful; + chainedInterceptor.$$pure = first.$$pure && second.$$pure; + + return chainedInterceptor; + } + function addInterceptor(parsedExpression, interceptorFn) { if (!interceptorFn) return parsedExpression; - var watchDelegate = parsedExpression.$$watchDelegate; - var useInputs = false; - var regularWatch = - watchDelegate !== oneTimeLiteralWatchDelegate && - watchDelegate !== oneTimeWatchDelegate; + // Extract any existing interceptors out of the parsedExpression + // to ensure the original parsedExpression is always the $$intercepted + if (parsedExpression.$$interceptor) { + interceptorFn = chainInterceptors(parsedExpression.$$interceptor, interceptorFn); + parsedExpression = parsedExpression.$$intercepted; + } + + var useInputs = false; - var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { + var fn = function interceptedExpression(scope, locals, assign, inputs) { var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); - return interceptorFn(value, scope, locals); - } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { - var value = parsedExpression(scope, locals, assign, inputs); - var result = interceptorFn(value, scope, locals); - // we only return the interceptor's result if the - // initial value is defined (for bind-once) - return isDefined(value) ? result : value; + return interceptorFn(value); }; - // Propagate $$watchDelegates other then inputsWatchDelegate - useInputs = !parsedExpression.inputs; - if (parsedExpression.$$watchDelegate && - parsedExpression.$$watchDelegate !== inputsWatchDelegate) { - fn.$$watchDelegate = parsedExpression.$$watchDelegate; - fn.inputs = parsedExpression.inputs; - } else if (!interceptorFn.$stateful) { - // If there is an interceptor, but no watchDelegate then treat the interceptor like - // we treat filters - it is assumed to be a pure function unless flagged with $stateful - fn.$$watchDelegate = inputsWatchDelegate; + // Maintain references to the interceptor/intercepted + fn.$$intercepted = parsedExpression; + fn.$$interceptor = interceptorFn; + + // Propagate the literal/oneTime/constant attributes + fn.literal = parsedExpression.literal; + fn.oneTime = parsedExpression.oneTime; + fn.constant = parsedExpression.constant; + + // Treat the interceptor like filters. + // If it is not $stateful then only watch its inputs. + // If the expression itself has no inputs then use the full expression as an input. + if (!interceptorFn.$stateful) { + useInputs = !parsedExpression.inputs; fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; + + if (!interceptorFn.$$pure) { + fn.inputs = fn.inputs.map(function(e) { + // Remove the isPure flag of inputs when it is not absolute because they are now wrapped in a + // non-pure interceptor function. + if (e.isPure === PURITY_RELATIVE) { + return function depurifier(s) { return e(s); }; + } + return e; + }); + } } - return fn; + return addWatchDelegate(fn); } }]; } @@ -16395,7 +17822,7 @@ return /******/ (function(modules) { // webpackBootstrap * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred * implementations, and the other which resembles ES6 (ES2015) promises to some degree. * - * # $q constructor + * ## $q constructor * * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` * function as the first argument. This is similar to the native Promise implementation from ES6, @@ -16483,7 +17910,7 @@ return /******/ (function(modules) { // webpackBootstrap * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the * section on serial or parallel joining of promises. * - * # The Deferred API + * ## The Deferred API * * A new instance of deferred is constructed by calling `$q.defer()`. * @@ -16505,7 +17932,7 @@ return /******/ (function(modules) { // webpackBootstrap * - promise – `{Promise}` – promise object associated with this deferred. * * - * # The Promise API + * ## The Promise API * * A new promise instance is created when a deferred instance is created and can be retrieved by * calling `deferred.promise`. @@ -16537,7 +17964,7 @@ return /******/ (function(modules) { // webpackBootstrap * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * - * # Chaining promises + * ## Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily * possible to create a chain of promises: @@ -16557,17 +17984,17 @@ return /******/ (function(modules) { // webpackBootstrap * $http's response interceptors. * * - * # Differences between Kris Kowal's Q and $q + * ## Differences between Kris Kowal's Q and $q * * There are two main differences: * * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation - * mechanism in angular, which means faster propagation of resolution or rejection into your + * mechanism in AngularJS, which means faster propagation of resolution or rejection into your * models and avoiding unnecessary browser repaints, which would result in flickering UI. * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains * all the important functionality needed for common async tasks. * - * # Testing + * ## Testing * * ```js * it('should simulate promise', inject(function($q, $rootScope) { @@ -16660,7 +18087,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {function(function)} nextTick Function for executing functions in the next turn. * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for * debugging purposes. - @ param {=boolean} errorOnUnhandledRejections Whether an error should be generated on unhandled + * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled * promises rejections. * @returns {object} Promise manager. */ @@ -16731,7 +18158,7 @@ return /******/ (function(modules) { // webpackBootstrap state.pending = undefined; try { for (var i = 0, ii = pending.length; i < ii; ++i) { - state.pur = true; + markQStateExceptionHandled(state); promise = pending[i][0]; fn = pending[i][state.status]; try { @@ -16744,6 +18171,10 @@ return /******/ (function(modules) { // webpackBootstrap } } catch (e) { rejectPromise(promise, e); + // This error is explicitly marked for being passed to the $exceptionHandler + if (e && e.$$passToExceptionHandler === true) { + exceptionHandler(e); + } } } } finally { @@ -16758,10 +18189,10 @@ return /******/ (function(modules) { // webpackBootstrap // eslint-disable-next-line no-unmodified-loop-condition while (!queueSize && checkQueue.length) { var toCheck = checkQueue.shift(); - if (!toCheck.pur) { - toCheck.pur = true; + if (!isStateExceptionHandled(toCheck)) { + markQStateExceptionHandled(toCheck); var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value); - if (toCheck.value instanceof Error) { + if (isError(toCheck.value)) { exceptionHandler(toCheck.value, errorMessage); } else { exceptionHandler(errorMessage); @@ -16771,7 +18202,7 @@ return /******/ (function(modules) { // webpackBootstrap } function scheduleProcessQueue(state) { - if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !state.pur) { + if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) { if (queueSize === 0 && checkQueue.length === 0) { nextTick(processChecks); } @@ -17052,6 +18483,22 @@ return /******/ (function(modules) { // webpackBootstrap return $Q; } + function isStateExceptionHandled(state) { + return !!state.pur; + } + function markQStateExceptionHandled(state) { + state.pur = true; + } + function markQExceptionHandled(q) { + // Built-in `$q` promises will always have a `$$state` property. This check is to allow + // overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises). + // (Currently, this is the only method that might be called with a promise, even if it is not + // created by the built-in `$q`.) + if (q.$$state) { + markQStateExceptionHandled(q.$$state); + } + } + /** @this */ function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { @@ -17174,6 +18621,7 @@ return /******/ (function(modules) { // webpackBootstrap this.$$watchersCount = 0; this.$id = nextUid(); this.$$ChildScope = null; + this.$$suspended = false; } ChildScope.prototype = parent; return ChildScope; @@ -17226,7 +18674,7 @@ return /******/ (function(modules) { // webpackBootstrap * an in-depth introduction and usage examples. * * - * # Inheritance + * ## Inheritance * A scope can inherit from a parent scope, as in this example: * ```js var parent = $rootScope; @@ -17261,6 +18709,7 @@ return /******/ (function(modules) { // webpackBootstrap this.$$childHead = this.$$childTail = null; this.$root = this; this.$$destroyed = false; + this.$$suspended = false; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount = 0; @@ -17380,8 +18829,8 @@ return /******/ (function(modules) { // webpackBootstrap * according to the {@link angular.equals} function. To save the value of the object for * later comparison, the {@link angular.copy} function is used. This therefore means that * watching complex objects will have adverse memory and performance implications. - * - This should not be used to watch for changes in objects that are - * or contain [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}. + * - This should not be used to watch for changes in objects that are (or contain) + * [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link angular.copy `angular.copy`}. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. * This is achieved by rerunning the watchers until no changes are detected. The rerun * iteration limit is 10 to prevent an infinite loop deadlock. @@ -17401,7 +18850,7 @@ return /******/ (function(modules) { // webpackBootstrap * * * - * # Example + * @example * ```js // let's assume that scope was dependency injected as the $rootScope var scope = $rootScope; @@ -17477,14 +18926,15 @@ return /******/ (function(modules) { // webpackBootstrap */ $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { var get = $parse(watchExp); + var fn = isFunction(listener) ? listener : noop; if (get.$$watchDelegate) { - return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); + return get.$$watchDelegate(this, fn, objectEquality, get, watchExp); } var scope = this, array = scope.$$watchers, watcher = { - fn: listener, + fn: fn, last: initWatchVal, get: get, exp: prettyPrintExpression || watchExp, @@ -17493,10 +18943,6 @@ return /******/ (function(modules) { // webpackBootstrap lastDirtyWatch = null; - if (!isFunction(listener)) { - watcher.fn = noop; - } - if (!array) { array = scope.$$watchers = []; array.$$digestWatchIndex = -1; @@ -17573,9 +19019,8 @@ return /******/ (function(modules) { // webpackBootstrap } forEach(watchExpressions, function(expr, i) { - var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { + var unwatchFn = self.$watch(expr, function watchGroupSubAction(value) { newValues[i] = value; - oldValues[i] = oldValue; if (!changeReactionScheduled) { changeReactionScheduled = true; self.$evalAsync(watchGroupAction); @@ -17587,11 +19032,17 @@ return /******/ (function(modules) { // webpackBootstrap function watchGroupAction() { changeReactionScheduled = false; - if (firstRun) { - firstRun = false; - listener(newValues, newValues, self); - } else { - listener(newValues, oldValues, self); + try { + if (firstRun) { + firstRun = false; + listener(newValues, newValues, self); + } else { + listener(newValues, oldValues, self); + } + } finally { + for (var i = 0; i < watchExpressions.length; i++) { + oldValues[i] = newValues[i]; + } } } @@ -17619,7 +19070,7 @@ return /******/ (function(modules) { // webpackBootstrap * adding, removing, and moving items belonging to an object or array. * * - * # Example + * @example * ```js $scope.names = ['igor', 'matias', 'misko', 'james']; $scope.dataCount = 4; @@ -17659,7 +19110,11 @@ return /******/ (function(modules) { // webpackBootstrap * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { - $watchCollectionInterceptor.$stateful = true; + // Mark the interceptor as + // ... $$pure when literal since the instance will change when any input changes + $watchCollectionInterceptor.$$pure = $parse(obj).literal; + // ... $stateful when non-literal since we must read the state of the collection + $watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure; var self = this; // the current value, updated on each dirty-check run @@ -17817,7 +19272,7 @@ return /******/ (function(modules) { // webpackBootstrap * * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. * - * # Example + * @example * ```js var scope = ...; scope.name = 'misko'; @@ -17847,7 +19302,7 @@ return /******/ (function(modules) { // webpackBootstrap var watch, value, last, fn, get, watchers, dirty, ttl = TTL, - next, current, target = this, + next, current, target = asyncQueue.length ? $rootScope : this, watchLog = [], logIdx, asyncTask; @@ -17869,12 +19324,13 @@ return /******/ (function(modules) { // webpackBootstrap current = target; // It's safe for asyncQueuePosition to be a local variable here because this loop can't - // be reentered recursively. Calling $digest from a function passed to $applyAsync would + // be reentered recursively. Calling $digest from a function passed to $evalAsync would // lead to a '$digest already in progress' error. for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) { try { asyncTask = asyncQueue[asyncQueuePosition]; - asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); + fn = asyncTask.fn; + fn(asyncTask.scope, asyncTask.locals); } catch (e) { $exceptionHandler(e); } @@ -17884,7 +19340,7 @@ return /******/ (function(modules) { // webpackBootstrap traverseScopesLoop: do { // "traverse the scopes" loop - if ((watchers = current.$$watchers)) { + if ((watchers = !current.$$suspended && current.$$watchers)) { // process our watches watchers.$$digestWatchIndex = watchers.length; while (watchers.$$digestWatchIndex--) { @@ -17928,7 +19384,9 @@ return /******/ (function(modules) { // webpackBootstrap // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $broadcast - if (!(next = ((current.$$watchersCount && current.$$childHead) || + // (though it differs due to having the extra check for $$suspended and does not + // check $$listenerCount) + if (!(next = ((!current.$$suspended && current.$$watchersCount && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; @@ -17959,8 +19417,101 @@ return /******/ (function(modules) { // webpackBootstrap } } postDigestQueue.length = postDigestQueuePosition = 0; + + // Check for changes to browser url that happened during the $digest + // (for which no event is fired; e.g. via `history.pushState()`) + $browser.$$checkUrlChange(); + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$suspend + * @kind function + * + * @description + * Suspend watchers of this scope subtree so that they will not be invoked during digest. + * + * This can be used to optimize your application when you know that running those watchers + * is redundant. + * + * **Warning** + * + * Suspending scopes from the digest cycle can have unwanted and difficult to debug results. + * Only use this approach if you are confident that you know what you are doing and have + * ample tests to ensure that bindings get updated as you expect. + * + * Some of the things to consider are: + * + * * Any external event on a directive/component will not trigger a digest while the hosting + * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`. + * * Transcluded content exists on a scope that inherits from outside a directive but exists + * as a child of the directive's containing scope. If the containing scope is suspended the + * transcluded scope will also be suspended, even if the scope from which the transcluded + * scope inherits is not suspended. + * * Multiple directives trying to manage the suspended status of a scope can confuse each other: + * * A call to `$suspend()` on an already suspended scope is a no-op. + * * A call to `$resume()` on a non-suspended scope is a no-op. + * * If two directives suspend a scope, then one of them resumes the scope, the scope will no + * longer be suspended. This could result in the other directive believing a scope to be + * suspended when it is not. + * * If a parent scope is suspended then all its descendants will be also excluded from future + * digests whether or not they have been suspended themselves. Note that this also applies to + * isolate child scopes. + * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers + * for that scope and its descendants. When digesting we only check whether the current scope is + * locally suspended, rather than checking whether it has a suspended ancestor. + * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be + * included in future digests until all its ancestors have been resumed. + * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()` + * against the `$rootScope` and so will still trigger a global digest even if the promise was + * initiated by a component that lives on a suspended scope. + */ + $suspend: function() { + this.$$suspended = true; + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$isSuspended + * @kind function + * + * @description + * Call this method to determine if this scope has been explicitly suspended. It will not + * tell you whether an ancestor has been suspended. + * To determine if this scope will be excluded from a digest triggered at the $rootScope, + * for example, you must check all its ancestors: + * + * ``` + * function isExcludedFromDigest(scope) { + * while(scope) { + * if (scope.$isSuspended()) return true; + * scope = scope.$parent; + * } + * return false; + * ``` + * + * Be aware that a scope may not be included in digests if it has a suspended ancestor, + * even if `$isSuspended()` returns false. + * + * @returns true if the current scope has been suspended. + */ + $isSuspended: function() { + return this.$$suspended; }, + /** + * @ngdoc method + * @name $rootScope.Scope#$resume + * @kind function + * + * @description + * Resume watchers of this scope subtree in case it was suspended. + * + * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach. + */ + $resume: function() { + this.$$suspended = false; + }, /** * @ngdoc event @@ -18038,10 +19589,10 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * Executes the `expression` on the current scope and returns the result. Any exceptions in - * the expression are propagated (uncaught). This is useful when evaluating Angular + * the expression are propagated (uncaught). This is useful when evaluating AngularJS * expressions. * - * # Example + * @example * ```js var scope = ng.$rootScope.Scope(); scope.a = 1; @@ -18051,7 +19602,7 @@ return /******/ (function(modules) { // webpackBootstrap expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); * ``` * - * @param {(string|function())=} expression An angular expression to be executed. + * @param {(string|function())=} expression An AngularJS expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. @@ -18086,7 +19637,7 @@ return /******/ (function(modules) { // webpackBootstrap * will be scheduled. However, it is encouraged to always call code that changes the model * from within an `$apply` call. That includes code evaluated via `$evalAsync`. * - * @param {(string|function())=} expression An angular expression to be executed. + * @param {(string|function())=} expression An AngularJS expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. @@ -18101,10 +19652,10 @@ return /******/ (function(modules) { // webpackBootstrap if (asyncQueue.length) { $rootScope.$digest(); } - }); + }, null, '$evalAsync'); } - asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); + asyncQueue.push({scope: this, fn: $parse(expr), locals: locals}); }, $$postDigest: function(fn) { @@ -18117,15 +19668,14 @@ return /******/ (function(modules) { // webpackBootstrap * @kind function * * @description - * `$apply()` is used to execute an expression in angular from outside of the angular + * `$apply()` is used to execute an expression in AngularJS from outside of the AngularJS * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). - * Because we are calling into the angular framework we need to perform proper scope life + * Because we are calling into the AngularJS framework we need to perform proper scope life * cycle of {@link ng.$exceptionHandler exception handling}, * {@link ng.$rootScope.Scope#$digest executing watches}. * - * ## Life cycle + * **Life cycle: Pseudo-Code of `$apply()`** * - * # Pseudo-Code of `$apply()` * ```js function $apply(expr) { try { @@ -18149,7 +19699,7 @@ return /******/ (function(modules) { // webpackBootstrap * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. * * - * @param {(string|function())=} exp An angular expression to be executed. + * @param {(string|function())=} exp An AngularJS expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. @@ -18189,7 +19739,7 @@ return /******/ (function(modules) { // webpackBootstrap * This can be used to queue up multiple expressions which need to be evaluated in the same * digest. * - * @param {(string|function())=} exp An angular expression to be executed. + * @param {(string|function())=} exp An AngularJS expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. @@ -18253,7 +19803,10 @@ return /******/ (function(modules) { // webpackBootstrap return function() { var indexOfListener = namedListeners.indexOf(listener); if (indexOfListener !== -1) { - namedListeners[indexOfListener] = null; + // Use delete in the hope of the browser deallocating the memory for the array entry, + // while not shifting the array indexes of other listeners. + // See issue https://github.com/angular/angular.js/issues/16135 + delete namedListeners[indexOfListener]; decrementListenerCount(self, 1, name); } }; @@ -18320,8 +19873,7 @@ return /******/ (function(modules) { // webpackBootstrap } //if any listener on the current scope stops propagation, prevent bubbling if (stopPropagation) { - event.currentScope = null; - return event; + break; } //traverse upwards scope = scope.$parent; @@ -18395,7 +19947,8 @@ return /******/ (function(modules) { // webpackBootstrap // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $digest - // (though it differs due to having the extra check for $$listenerCount) + // (though it differs due to having the extra check for $$listenerCount and + // does not check $$suspended) if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { @@ -18470,7 +20023,7 @@ return /******/ (function(modules) { // webpackBootstrap if (applyAsyncId === null) { applyAsyncId = $browser.defer(function() { $rootScope.$apply(flushApplyAsync); - }); + }, null, '$applyAsync'); } } }]; @@ -18481,7 +20034,7 @@ return /******/ (function(modules) { // webpackBootstrap * @name $rootElement * * @description - * The root element of Angular application. This is either the element where {@link + * The root element of AngularJS application. This is either the element where {@link * ng.directive:ngApp ngApp} was declared or the element passed into * {@link angular.bootstrap}. The element represents the root element of application. It is also the * location where the application's {@link auto.$injector $injector} service gets @@ -18497,63 +20050,74 @@ return /******/ (function(modules) { // webpackBootstrap * Private service to sanitize uris for links and images. Used by $compile and $sanitize. */ function $$SanitizeUriProvider() { - var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, - imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; + + var aHrefSanitizationTrustedUrlList = /^\s*(https?|s?ftp|mailto|tel|file):/, + imgSrcSanitizationTrustedUrlList = /^\s*((https?|ftp|file|blob):|data:image\/)/; /** * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * Retrieves or overrides the default regular expression that is used for determining trusted safe * urls during a[href] sanitization. * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links. * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring + * the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)` + * which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL. + * + * If the URL matches the `aHrefSanitizationTrustedUrlList` regular expression, it is returned unchanged. * - * @param {RegExp=} regexp New regexp to whitelist urls with. + * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written + * to the DOM it is inactive and potentially malicious code will not be executed. + * + * @param {RegExp=} regexp New regexp to trust urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.aHrefSanitizationWhitelist = function(regexp) { + this.aHrefSanitizationTrustedUrlList = function(regexp) { if (isDefined(regexp)) { - aHrefSanitizationWhitelist = regexp; + aHrefSanitizationTrustedUrlList = regexp; return this; } - return aHrefSanitizationWhitelist; + return aHrefSanitizationTrustedUrlList; }; /** * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * Retrieves or overrides the default regular expression that is used for determining trusted safe * urls during img[src] sanitization. * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links. * - * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring + * the $sce.MEDIA_URL security context. When interpolation occurs a call is made to + * `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize + * the potentially malicious URL. + * + * If the URL matches the `imgSrcSanitizationTrustedUrlList` regular expression, it is returned + * unchanged. * - * @param {RegExp=} regexp New regexp to whitelist urls with. + * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written + * to the DOM it is inactive and potentially malicious code will not be executed. + * + * @param {RegExp=} regexp New regexp to trust urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.imgSrcSanitizationWhitelist = function(regexp) { + this.imgSrcSanitizationTrustedUrlList = function(regexp) { if (isDefined(regexp)) { - imgSrcSanitizationWhitelist = regexp; + imgSrcSanitizationTrustedUrlList = regexp; return this; } - return imgSrcSanitizationWhitelist; + return imgSrcSanitizationTrustedUrlList; }; this.$get = function() { - return function sanitizeUri(uri, isImage) { - var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; - var normalizedVal; - normalizedVal = urlResolve(uri).href; + return function sanitizeUri(uri, isMediaUrl) { + // if (!uri) return uri; + var regex = isMediaUrl ? imgSrcSanitizationTrustedUrlList : aHrefSanitizationTrustedUrlList; + var normalizedVal = urlResolve(uri && uri.trim()).href; if (normalizedVal !== '' && !normalizedVal.match(regex)) { return 'unsafe:' + normalizedVal; } @@ -18578,12 +20142,26 @@ return /******/ (function(modules) { // webpackBootstrap var $sceMinErr = minErr('$sce'); var SCE_CONTEXTS = { + // HTML is used when there's HTML rendered (e.g. ng-bind-html, iframe srcdoc binding). HTML: 'html', + + // Style statements or stylesheets. Currently unused in AngularJS. CSS: 'css', + + // An URL used in a context where it refers to the source of media, which are not expected to be run + // as scripts, such as an image, audio, video, etc. + MEDIA_URL: 'mediaUrl', + + // An URL used in a context where it does not refer to a resource that loads code. + // A value that can be trusted as a URL can also trusted as a MEDIA_URL. URL: 'url', - // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a - // url. (e.g. ng-include, script src, templateUrl) + + // RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as + // code. (e.g. ng-include, script src binding, templateUrl) + // A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL. RESOURCE_URL: 'resourceUrl', + + // Script. Currently unused in AngularJS. JS: 'js' }; @@ -18645,6 +20223,16 @@ return /******/ (function(modules) { // webpackBootstrap * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict * Contextual Escaping (SCE)} services to AngularJS. * + * For an overview of this service and the functionnality it provides in AngularJS, see the main + * page for {@link ng.$sce SCE}. The current page is targeted for developers who need to alter how + * SCE works in their application, which shouldn't be needed in most cases. + * + *
+ * AngularJS strongly relies on contextual escaping for the security of bindings: disabling or + * modifying this might cause cross site scripting (XSS) vulnerabilities. For libraries owners, + * changes to this service will also influence users, so be extra careful and document your changes. + *
+ * * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to @@ -18656,10 +20244,10 @@ return /******/ (function(modules) { // webpackBootstrap * The default instance of `$sceDelegate` should work out of the box with little pain. While you * can override it completely to change the behavior of `$sce`, the common case would * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting - * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as - * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist - * $sceDelegateProvider.resourceUrlWhitelist} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * your own trusted and banned resource lists for trusting URLs used for loading AngularJS resources + * such as templates. Refer {@link ng.$sceDelegateProvider#trustedResourceUrlList + * $sceDelegateProvider.trustedResourceUrlList} and {@link + * ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList} */ /** @@ -18670,12 +20258,16 @@ return /******/ (function(modules) { // webpackBootstrap * @description * * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate - * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure - * that the URLs used for sourcing Angular templates are safe. Refer {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and - * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * $sceDelegate service}, used as a delegate for {@link ng.$sce Strict Contextual Escaping (SCE)}. * - * For the general details about this service in Angular, read the main page for {@link ng.$sce + * The `$sceDelegateProvider` allows one to get/set the `trustedResourceUrlList` and + * `bannedResourceUrlList` used to ensure that the URLs used for sourcing AngularJS templates and + * other script-running URLs are safe (all places that use the `$sce.RESOURCE_URL` context). See + * {@link ng.$sceDelegateProvider#trustedResourceUrlList + * $sceDelegateProvider.trustedResourceUrlList} and + * {@link ng.$sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider.bannedResourceUrlList}, + * + * For the general details about this service in AngularJS, read the main page for {@link ng.$sce * Strict Contextual Escaping (SCE)}. * * **Example**: Consider the following case. @@ -18689,94 +20281,140 @@ return /******/ (function(modules) { // webpackBootstrap * * ``` * angular.module('myApp', []).config(function($sceDelegateProvider) { - * $sceDelegateProvider.resourceUrlWhitelist([ + * $sceDelegateProvider.trustedResourceUrlList([ * // Allow same origin resource loads. * 'self', * // Allow loading from our assets domain. Notice the difference between * and **. * 'http://srv*.assets.example.com/**' * ]); * - * // The blacklist overrides the whitelist so the open redirect here is blocked. - * $sceDelegateProvider.resourceUrlBlacklist([ + * // The banned resource URL list overrides the trusted resource URL list so the open redirect + * // here is blocked. + * $sceDelegateProvider.bannedResourceUrlList([ * 'http://myapp.example.com/clickThru**' * ]); * }); * ``` + * Note that an empty trusted resource URL list will block every resource URL from being loaded, and will require + * you to manually mark each one as trusted with `$sce.trustAsResourceUrl`. However, templates + * requested by {@link ng.$templateRequest $templateRequest} that are present in + * {@link ng.$templateCache $templateCache} will not go through this check. If you have a mechanism + * to populate your templates in that cache at config time, then it is a good idea to remove 'self' + * from the trusted resource URL lsit. This helps to mitigate the security impact of certain types + * of issues, like for instance attacker-controlled `ng-includes`. */ function $SceDelegateProvider() { this.SCE_CONTEXTS = SCE_CONTEXTS; // Resource URLs can also be trusted by policy. - var resourceUrlWhitelist = ['self'], - resourceUrlBlacklist = []; + var trustedResourceUrlList = ['self'], + bannedResourceUrlList = []; /** * @ngdoc method - * @name $sceDelegateProvider#resourceUrlWhitelist + * @name $sceDelegateProvider#trustedResourceUrlList * @kind function * - * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. + * @param {Array=} trustedResourceUrlList When provided, replaces the trustedResourceUrlList with + * the value provided. This must be an array or null. A snapshot of this array is used so + * further changes to the array are ignored. + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. + * @return {Array} The currently set trusted resource URL array. * - *
- * **Note:** an empty whitelist array will block all URLs! - *
- * - * @return {Array} the currently set whitelist array. + * @description + * Sets/Gets the list trusted of resource URLs. * - * The **default value** when no whitelist has been explicitly set is `['self']` allowing only - * same origin resource requests. + * The **default value** when no `trustedResourceUrlList` has been explicitly set is `['self']` + * allowing only same origin resource requests. * - * @description - * Sets/Gets the whitelist of trusted resource URLs. + *
+ * **Note:** the default `trustedResourceUrlList` of 'self' is not recommended if your app shares + * its origin with other apps! It is a good idea to limit it to only your application's directory. + *
*/ - this.resourceUrlWhitelist = function(value) { + this.trustedResourceUrlList = function(value) { if (arguments.length) { - resourceUrlWhitelist = adjustMatchers(value); + trustedResourceUrlList = adjustMatchers(value); } - return resourceUrlWhitelist; + return trustedResourceUrlList; }; /** * @ngdoc method - * @name $sceDelegateProvider#resourceUrlBlacklist + * @name $sceDelegateProvider#resourceUrlWhitelist * @kind function * - * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * The typical usage for the blacklist is to **block - * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as - * these would otherwise be trusted but actually return content from the redirected domain. + * @deprecated + * sinceVersion="1.8.1" * - * Finally, **the blacklist overrides the whitelist** and has the final say. + * This method is deprecated. Use {@link $sceDelegateProvider#trustedResourceUrlList + * trustedResourceUrlList} instead. + */ + Object.defineProperty(this, 'resourceUrlWhitelist', { + get: function() { + return this.trustedResourceUrlList; + }, + set: function(value) { + this.trustedResourceUrlList = value; + } + }); + + /** + * @ngdoc method + * @name $sceDelegateProvider#bannedResourceUrlList + * @kind function * - * @return {Array} the currently set blacklist array. + * @param {Array=} bannedResourceUrlList When provided, replaces the `bannedResourceUrlList` with + * the value provided. This must be an array or null. A snapshot of this array is used so + * further changes to the array are ignored.

+ * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array.

+ * The typical usage for the `bannedResourceUrlList` is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. + *

+ * Finally, **the banned resource URL list overrides the trusted resource URL list** and has + * the final say. * - * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there - * is no blacklist.) + * @return {Array} The currently set `bannedResourceUrlList` array. * * @description - * Sets/Gets the blacklist of trusted resource URLs. + * Sets/Gets the `bannedResourceUrlList` of trusted resource URLs. + * + * The **default value** when no trusted resource URL list has been explicitly set is the empty + * array (i.e. there is no `bannedResourceUrlList`.) */ - - this.resourceUrlBlacklist = function(value) { + this.bannedResourceUrlList = function(value) { if (arguments.length) { - resourceUrlBlacklist = adjustMatchers(value); + bannedResourceUrlList = adjustMatchers(value); } - return resourceUrlBlacklist; + return bannedResourceUrlList; }; - this.$get = ['$injector', function($injector) { + /** + * @ngdoc method + * @name $sceDelegateProvider#resourceUrlBlacklist + * @kind function + * + * @deprecated + * sinceVersion="1.8.1" + * + * This method is deprecated. Use {@link $sceDelegateProvider#bannedResourceUrlList + * bannedResourceUrlList} instead. + */ + Object.defineProperty(this, 'resourceUrlBlacklist', { + get: function() { + return this.bannedResourceUrlList; + }, + set: function(value) { + this.bannedResourceUrlList = value; + } + }); + + this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) { var htmlSanitizer = function htmlSanitizer(html) { throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); @@ -18789,7 +20427,7 @@ return /******/ (function(modules) { // webpackBootstrap function matchUrl(matcher, parsedUrl) { if (matcher === 'self') { - return urlIsSameOrigin(parsedUrl); + return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl); } else { // definitely a regex. See adjustMatchers() return !!matcher.exec(parsedUrl.href); @@ -18799,17 +20437,17 @@ return /******/ (function(modules) { // webpackBootstrap function isResourceUrlAllowedByPolicy(url) { var parsedUrl = urlResolve(url.toString()); var i, n, allowed = false; - // Ensure that at least one item from the whitelist allows this url. - for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { - if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { + // Ensure that at least one item from the trusted resource URL list allows this url. + for (i = 0, n = trustedResourceUrlList.length; i < n; i++) { + if (matchUrl(trustedResourceUrlList[i], parsedUrl)) { allowed = true; break; } } if (allowed) { - // Ensure that no item from the blacklist blocked this url. - for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { - if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { + // Ensure that no item from the banned resource URL list has blocked this url. + for (i = 0, n = bannedResourceUrlList.length; i < n; i++) { + if (matchUrl(bannedResourceUrlList[i], parsedUrl)) { allowed = false; break; } @@ -18841,7 +20479,8 @@ return /******/ (function(modules) { // webpackBootstrap byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]); byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); @@ -18850,17 +20489,24 @@ return /******/ (function(modules) { // webpackBootstrap * @name $sceDelegate#trustAs * * @description - * Returns an object that is trusted by angular for use in specified strict - * contextual escaping contexts (such as ng-bind-html, ng-include, any src - * attribute interpolation, any dom event binding attribute interpolation - * such as for onclick, etc.) that uses the provided value. - * See {@link ng.$sce $sce} for enabling strict contextual escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resourceUrl, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. + * Returns a trusted representation of the parameter for the specified context. This trusted + * object will later on be used as-is, without any security check, by bindings or directives + * that require this security context. + * For instance, marking a string as trusted for the `$sce.HTML` context will entirely bypass + * the potential `$sanitize` call in corresponding `$sce.HTML` bindings or directives, such as + * `ng-bind-html`. Note that in most cases you won't need to call this function: if you have the + * sanitizer loaded, passing the value itself will render all the HTML that does not pose a + * security risk. + * + * See {@link ng.$sceDelegate#getTrusted getTrusted} for the function that will consume those + * trusted values, and {@link ng.$sce $sce} for general documentation about strict contextual + * escaping. + * + * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`, + * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`. + * + * @param {*} value The value that should be considered trusted. + * @return {*} A trusted representation of value, that can be used in the given context. */ function trustAs(type, trustedValue) { var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); @@ -18892,11 +20538,11 @@ return /******/ (function(modules) { // webpackBootstrap * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. * * If the passed parameter is not a value that had been returned by {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, it must be returned as-is. * * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} - * call or anything else. - * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs + * call or anything else. + * @return {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns * `value` unchanged. */ @@ -18913,33 +20559,56 @@ return /******/ (function(modules) { // webpackBootstrap * @name $sceDelegate#getTrusted * * @description - * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and - * returns the originally supplied value if the queried context type is a supertype of the - * created type. If this condition isn't satisfied, throws an exception. + * Given an object and a security context in which to assign it, returns a value that's safe to + * use in this context, which was represented by the parameter. To do so, this function either + * unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on + * the context and sanitizer availablility. + * + * The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available + * by default, and the third one relies on the `$sanitize` service (which may be loaded through + * the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be + * accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#trustedResourceUrlList + * `$sceDelegateProvider.trustedResourceUrlList`} and {@link ng.$sceDelegateProvider#bannedResourceUrlList + * `$sceDelegateProvider.bannedResourceUrlList`} accepts that resource. + * + * This function will throw if the safe type isn't appropriate for this context, or if the + * value given cannot be accepted in the context (which might be caused by sanitization not + * being available, or the value not being recognized as safe). * *

* Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting * (XSS) vulnerability in your application. *
* - * @param {string} type The kind of context in which this value is to be used. + * @param {string} type The context in which this value is to be used (such as `$sce.HTML`). * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} call. - * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. + * `$sceDelegate.trustAs`} call, or anything else (which will not be considered trusted.) + * @return {*} A version of the value that's safe to use in the given context, or throws an + * exception if this is impossible. */ function getTrusted(type, maybeTrusted) { if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { return maybeTrusted; } var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + // If maybeTrusted is a trusted class instance or subclass instance, then unwrap and return + // as-is. if (constructor && maybeTrusted instanceof constructor) { return maybeTrusted.$$unwrapTrustedValue(); } - // If we get here, then we may only take one of two actions. - // 1. sanitize the value for the requested type, or - // 2. throw an exception. - if (type === SCE_CONTEXTS.RESOURCE_URL) { + + // If maybeTrusted is a trusted class instance but not of the correct trusted type + // then unwrap it and allow it to pass through to the rest of the checks + if (isFunction(maybeTrusted.$$unwrapTrustedValue)) { + maybeTrusted = maybeTrusted.$$unwrapTrustedValue(); + } + + // If we get here, then we will either sanitize the value or throw an exception. + if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) { + // we attempt to sanitize non-resource URLs + return $$sanitizeUri(maybeTrusted.toString(), type === SCE_CONTEXTS.MEDIA_URL); + } else if (type === SCE_CONTEXTS.RESOURCE_URL) { if (isResourceUrlAllowedByPolicy(maybeTrusted)) { return maybeTrusted; } else { @@ -18948,8 +20617,10 @@ return /******/ (function(modules) { // webpackBootstrap maybeTrusted.toString()); } } else if (type === SCE_CONTEXTS.HTML) { + // htmlSanitizer throws its own error when no sanitizer is available. return htmlSanitizer(maybeTrusted); } + // Default error when the $sce service has no way to make the input safe. throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); } @@ -18983,23 +20654,29 @@ return /******/ (function(modules) { // webpackBootstrap * * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. * - * # Strict Contextual Escaping + * ## Strict Contextual Escaping * - * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain - * contexts to result in a value that is marked as safe to use for that context. One example of - * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer - * to these contexts as privileged or SCE contexts. + * Strict Contextual Escaping (SCE) is a mode in which AngularJS constrains bindings to only render + * trusted values. Its goal is to assist in writing code in a way that (a) is secure by default, and + * (b) makes auditing for security vulnerabilities such as XSS, clickjacking, etc. a lot easier. * - * As of version 1.2, Angular ships with SCE enabled by default. + * ### Overview * - * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow - * one to execute arbitrary javascript by the use of the expression() syntax. Refer - * to learn more about them. - * You can ensure your document is in standards mode and not quirks mode by adding `` - * to the top of your HTML document. + * To systematically block XSS security bugs, AngularJS treats all values as untrusted by default in + * HTML or sensitive URL bindings. When binding untrusted values, AngularJS will automatically + * run security checks on them (sanitizations, trusted URL resource, depending on context), or throw + * when it cannot guarantee the security of the result. That behavior depends strongly on contexts: + * HTML can be sanitized, but template URLs cannot, for instance. * - * SCE assists in writing code in a way that (a) is secure by default and (b) makes auditing for - * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. + * To illustrate this, consider the `ng-bind-html` directive. It renders its value directly as HTML: + * we call that the *context*. When given an untrusted input, AngularJS will attempt to sanitize it + * before rendering if a sanitizer is available, and throw otherwise. To bypass sanitization and + * render the input as-is, you will need to mark it as trusted for that context before attempting + * to bind it. + * + * As of version 1.2, AngularJS ships with SCE enabled by default. + * + * ### In practice * * Here's an example of a binding in a privileged context: * @@ -19009,10 +20686,10 @@ return /******/ (function(modules) { // webpackBootstrap * ``` * * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE - * disabled, this application allows the user to render arbitrary HTML into the DIV. - * In a more realistic example, one may be rendering user comments, blog articles, etc. via - * bindings. (HTML is just one example of a context where rendering user controlled input creates - * security vulnerabilities.) + * disabled, this application allows the user to render arbitrary HTML into the DIV, which would + * be an XSS security bug. In a more realistic example, one may be rendering user comments, blog + * articles, etc. via bindings. (HTML is just one example of a context where rendering user + * controlled input creates security vulnerabilities.) * * For the case of HTML, you might use a library, either on the client side, or on the server side, * to sanitize unsafe HTML before binding to the value and rendering it in the document. @@ -19022,25 +20699,29 @@ return /******/ (function(modules) { // webpackBootstrap * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some * properties/fields and forgot to update the binding to the sanitized value? * - * To be secure by default, you want to ensure that any such bindings are disallowed unless you can - * determine that something explicitly says it's safe to use a value for binding in that - * context. You can then audit your code (a simple grep would do) to ensure that this is only done - * for those values that you can easily tell are safe - because they were received from your server, - * sanitized by your library, etc. You can organize your codebase to help with this - perhaps - * allowing only the files in a specific directory to do this. Ensuring that the internal API - * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. + * To be secure by default, AngularJS makes sure bindings go through that sanitization, or + * any similar validation process, unless there's a good reason to trust the given value in this + * context. That trust is formalized with a function call. This means that as a developer, you + * can assume all untrusted bindings are safe. Then, to audit your code for binding security issues, + * you just need to ensure the values you mark as trusted indeed are safe - because they were + * received from your server, sanitized by your library, etc. You can organize your codebase to + * help with this - perhaps allowing only the files in a specific directory to do this. + * Ensuring that the internal API exposed by that code doesn't markup arbitrary values as safe then + * becomes a more manageable task. * * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to - * obtain values that will be accepted by SCE / privileged contexts. + * build the trusted versions of your values. * - * - * ## How does it work? + * ### How does it work? * * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted - * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the - * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * $sce.getTrusted(context, value)} rather than to the value directly. Think of this function as + * a way to enforce the required security context in your data sink. Directives use {@link + * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs + * the {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. Also, + * when binding without directives, AngularJS will understand the context of your bindings + * automatically. * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly @@ -19056,16 +20737,16 @@ return /******/ (function(modules) { // webpackBootstrap * }]; * ``` * - * ## Impact on loading templates + * ### Impact on loading templates * * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as * `templateUrl`'s specified by {@link guide/directive directives}. * - * By default, Angular only loads templates from the same domain and protocol as the application + * By default, AngularJS only loads templates from the same domain and protocol as the application * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or - * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist - * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. + * protocols, you may either add them to the {@link ng.$sceDelegateProvider#trustedResourceUrlList + * trustedResourceUrlList} or {@link ng.$sce#trustAsResourceUrl wrap them} into trusted values. * * *Please note*: * The browser's @@ -19076,40 +20757,58 @@ return /******/ (function(modules) { // webpackBootstrap * won't work on all browsers. Also, loading templates from `file://` URL does not work on some * browsers. * - * ## This feels like too much overhead + * ### This feels like too much overhead * * It's important to remember that SCE only applies to interpolation expressions. * * If your expressions are constant literals, they're automatically trusted and you don't need to - * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. - * `
`) just works. - * - * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them - * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. + * call `$sce.trustAs` on them (e.g. + * `
`) just works (remember to include the + * `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available + * when binding untrusted values to `$sce.HTML` context. + * AngularJS provides an implementation in `angular-sanitize.js`, and if you + * wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in + * your application. * * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load * templates in `ng-include` from your application's domain without having to even know about SCE. * It blocks loading templates from other domains or loading templates over http from an https * served document. You can change these by setting your own custom {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. + * ng.$sceDelegateProvider#trustedResourceUrlList trusted resource URL list} and {@link + * ng.$sceDelegateProvider#bannedResourceUrlList banned resource URL list} for matching such URLs. * * This significantly reduces the overhead. It is far easier to pay the small overhead and have an * application that's secure and can be audited to verify that with much more ease than bolting * security onto an application later. * * - * ## What trusted context types are supported? + * ### What trusted context types are supported? * * | Context | Notes | * |---------------------|----------------| * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | - * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | + * | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. | + * | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.| + * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.)

Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required.

The {@link $sceDelegateProvider#trustedResourceUrlList $sceDelegateProvider#trustedResourceUrlList()} and {@link $sceDelegateProvider#bannedResourceUrlList $sceDelegateProvider#bannedResourceUrlList()} can be used to restrict trusted origins for `RESOURCE_URL` | * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | * - * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
+ * + *
+ * Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their + * interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}. + * + * **As of 1.7.0, this is no longer the case.** + * + * Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL` + * (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate` + * service evaluates the expressions. + *
+ * + * There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs` + * functions aren't useful yet. This might evolve. + * + * ### Format of items in {@link ng.$sceDelegateProvider#trustedResourceUrlList trustedResourceUrlList}/{@link ng.$sceDelegateProvider#bannedResourceUrlList bannedResourceUrlList} * * Each element in these arrays must be one of the following: * @@ -19123,7 +20822,7 @@ return /******/ (function(modules) { // webpackBootstrap * match themselves. * - `*`: matches zero or more occurrences of any character other than one of the following 6 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use - * in a whitelist. + * for matching resource URL lists. * - `**`: matches zero or more occurrences of *any* character. As such, it's not * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might @@ -19156,7 +20855,7 @@ return /******/ (function(modules) { // webpackBootstrap * * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. * - * ## Show me an example using SCE. + * ### Show me an example using SCE. * * * @@ -19226,14 +20925,15 @@ return /******/ (function(modules) { // webpackBootstrap * for little coding overhead. It will be much harder to take an SCE disabled application and * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE * for cases where you have a lot of existing code that was written before SCE was introduced and - * you're migrating them a module at a time. + * you're migrating them a module at a time. Also do note that this is an app-wide setting, so if + * you are writing a library, you will cause security bugs applications using it. * * That said, here's how you can completely disable SCE: * * ``` * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { * // Completely disable SCE. For demonstration purposes only! - * // Do not use in new projects. + * // Do not use in new projects or libraries. * $sceProvider.enabled(false); * }); * ``` @@ -19248,8 +20948,8 @@ return /******/ (function(modules) { // webpackBootstrap * @name $sceProvider#enabled * @kind function * - * @param {boolean=} value If provided, then enables/disables SCE. - * @return {boolean} true if SCE is enabled, false otherwise. + * @param {boolean=} value If provided, then enables/disables SCE application-wide. + * @return {boolean} True if SCE is enabled, false otherwise. * * @description * Enables/disables SCE and returns the current value. @@ -19280,7 +20980,7 @@ return /******/ (function(modules) { // webpackBootstrap * such a value. * * - getTrusted(contextEnum, value) - * This function should return the a value that is safe to use in the context specified by + * This function should return the value that is safe to use in the context specified by * contextEnum or throw and exception otherwise. * * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be @@ -19303,9 +21003,9 @@ return /******/ (function(modules) { // webpackBootstrap * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) * will also succeed. * - * Inheritance happens to capture this in a natural way. In some future, we - * may not use inheritance anymore. That is OK because no code outside of - * sce.js and sceSpecs.js would need to be aware of this detail. + * Inheritance happens to capture this in a natural way. In some future, we may not use + * inheritance anymore. That is OK because no code outside of sce.js and sceSpecs.js would need to + * be aware of this detail. */ this.$get = ['$parse', '$sceDelegate', function( @@ -19327,8 +21027,8 @@ return /******/ (function(modules) { // webpackBootstrap * @name $sce#isEnabled * @kind function * - * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you - * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. + * @return {Boolean} True if SCE is enabled, false otherwise. If you want to set the value, you + * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. * * @description * Returns a boolean indicating if SCE is enabled. @@ -19350,19 +21050,19 @@ return /******/ (function(modules) { // webpackBootstrap * @name $sce#parseAs * * @description - * Converts Angular {@link guide/expression expression} into a function. This is like {@link + * Converts AngularJS {@link guide/expression expression} into a function. This is like {@link * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, * *result*)} * - * @param {string} type The kind of SCE context in which this result will be used. + * @param {string} type The SCE context in which this result will be used. * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: + * @return {function(context, locals)} A function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the + * strings are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values + * in `context`. */ sce.parseAs = function sceParseAs(type, expr) { var parsed = $parse(expr); @@ -19380,18 +21080,18 @@ return /******/ (function(modules) { // webpackBootstrap * @name $sce#trustAs * * @description - * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, - * returns an object that is trusted by angular for use in specified strict contextual - * escaping contexts (such as ng-bind-html, ng-include, any src attribute - * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) - * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual - * escaping. + * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns a + * wrapped object that represents your value, and the trust you have in its safety for the given + * context. AngularJS can then use that value as-is in bindings of the specified secure context. + * This is used in bindings for `ng-bind-html`, `ng-include`, and most `src` attribute + * interpolations. See {@link ng.$sce $sce} for strict contextual escaping. * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resourceUrl, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. + * @param {string} type The context in which this value is safe for use, e.g. `$sce.URL`, + * `$sce.RESOURCE_URL`, `$sce.HTML`, `$sce.JS` or `$sce.CSS`. + * + * @param {*} value The value that that should be considered trusted. + * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` + * in the context you specified. */ /** @@ -19402,26 +21102,37 @@ return /******/ (function(modules) { // webpackBootstrap * Shorthand method. `$sce.trustAsHtml(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml - * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + * @param {*} value The value to mark as trusted for `$sce.HTML` context. + * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` + * in `$sce.HTML` context (like `ng-bind-html`). */ /** * @ngdoc method - * @name $sce#trustAsUrl + * @name $sce#trustAsCss * * @description - * Shorthand method. `$sce.trustAsUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * Shorthand method. `$sce.trustAsCss(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.CSS, value)`} * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl - * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + * @param {*} value The value to mark as trusted for `$sce.CSS` context. + * @return {*} A wrapped version of value that can be used as a trusted variant + * of your `value` in `$sce.CSS` context. This context is currently unused, so there are + * almost no reasons to use this function so far. + */ + + /** + * @ngdoc method + * @name $sce#trustAsUrl + * + * @description + * Shorthand method. `$sce.trustAsUrl(value)` → + * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * + * @param {*} value The value to mark as trusted for `$sce.URL` context. + * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` + * in `$sce.URL` context. That context is currently unused, so there are almost no reasons + * to use this function so far. */ /** @@ -19432,11 +21143,10 @@ return /******/ (function(modules) { // webpackBootstrap * Shorthand method. `$sce.trustAsResourceUrl(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the return - * value of {@link ng.$sce#trustAs $sce.trustAs}.) + * @param {*} value The value to mark as trusted for `$sce.RESOURCE_URL` context. + * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` + * in `$sce.RESOURCE_URL` context (template URLs in `ng-include`, most `src` attribute + * bindings, ...) */ /** @@ -19447,11 +21157,10 @@ return /******/ (function(modules) { // webpackBootstrap * Shorthand method. `$sce.trustAsJs(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs - * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + * @param {*} value The value to mark as trusted for `$sce.JS` context. + * @return {*} A wrapped version of value that can be used as a trusted variant of your `value` + * in `$sce.JS` context. That context is currently unused, so there are almost no reasons to + * use this function so far. */ /** @@ -19460,16 +21169,17 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, - * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the - * originally supplied value if the queried context type is a supertype of the created type. - * If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} - * call. - * @returns {*} The value the was originally provided to - * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. - * Otherwise, throws an exception. + * takes any input, and either returns a value that's safe to use in the specified context, + * or throws an exception. This function is aware of trusted values created by the `trustAs` + * function and its shorthands, and when contexts are appropriate, returns the unwrapped value + * as-is. Finally, this function can also throw when there is no way to turn `maybeTrusted` in a + * safe value (e.g., no sanitization is available or possible.) + * + * @param {string} type The context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs + * `$sce.trustAs`} call, or anything else (which will not be considered trusted.) + * @return {*} A version of the value that's safe to use in the given context, or throws an + * exception if this is impossible. */ /** @@ -19481,7 +21191,7 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` + * @return {*} The return value of `$sce.getTrusted($sce.HTML, value)` */ /** @@ -19493,7 +21203,7 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` + * @return {*} The return value of `$sce.getTrusted($sce.CSS, value)` */ /** @@ -19505,7 +21215,7 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` + * @return {*} The return value of `$sce.getTrusted($sce.URL, value)` */ /** @@ -19517,7 +21227,7 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} * * @param {*} value The value to pass to `$sceDelegate.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` + * @return {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` */ /** @@ -19529,7 +21239,7 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` + * @return {*} The return value of `$sce.getTrusted($sce.JS, value)` */ /** @@ -19541,12 +21251,12 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} * * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: + * @return {function(context, locals)} A function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the + * strings are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values + * in `context`. */ /** @@ -19558,12 +21268,12 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} * * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: + * @return {function(context, locals)} A function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the + * strings are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values + * in `context`. */ /** @@ -19575,12 +21285,12 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} * * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: + * @return {function(context, locals)} A function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the + * strings are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values + * in `context`. */ /** @@ -19592,12 +21302,12 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} * * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: + * @return {function(context, locals)} A function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the + * strings are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values + * in `context`. */ /** @@ -19609,12 +21319,12 @@ return /******/ (function(modules) { // webpackBootstrap * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} * * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: + * @return {function(context, locals)} A function which represents the compiled expression: * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. + * * `context` – `{object}` – an object against which any expressions embedded in the + * strings are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values + * in `context`. */ // Shorthand delegations. @@ -19664,7 +21374,10 @@ return /******/ (function(modules) { // webpackBootstrap // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by // the presence of an extension runtime ID and the absence of other Chrome runtime APIs // (see https://developer.chrome.com/apps/manifest/sandbox). + // (NW.js apps have access to Chrome APIs, but do support `history`.) + isNw = $window.nw && $window.nw.process, isChromePackagedApp = + !isNw && $window.chrome && ($window.chrome.app && $window.chrome.app.runtime || !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id), @@ -19719,7 +21432,128 @@ return /******/ (function(modules) { // webpackBootstrap }]; } - var $templateRequestMinErr = minErr('$compile'); + /** + * ! This is a private undocumented service ! + * + * @name $$taskTrackerFactory + * @description + * A function to create `TaskTracker` instances. + * + * A `TaskTracker` can keep track of pending tasks (grouped by type) and can notify interested + * parties when all pending tasks (or tasks of a specific type) have been completed. + * + * @param {$log} log - A logger instance (such as `$log`). Used to log error during callback + * execution. + * + * @this + */ + function $$TaskTrackerFactoryProvider() { + this.$get = valueFn(function(log) { return new TaskTracker(log); }); + } + + function TaskTracker(log) { + var self = this; + var taskCounts = {}; + var taskCallbacks = []; + + var ALL_TASKS_TYPE = self.ALL_TASKS_TYPE = '$$all$$'; + var DEFAULT_TASK_TYPE = self.DEFAULT_TASK_TYPE = '$$default$$'; + + /** + * Execute the specified function and decrement the appropriate `taskCounts` counter. + * If the counter reaches 0, all corresponding `taskCallbacks` are executed. + * + * @param {Function} fn - The function to execute. + * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task that is being completed. + */ + self.completeTask = completeTask; + + /** + * Increase the task count for the specified task type (or the default task type if non is + * specified). + * + * @param {string=} [taskType=DEFAULT_TASK_TYPE] - The type of task whose count will be increased. + */ + self.incTaskCount = incTaskCount; + + /** + * Execute the specified callback when all pending tasks have been completed. + * + * If there are no pending tasks, the callback is executed immediately. You can optionally limit + * the tasks that will be waited for to a specific type, by passing a `taskType`. + * + * @param {function} callback - The function to call when there are no pending tasks. + * @param {string=} [taskType=ALL_TASKS_TYPE] - The type of tasks that will be waited for. + */ + self.notifyWhenNoPendingTasks = notifyWhenNoPendingTasks; + + function completeTask(fn, taskType) { + taskType = taskType || DEFAULT_TASK_TYPE; + + try { + fn(); + } finally { + decTaskCount(taskType); + + var countForType = taskCounts[taskType]; + var countForAll = taskCounts[ALL_TASKS_TYPE]; + + // If at least one of the queues (`ALL_TASKS_TYPE` or `taskType`) is empty, run callbacks. + if (!countForAll || !countForType) { + var getNextCallback = !countForAll ? getLastCallback : getLastCallbackForType; + var nextCb; + + while ((nextCb = getNextCallback(taskType))) { + try { + nextCb(); + } catch (e) { + log.error(e); + } + } + } + } + } + + function decTaskCount(taskType) { + taskType = taskType || DEFAULT_TASK_TYPE; + if (taskCounts[taskType]) { + taskCounts[taskType]--; + taskCounts[ALL_TASKS_TYPE]--; + } + } + + function getLastCallback() { + var cbInfo = taskCallbacks.pop(); + return cbInfo && cbInfo.cb; + } + + function getLastCallbackForType(taskType) { + for (var i = taskCallbacks.length - 1; i >= 0; --i) { + var cbInfo = taskCallbacks[i]; + if (cbInfo.type === taskType) { + taskCallbacks.splice(i, 1); + return cbInfo.cb; + } + } + } + + function incTaskCount(taskType) { + taskType = taskType || DEFAULT_TASK_TYPE; + taskCounts[taskType] = (taskCounts[taskType] || 0) + 1; + taskCounts[ALL_TASKS_TYPE] = (taskCounts[ALL_TASKS_TYPE] || 0) + 1; + } + + function notifyWhenNoPendingTasks(callback, taskType) { + taskType = taskType || ALL_TASKS_TYPE; + if (!taskCounts[taskType]) { + callback(); + } else { + taskCallbacks.push({type: taskType, cb: callback}); + } + } + } + + var $templateRequestMinErr = minErr('$templateRequest'); /** * @ngdoc provider @@ -19772,6 +21606,12 @@ return /******/ (function(modules) { // webpackBootstrap * If you want to pass custom options to the `$http` service, such as setting the Accept header you * can configure this via {@link $templateRequestProvider#httpOptions}. * + * `$templateRequest` is used internally by {@link $compile}, {@link ngRoute.$route}, and directives such + * as {@link ngInclude} to download and cache templates. + * + * 3rd party modules should use `$templateRequest` if their services or directives are loading + * templates. + * * @param {string|TrustedResourceUrl} tpl The HTTP request template URL * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty * @@ -19786,10 +21626,10 @@ return /******/ (function(modules) { // webpackBootstrap handleRequestFn.totalPendingRequests++; // We consider the template cache holds only trusted templates, so - // there's no need to go through whitelisting again for keys that already - // are included in there. This also makes Angular accept any script - // directive, no matter its name. However, we still need to unwrap trusted - // types. + // there's no need to go through adding the template again to the trusted + // resources for keys that already are included in there. This also makes + // AngularJS accept any script directive, no matter its name. However, we + // still need to unwrap trusted types. if (!isString(tpl) || isUndefined($templateCache.get(tpl))) { tpl = $sce.getTrustedResourceUrl(tpl); } @@ -19812,8 +21652,7 @@ return /******/ (function(modules) { // webpackBootstrap handleRequestFn.totalPendingRequests--; }) .then(function(response) { - $templateCache.put(tpl, response.data); - return response.data; + return $templateCache.put(tpl, response.data); }, handleError); function handleError(resp) { @@ -19940,7 +21779,15 @@ return /******/ (function(modules) { // webpackBootstrap * @name $$testability#whenStable * * @description - * Calls the callback when $timeout and $http requests are completed. + * Calls the callback when all pending tasks are completed. + * + * Types of tasks waited for include: + * - Pending timeouts (via {@link $timeout}). + * - Pending HTTP requests (via {@link $http}). + * - In-progress route transitions (via {@link $route}). + * - Pending tasks scheduled via {@link $rootScope#$applyAsync}. + * - Pending tasks scheduled via {@link $rootScope#$evalAsync}. + * These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises). * * @param {function} callback */ @@ -19952,6 +21799,8 @@ return /******/ (function(modules) { // webpackBootstrap }]; } + var $timeoutMinErr = minErr('$timeout'); + /** @this */ function $TimeoutProvider() { this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', @@ -19960,35 +21809,35 @@ return /******/ (function(modules) { // webpackBootstrap var deferreds = {}; - /** - * @ngdoc service - * @name $timeout - * - * @description - * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch - * block and delegates any exceptions to - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * The return value of calling `$timeout` is a promise, which will be resolved when - * the delay has passed and the timeout function, if provided, is executed. - * - * To cancel a timeout request, call `$timeout.cancel(promise)`. - * - * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to - * synchronously flush the queue of deferred functions. - * - * If you only want a promise that will be resolved after some specified delay - * then you can call `$timeout` without the `fn` function. - * - * @param {function()=} fn A function, whose execution should be delayed. - * @param {number=} [delay=0] Delay in milliseconds. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @param {...*=} Pass additional parameters to the executed function. - * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise - * will be resolved with the return value of the `fn` function. - * - */ + /** + * @ngdoc service + * @name $timeout + * + * @description + * AngularJS's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of calling `$timeout` is a promise, which will be resolved when + * the delay has passed and the timeout function, if provided, is executed. + * + * To cancel a timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * If you only want a promise that will be resolved after some specified delay + * then you can call `$timeout` without the `fn` function. + * + * @param {function()=} fn A function, whose execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise + * will be resolved with the return value of the `fn` function. + * + */ function timeout(fn, delay, invokeApply) { if (!isFunction(fn)) { invokeApply = delay; @@ -20013,7 +21862,7 @@ return /******/ (function(modules) { // webpackBootstrap } if (!skipApply) $rootScope.$apply(); - }, delay); + }, delay, '$timeout'); promise.$$timeoutId = timeoutId; deferreds[timeoutId] = deferred; @@ -20022,27 +21871,37 @@ return /******/ (function(modules) { // webpackBootstrap } - /** - * @ngdoc method - * @name $timeout#cancel - * - * @description - * Cancels a task associated with the `promise`. As a result of this, the promise will be - * resolved with a rejection. - * - * @param {Promise=} promise Promise returned by the `$timeout` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ + /** + * @ngdoc method + * @name $timeout#cancel + * + * @description + * Cancels a task associated with the `promise`. As a result of this, the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ timeout.cancel = function(promise) { - if (promise && promise.$$timeoutId in deferreds) { - // Timeout cancels should not report an unhandled promise. - deferreds[promise.$$timeoutId].promise.catch(noop); - deferreds[promise.$$timeoutId].reject('canceled'); - delete deferreds[promise.$$timeoutId]; - return $browser.defer.cancel(promise.$$timeoutId); + if (!promise) return false; + + if (!promise.hasOwnProperty('$$timeoutId')) { + throw $timeoutMinErr('badprom', + '`$timeout.cancel()` called with a promise that was not generated by `$timeout()`.'); } - return false; + + if (!deferreds.hasOwnProperty(promise.$$timeoutId)) return false; + + var id = promise.$$timeoutId; + var deferred = deferreds[id]; + + // Timeout cancels should not report an unhandled promise. + markQExceptionHandled(deferred.promise); + deferred.reject('canceled'); + delete deferreds[id]; + + return $browser.defer.cancel(id); }; return timeout; @@ -20058,7 +21917,14 @@ return /******/ (function(modules) { // webpackBootstrap // service. var urlParsingNode = window.document.createElement('a'); var originUrl = urlResolve(window.location.href); + var baseUrlParsingNode; + + urlParsingNode.href = 'http://[::1]'; + // Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview) + // IE/Edge don't wrap IPv6 addresses' hostnames in square brackets + // when parsed out of an anchor element. + var ipv6InBrackets = urlParsingNode.hostname === '[::1]'; /** * @@ -20069,7 +21935,7 @@ return /******/ (function(modules) { // webpackBootstrap * URL will be resolved into an absolute URL in the context of the application document. * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related * properties are all populated to reflect the normalized URL. This approach has wide - * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * compatibility - Safari 1+, Mozilla 1+ etc. See * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html * * Implementation Notes for IE @@ -20089,23 +21955,26 @@ return /******/ (function(modules) { // webpackBootstrap * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ * * @kind function - * @param {string} url The URL to be parsed. + * @param {string|object} url The URL to be parsed. If `url` is not a string, it will be returned + * unchanged. * @description Normalizes and parses a URL. * @returns {object} Returns the normalized URL as a dictionary. * - * | member name | Description | - * |---------------|----------------| + * | member name | Description | + * |---------------|------------------------------------------------------------------------| * | href | A normalized version of the provided URL if it was not an absolute URL | - * | protocol | The protocol including the trailing colon | + * | protocol | The protocol without the trailing colon | * | host | The host and port (if the port is non-default) of the normalizedUrl | * | search | The search params, minus the question mark | - * | hash | The hash string, minus the hash symbol - * | hostname | The hostname - * | port | The port, without ":" - * | pathname | The pathname, beginning with "/" + * | hash | The hash string, minus the hash symbol | + * | hostname | The hostname | + * | port | The port, without ":" | + * | pathname | The pathname, beginning with "/" | * */ function urlResolve(url) { + if (!isString(url)) return url; + var href = url; // Support: IE 9-11 only @@ -20118,14 +21987,19 @@ return /******/ (function(modules) { // webpackBootstrap urlParsingNode.setAttribute('href', href); - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + var hostname = urlParsingNode.hostname; + + if (!ipv6InBrackets && hostname.indexOf(':') > -1) { + hostname = '[' + hostname + ']'; + } + return { href: urlParsingNode.href, protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', host: urlParsingNode.host, search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, + hostname: hostname, port: urlParsingNode.port, pathname: (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname @@ -20134,16 +22008,96 @@ return /******/ (function(modules) { // webpackBootstrap } /** - * Parse a request URL and determine whether this is a same-origin request as the application document. + * Parse a request URL and determine whether this is a same-origin request as the application + * document. * * @param {string|object} requestUrl The url of the request as a string that will be resolved * or a parsed URL object. * @returns {boolean} Whether the request is for the same origin as the application document. */ function urlIsSameOrigin(requestUrl) { - var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); + return urlsAreSameOrigin(requestUrl, originUrl); + } + + /** + * Parse a request URL and determine whether it is same-origin as the current document base URL. + * + * Note: The base URL is usually the same as the document location (`location.href`) but can + * be overriden by using the `` tag. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the URL is same-origin as the document base URL. + */ + function urlIsSameOriginAsBaseUrl(requestUrl) { + return urlsAreSameOrigin(requestUrl, getBaseUrl()); + } + + /** + * Create a function that can check a URL's origin against a list of allowed/trusted origins. + * The current location's origin is implicitly trusted. + * + * @param {string[]} trustedOriginUrls - A list of URLs (strings), whose origins are trusted. + * + * @returns {Function} - A function that receives a URL (string or parsed URL object) and returns + * whether it is of an allowed origin. + */ + function urlIsAllowedOriginFactory(trustedOriginUrls) { + var parsedAllowedOriginUrls = [originUrl].concat(trustedOriginUrls.map(urlResolve)); + + /** + * Check whether the specified URL (string or parsed URL object) has an origin that is allowed + * based on a list of trusted-origin URLs. The current location's origin is implicitly + * trusted. + * + * @param {string|Object} requestUrl - The URL to be checked (provided as a string that will be + * resolved or a parsed URL object). + * + * @returns {boolean} - Whether the specified URL is of an allowed origin. + */ + return function urlIsAllowedOrigin(requestUrl) { + var parsedUrl = urlResolve(requestUrl); + return parsedAllowedOriginUrls.some(urlsAreSameOrigin.bind(null, parsedUrl)); + }; + } + + /** + * Determine if two URLs share the same origin. + * + * @param {string|Object} url1 - First URL to compare as a string or a normalized URL in the form of + * a dictionary object returned by `urlResolve()`. + * @param {string|object} url2 - Second URL to compare as a string or a normalized URL in the form + * of a dictionary object returned by `urlResolve()`. + * + * @returns {boolean} - True if both URLs have the same origin, and false otherwise. + */ + function urlsAreSameOrigin(url1, url2) { + url1 = urlResolve(url1); + url2 = urlResolve(url2); + + return (url1.protocol === url2.protocol && + url1.host === url2.host); + } + + /** + * Returns the current document base URL. + * @returns {string} + */ + function getBaseUrl() { + if (window.document.baseURI) { + return window.document.baseURI; + } + + // `document.baseURI` is available everywhere except IE + if (!baseUrlParsingNode) { + baseUrlParsingNode = window.document.createElement('a'); + baseUrlParsingNode.href = '.'; + + // Work-around for IE bug described in Implementation Notes. The fix in `urlResolve()` is not + // suitable here because we need to track changes to the base URL. + baseUrlParsingNode = baseUrlParsingNode.cloneNode(false); + } + return baseUrlParsingNode.href; } /** @@ -20154,7 +22108,7 @@ return /******/ (function(modules) { // webpackBootstrap * @description * A reference to the browser's `window` object. While `window` * is globally available in JavaScript, it causes testability problems, because - * it is a global variable. In angular we always refer to it through the + * it is a global variable. In AngularJS we always refer to it through the * `$window` service, so it may be overridden, removed or mocked for testing. * * Expressions, like the one defined for the `ngClick` directive in the example @@ -20277,7 +22231,7 @@ return /******/ (function(modules) { // webpackBootstrap * annotated with dependencies and is responsible for creating a filter function. * *
- * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. + * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * (`myapp_subsection_filterx`). @@ -20320,8 +22274,8 @@ return /******/ (function(modules) { // webpackBootstrap * ``` * * - * For more information about how angular filters work, and how to create your own filters, see - * {@link guide/filter Filters} in the Angular Developer Guide. + * For more information about how AngularJS filters work, and how to create your own filters, see + * {@link guide/filter Filters} in the AngularJS Developer Guide. */ /** @@ -20331,7 +22285,7 @@ return /******/ (function(modules) { // webpackBootstrap * @description * Filters are used for formatting data displayed to the user. * - * They can be used in view templates, controllers or services.Angular comes + * They can be used in view templates, controllers or services. AngularJS comes * with a collection of [built-in filters](api/ng/filter), but it is easy to * define your own as well. * @@ -20373,7 +22327,7 @@ return /******/ (function(modules) { // webpackBootstrap * the keys are the filter names and the values are the filter factories. * *
- * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. + * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`. * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * (`myapp_subsection_filterx`). @@ -20435,6 +22389,9 @@ return /******/ (function(modules) { // webpackBootstrap * Selects a subset of items from `array` and returns it as a new array. * * @param {Array} array The source array. + *
+ * **Note**: If the array contains objects that reference themselves, filtering is not possible. + *
* @param {string|Object|function()} expression The predicate to be used for selecting items from * `array`. * @@ -20468,8 +22425,9 @@ return /******/ (function(modules) { // webpackBootstrap * The final result is an array of those elements that the predicate returned true for. * * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in - * determining if the expected value (from the filter expression) and actual value (from - * the object in the array) should be considered a match. + * determining if values retrieved using `expression` (when it is not a function) should be + * considered a match based on the expected value (from the filter expression) and actual + * value (from the object in the array). * * Can be one of: * @@ -20652,7 +22610,10 @@ return /******/ (function(modules) { // webpackBootstrap var key; if (matchAgainstAnyProp) { for (key in actual) { - if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { + // Under certain, rare, circumstances, key may not be a string and `charAt` will be undefined + // See: https://github.com/angular/angular.js/issues/15644 + if (key.charAt && (key.charAt(0) !== '$') && + deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { return true; } } @@ -20754,11 +22715,14 @@ return /******/ (function(modules) { // webpackBootstrap fractionSize = formats.PATTERNS[1].maxFrac; } + // If the currency symbol is empty, trim whitespace around the symbol + var currencySymbolRe = !currencySymbol ? /\s*\u00A4\s*/g : /\u00A4/g; + // if null or undefined pass it through return (amount == null) ? amount : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). - replace(/\u00A4/g, currencySymbol); + replace(currencySymbolRe, currencySymbol); }; } @@ -21161,7 +23125,7 @@ return /******/ (function(modules) { // webpackBootstrap GGGG: longEraGetter }; - var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, + var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/, NUMBER_STRING = /^-?\d+$/; /** @@ -21220,6 +23184,8 @@ return /******/ (function(modules) { // webpackBootstrap * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence * (e.g. `"h 'o''clock'"`). * + * Any other characters in the `format` string will be output as-is. + * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is @@ -21382,6 +23348,9 @@ return /******/ (function(modules) { // webpackBootstrap * @kind function * @description * Converts string to lowercase. + * + * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example. + * * @see angular.lowercase */ var lowercaseFilter = valueFn(lowercase); @@ -21393,7 +23362,23 @@ return /******/ (function(modules) { // webpackBootstrap * @kind function * @description * Converts string to uppercase. - * @see angular.uppercase + * @example + + + +
+ +

{{title}}

+ +

{{title | uppercase}}

+
+
+
*/ var uppercaseFilter = valueFn(uppercase); @@ -21569,6 +23554,7 @@ return /******/ (function(modules) { // webpackBootstrap * index: ... * } * ``` + * **Note:** `null` values use `'null'` as their type. * 2. The comparator function is used to sort the items, based on the derived values, types and * indices. * @@ -21582,6 +23568,9 @@ return /******/ (function(modules) { // webpackBootstrap * dummy predicate that returns the item's index as `value`. * (If you are using a custom comparator, make sure it can handle this predicate as well.) * + * If a custom comparator still can't distinguish between two items, then they will be sorted based + * on their index using the built-in comparator. + * * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted * value for an item, `orderBy` will try to convert that object to a primitive value, before passing * it to the comparator. The following rules govern the conversion: @@ -21600,11 +23589,15 @@ return /******/ (function(modules) { // webpackBootstrap * * The default, built-in comparator should be sufficient for most usecases. In short, it compares * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to - * using their index in the original collection, and sorts values of different types by type. + * using their index in the original collection, sorts values of different types by type and puts + * `undefined` and `null` values at the end of the sorted list. * * More specifically, it follows these steps to determine the relative order of items: * - * 1. If the compared values are of different types, compare the types themselves alphabetically. + * 1. If the compared values are of different types: + * - If one of the values is undefined, consider it "greater than" the other. + * - Else if one of the values is null, consider it "greater than" the other. + * - Else compare the types themselves alphabetically. * 2. If both values are of type `string`, compare them alphabetically in a case- and * locale-insensitive way. * 3. If both values are objects, compare their indices instead. @@ -21615,9 +23608,10 @@ return /******/ (function(modules) { // webpackBootstrap * * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being * saved as numbers and not strings. - * **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e. - * `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to - * other values. + * **Note:** For the purpose of sorting, `null` and `undefined` are considered "greater than" + * any other value (with undefined "greater than" null). This effectively means that `null` + * and `undefined` values end up at the end of a list sorted in ascending order. + * **Note:** `null` values use `'null'` as their type to be able to distinguish them from objects. * * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort. * @param {(Function|string|Array.)=} expression - A predicate (or list of @@ -21627,7 +23621,7 @@ return /******/ (function(modules) { // webpackBootstrap * * - `Function`: A getter function. This function will be called with each item as argument and * the return value will be used for sorting. - * - `string`: An Angular expression. This expression will be evaluated against each item and the + * - `string`: An AngularJS expression. This expression will be evaluated against each item and the * result will be used for sorting. For example, use `'label'` to sort by a property called * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label` * property.
@@ -22128,7 +24122,7 @@ return /******/ (function(modules) { // webpackBootstrap } } - return compare(v1.tieBreaker, v2.tieBreaker) * descending; + return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending; } }; @@ -22184,8 +24178,7 @@ return /******/ (function(modules) { // webpackBootstrap function getPredicateValue(value, index) { var type = typeof value; if (value === null) { - type = 'string'; - value = 'null'; + type = 'null'; } else if (type === 'object') { value = objectValue(value); } @@ -22216,7 +24209,11 @@ return /******/ (function(modules) { // webpackBootstrap result = value1 < value2 ? -1 : 1; } } else { - result = type1 < type2 ? -1 : 1; + result = (type1 === 'undefined') ? 1 : + (type2 === 'undefined') ? -1 : + (type1 === 'null') ? 1 : + (type2 === 'null') ? -1 : + (type1 < type2) ? -1 : 1; } return result; @@ -22273,10 +24270,10 @@ return /******/ (function(modules) { // webpackBootstrap * @priority 99 * * @description - * Using Angular markup like `{{hash}}` in an href attribute will + * Using AngularJS markup like `{{hash}}` in an href attribute will * make the link go to the wrong URL if the user clicks it before - * Angular has a chance to replace the `{{hash}}` markup with its - * value. Until Angular replaces the markup the link will be broken + * AngularJS has a chance to replace the `{{hash}}` markup with its + * value. Until AngularJS replaces the markup the link will be broken * and will most likely return a 404 error. The `ngHref` directive * solves this problem. * @@ -22324,7 +24321,7 @@ return /******/ (function(modules) { // webpackBootstrap element(by.id('link-3')).click(); - // At this point, we navigate away from an Angular page, so we need + // At this point, we navigate away from an AngularJS page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { @@ -22353,7 +24350,7 @@ return /******/ (function(modules) { // webpackBootstrap element(by.id('link-6')).click(); - // At this point, we navigate away from an Angular page, so we need + // At this point, we navigate away from an AngularJS page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { @@ -22372,9 +24369,9 @@ return /******/ (function(modules) { // webpackBootstrap * @priority 99 * * @description - * Using Angular markup like `{{hash}}` in a `src` attribute doesn't + * Using AngularJS markup like `{{hash}}` in a `src` attribute doesn't * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside + * text `{{hash}}` until AngularJS replaces the expression inside * `{{hash}}`. The `ngSrc` directive solves this problem. * * The buggy way to write it: @@ -22398,9 +24395,9 @@ return /******/ (function(modules) { // webpackBootstrap * @priority 99 * * @description - * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't + * Using AngularJS markup like `{{hash}}` in a `srcset` attribute doesn't * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside + * text `{{hash}}` until AngularJS replaces the expression inside * `{{hash}}`. The `ngSrcset` directive solves this problem. * * The buggy way to write it: @@ -22425,7 +24422,8 @@ return /******/ (function(modules) { // webpackBootstrap * * @description * - * This directive sets the `disabled` attribute on the element if the + * This directive sets the `disabled` attribute on the element (typically a form control, + * e.g. `input`, `button`, `select` etc.) if the * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. * * A special directive is necessary because we cannot use interpolation inside the `disabled` @@ -22446,7 +24444,6 @@ return /******/ (function(modules) { // webpackBootstrap * - * @element INPUT * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, * then the `disabled` attribute will be set on the element */ @@ -22470,14 +24467,14 @@ return /******/ (function(modules) { // webpackBootstrap * @example -
- +
+
it('should check both checkBoxes', function() { - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); - element(by.model('master')).click(); - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); + expect(element(by.id('checkFollower')).getAttribute('checked')).toBeFalsy(); + element(by.model('leader')).click(); + expect(element(by.id('checkFollower')).getAttribute('checked')).toBeTruthy(); });
@@ -22507,7 +24504,7 @@ return /******/ (function(modules) { // webpackBootstrap
- +
it('should toggle readonly attr', function() { @@ -22582,15 +24579,20 @@ return /******/ (function(modules) { // webpackBootstrap * * ## A note about browser compatibility * - * Edge, Firefox, and Internet Explorer do not support the `details` element, it is + * Internet Explorer and Edge do not support the `details` element, it is * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. * * @example -
+
- Show/Hide me + List +
    +
  • Apple
  • +
  • Orange
  • +
  • Durian
  • +
@@ -22668,7 +24670,7 @@ return /******/ (function(modules) { // webpackBootstrap // ng-src, ng-srcset, ng-href are interpolated forEach(['src', 'srcset', 'href'], function(attrName) { var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { + ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) { return { priority: 99, // it needs to run after the attributes are interpolated link: function(scope, element, attr) { @@ -22682,6 +24684,10 @@ return /******/ (function(modules) { // webpackBootstrap propName = null; } + // We need to sanitize the url at least once, in case it is a constant + // non-interpolated attribute. + attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized])); + attr.$observe(normalized, function(value) { if (!value) { if (attrName === 'href') { @@ -22696,24 +24702,26 @@ return /******/ (function(modules) { // webpackBootstrap // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need // to set the property as well to achieve the desired effect. - // We use attr[attrName] value since $set can sanitize the url. + // We use attr[attrName] value since $set might have sanitized the url. if (msie && propName) element.prop(propName, attr[name]); }); } }; - }; + }]; }); /* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS */ var nullFormCtrl = { $addControl: noop, + $getControls: valueFn([]), $$renameControl: nullFormRenameControl, $removeControl: noop, $setValidity: noop, $setDirty: noop, $setPristine: noop, - $setSubmitted: noop + $setSubmitted: noop, + $$setSubmitted: noop }, PENDING_CLASS = 'ng-pending', SUBMITTED_CLASS = 'ng-submitted'; @@ -22730,17 +24738,23 @@ return /******/ (function(modules) { // webpackBootstrap * @property {boolean} $dirty True if user has already interacted with the form. * @property {boolean} $valid True if all of the containing forms and controls are valid. * @property {boolean} $invalid True if at least one containing control or form is invalid. - * @property {boolean} $pending True if at least one containing control or form is pending. * @property {boolean} $submitted True if user has submitted the form even if its invalid. * - * @property {Object} $error Is an object hash, containing references to controls or - * forms with failing validators, where: + * @property {Object} $pending An object hash, containing references to controls or forms with + * pending validators, where: + * + * - keys are validations tokens (error names). + * - values are arrays of controls or forms that have a pending validator for the given error name. + * + * See {@link form.FormController#$error $error} for a list of built-in validation tokens. + * + * @property {Object} $error An object hash, containing references to controls or forms with failing + * validators, where: * * - keys are validation tokens (error names), - * - values are arrays of controls or forms that have a failing validator for given error name. + * - values are arrays of controls or forms that have a failing validator for the given error name. * * Built-in validation tokens: - * * - `email` * - `max` * - `maxlength` @@ -22856,6 +24870,30 @@ return /******/ (function(modules) { // webpackBootstrap control.$$parentForm = this; }, + /** + * @ngdoc method + * @name form.FormController#$getControls + * @returns {Array} the controls that are currently part of this form + * + * @description + * This method returns a **shallow copy** of the controls that are currently part of this form. + * The controls can be instances of {@link form.FormController `FormController`} + * ({@link ngForm "child-forms"}) and of {@link ngModel.NgModelController `NgModelController`}. + * If you need access to the controls of child-forms, you have to call `$getControls()` + * recursively on them. + * This can be used for example to iterate over all controls to validate them. + * + * The controls can be accessed normally, but adding to, or removing controls from the array has + * no effect on the form. Instead, use {@link form.FormController#$addControl `$addControl()`} and + * {@link form.FormController#$removeControl `$removeControl()`} for this use-case. + * Likewise, adding a control to, or removing a control from the form is not reflected + * in the shallow copy. That means you should get a fresh copy from `$getControls()` every time + * you need access to the controls. + */ + $getControls: function() { + return shallowCopy(this.$$controls); + }, + // Private API: rename a form control $$renameControl: function(control, newName) { var oldName = control.$name; @@ -22972,12 +25010,25 @@ return /******/ (function(modules) { // webpackBootstrap * @name form.FormController#$setSubmitted * * @description - * Sets the form to its submitted state. + * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and + * parent forms of the form. */ $setSubmitted: function() { + var rootForm = this; + while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) { + rootForm = rootForm.$$parentForm; + } + rootForm.$$setSubmitted(); + }, + + $$setSubmitted: function() { this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); this.$submitted = true; - this.$$parentForm.$setSubmitted(); + forEach(this.$$controls, function(control) { + if (control.$$setSubmitted) { + control.$$setSubmitted(); + } + }); } }; @@ -22986,9 +25037,24 @@ return /******/ (function(modules) { // webpackBootstrap * @name form.FormController#$setValidity * * @description - * Sets the validity of a form control. - * - * This method will also propagate to parent forms. + * Change the validity state of the form, and notify the parent form (if any). + * + * Application developers will rarely need to call this method directly. It is used internally, by + * {@link ngModel.NgModelController#$setValidity NgModelController.$setValidity()}, to propagate a + * control's validity state to the parent `FormController`. + * + * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be + * assigned to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` (for + * unfulfilled `$asyncValidators`), so that it is available for data-binding. The + * `validationErrorKey` should be in camelCase and will get converted into dash-case for + * class name. Example: `myError` will result in `ng-valid-my-error` and + * `ng-invalid-my-error` classes and can be bound to as `{{ someForm.$error.myError }}`. + * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending + * (undefined), or skipped (null). Pending is used for unfulfilled `$asyncValidators`. + * Skipped is used by AngularJS when validators do not run because of parse errors and when + * `$asyncValidators` do not run because any of the `$validators` failed. + * @param {NgModelController | FormController} controller - The controller whose validity state is + * triggering the change. */ addSetValidityMethod({ clazz: FormController, @@ -23021,16 +25087,21 @@ return /******/ (function(modules) { // webpackBootstrap * @restrict EAC * * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. + * Helper directive that makes it possible to create control groups inside a + * {@link ng.directive:form `form`} directive. + * These "child forms" can be used, for example, to determine the validity of a sub-group of + * controls. * - * Note: the purpose of `ngForm` is to group controls, - * but not to be a replacement for the `
` tag with all of its capabilities - * (e.g. posting to the server, ...). + *
+ * **Note**: `ngForm` cannot be used as a replacement for ``, because it lacks its + * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element). + * Specifically, you cannot submit `ngForm` like a `` tag. That means, + * you cannot send data to the server with `ngForm`, or integrate it with + * {@link ng.directive:ngSubmit `ngSubmit`}. + *
* - * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into - * related scope, under this name. + * @param {string=} ngForm|name Name of the form. If specified, the form controller will + * be published into the related scope, under this name. * */ @@ -23046,15 +25117,15 @@ return /******/ (function(modules) { // webpackBootstrap * If the `name` attribute is specified, the form controller is published onto the current scope under * this name. * - * # Alias: {@link ng.directive:ngForm `ngForm`} + * ## Alias: {@link ng.directive:ngForm `ngForm`} * - * In Angular, forms can be nested. This means that the outer form is valid when all of the child + * In AngularJS, forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `` elements, so - * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to + * AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group * of controls needs to be determined. * - * # CSS classes + * ## CSS classes * - `ng-valid` is set if the form is valid. * - `ng-invalid` is set if the form is invalid. * - `ng-pending` is set if the form is pending. @@ -23065,14 +25136,14 @@ return /******/ (function(modules) { // webpackBootstrap * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * - * # Submitting a form and preventing the default action + * ## Submitting a form and preventing the default action * - * Since the role of forms in client-side Angular applications is different than in classical + * Since the role of forms in client-side AngularJS applications is different than in classical * roundtrip apps, it is desirable for the browser not to translate the form submission into a full * page reload that sends the data to the server. Instead some javascript logic should be triggered * to handle the form submission in an application-specific way. * - * For this reason, Angular prevents the default action (form submission to the server) unless the + * For this reason, AngularJS prevents the default action (form submission to the server) unless the * `` element has an `action` attribute specified. * * You can use one of the following two ways to specify what javascript method should be called when @@ -23098,8 +25169,7 @@ return /******/ (function(modules) { // webpackBootstrap * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * - * ## Animation Hooks - * + * @animations * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any * other validations that are performed within the form. Animations in ngForm are similar to how @@ -23411,10 +25481,10 @@ return /******/ (function(modules) { // webpackBootstrap * @name input[text] * * @description - * Standard HTML text input with angular data binding, inherited by most of the `input` elements. + * Standard HTML text input with AngularJS data binding, inherited by most of the `input` elements. * * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Adds `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to @@ -23429,7 +25499,7 @@ return /******/ (function(modules) { // webpackBootstrap * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} - * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -23437,9 +25507,9 @@ return /******/ (function(modules) { // webpackBootstrap * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. * This parameter is ignored for input[type=password] controls, which will never trim the * input. * @@ -23513,13 +25583,13 @@ return /******/ (function(modules) { // webpackBootstrap * modern browsers do not yet support this input type, it is important to provide cues to users on the * expected input format via a placeholder or label. * - * The model must always be a Date object, otherwise Angular will throw an error. + * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute @@ -23537,7 +25607,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -23615,13 +25685,17 @@ return /******/ (function(modules) { // webpackBootstrap * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. * - * The model must always be a Date object, otherwise Angular will throw an error. + * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * The format of the displayed time can be adjusted with the + * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` + * and `timeStripZeroSeconds`. + * + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation @@ -23639,7 +25713,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -23718,13 +25792,18 @@ return /******/ (function(modules) { // webpackBootstrap * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. * - * The model must always be a Date object, otherwise Angular will throw an error. + * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using - * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. + * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions}. By default, + * this is the timezone of the browser. + * + * The format of the displayed time can be adjusted with the + * {@link ng.directive:ngModelOptions#ngModelOptions-arguments ngModelOptions} `timeSecondsFormat` + * and `timeStripZeroSeconds`. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this @@ -23742,7 +25821,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -23820,13 +25899,17 @@ return /******/ (function(modules) { // webpackBootstrap * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * week format (yyyy-W##), for example: `2013-W02`. * - * The model must always be a Date object, otherwise Angular will throw an error. + * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * + * The value of the resulting Date object will be set to Thursday at 00:00:00 of the requested week, + * due to ISO-8601 week numbering standards. Information on ISO's system for numbering the weeks of the + * year can be found at: https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this @@ -23844,7 +25927,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -23922,7 +26005,7 @@ return /******/ (function(modules) { // webpackBootstrap * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * month format (yyyy-MM), for example: `2009-01`. * - * The model must always be a Date object, otherwise Angular will throw an error. + * The model must always be a Date object, otherwise AngularJS will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * If the model is not set to the first of the month, the next view to model update will set it * to the first of the month. @@ -23930,7 +26013,7 @@ return /******/ (function(modules) { // webpackBootstrap * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this @@ -23949,7 +26032,7 @@ return /******/ (function(modules) { // webpackBootstrap * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -24027,12 +26110,16 @@ return /******/ (function(modules) { // webpackBootstrap * error if not a valid number. * *
- * The model must always be of type `number` otherwise Angular will throw an error. + * The model must always be of type `number` otherwise AngularJS will throw an error. * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} * error docs for more information and an example of how to convert your model if necessary. *
* - * ## Issues with HTML5 constraint validation + * + * + * @knownIssue + * + * ### HTML5 constraint validation and `allowInvalid` * * In browsers that follow the * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), @@ -24041,8 +26128,19 @@ return /******/ (function(modules) { // webpackBootstrap * which means the view / model values in `ngModel` and subsequently the scope value * will also be an empty string. * + * @knownIssue * - * @param {string} ngModel Assignable angular expression to data-bind to. + * ### Large numbers and `step` validation + * + * The `step` validation will not work correctly for very large numbers (e.g. 9999999999) due to + * Javascript's arithmetic limitations. If you need to handle large numbers, purpose-built + * libraries (e.g. https://github.com/MikeMcl/big.js/), can be included into AngularJS by + * {@link guide/forms#modifying-built-in-validators overwriting the validators} + * for `number` and / or `step`, or by {@link guide/forms#custom-validation applying custom validators} + * to an `input[text]` element. The source for `input[number]` type can be used as a starting + * point for both implementations. + * + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * Can be interpolated. @@ -24069,7 +26167,7 @@ return /******/ (function(modules) { // webpackBootstrap * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} - * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -24077,7 +26175,7 @@ return /******/ (function(modules) { // webpackBootstrap * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -24152,7 +26250,7 @@ return /******/ (function(modules) { // webpackBootstrap * the built-in validators (see the {@link guide/forms Forms guide}) *
* - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to @@ -24167,7 +26265,7 @@ return /******/ (function(modules) { // webpackBootstrap * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} - * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -24175,7 +26273,7 @@ return /******/ (function(modules) { // webpackBootstrap * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -24247,11 +26345,13 @@ return /******/ (function(modules) { // webpackBootstrap * *
* **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex - * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can - * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) + * used in Chromium, which may not fulfill your app's requirements. + * If you need stricter (e.g. requiring a top-level domain), or more relaxed validation + * (e.g. allowing IPv6 address literals) you can use `ng-pattern` or + * modify the built-in validators (see the {@link guide/forms Forms guide}). *
* - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to @@ -24266,7 +26366,7 @@ return /******/ (function(modules) { // webpackBootstrap * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} - * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -24274,7 +26374,7 @@ return /******/ (function(modules) { // webpackBootstrap * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -24342,14 +26442,41 @@ return /******/ (function(modules) { // webpackBootstrap * @description * HTML radio button. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * **Note:**
+ * All inputs controlled by {@link ngModel ngModel} (including those of type `radio`) will use the + * value of their `name` attribute to determine the property under which their + * {@link ngModel.NgModelController NgModelController} will be published on the parent + * {@link form.FormController FormController}. Thus, if you use the same `name` for multiple + * inputs of a form (e.g. a group of radio inputs), only _one_ `NgModelController` will be + * published on the parent `FormController` under that name. The rest of the controllers will + * continue to work as expected, but you won't be able to access them as properties on the parent + * `FormController`. + * + *
+ *

+ * In plain HTML forms, the `name` attribute is used to identify groups of radio inputs, so + * that the browser can manage their state (checked/unchecked) based on the state of other + * inputs in the same group. + *

+ *

+ * In AngularJS forms, this is not necessary. The input's state will be updated based on the + * value of the underlying model data. + *

+ *
+ * + *
+ * If you omit the `name` attribute on a radio input, `ngModel` will automatically assign it a + * unique name. + *
+ * + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string} value The value to which the `ngModel` expression should be set when selected. * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, * too. Use `ngValue` if you need complex models (`number`, `object`, ...). * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. - * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio + * @param {string} ngValue AngularJS expression to which `ngModel` will be be set when the radio * is selected. Should be used instead of the `value` attribute if you need * a non-string `ngModel` (`boolean`, `array`, ...). * @@ -24427,34 +26554,34 @@ return /******/ (function(modules) { // webpackBootstrap * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) * for more info. * - * This has the following consequences for Angular: + * This has the following consequences for AngularJS: * * Since the element value should always reflect the current model value, a range input * will set the bound ngModel expression to the value that the browser has set for the * input element. For example, in the following input ``, * if the application sets `model.value = null`, the browser will set the input to `'50'`. - * Angular will then set the model to `50`, to prevent input and model value being out of sync. + * AngularJS will then set the model to `50`, to prevent input and model value being out of sync. * * That means the model for range will immediately be set to `50` after `ngModel` has been * initialized. It also means a range input can never have the required error. * * This does not only affect changes to the model value, but also to the values of the `min`, * `max`, and `step` attributes. When these change in a way that will cause the browser to modify - * the input value, Angular will also update the model value. + * the input value, AngularJS will also update the model value. * * Automatic value adjustment also means that a range input element can never have the `required`, * `min`, or `max` errors. * * However, `step` is currently only fully implemented by Firefox. Other browsers have problems * when the step value changes dynamically - they do not adjust the element value correctly, but - * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step` + * instead may set the `stepMismatch` error. If that's the case, the AngularJS will set the `step` * error on the input, and set the model to `undefined`. * * Note that `input[range]` is not compatible with`ngMax`, `ngMin`, and `ngStep`, because they do * not set the `min` and `max` attributes, which means that the browser won't automatically adjust * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation to ensure that the value entered is greater * than `min`. Can be interpolated. @@ -24462,8 +26589,8 @@ return /******/ (function(modules) { // webpackBootstrap * Can be interpolated. * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` * Can be interpolated. - * @param {string=} ngChange Angular expression to be executed when the ngModel value changes due - * to user interaction with the input element. + * @param {expression=} ngChange AngularJS expression to be executed when the ngModel value changes due + * to user interaction with the input element. * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the * element. **Note** : `ngChecked` should not be used alongside `ngModel`. * Checkout {@link ng.directive:ngChecked ngChecked} for usage. @@ -24529,11 +26656,11 @@ return /******/ (function(modules) { // webpackBootstrap * @description * HTML checkbox. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {expression=} ngTrueValue The value to which the expression should be set when selected. * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. * * @example @@ -24610,6 +26737,16 @@ return /******/ (function(modules) { // webpackBootstrap composing = true; }); + // Support: IE9+ + element.on('compositionupdate', function(ev) { + // End composition when ev.data is empty string on 'compositionupdate' event. + // When the input de-focusses (e.g. by clicking away), IE triggers 'compositionupdate' + // instead of 'compositionend'. + if (isUndefined(ev.data) || ev.data === '') { + composing = false; + } + }); + element.on('compositionend', function() { composing = false; listener(); @@ -24668,9 +26805,9 @@ return /******/ (function(modules) { // webpackBootstrap deferListener(event, this, this.value); }); - // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + // if user modifies input value using context menu in IE, we need "paste", "cut" and "drop" events to catch it if ($sniffer.hasEvent('paste')) { - element.on('paste cut', deferListener); + element.on('paste cut drop', deferListener); } } @@ -24740,7 +26877,7 @@ return /******/ (function(modules) { // webpackBootstrap } function createDateParser(regexp, mapping) { - return function(iso, date) { + return function(iso, previousDate) { var parts, map; if (isDate(iso)) { @@ -24762,15 +26899,15 @@ return /******/ (function(modules) { // webpackBootstrap if (parts) { parts.shift(); - if (date) { + if (previousDate) { map = { - yyyy: date.getFullYear(), - MM: date.getMonth() + 1, - dd: date.getDate(), - HH: date.getHours(), - mm: date.getMinutes(), - ss: date.getSeconds(), - sss: date.getMilliseconds() / 1000 + yyyy: previousDate.getFullYear(), + MM: previousDate.getMonth() + 1, + dd: previousDate.getDate(), + HH: previousDate.getHours(), + mm: previousDate.getMinutes(), + ss: previousDate.getSeconds(), + sss: previousDate.getMilliseconds() / 1000 }; } else { map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; @@ -24781,7 +26918,15 @@ return /******/ (function(modules) { // webpackBootstrap map[mapping[index]] = +part; } }); - return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); + + var date = new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); + if (map.yyyy < 100) { + // In the constructor, 2-digit years map to 1900-1999. + // Use `setFullYear()` to set the correct year. + date.setFullYear(map.yyyy); + } + + return date; } } @@ -24790,25 +26935,24 @@ return /******/ (function(modules) { // webpackBootstrap } function createDateInputType(type, regexp, parseDate, format) { - return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { - badInputChecker(scope, element, attr, ctrl); + return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { + badInputChecker(scope, element, attr, ctrl, type); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var timezone = ctrl && ctrl.$options.getOption('timezone'); + + var isTimeType = type === 'time' || type === 'datetimelocal'; var previousDate; + var previousTimezone; - ctrl.$$parserName = type; ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; + if (regexp.test(value)) { // Note: We cannot read ctrl.$modelValue, as there might be a different // parser/formatter in the processing chain so that the model // contains some different data format! - var parsedDate = parseDate(value, previousDate); - if (timezone) { - parsedDate = convertTimezoneToLocal(parsedDate, timezone); - } - return parsedDate; + return parseDateAndConvertTimeZoneToLocal(value, previousDate); } + ctrl.$$parserName = type; return undefined; }); @@ -24818,35 +26962,50 @@ return /******/ (function(modules) { // webpackBootstrap } if (isValidDate(value)) { previousDate = value; - if (previousDate && timezone) { + var timezone = ctrl.$options.getOption('timezone'); + + if (timezone) { + previousTimezone = timezone; previousDate = convertTimezoneToLocal(previousDate, timezone, true); } - return $filter('date')(value, format, timezone); + + return formatter(value, timezone); } else { previousDate = null; + previousTimezone = null; return ''; } }); if (isDefined(attr.min) || attr.ngMin) { - var minVal; + var minVal = attr.min || $parse(attr.ngMin)(scope); + var parsedMinVal = parseObservedDateValue(minVal); + ctrl.$validators.min = function(value) { - return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; + return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal; }; attr.$observe('min', function(val) { - minVal = parseObservedDateValue(val); - ctrl.$validate(); + if (val !== minVal) { + parsedMinVal = parseObservedDateValue(val); + minVal = val; + ctrl.$validate(); + } }); } if (isDefined(attr.max) || attr.ngMax) { - var maxVal; + var maxVal = attr.max || $parse(attr.ngMax)(scope); + var parsedMaxVal = parseObservedDateValue(maxVal); + ctrl.$validators.max = function(value) { - return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; + return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal; }; attr.$observe('max', function(val) { - maxVal = parseObservedDateValue(val); - ctrl.$validate(); + if (val !== maxVal) { + parsedMaxVal = parseObservedDateValue(val); + maxVal = val; + ctrl.$validate(); + } }); } @@ -24856,27 +27015,68 @@ return /******/ (function(modules) { // webpackBootstrap } function parseObservedDateValue(val) { - return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; + return isDefined(val) && !isDate(val) ? parseDateAndConvertTimeZoneToLocal(val) || undefined : val; + } + + function parseDateAndConvertTimeZoneToLocal(value, previousDate) { + var timezone = ctrl.$options.getOption('timezone'); + + if (previousTimezone && previousTimezone !== timezone) { + // If the timezone has changed, adjust the previousDate to the default timezone + // so that the new date is converted with the correct timezone offset + previousDate = addDateMinutes(previousDate, timezoneToOffset(previousTimezone)); + } + + var parsedDate = parseDate(value, previousDate); + + if (!isNaN(parsedDate) && timezone) { + parsedDate = convertTimezoneToLocal(parsedDate, timezone); + } + return parsedDate; + } + + function formatter(value, timezone) { + var targetFormat = format; + + if (isTimeType && isString(ctrl.$options.getOption('timeSecondsFormat'))) { + targetFormat = format + .replace('ss.sss', ctrl.$options.getOption('timeSecondsFormat')) + .replace(/:$/, ''); + } + + var formatted = $filter('date')(value, targetFormat, timezone); + + if (isTimeType && ctrl.$options.getOption('timeStripZeroSeconds')) { + formatted = formatted.replace(/(?::00)?(?:\.000)?$/, ''); + } + + return formatted; } }; } - function badInputChecker(scope, element, attr, ctrl) { + function badInputChecker(scope, element, attr, ctrl, parserName) { var node = element[0]; var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); if (nativeValidation) { ctrl.$parsers.push(function(value) { var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; - return validity.badInput || validity.typeMismatch ? undefined : value; + if (validity.badInput || validity.typeMismatch) { + ctrl.$$parserName = parserName; + return undefined; + } + + return value; }); } } function numberFormatterParser(ctrl) { - ctrl.$$parserName = 'number'; ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; if (NUMBER_REGEXP.test(value)) return parseFloat(value); + + ctrl.$$parserName = 'number'; return undefined; }); @@ -24931,69 +27131,99 @@ return /******/ (function(modules) { // webpackBootstrap // and `viewValue` is expected to be a valid stringified number. var value = Number(viewValue); + var isNonIntegerValue = !isNumberInteger(value); + var isNonIntegerStepBase = !isNumberInteger(stepBase); + var isNonIntegerStep = !isNumberInteger(step); + // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. - if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) { - var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step)); + if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) { + var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0; + var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0; + var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0; + + var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals); var multiplier = Math.pow(10, decimalCount); value = value * multiplier; stepBase = stepBase * multiplier; step = step * multiplier; + + if (isNonIntegerValue) value = Math.round(value); + if (isNonIntegerStepBase) stepBase = Math.round(stepBase); + if (isNonIntegerStep) step = Math.round(step); } return (value - stepBase) % step === 0; } - function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - badInputChecker(scope, element, attr, ctrl); + function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { + badInputChecker(scope, element, attr, ctrl, 'number'); numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var minVal; - var maxVal; + var parsedMinVal; if (isDefined(attr.min) || attr.ngMin) { - ctrl.$validators.min = function(value) { - return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; + var minVal = attr.min || $parse(attr.ngMin)(scope); + parsedMinVal = parseNumberAttrVal(minVal); + + ctrl.$validators.min = function(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal; }; attr.$observe('min', function(val) { - minVal = parseNumberAttrVal(val); - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); + if (val !== minVal) { + parsedMinVal = parseNumberAttrVal(val); + minVal = val; + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } }); } if (isDefined(attr.max) || attr.ngMax) { - ctrl.$validators.max = function(value) { - return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; + var maxVal = attr.max || $parse(attr.ngMax)(scope); + var parsedMaxVal = parseNumberAttrVal(maxVal); + + ctrl.$validators.max = function(modelValue, viewValue) { + return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal; }; attr.$observe('max', function(val) { - maxVal = parseNumberAttrVal(val); - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); + if (val !== maxVal) { + parsedMaxVal = parseNumberAttrVal(val); + maxVal = val; + // TODO(matsko): implement validateLater to reduce number of validations + ctrl.$validate(); + } }); } if (isDefined(attr.step) || attr.ngStep) { - var stepVal; + var stepVal = attr.step || $parse(attr.ngStep)(scope); + var parsedStepVal = parseNumberAttrVal(stepVal); + ctrl.$validators.step = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || - isValidForStep(viewValue, minVal || 0, stepVal); + return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) || + isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal); }; attr.$observe('step', function(val) { - stepVal = parseNumberAttrVal(val); // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); + if (val !== stepVal) { + parsedStepVal = parseNumberAttrVal(val); + stepVal = val; + ctrl.$validate(); + } + }); + } } function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { - badInputChecker(scope, element, attr, ctrl); + badInputChecker(scope, element, attr, ctrl, 'range'); numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); @@ -25018,6 +27248,8 @@ return /******/ (function(modules) { // webpackBootstrap originalRender; if (hasMinAttr) { + minVal = parseNumberAttrVal(attr.min); + ctrl.$validators.min = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMinValidator() { return true; } : @@ -25030,6 +27262,8 @@ return /******/ (function(modules) { // webpackBootstrap } if (hasMaxAttr) { + maxVal = parseNumberAttrVal(attr.max); + ctrl.$validators.max = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMaxValidator() { return true; } : @@ -25042,6 +27276,8 @@ return /******/ (function(modules) { // webpackBootstrap } if (hasStepAttr) { + stepVal = parseNumberAttrVal(attr.step); + ctrl.$validators.step = supportsRange ? function nativeStepValidator() { // Currently, only FF implements the spec on step change correctly (i.e. adjusting the @@ -25063,7 +27299,13 @@ return /******/ (function(modules) { // webpackBootstrap // attribute value when the input is first rendered, so that the browser can adjust the // input value based on the min/max value element.attr(htmlAttrName, attr[htmlAttrName]); - attr.$observe(htmlAttrName, changeFn); + var oldVal = attr[htmlAttrName]; + attr.$observe(htmlAttrName, function wrappedObserver(val) { + if (val !== oldVal) { + oldVal = val; + changeFn(val); + } + }); } function minChange(val) { @@ -25117,11 +27359,11 @@ return /******/ (function(modules) { // webpackBootstrap } // Some browsers don't adjust the input value correctly, but set the stepMismatch error - if (supportsRange && ctrl.$viewValue !== element.val()) { - ctrl.$setViewValue(element.val()); - } else { + if (!supportsRange) { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); + } else if (ctrl.$viewValue !== element.val()) { + ctrl.$setViewValue(element.val()); } } } @@ -25132,7 +27374,6 @@ return /******/ (function(modules) { // webpackBootstrap baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); - ctrl.$$parserName = 'url'; ctrl.$validators.url = function(modelValue, viewValue) { var value = modelValue || viewValue; return ctrl.$isEmpty(value) || URL_REGEXP.test(value); @@ -25145,7 +27386,6 @@ return /******/ (function(modules) { // webpackBootstrap baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); - ctrl.$$parserName = 'email'; ctrl.$validators.email = function(modelValue, viewValue) { var value = modelValue || viewValue; return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); @@ -25170,7 +27410,7 @@ return /******/ (function(modules) { // webpackBootstrap } }; - element.on('click', listener); + element.on('change', listener); ctrl.$render = function() { var value = attr.value; @@ -25204,7 +27444,7 @@ return /******/ (function(modules) { // webpackBootstrap ctrl.$setViewValue(element[0].checked, ev && ev.type); }; - element.on('click', listener); + element.on('change', listener); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; @@ -25233,11 +27473,11 @@ return /******/ (function(modules) { // webpackBootstrap * @restrict E * * @description - * HTML textarea element control with angular data-binding. The data-binding and validation + * HTML textarea element control with AngularJS data-binding. The data-binding and validation * properties of this element are exactly the same as those of the * {@link ng.directive:input input element}. * - * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} ngModel Assignable AngularJS expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to @@ -25249,7 +27489,7 @@ return /******/ (function(modules) { // webpackBootstrap * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any * length. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} - * does not match a RegExp found by evaluating the Angular expression given in the attribute value. + * does not match a RegExp found by evaluating the AngularJS expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -25257,15 +27497,15 @@ return /******/ (function(modules) { // webpackBootstrap * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. - * @param {string=} ngChange Angular expression to be executed when input changes due to user + * @param {string=} ngChange AngularJS expression to be executed when input changes due to user * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * @param {boolean=} [ngTrim=true] If set to false AngularJS will not automatically trim the input. * * @knownIssue * * When specifying the `placeholder` attribute of ` @@ -27837,8 +30248,6 @@ return /******/ (function(modules) { // webpackBootstrap * * * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. */ var ngListDirective = function() { return { @@ -27909,36 +30318,60 @@ return /******/ (function(modules) { // webpackBootstrap /** * @ngdoc type * @name ngModel.NgModelController - * * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue * is set. + * * @property {*} $modelValue The value in the model that the control is bound to. + * * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. The functions are called in array order, each passing - its return value through to the next. The last return value is forwarded to the - {@link ngModel.NgModelController#$validators `$validators`} collection. + * the control updates the ngModelController with a new {@link ngModel.NgModelController#$viewValue + `$viewValue`} from the DOM, usually via user input. + See {@link ngModel.NgModelController#$setViewValue `$setViewValue()`} for a detailed lifecycle explanation. + Note that the `$parsers` are not called when the bound ngModel expression changes programmatically. + + The functions are called in array order, each passing + its return value through to the next. The last return value is forwarded to the + {@link ngModel.NgModelController#$validators `$validators`} collection. - Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue - `$viewValue`}. + Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue + `$viewValue`}. - Returning `undefined` from a parser means a parse error occurred. In that case, - no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` - will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} - is set to `true`. The parse error is stored in `ngModel.$error.parse`. + Returning `undefined` from a parser means a parse error occurred. In that case, + no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` + will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} + is set to `true`. The parse error is stored in `ngModel.$error.parse`. + + This simple example shows a parser that would convert text input value to lowercase: + * ```js + * function parse(value) { + * if (value) { + * return value.toLowerCase(); + * } + * } + * ngModelController.$parsers.push(parse); + * ``` * * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever - the model value changes. The functions are called in reverse array order, each passing the value through to the - next. The last return value is used as the actual DOM value. - Used to format / convert values for display in the control. + the bound ngModel expression changes programmatically. The `$formatters` are not called when the + value of the control is changed by user interaction. + + Formatters are used to format / convert the {@link ngModel.NgModelController#$modelValue + `$modelValue`} for display in the control. + + The functions are called in reverse array order, each passing the value through to the + next. The last return value is used as the actual DOM value. + + This simple example shows a formatter that would convert the model value to uppercase: + * ```js - * function formatter(value) { + * function format(value) { * if (value) { * return value.toUpperCase(); * } * } - * ngModel.$formatters.push(formatter); + * ngModel.$formatters.push(format); * ``` * * @property {Object.} $validators A collection of validators that are applied @@ -27985,8 +30418,10 @@ return /******/ (function(modules) { // webpackBootstrap * }; * ``` * - * @property {Array.} $viewChangeListeners Array of functions to execute whenever the - * view value has changed. It is called with no arguments, and its return value is ignored. + * @property {Array.} $viewChangeListeners Array of functions to execute whenever + * a change to {@link ngModel.NgModelController#$viewValue `$viewValue`} has caused a change + * to {@link ngModel.NgModelController#$modelValue `$modelValue`}. + * It is called with no arguments, and its return value is ignored. * This can be used in place of additional $watches against the model value. * * @property {Object} $error An object hash with all failing validator ids as keys. @@ -28008,7 +30443,7 @@ return /******/ (function(modules) { // webpackBootstrap * listening to DOM events. * Such DOM related logic should be provided by other directives which make use of * `NgModelController` for data-binding to control elements. - * Angular provides this DOM logic for most {@link input `input`} elements. + * AngularJS provides this DOM logic for most {@link input `input`} elements. * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. * @@ -28128,6 +30563,9 @@ return /******/ (function(modules) { // webpackBootstrap this.$name = $interpolate($attr.name || '', false)($scope); this.$$parentForm = nullFormCtrl; this.$options = defaultModelOptions; + this.$$updateEvents = ''; + // Attach the correct context to the event handler function for updateOn + this.$$updateEventHandler = this.$$updateEventHandler.bind(this); this.$$parsedNgModel = $parse($attr.ngModel); this.$$parsedNgModelAssign = this.$$parsedNgModel.assign; @@ -28135,10 +30573,12 @@ return /******/ (function(modules) { // webpackBootstrap this.$$ngModelSet = this.$$parsedNgModelAssign; this.$$pendingDebounce = null; this.$$parserValid = undefined; + this.$$parserName = 'parse'; this.$$currentValidationRunId = 0; this.$$scope = $scope; + this.$$rootScope = $scope.$root; this.$$attr = $attr; this.$$element = $element; this.$$animate = $animate; @@ -28322,13 +30762,14 @@ return /******/ (function(modules) { // webpackBootstrap * and reset the input to the last committed view value. * * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` - * programmatically before these debounced/future events have resolved/occurred, because Angular's + * programmatically before these debounced/future events have resolved/occurred, because AngularJS's * dirty checking mechanism is not able to tell whether the model has actually changed or not. * * The `$rollbackViewValue()` method should be called before programmatically changing the model of an * input which may have such events pending. This is important in order to make sure that the * input field will be updated with the new model value and any pending operations are cancelled. * + * @example * * * angular.module('cancel-update-example', []) @@ -28412,6 +30853,7 @@ return /******/ (function(modules) { // webpackBootstrap * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. */ $validate: function() { + // ignore $validate before model is initialized if (isNumberNaN(this.$modelValue)) { return; @@ -28464,7 +30906,8 @@ return /******/ (function(modules) { // webpackBootstrap processAsyncValidators(); function processParseErrors() { - var errorKey = that.$$parserName || 'parse'; + var errorKey = that.$$parserName; + if (isUndefined(that.$$parserValid)) { setValidity(errorKey, null); } else { @@ -28476,6 +30919,7 @@ return /******/ (function(modules) { // webpackBootstrap setValidity(name, null); }); } + // Set the parse error last, to prevent unsetting it, should a $validators key == parserName setValidity(errorKey, that.$$parserValid); return that.$$parserValid; @@ -28578,6 +31022,10 @@ return /******/ (function(modules) { // webpackBootstrap this.$$parserValid = isUndefined(modelValue) ? undefined : true; + // Reset any previous parse error + this.$setValidity(this.$$parserName, null); + this.$$parserName = 'parse'; + if (this.$$parserValid) { for (var i = 0; i < this.$parsers.length; i++) { modelValue = this.$parsers[i](modelValue); @@ -28646,9 +31094,10 @@ return /******/ (function(modules) { // webpackBootstrap * * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged - * value sent directly for processing, finally to be applied to `$modelValue` and then the - * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, - * in the `$viewChangeListeners` list, are called. + * value is sent directly for processing through the `$parsers` pipeline. After this, the `$validators` and + * `$asyncValidators` are called and the value is applied to `$modelValue`. + * Finally, the value is set to the **expression** specified in the `ng-model` attribute and + * all the registered change listeners, in the `$viewChangeListeners` list are called. * * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` * and the `default` trigger is not listed, all those actions will remain pending until one of the @@ -28694,8 +31143,12 @@ return /******/ (function(modules) { // webpackBootstrap if (isNumber(debounceDelay[trigger])) { debounceDelay = debounceDelay[trigger]; - } else if (isNumber(debounceDelay['default'])) { + } else if (isNumber(debounceDelay['default']) && + this.$options.getOption('updateOn').indexOf(trigger) === -1 + ) { debounceDelay = debounceDelay['default']; + } else if (isNumber(debounceDelay['*'])) { + debounceDelay = debounceDelay['*']; } this.$$timeout.cancel(this.$$pendingDebounce); @@ -28704,13 +31157,209 @@ return /******/ (function(modules) { // webpackBootstrap this.$$pendingDebounce = this.$$timeout(function() { that.$commitViewValue(); }, debounceDelay); - } else if (this.$$scope.$root.$$phase) { + } else if (this.$$rootScope.$$phase) { this.$commitViewValue(); } else { this.$$scope.$apply(function() { that.$commitViewValue(); }); } + }, + + /** + * @ngdoc method + * + * @name ngModel.NgModelController#$overrideModelOptions + * + * @description + * + * Override the current model options settings programmatically. + * + * The previous `ModelOptions` value will not be modified. Instead, a + * new `ModelOptions` object will inherit from the previous one overriding + * or inheriting settings that are defined in the given parameter. + * + * See {@link ngModelOptions} for information about what options can be specified + * and how model option inheritance works. + * + *
+ * **Note:** this function only affects the options set on the `ngModelController`, + * and not the options on the {@link ngModelOptions} directive from which they might have been + * obtained initially. + *
+ * + *
+ * **Note:** it is not possible to override the `getterSetter` option. + *
+ * + * @param {Object} options a hash of settings to override the previous options + * + */ + $overrideModelOptions: function(options) { + this.$options = this.$options.createChild(options); + this.$$setUpdateOnEvents(); + }, + + /** + * @ngdoc method + * + * @name ngModel.NgModelController#$processModelValue + + * @description + * + * Runs the model -> view pipeline on the current + * {@link ngModel.NgModelController#$modelValue $modelValue}. + * + * The following actions are performed by this method: + * + * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters} + * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue} + * - the `ng-empty` or `ng-not-empty` class is set on the element + * - if the `$viewValue` has changed: + * - {@link ngModel.NgModelController#$render $render} is called on the control + * - the {@link ngModel.NgModelController#$validators $validators} are run and + * the validation status is set. + * + * This method is called by ngModel internally when the bound scope value changes. + * Application developers usually do not have to call this function themselves. + * + * This function can be used when the `$viewValue` or the rendered DOM value are not correctly + * formatted and the `$modelValue` must be run through the `$formatters` again. + * + * @example + * Consider a text input with an autocomplete list (for fruit), where the items are + * objects with a name and an id. + * A user enters `ap` and then selects `Apricot` from the list. + * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`, + * but the rendered value will still be `ap`. + * The widget can then call `ctrl.$processModelValue()` to run the model -> view + * pipeline again, which formats the object to the string `Apricot`, + * then updates the `$viewValue`, and finally renders it in the DOM. + * + * + +
+
+ Search Fruit: + +
+
+ Model:
+
{{selectedFruit | json}}
+
+
+
+ + angular.module('inputExample', []) + .controller('inputController', function($scope) { + $scope.items = [ + {name: 'Apricot', id: 443}, + {name: 'Clementine', id: 972}, + {name: 'Durian', id: 169}, + {name: 'Jackfruit', id: 982}, + {name: 'Strawberry', id: 863} + ]; + }) + .component('basicAutocomplete', { + bindings: { + items: '<', + onSelect: '&' + }, + templateUrl: 'autocomplete.html', + controller: function($element, $scope) { + var that = this; + var ngModel; + + that.$postLink = function() { + ngModel = $element.find('input').controller('ngModel'); + + ngModel.$formatters.push(function(value) { + return (value && value.name) || value; + }); + + ngModel.$parsers.push(function(value) { + var match = value; + for (var i = 0; i < that.items.length; i++) { + if (that.items[i].name === value) { + match = that.items[i]; + break; + } + } + + return match; + }); + }; + + that.selectItem = function(item) { + ngModel.$setViewValue(item); + ngModel.$processModelValue(); + that.onSelect({item: item}); + }; + } + }); + + +
+ +
    +
  • + +
  • +
+
+
+ *
+ * + */ + $processModelValue: function() { + var viewValue = this.$$format(); + + if (this.$viewValue !== viewValue) { + this.$$updateEmptyClasses(viewValue); + this.$viewValue = this.$$lastCommittedViewValue = viewValue; + this.$render(); + // It is possible that model and view value have been updated during render + this.$$runValidators(this.$modelValue, this.$viewValue, noop); + } + }, + + /** + * This method is called internally to run the $formatters on the $modelValue + */ + $$format: function() { + var formatters = this.$formatters, + idx = formatters.length; + + var viewValue = this.$modelValue; + while (idx--) { + viewValue = formatters[idx](viewValue); + } + + return viewValue; + }, + + /** + * This method is called internally when the bound scope value changes. + */ + $$setModelValue: function(modelValue) { + this.$modelValue = this.$$rawModelValue = modelValue; + this.$$parserValid = undefined; + this.$processModelValue(); + }, + + $$setUpdateOnEvents: function() { + if (this.$$updateEvents) { + this.$$element.off(this.$$updateEvents, this.$$updateEventHandler); + } + + this.$$updateEvents = this.$options.getOption('updateOn'); + if (this.$$updateEvents) { + this.$$element.on(this.$$updateEvents, this.$$updateEventHandler); + } + }, + + $$updateEventHandler: function(ev) { + this.$$debounceViewValueCommit(ev && ev.type); } }; @@ -28723,34 +31372,18 @@ return /******/ (function(modules) { // webpackBootstrap // -> scope value did not change since the last digest as // ng-change executes in apply phase // 4. view should be changed back to 'a' - ctrl.$$scope.$watch(function ngModelWatch() { - var modelValue = ctrl.$$ngModelGet(ctrl.$$scope); + ctrl.$$scope.$watch(function ngModelWatch(scope) { + var modelValue = ctrl.$$ngModelGet(scope); // if scope model value and ngModel value are out of sync - // TODO(perf): why not move this to the action fn? + // This cannot be moved to the action function, because it would not catch the + // case where the model is changed in the ngChange function or the model setter if (modelValue !== ctrl.$modelValue && - // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator - // eslint-disable-next-line no-self-compare - (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) + // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator + // eslint-disable-next-line no-self-compare + (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) ) { - ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; - ctrl.$$parserValid = undefined; - - var formatters = ctrl.$formatters, - idx = formatters.length; - - var viewValue = modelValue; - while (idx--) { - viewValue = formatters[idx](viewValue); - } - if (ctrl.$viewValue !== viewValue) { - ctrl.$$updateEmptyClasses(viewValue); - ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; - ctrl.$render(); - - // It is possible that model and view value have been updated during render - ctrl.$$runValidators(ctrl.$modelValue, ctrl.$viewValue, noop); - } + ctrl.$$setModelValue(modelValue); } return modelValue; @@ -28773,10 +31406,10 @@ return /******/ (function(modules) { // webpackBootstrap * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. * The `validationErrorKey` should be in camelCase and will get converted into dash-case * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` - * class and can be bound to as `{{someForm.someControl.$error.myError}}` . + * classes and can be bound to as `{{ someForm.someControl.$error.myError }}`. * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. - * Skipped is used by Angular when validators do not run because of parse errors and + * Skipped is used by AngularJS when validators do not run because of parse errors and * when `$asyncValidators` do not run because any of the `$validators` failed. */ addSetValidityMethod({ @@ -28793,9 +31426,9 @@ return /******/ (function(modules) { // webpackBootstrap /** * @ngdoc directive * @name ngModel - * - * @element input + * @restrict A * @priority 1 + * @param {expression} ngModel assignable {@link guide/expression Expression} to bind to. * * @description * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a @@ -28837,7 +31470,7 @@ return /******/ (function(modules) { // webpackBootstrap * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * - * # Complex Models (objects or collections) + * ## Complex Models (objects or collections) * * By default, `ngModel` watches the model by reference, not value. This is important to know when * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the @@ -28853,7 +31486,7 @@ return /******/ (function(modules) { // webpackBootstrap * first level of the object (or only changing the properties of an item in the collection if it's an array) will still * not trigger a re-rendering of the model. * - * # CSS classes + * ## CSS classes * The following CSS classes are added and removed on the associated input/select/textarea element * depending on the validity of the model. * @@ -28872,8 +31505,7 @@ return /******/ (function(modules) { // webpackBootstrap * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * - * ## Animation Hooks - * + * @animations * Animations within models are triggered when any of the associated CSS classes are added and removed * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. @@ -28897,6 +31529,7 @@ return /******/ (function(modules) { // webpackBootstrap * * * @example + * ### Basic Usage *