From caa60677f792040796efb125473a327d912aa9b6 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Wed, 6 Sep 2017 15:41:54 +0200 Subject: [PATCH 1/2] Creation of the maintenance branche for Glances 2.x --- NEWS | 5 +++++ glances/__init__.py | 2 +- snap/snapcraft.yaml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 072a03ab..a1761154 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ Glances Version 2 ============================================================================== +Version 2.11.1 +============== + + * ... + Version 2.11 ============ diff --git a/glances/__init__.py b/glances/__init__.py index f9f3db5d..604a83a3 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -27,7 +27,7 @@ import signal import sys # Global name -__version__ = '2.11' +__version__ = '2.11.1_DEVELOP' __author__ = 'Nicolas Hennion ' __license__ = 'LGPLv3' diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 0f2393b8..83f7b14c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: glances -version: '2.11' +version: '2.11.1' summary: Glances an Eye on your system. A top/htop alternative. description: | Glances is a cross-platform monitoring tool which aims to present From 6cb56c932de75f713925adc4517eedccc757324d Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Wed, 6 Sep 2017 23:38:50 +0200 Subject: [PATCH 2/2] fix sensors plugin not displayed in web UI --- .../static/js/components/glances/view.html | 8 +- .../components/plugin-sensors/controller.js | 2 +- glances/outputs/static/public/js/main.min.js | 2 +- .../outputs/static/public/js/templates.min.js | 2 +- .../outputs/static/public/js/vendor.min.js | 1049 ++++++++++++----- 5 files changed, 790 insertions(+), 273 deletions(-) diff --git a/glances/outputs/static/js/components/glances/view.html b/glances/outputs/static/js/components/glances/view.html index e2cf716b..cfc71d44 100644 --- a/glances/outputs/static/js/components/glances/view.html +++ b/glances/outputs/static/js/components/glances/view.html @@ -65,10 +65,10 @@ - - - - + + + +
diff --git a/glances/outputs/static/js/components/plugin-sensors/controller.js b/glances/outputs/static/js/components/plugin-sensors/controller.js index 351a693a..121a42da 100644 --- a/glances/outputs/static/js/components/plugin-sensors/controller.js +++ b/glances/outputs/static/js/components/plugin-sensors/controller.js @@ -19,7 +19,7 @@ function GlancesPluginSensorsController($scope, GlancesStats, GlancesPluginHelpe return (_.isArray(sensor.value) && _.isEmpty(sensor.value)) || sensor.value === 0; }); - vm.sensors = data; + vm.sensors = stats; }; vm.getAlert = function (sensor) { diff --git a/glances/outputs/static/public/js/main.min.js b/glances/outputs/static/public/js/main.min.js index ab12f5b8..9e46b397 100644 --- a/glances/outputs/static/public/js/main.min.js +++ b/glances/outputs/static/public/js/main.min.js @@ -1845,7 +1845,7 @@ function GlancesPluginSensorsController($scope, GlancesStats, GlancesPluginHelpe return (_.isArray(sensor.value) && _.isEmpty(sensor.value)) || sensor.value === 0; }); - vm.sensors = data; + vm.sensors = stats; }; vm.getAlert = function (sensor) { diff --git a/glances/outputs/static/public/js/templates.min.js b/glances/outputs/static/public/js/templates.min.js index 070a18ca..cf69ad53 100644 --- a/glances/outputs/static/public/js/templates.min.js +++ b/glances/outputs/static/public/js/templates.min.js @@ -1,4 +1,4 @@ -angular.module('glancesApp').run(['$templateCache', function($templateCache) {$templateCache.put('components/glances/view.html','
\n
\n \n
Loading...
\n
\n\n \n\n
\n
\n
\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n\n
\n \n
\n \n
\n
\n \n
\n \n
\n \n
\n \n
\n \n
\n
\n \n
\n
\n
\n \n
\n \n \n \n
\n
\n
\n
\n
\n'); +angular.module('glancesApp').run(['$templateCache', function($templateCache) {$templateCache.put('components/glances/view.html','
\n
\n \n
Loading...
\n
\n\n \n\n
\n
\n
\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n\n
\n \n
\n \n
\n
\n \n
\n \n
\n \n
\n \n
\n \n
\n
\n \n
\n
\n
\n \n
\n \n \n \n
\n
\n
\n
\n\n'); $templateCache.put('components/help/view.html','
\n
\n
{{vm.help.version}} {{vm.help.psutil_version}}
\n
\n
 
\n
\n
{{vm.help.configuration_file}}
\n
\n
 
\n
\n
{{vm.help.sort_auto}}
\n
{{vm.help.sort_network}}
\n
\n
\n
{{vm.help.sort_cpu}}
\n
{{vm.help.show_hide_alert}}
\n
\n
\n
{{vm.help.sort_mem}}
\n
{{vm.help.percpu}}
\n
\n
\n
{{vm.help.sort_user}}
\n
{{vm.help.show_hide_ip}}
\n
\n
\n
{{vm.help.sort_proc}}
\n
{{vm.help.enable_disable_docker}}
\n
\n
\n
{{vm.help.sort_io}}
\n
{{vm.help.view_network_io_combination}}
\n
\n
\n
{{vm.help.sort_cpu_times}}
\n
{{vm.help.view_cumulative_network}}
\n
\n
\n
{{vm.help.show_hide_diskio}}
\n
{{vm.help.show_hide_filesytem_freespace}}
\n
\n
\n
{{vm.help.show_hide_filesystem}}
\n
{{vm.help.show_hide_vm.help}}
\n
\n
\n
{{vm.help.show_hide_network}}
\n
{{vm.help.diskio_iops}}
\n
\n
\n
{{vm.help.show_hide_sensors}}
\n
{{vm.help.show_hide_top_menu}}
\n
\n
\n
{{vm.help.show_hide_left_sidebar}}
\n
{{vm.help.show_hide_amp}}
\n
\n
\n
{{vm.help.enable_disable_process_stats}}
\n
{{vm.help.show_hide_irq}}
\n
\n
\n
{{vm.help.enable_disable_gpu}}
\n
{{vm.help.enable_disable_mean_gpu}}
\n
\n
\n
{{vm.help.enable_disable_quick_look}}
\n
\n
\n
\n
{{vm.help.enable_disable_short_processname}}
\n
\n
\n
\n
{{vm.help.enable_disable_ports}}
\n
\n
\n\n
\n'); $templateCache.put('components/plugin-alert/view.html','
\n No warning or critical alert detected\n Warning or critical alerts (lasts {{vm.count()}} entries)\n
\n
\n
\n
\n
\n {{alert.begin | date : \'yyyy-MM-dd H:mm:ss\'}} ({{ alert.ongoing ? \'ongoing\' : alert.duration }}) - {{alert.level}} on {{alert.name}}\n ({{alert.max}})\n
\n
\n
\n
\n'); $templateCache.put('components/plugin-amps/view.html','
\n
\n
\n
{{ process.name }}
\n
{{ process.count }}
\n
{{ process.result }}
\n
\n
\n
\n'); diff --git a/glances/outputs/static/public/js/vendor.min.js b/glances/outputs/static/public/js/vendor.min.js index 7ac0fa40..8b58ec86 100644 --- a/glances/outputs/static/public/js/vendor.min.js +++ b/glances/outputs/static/public/js/vendor.min.js @@ -1,10 +1,60 @@ /** - * @license AngularJS v1.6.4 + * @license AngularJS v1.6.6 * (c) 2010-2017 Google, Inc. http://angularjs.org * License: MIT */ (function(window) {'use strict'; +/* exported + minErrConfig, + errorHandlingConfig, + isValidObjectMaxDepth +*/ + +var minErrConfig = { + objectMaxDepth: 5 +}; + +/** + * @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 + */ +function errorHandlingConfig(config) { + if (isObject(config)) { + if (isDefined(config.objectMaxDepth)) { + minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; + } + } else { + return minErrConfig; + } +} + +/** + * @private + * @param {Number} maxDepth + * @return {boolean} + */ +function isValidObjectMaxDepth(maxDepth) { + return isNumber(maxDepth) && maxDepth > 0; +} + /** * @description * @@ -56,7 +106,7 @@ function minErr(module, ErrorConstructor) { return match; }); - message += '\nhttp://errors.angularjs.org/1.6.4/' + + message += '\nhttp://errors.angularjs.org/1.6.6/' + (module ? module + '/' : '') + code; for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { @@ -112,6 +162,7 @@ function minErr(module, ErrorConstructor) { isNumber, isNumberNaN, isDate, + isError, isArray, isFunction, isRegExp, @@ -196,50 +247,6 @@ var VALIDITY_STATE_PROPERTY = 'validity'; var hasOwnProperty = Object.prototype.hasOwnProperty; -var minErrConfig = { - objectMaxDepth: 5 -}; - - /** - * @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 - */ -function errorHandlingConfig(config) { - if (isObject(config)) { - if (isDefined(config.objectMaxDepth)) { - minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; - } - } else { - return minErrConfig; - } -} - -/** - * @private - * @param {Number} maxDepth - * @return {boolean} - */ -function isValidObjectMaxDepth(maxDepth) { - return isNumber(maxDepth) && maxDepth > 0; -} - /** * @ngdoc function * @name angular.lowercase @@ -547,6 +554,20 @@ function extend(dst) { * 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 [lodash's merge()](https://lodash.com/docs/4.17.4#merge) instead. +* +* @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}; +* * @param {Object} dst Destination object. * @param {...Object} src Source object(s). * @returns {Object} Reference to `dst`. @@ -756,6 +777,24 @@ function isDate(value) { */ var isArray = Array.isArray; +/** + * @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 * @name angular.isFunction @@ -1436,7 +1475,7 @@ function fromJson(json) { 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; @@ -1463,12 +1502,7 @@ function convertTimezoneToLocal(date, timezone, reverse) { * @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 */ } + element = jqLite(element).clone().empty(); var elemHtml = jqLite('
').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : @@ -1606,6 +1640,7 @@ function allowAutoBootstrap(document) { var script = document.currentScript; if (!script) { + // Support: IE 9-11 only // IE does not have `document.currentScript` return true; } @@ -2597,7 +2632,7 @@ function shallowCopy(src, dst) { return dst || src; } -/* global toDebugString: true */ +/* exported toDebugString */ function serializeObject(obj, maxDepth) { var seen = []; @@ -2606,7 +2641,9 @@ function serializeObject(obj, maxDepth) { // and a very deep object can cause a performance issue, so we copy the object // based on this specific depth and then stringify it. if (isValidObjectMaxDepth(maxDepth)) { - obj = copy(obj, null, maxDepth); + // This file is also included in `angular-loader`, so `copy()` might not always be available in + // the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed. + obj = angular.copy(obj, null, maxDepth); } return JSON.stringify(obj, function(key, val) { val = toJsonReplacer(key, val); @@ -2747,11 +2784,11 @@ function toDebugString(obj, maxDepth) { var version = { // These placeholder strings will be replaced by grunt's `build` task. // They need to be double- or single-quoted. - full: '1.6.4', + full: '1.6.6', major: 1, minor: 6, - dot: 4, - codeName: 'phenomenal-footnote' + dot: 6, + codeName: 'interdimensional-cable' }; @@ -2897,7 +2934,7 @@ function publishExternalAPI(angular) { }); } ]) - .info({ angularVersion: '1.6.4' }); + .info({ angularVersion: '1.6.6' }); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -5486,6 +5523,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() { var $AnimateProvider = ['$provide', /** @this */ function($provide) { var provider = this; var classNameFilter = null; + var customFilter = null; this.$$registeredAnimations = Object.create(null); @@ -5538,6 +5576,51 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { $provide.factory(key, factory); }; + /** + * @ngdoc method + * @name $animateProvider#customFilter + * + * @description + * Sets and/or returns the custom filter function that is used to "filter" animations, i.e. + * determine if an animation is allowed or not. When no filter is specified (the default), no + * animation will be blocked. Setting the `customFilter` value will only allow animations for + * which the filter function's return value is truthy. + * + * This allows to easily create arbitrarily complex rules for filtering animations, such as + * allowing specific events only, or enabling animations on specific subtrees of the DOM, etc. + * Filtering animations can also boost performance for low-powered devices, as well as + * applications containing a lot of structural operations. + * + *
+ * **Best Practice:** + * Keep the filtering function as lean as possible, because it will be called for each DOM + * action (e.g. insertion, removal, class change) performed by "animation-aware" directives. + * See {@link guide/animations#which-directives-support-animations- here} for a list of built-in + * directives that support animations. + * Performing computationally expensive or time-consuming operations on each call of the + * filtering function can make your animations sluggish. + *
+ * + * **Note:** If present, `customFilter` will be checked before + * {@link $animateProvider#classNameFilter classNameFilter}. + * + * @param {Function=} filterFn - The filter function which will be used to filter all animations. + * If a falsy value is returned, no animation will be performed. The function will be called + * with the following arguments: + * - **node** `{DOMElement}` - The DOM element to be animated. + * - **event** `{String}` - The name of the animation event (e.g. `enter`, `leave`, `addClass` + * etc). + * - **options** `{Object}` - A collection of options/styles used for the animation. + * @return {Function} The current filter function or `null` if there is none set. + */ + this.customFilter = function(filterFn) { + if (arguments.length === 1) { + customFilter = isFunction(filterFn) ? filterFn : null; + } + + return customFilter; + }; + /** * @ngdoc method * @name $animateProvider#classNameFilter @@ -5549,6 +5632,11 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * When setting the `classNameFilter` value, animations will only be performed on elements * that successfully match the filter expression. This in turn can boost performance * for low-powered devices as well as applications containing a lot of structural operations. + * + * **Note:** If present, `classNameFilter` will be checked after + * {@link $animateProvider#customFilter customFilter}. If `customFilter` is present and returns + * false, `classNameFilter` will not be checked. + * * @param {RegExp=} expression The className expression which will be checked against all animations * @return {RegExp} The current CSS className expression value. If null then there is no expression value */ @@ -8138,7 +8226,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @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): @@ -8221,6 +8310,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * 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) { @@ -8402,6 +8496,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return preAssignBindingsEnabled; }; + /** + * @ngdoc method + * @name $compileProvider#strictComponentBindingsEnabled + * + * @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just 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 strict component bindings check. If enabled, the compiler will enforce that + * for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided + * on the component's HTML tag. + * + * The default value is false. + */ + var strictComponentBindingsEnabled = false; + this.strictComponentBindingsEnabled = function(enabled) { + if (isDefined(enabled)) { + strictComponentBindingsEnabled = enabled; + return this; + } + return strictComponentBindingsEnabled; + }; var TTL = 10; /** @@ -10156,7 +10275,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } linkQueue = null; }).catch(function(error) { - if (error instanceof Error) { + if (isError(error)) { $exceptionHandler(error); } }); @@ -10429,12 +10548,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } + 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, @@ -10446,7 +10573,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { 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)) { @@ -10473,6 +10602,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { case '=': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; + strictBindingsCheck(attrName, directive.name); attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; @@ -10517,6 +10647,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { case '<': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; + strictBindingsCheck(attrName, directive.name); attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; @@ -10542,6 +10673,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { 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; @@ -11074,7 +11208,7 @@ function $HttpParamSerializerProvider() { 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))); @@ -11170,10 +11304,15 @@ function defaultHttpResponseTransform(data, headers) { if (tempData) { var contentType = headers('Content-Type'); - if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(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); } @@ -11299,12 +11438,6 @@ function $HttpProvider() { * {@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. @@ -11313,15 +11446,38 @@ function $HttpProvider() { * - **`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 = { @@ -11469,6 +11625,7 @@ function $HttpProvider() { * - **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 @@ -11585,15 +11742,18 @@ function $HttpProvider() { * * Angular 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 @@ -12307,9 +12467,9 @@ function $HttpProvider() { } 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 { @@ -12366,10 +12526,10 @@ function $HttpProvider() { * - 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); @@ -12377,7 +12537,7 @@ function $HttpProvider() { } function resolveHttpPromise() { - resolvePromise(response, status, headersString, statusText); + resolvePromise(response, status, headersString, statusText, xhrStatus); } if (useApplyAsync) { @@ -12392,7 +12552,7 @@ function $HttpProvider() { /** * 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; @@ -12401,12 +12561,13 @@ function $HttpProvider() { 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() { @@ -12507,7 +12668,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc 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 { @@ -12542,18 +12703,29 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc 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, '', '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.onabort = requestAborted; + xhr.ontimeout = requestTimeout; forEach(eventHandlers, function(value, key) { xhr.addEventListener(key, value); @@ -12603,14 +12775,14 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc } } - 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); } }; @@ -13230,7 +13402,7 @@ function $IntervalProvider() { interval.cancel = function(promise) { if (promise && promise.$$intervalId in intervals) { // Interval cancels should not report as unhandled promise. - intervals[promise.$$intervalId].promise.catch(noop); + markQExceptionHandled(intervals[promise.$$intervalId].promise); intervals[promise.$$intervalId].reject('canceled'); $window.clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; @@ -14372,6 +14544,14 @@ function $LocationProvider() { * * 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. * @@ -14493,7 +14673,7 @@ function $LogProvider() { }; function formatError(arg) { - if (arg instanceof Error) { + if (isError(arg)) { if (arg.stack && formatStackTrace) { arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ? 'Error: ' + arg.message + '\n' + arg.stack @@ -14507,29 +14687,17 @@ function $LogProvider() { function consoleLog(type) { var console = $window.console || {}, - logFn = console[type] || console.log || noop, - hasApply = false; + logFn = console[type] || console.log || noop; - // 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); - }; - } - - // 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); }; } }]; @@ -15157,15 +15325,47 @@ function isStateless($filter, filterName) { 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; @@ -15175,26 +15375,26 @@ function findConstantAndWatchExpressions(ast, $filter) { 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; @@ -15203,30 +15403,28 @@ function findConstantAndWatchExpressions(ast, $filter) { 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; @@ -15234,11 +15432,9 @@ function findConstantAndWatchExpressions(ast, $filter) { 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; @@ -15247,18 +15443,15 @@ function findConstantAndWatchExpressions(ast, $filter) { 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) { - findConstantAndWatchExpressions(property.key, $filter); - if (!property.key.constant) { - argsToWatch.push.apply(argsToWatch, property.key.toWatch); - } + //`{[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; ast.toWatch = argsToWatch; @@ -15338,7 +15531,7 @@ ASTCompiler.prototype = { 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'; @@ -15374,13 +15567,16 @@ ASTCompiler.prototype = { 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(''); }, @@ -15786,6 +15982,7 @@ ASTInterpreter.prototype = { inputs = []; forEach(toWatch, function(watch, key) { var input = self.recurse(watch); + input.isPure = watch.isPure; watch.input = input; inputs.push(input); watch.watchId = key; @@ -16300,8 +16497,8 @@ function $ParseProvider() { if (parsedExpression.constant) { parsedExpression.$$watchDelegate = constantWatchDelegate; } else if (oneTime) { - parsedExpression.oneTime = true; - parsedExpression.$$watchDelegate = oneTimeWatchDelegate; + parsedExpression.$$watchDelegate = parsedExpression.literal ? + oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; } else if (parsedExpression.inputs) { parsedExpression.$$watchDelegate = inputsWatchDelegate; } @@ -16352,7 +16549,7 @@ function $ParseProvider() { inputExpressions = inputExpressions[0]; return scope.$watch(function expressionInputWatch(scope) { var newInputValue = inputExpressions(scope); - if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, parsedExpression.literal)) { + if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) { lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); oldInputValueOf = newInputValue && getValueOf(newInputValue); } @@ -16372,7 +16569,7 @@ function $ParseProvider() { for (var i = 0, ii = inputExpressions.length; i < ii; i++) { var newInputValue = inputExpressions[i](scope); - if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], parsedExpression.literal))) { + if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) { oldInputValues[i] = newInputValue; oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); } @@ -16387,7 +16584,6 @@ function $ParseProvider() { } 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); @@ -16404,9 +16600,9 @@ function $ParseProvider() { if (isFunction(listener)) { listener(value, old, scope); } - if (isDone(value)) { + if (isDefined(value)) { scope.$$postDigest(function() { - if (isDone(lastValue)) { + if (isDefined(lastValue)) { unwatch(); } }); @@ -16414,12 +16610,31 @@ function $ParseProvider() { } } - function isAllDefined(value) { - var allDefined = true; - forEach(value, function(val) { - if (!isDefined(val)) allDefined = false; - }); - return allDefined; + 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(); + }); + } + }, objectEquality); + + return unwatch; + + function isAllDefined(value) { + var allDefined = true; + forEach(value, function(val) { + if (!isDefined(val)) allDefined = false; + }); + return allDefined; + } } function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { @@ -16435,39 +16650,43 @@ function $ParseProvider() { var watchDelegate = parsedExpression.$$watchDelegate; var useInputs = false; - var isDone = parsedExpression.literal ? isAllDefined : isDefined; + var regularWatch = + watchDelegate !== oneTimeLiteralWatchDelegate && + watchDelegate !== oneTimeWatchDelegate; - function regularInterceptedExpression(scope, locals, assign, inputs) { + var fn = regularWatch ? function regularInterceptedExpression(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 = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); + } : 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 isDone(value) ? result : value; - } + return isDefined(value) ? result : value; + }; - var fn = parsedExpression.oneTime ? oneTimeInterceptedExpression : regularInterceptedExpression; - - // Propogate the literal/oneTime attributes - fn.literal = parsedExpression.literal; - fn.oneTime = parsedExpression.oneTime; - - // Propagate or create inputs / $$watchDelegates + // Propagate $$watchDelegates other then inputsWatchDelegate useInputs = !parsedExpression.inputs; if (watchDelegate && watchDelegate !== inputsWatchDelegate) { fn.$$watchDelegate = 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 + // Treat interceptor like filters - assume non-stateful by default and use the inputsWatchDelegate fn.$$watchDelegate = inputsWatchDelegate; fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; } + if (fn.inputs) { + 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 + // potentially non-pure interceptor function. + if (e.isPure === PURITY_RELATIVE) { + return function depurifier(s) { return e(s); }; + } + return e; + }); + } + return fn; } }]; @@ -16753,7 +16972,7 @@ function $$QProvider() { * @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. */ @@ -16824,7 +17043,7 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { 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 { @@ -16851,10 +17070,10 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { // 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); @@ -16864,7 +17083,7 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { } 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); } @@ -17145,6 +17364,16 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { return $Q; } +function isStateExceptionHandled(state) { + return !!state.pur; +} +function markQStateExceptionHandled(state) { + state.pur = true; +} +function markQExceptionHandled(q) { + markQStateExceptionHandled(q.$$state); +} + /** @this */ function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { @@ -17625,6 +17854,12 @@ function $RootScopeProvider() { * values are examined for changes on every call to `$digest`. * - The `listener` is called whenever any expression in the `watchExpressions` array changes. * + * `$watchGroup` is more performant than watching each expression individually, and should be + * used when the listener does not need to know which expression has changed. + * If the listener needs to know which expression has changed, + * {@link ng.$rootScope.Scope#$watch $watch()} or + * {@link ng.$rootScope.Scope#$watchCollection $watchCollection()} should be used. + * * @param {Array.} watchExpressions Array of expressions that will be individually * watched using {@link ng.$rootScope.Scope#$watch $watch()} * @@ -17633,7 +17868,34 @@ function $RootScopeProvider() { * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching * those of `watchExpression` * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` + * those of `watchExpression`. + * + * Note that `newValues` and `oldValues` reflect the differences in each **individual** + * expression, and not the difference of the values between each call of the listener. + * That means the difference between `newValues` and `oldValues` cannot be used to determine + * which expression has changed / remained stable: + * + * ```js + * + * $scope.$watchGroup(['v1', 'v2'], function(newValues, oldValues) { + * console.log(newValues, oldValues); + * }); + * + * // newValues, oldValues initially + * // [undefined, undefined], [undefined, undefined] + * + * $scope.v1 = 'a'; + * $scope.v2 = 'a'; + * + * // ['a', 'a'], [undefined, undefined] + * + * $scope.v2 = 'b' + * + * // v1 hasn't changed since it became `'a'`, therefore its oldValue is still `undefined` + * // ['a', 'b'], [undefined, 'a'] + * + * ``` + * * The `scope` refers to the current scope. * @returns {function()} Returns a de-registration function for all listeners. */ @@ -20208,7 +20470,7 @@ function $TimeoutProvider() { timeout.cancel = function(promise) { if (promise && promise.$$timeoutId in deferreds) { // Timeout cancels should not report an unhandled promise. - deferreds[promise.$$timeoutId].promise.catch(noop); + markQExceptionHandled(deferreds[promise.$$timeoutId].promise); deferreds[promise.$$timeoutId].reject('canceled'); delete deferreds[promise.$$timeoutId]; return $browser.defer.cancel(promise.$$timeoutId); @@ -20643,7 +20905,7 @@ function $FilterProvider($provide) { * * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in * determining if values retrieved using `expression` (when it is not a function) should be - * considered a match based on the the expected value (from the filter expression) and actual + * 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: @@ -21562,6 +21824,9 @@ function jsonFilter() { * @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); @@ -21573,7 +21838,23 @@ var lowercaseFilter = valueFn(lowercase); * @kind function * @description * Converts string to uppercase. - * @see angular.uppercase + * @example + + + +
+ +

{{title}}

+ +

{{title | uppercase}}

+
+
+
*/ var uppercaseFilter = valueFn(uppercase); @@ -21762,6 +22043,9 @@ function sliceFn(input, begin, end) { * 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: @@ -22308,7 +22592,7 @@ function orderByFilter($parse) { } } - return compare(v1.tieBreaker, v2.tieBreaker) * descending; + return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending; } }; @@ -22763,15 +23047,20 @@ var htmlAnchorDirective = valueFn({ * * ## 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
  • +
@@ -22911,17 +23200,23 @@ function nullFormRenameControl(control, name) { * @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` @@ -23167,9 +23462,24 @@ FormController.prototype = { * @name form.FormController#$setValidity * * @description - * Sets the validity of a form control. + * Change the validity state of the form, and notify the parent form (if any). * - * This method will also propagate to parent forms. + * 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, @@ -26014,6 +26324,13 @@ function classDirective(name, selector) { return { restrict: 'AC', link: function(scope, element, attr) { + var expression = attr[name].trim(); + var isOneTime = (expression.charAt(0) === ':') && (expression.charAt(1) === ':'); + + var watchInterceptor = isOneTime ? toFlatValue : toClassString; + var watchExpression = $parse(expression, watchInterceptor); + var watchAction = isOneTime ? ngClassOneTimeWatchAction : ngClassWatchAction; + var classCounts = element.data('$classCounts'); var oldModulo = true; var oldClassString; @@ -26036,7 +26353,7 @@ function classDirective(name, selector) { scope.$watch(indexWatchExpression, ngClassIndexWatchAction); } - scope.$watch($parse(attr[name], toClassString), ngClassWatchAction); + scope.$watch(watchExpression, watchAction, isOneTime); function addClasses(classString) { classString = digestClassCounts(split(classString), 1); @@ -26078,9 +26395,9 @@ function classDirective(name, selector) { } function ngClassIndexWatchAction(newModulo) { - // This watch-action should run before the `ngClassWatchAction()`, thus it + // This watch-action should run before the `ngClass[OneTime]WatchAction()`, thus it // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the - // `ngClassWatchAction()` will update the classes. + // `ngClass[OneTime]WatchAction()` will update the classes. if (newModulo === selector) { addClasses(oldClassString); } else { @@ -26090,13 +26407,15 @@ function classDirective(name, selector) { oldModulo = newModulo; } - function ngClassWatchAction(newClassString) { - // When using a one-time binding the newClassString will return - // the pre-interceptor value until the one-time is complete - if (!isString(newClassString)) { - newClassString = toClassString(newClassString); - } + function ngClassOneTimeWatchAction(newClassValue) { + var newClassString = toClassString(newClassValue); + if (newClassString !== oldClassString) { + ngClassWatchAction(newClassString); + } + } + + function ngClassWatchAction(newClassString) { if (oldModulo === selector) { updateClasses(oldClassString, newClassString); } @@ -26143,6 +26462,34 @@ function classDirective(name, selector) { return classString; } + + function toFlatValue(classValue) { + var flatValue = classValue; + + if (isArray(classValue)) { + flatValue = classValue.map(toFlatValue); + } else if (isObject(classValue)) { + var hasUndefined = false; + + flatValue = Object.keys(classValue).filter(function(key) { + var value = classValue[key]; + + if (!hasUndefined && isUndefined(value)) { + hasUndefined = true; + } + + return value; + }); + + if (hasUndefined) { + // Prevent the `oneTimeLiteralWatchInterceptor` from unregistering + // the watcher, by including at least one `undefined` value. + flatValue.push(undefined); + } + } + + return flatValue; + } } /** @@ -28983,7 +29330,7 @@ function setupModelWatcher(ctrl) { * (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 @@ -30058,7 +30405,8 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, } - // we can't just jqLite(' + * + *
+ * Error: The current model doesn't match any option + * + *
+ * + *
+ * + * + * angular.module('staticSelect', []) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.selected = null; + * + * $scope.forceUnknownOption = function() { + * $scope.selected = 'nonsense'; + * }; + * }]) + * .directive('unknownValueError', function() { + * return { + * require: ['ngModel', 'select'], + * link: function(scope, element, attrs, ctrls) { + * var ngModelCtrl = ctrls[0]; + * var selectCtrl = ctrls[1]; + * + * ngModelCtrl.$validators.unknownValue = function(modelValue, viewValue) { + * if (selectCtrl.$isUnknownOptionSelected()) { + * return false; + * } + * + * return true; + * }; + * } + * + * }; + * }); + * + * + * + * + * @example + * ### Set the "required" error when the unknown option is selected. + * + * By default, the "required" error on the ngModelController is only set on a required select + * when the empty option is selected. This example adds a custom directive that also sets the + * error when the unknown option is selected. + * + * + * + *
+ *
+ *
+ *
+ * Error: Please select a value
+ * + *
+ *
+ *
+ *
+ * + * angular.module('staticSelect', []) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.selected = null; + * + * $scope.forceUnknownOption = function() { + * $scope.selected = 'nonsense'; + * }; + * }]) + * .directive('unknownValueRequired', function() { + * return { + * priority: 1, // This directive must run after the required directive has added its validator + * require: ['ngModel', 'select'], + * link: function(scope, element, attrs, ctrls) { + * var ngModelCtrl = ctrls[0]; + * var selectCtrl = ctrls[1]; + * + * var originalRequiredValidator = ngModelCtrl.$validators.required; + * + * ngModelCtrl.$validators.required = function() { + * if (attrs.required && selectCtrl.$isUnknownOptionSelected()) { + * return false; + * } + * + * return originalRequiredValidator.apply(this, arguments); + * }; + * } + * }; + * }); + * + *
+ * + * */ var SelectController = ['$element', '$scope', /** @this */ function($element, $scope) { @@ -32181,15 +32632,18 @@ var SelectController = // does not match any of the options. When it is rendered the value of the unknown // option is '? XXX ?' where XXX is the hashKey of the value that is not known. // + // Support: IE 9 only // We can't just jqLite('