diff --git a/angular.js b/angular.js index b4f38af34c..88116c9653 100644 --- a/angular.js +++ b/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.6.2-build.5241+sha.5b6763f + * @license AngularJS v1.5.11 * (c) 2010-2017 Google, Inc. http://angularjs.org * License: MIT */ @@ -57,7 +57,7 @@ function minErr(module, ErrorConstructor) { return match; }); - message += '\nhttp://errors.angularjs.org/1.6.2-build.5241+sha.5b6763f/' + + message += '\nhttp://errors.angularjs.org/1.5.11/' + (module ? module + '/' : '') + code; for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { @@ -157,7 +157,6 @@ function minErr(module, ErrorConstructor) { getBlockNodes, hasOwnProperty, createMap, - stringify, NODE_TYPE_ELEMENT, NODE_TYPE_ATTRIBUTE, @@ -191,41 +190,9 @@ var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; // This is used so that it's possible for internal tests to create mock ValidityStates. var VALIDITY_STATE_PROPERTY = 'validity'; - 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. - * - * @description Converts the specified string to lowercase. - * @param {string} string String to be converted to lowercase. - * @returns {string} Lowercased string. - */ 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. - * - * @description Converts the specified string to uppercase. - * @param {string} string String to be converted to uppercase. - * @returns {string} Uppercased string. - */ var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; @@ -270,7 +237,6 @@ var angularModule, uid = 0; -// Support: IE 9-11 only /** * documentMode is an IE-only property * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx @@ -346,7 +312,9 @@ function forEach(obj, iterator, context) { if (obj) { if (isFunction(obj)) { for (key in obj) { - if (key !== 'prototype' && key !== 'length' && key !== 'name' && obj.hasOwnProperty(key)) { + // Need to check if hasOwnProperty exists, + // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function + if (key !== 'prototype' && key !== 'length' && key !== 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { iterator.call(context, obj[key], key, obj); } } @@ -1381,7 +1349,6 @@ function fromJson(json) { var ALL_COLONS = /:/g; function timezoneToOffset(timezone, fallback) { - // Support: IE 9-11 only, Edge 13-14+ // 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; @@ -1517,7 +1484,7 @@ function encodeUriSegment(val) { * This method is intended for encoding *key* or *value* parts of query component. We need a custom * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) + * query = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * pct-encoded = "%" HEXDIG HEXDIG @@ -2065,27 +2032,6 @@ function createMap() { return Object.create(null); } -function stringify(value) { - if (value == null) { // null || undefined - return ''; - } - switch (typeof value) { - case 'string': - break; - case 'number': - value = '' + value; - break; - default: - if (hasCustomToString(value) && !isArray(value) && !isDate(value)) { - value = value.toString(); - } else { - value = toJson(value); - } - } - - return value; -} - var NODE_TYPE_ELEMENT = 1; var NODE_TYPE_ATTRIBUTE = 2; var NODE_TYPE_TEXT = 3; @@ -2296,7 +2242,7 @@ function setupModuleLoader(window) { * @description * See {@link auto.$provide#decorator $provide.decorator()}. */ - decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks), + decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), /** * @ngdoc method @@ -2442,11 +2388,10 @@ function setupModuleLoader(window) { * @param {string} method * @returns {angular.Module} */ - function invokeLaterAndSetModuleName(provider, method, queue) { - if (!queue) queue = invokeQueue; + function invokeLaterAndSetModuleName(provider, method) { return function(recipeName, factoryFunction) { if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; - queue.push([provider, method, arguments]); + invokeQueue.push([provider, method, arguments]); return moduleInstance; }; } @@ -2575,7 +2520,6 @@ function toDebugString(obj) { $ControllerProvider, $DateProvider, $DocumentProvider, - $$IsDocumentHiddenProvider, $ExceptionHandlerProvider, $FilterProvider, $$ForceReflowProvider, @@ -2627,11 +2571,11 @@ function toDebugString(obj) { var version = { // These placeholder strings will be replaced by grunt's `build` task. // They need to be double- or single-quoted. - full: '1.6.2-build.5241+sha.5b6763f', + full: '1.5.11', major: 1, - minor: 6, - dot: 2, - codeName: 'snapshot' + minor: 5, + dot: 11, + codeName: 'princely-quest' }; @@ -2664,12 +2608,9 @@ function publishExternalAPI(angular) { 'uppercase': uppercase, 'callbacks': {$$counter: 0}, 'getTestability': getTestability, - 'reloadWithDebugInfo': reloadWithDebugInfo, '$$minErr': minErr, '$$csp': csp, - '$$encodeUriSegment': encodeUriSegment, - '$$encodeUriQuery': encodeUriQuery, - '$$stringify': stringify + 'reloadWithDebugInfo': reloadWithDebugInfo }); angularModule = setupModuleLoader(window); @@ -2743,7 +2684,6 @@ function publishExternalAPI(angular) { $cacheFactory: $CacheFactoryProvider, $controller: $ControllerProvider, $document: $DocumentProvider, - $$isDocumentHidden: $$IsDocumentHiddenProvider, $exceptionHandler: $ExceptionHandlerProvider, $filter: $FilterProvider, $$forceReflow: $$ForceReflowProvider, @@ -2789,8 +2729,9 @@ function publishExternalAPI(angular) { * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* global - JQLitePrototype: true, +/* global JQLitePrototype: true, + addEventListenerFn: true, + removeEventListenerFn: true, BOOLEAN_ATTR: true, ALIASED_ATTR: true */ @@ -2834,7 +2775,7 @@ function publishExternalAPI(angular) { * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters - * - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData + * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData * - [`children()`](http://api.jquery.com/children/) - Does not support selectors * - [`clone()`](http://api.jquery.com/clone/) * - [`contents()`](http://api.jquery.com/contents/) @@ -2854,7 +2795,7 @@ function publishExternalAPI(angular) { * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors * - [`prepend()`](http://api.jquery.com/prepend/) * - [`prop()`](http://api.jquery.com/prop/) - * - [`ready()`](http://api.jquery.com/ready/) (_deprecated_, use `angular.element(callback)` instead of `angular.element(document).ready(callback)`) + * - [`ready()`](http://api.jquery.com/ready/) * - [`remove()`](http://api.jquery.com/remove/) * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument @@ -2863,7 +2804,7 @@ function publishExternalAPI(angular) { * - [`text()`](http://api.jquery.com/text/) * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers - * - [`unbind()`](http://api.jquery.com/unbind/) (_deprecated_, use [`off()`](http://api.jquery.com/off/)) - Does not support namespaces or event object as parameter + * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter * - [`val()`](http://api.jquery.com/val/) * - [`wrap()`](http://api.jquery.com/wrap/) * @@ -2901,7 +2842,13 @@ function publishExternalAPI(angular) { JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, - jqId = 1; + jqId = 1, + addEventListenerFn = function(element, type, fn) { + element.addEventListener(type, fn, false); + }, + removeEventListenerFn = function(element, type, fn) { + element.removeEventListener(type, fn, false); + }; /* * !!! This is an undocumented "private" function !!! @@ -2914,31 +2861,22 @@ JQLite._data = function(node) { function jqNextId() { return ++jqId; } -var DASH_LOWERCASE_REGEXP = /-([a-z])/g; -var MS_HACK_REGEXP = /^-ms-/; +var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g; +var MOZ_HACK_REGEXP = /^moz([A-Z])/; var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; var jqLiteMinErr = minErr('jqLite'); /** - * Converts kebab-case to camelCase. - * There is also a special case for the ms prefix starting with a lowercase letter. - * @param name Name to normalize - */ -function cssKebabToCamel(name) { - return kebabToCamel(name.replace(MS_HACK_REGEXP, 'ms-')); -} - -function fnCamelCaseReplace(all, letter) { - return letter.toUpperCase(); -} - -/** - * Converts kebab-case to camelCase. + * Converts snake_case to camelCase. + * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ -function kebabToCamel(name) { - return name - .replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace); +function camelCase(name) { + return name. + replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }). + replace(MOZ_HACK_REGEXP, 'Moz$1'); } var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; @@ -3075,8 +3013,6 @@ function JQLite(element) { if (argIsString) { jqLiteAddNodes(this, jqLiteParseHTML(element)); - } else if (isFunction(element)) { - jqLiteReady(element); } else { jqLiteAddNodes(this, element); } @@ -3109,7 +3045,7 @@ function jqLiteOff(element, type, fn, unsupported) { if (!type) { for (type in events) { if (type !== '$destroy') { - element.removeEventListener(type, handle); + removeEventListenerFn(element, type, handle); } delete events[type]; } @@ -3121,7 +3057,7 @@ function jqLiteOff(element, type, fn, unsupported) { arrayRemove(listenerFns || [], fn); } if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { - element.removeEventListener(type, handle); + removeEventListenerFn(element, type, handle); delete events[type]; } }; @@ -3172,7 +3108,6 @@ function jqLiteExpandoStore(element, createIfNecessary) { function jqLiteData(element, key, value) { if (jqLiteAcceptsData(element)) { - var prop; var isSimpleSetter = isDefined(value); var isSimpleGetter = !isSimpleSetter && key && !isObject(key); @@ -3181,18 +3116,16 @@ function jqLiteData(element, key, value) { var data = expandoStore && expandoStore.data; if (isSimpleSetter) { // data('key', value) - data[kebabToCamel(key)] = value; + data[key] = value; } else { if (massGetter) { // data() return data; } else { if (isSimpleGetter) { // data('key') // don't force creation of expandoStore if it doesn't exist yet - return data && data[kebabToCamel(key)]; + return data && data[key]; } else { // mass-setter: data({key1: val1, key2: val2}) - for (prop in key) { - data[kebabToCamel(prop)] = key[prop]; - } + extend(data, key); } } } @@ -3311,32 +3244,29 @@ function jqLiteDocumentLoaded(action, win) { } } -function jqLiteReady(fn) { - function trigger() { - window.document.removeEventListener('DOMContentLoaded', trigger); - window.removeEventListener('load', trigger); - fn(); - } - - // check if document is already loaded - if (window.document.readyState === 'complete') { - window.setTimeout(fn); - } else { - // We can not use jqLite since we are not done loading and jQuery could be loaded later. - - // Works for modern browsers and IE9 - window.document.addEventListener('DOMContentLoaded', trigger); - - // Fallback to window.onload for others - window.addEventListener('load', trigger); - } -} - ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// var JQLitePrototype = JQLite.prototype = { - ready: jqLiteReady, + ready: function(fn) { + var fired = false; + + function trigger() { + if (fired) return; + fired = true; + fn(); + } + + // check if document is already loaded + if (window.document.readyState === 'complete') { + window.setTimeout(trigger); + } else { + this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 + // we can not use jqLite since we are not done loading and jQuery could be loaded later. + // eslint-disable-next-line new-cap + JQLite(window).on('load', trigger); // fallback to window.onload for others + } + }, toString: function() { var value = []; forEach(this, function(e) { value.push('' + e);}); @@ -3371,8 +3301,7 @@ var ALIASED_ATTR = { 'ngMaxlength': 'maxlength', 'ngMin': 'min', 'ngMax': 'max', - 'ngPattern': 'pattern', - 'ngStep': 'step' + 'ngPattern': 'pattern' }; function getBooleanAttrName(element, name) { @@ -3423,7 +3352,7 @@ forEach({ hasClass: jqLiteHasClass, css: function(element, name, value) { - name = cssKebabToCamel(name); + name = camelCase(name); if (isDefined(value)) { element.style[name] = value; @@ -3433,33 +3362,33 @@ forEach({ }, attr: function(element, name, value) { - var ret; var nodeType = element.nodeType; - if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT || - !element.getAttribute) { + if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { return; } - var lowercasedName = lowercase(name); - var isBooleanAttr = BOOLEAN_ATTR[lowercasedName]; - - if (isDefined(value)) { - // setter - - if (value === null || (value === false && isBooleanAttr)) { - element.removeAttribute(name); + if (BOOLEAN_ATTR[lowercasedName]) { + if (isDefined(value)) { + if (value) { + element[name] = true; + element.setAttribute(name, lowercasedName); + } else { + element[name] = false; + element.removeAttribute(lowercasedName); + } } else { - element.setAttribute(name, isBooleanAttr ? lowercasedName : value); - } - } else { - // getter - - ret = element.getAttribute(name); - - if (isBooleanAttr && ret !== null) { - ret = lowercasedName; - } - // Normalize non-existing attributes to undefined (as jQuery). + return (element[name] || + (element.attributes.getNamedItem(name) || noop).specified) + ? lowercasedName + : undefined; + } + } else if (isDefined(value)) { + element.setAttribute(name, value); + } else if (element.getAttribute) { + // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code + // some elements (e.g. Document) don't have get attribute, so return undefined + var ret = element.getAttribute(name, 2); + // normalize non-existing attributes to undefined (as jQuery) return ret === null ? undefined : ret; } }, @@ -3494,7 +3423,7 @@ forEach({ result.push(option.value || option.text); } }); - return result; + return result.length === 0 ? null : result; } return element.value; } @@ -3664,7 +3593,7 @@ forEach({ eventFns = events[type] = []; eventFns.specialHandlerWrapper = specialHandlerWrapper; if (type !== '$destroy' && !noEventListener) { - element.addEventListener(type, handle); + addEventListenerFn(element, type, handle); } } @@ -4815,18 +4744,14 @@ function createInjector(modulesToLoad, strictDi) { } function isClass(func) { - // Support: IE 9-11 only // IE 9-11 do not support classes and IE9 leaks with the code below. - if (msie || typeof func !== 'function') { + if (msie <= 11) { return false; } - var result = func.$$ngIsClass; - if (!isBoolean(result)) { - // Support: Edge 12-13 only - // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/ - result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func)); - } - return result; + // Support: Edge 12-13 only + // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/ + return typeof func === 'function' + && /^(?:class\b|constructor\()/.test(stringifyFn(func)); } function invoke(fn, self, locals, serviceName) { @@ -5401,6 +5326,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { var reservedRegex = new RegExp('(\\s+|\\/)' + NG_ANIMATE_CLASSNAME + '(\\s+|\\/)'); if (reservedRegex.test(this.$$classNameFilter.toString())) { throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); + } } } @@ -5836,8 +5762,8 @@ var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { }; var $$AnimateRunnerFactoryProvider = /** @this */ function() { - this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout', - function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) { + this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', + function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { var INITIAL_STATE = 0; var DONE_PENDING_STATE = 1; @@ -5889,7 +5815,11 @@ var $$AnimateRunnerFactoryProvider = /** @this */ function() { this._doneCallbacks = []; this._tick = function(fn) { - if ($$isDocumentHidden()) { + var doc = $document[0]; + + // the document may not be ready or attached + // to the module for some internal tests + if (doc && doc.hidden) { timeoutTick(fn); } else { rafTick(fn); @@ -6163,6 +6093,7 @@ function Browser(window, document, $log, $sniffer) { }; cacheState(); + lastHistoryState = cachedState; /** * @name $browser#url @@ -6216,6 +6147,8 @@ function Browser(window, document, $log, $sniffer) { if ($sniffer.history && (!sameBase || !sameState)) { history[replace ? 'replaceState' : 'pushState'](state, '', url); cacheState(); + // Do the assignment again so that those two variables are referentially identical. + lastHistoryState = cachedState; } else { if (!sameBase) { pendingLocation = url; @@ -6264,7 +6197,8 @@ function Browser(window, document, $log, $sniffer) { function cacheStateAndFireUrlChange() { pendingLocation = null; - fireStateOrUrlChange(); + cacheState(); + fireUrlChange(); } // This variable should be used *only* inside the cacheState function. @@ -6278,16 +6212,11 @@ function Browser(window, document, $log, $sniffer) { if (equals(cachedState, lastCachedState)) { cachedState = lastCachedState; } - lastCachedState = cachedState; - lastHistoryState = cachedState; } - function fireStateOrUrlChange() { - var prevLastHistoryState = lastHistoryState; - cacheState(); - - if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) { + function fireUrlChange() { + if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { return; } @@ -6353,7 +6282,7 @@ function Browser(window, document, $log, $sniffer) { * Needs to be exported to be able to check for changes that have been done in sync, * as hashchange/popstate events fire in async. */ - self.$$checkUrlChange = fireStateOrUrlChange; + self.$$checkUrlChange = fireUrlChange; ////////////////////////////////////////////////////////////// // Misc API @@ -6963,8 +6892,7 @@ function $TemplateCacheProvider() { * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a - * component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will - * also be called when your bindings are initialized. + * component such as cloning the bound value to prevent accidental mutation of the outer value. * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on * changes. Any actions that you wish to take in response to the changes that you detect must be * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook @@ -8225,7 +8153,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * * The default value is true in Angular 1.5.x but will switch to false in Angular 1.6.x. */ - var preAssignBindingsEnabled = false; + var preAssignBindingsEnabled = true; this.preAssignBindingsEnabled = function(enabled) { if (isDefined(enabled)) { preAssignBindingsEnabled = enabled; @@ -8687,15 +8615,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // modify it. $compileNodes = jqLite($compileNodes); } + + var NOT_EMPTY = /\S+/; + + // We can not compile top level text elements since text nodes can be merged and we will + // not be able to attach scope data to them, so we will wrap them in + for (var i = 0, len = $compileNodes.length; i < len; i++) { + var domNode = $compileNodes[i]; + + if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) { + jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span')); + } + } + var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); compile.$$addScopeClass($compileNodes); var namespace = null; return function publicLinkFn(scope, cloneConnectFn, options) { - if (!$compileNodes) { - throw $compileMinErr('multilink', 'This element has already been linked.'); - } assertArg(scope, 'scope'); if (previousCompileContext && previousCompileContext.needsNewScope) { @@ -8750,10 +8688,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (cloneConnectFn) cloneConnectFn($linkNode, scope); if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); - - if (!cloneConnectFn) { - $compileNodes = compositeLinkFn = null; - } return $linkNode; }; } @@ -8786,23 +8720,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { var linkFns = [], - // `nodeList` can be either an element's `.childNodes` (live NodeList) - // or a jqLite/jQuery collection or an array - notLiveList = isArray(nodeList) || (nodeList instanceof jqLite), attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; - for (var i = 0; i < nodeList.length; i++) { attrs = new Attributes(); - // Support: IE 11 only - // Workaround for #11781 and #14924 - if (msie === 11) { - mergeConsecutiveTextNodes(nodeList, i, notLiveList); - } - - // We must always refer to `nodeList[i]` hereafter, - // since the nodes can be replaced underneath us. + // we must always refer to nodeList[i] since the nodes can be replaced underneath us. directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective); @@ -8893,32 +8816,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } - function mergeConsecutiveTextNodes(nodeList, idx, notLiveList) { - var node = nodeList[idx]; - var parent = node.parentNode; - var sibling; - - if (node.nodeType !== NODE_TYPE_TEXT) { - return; - } - - while (true) { - sibling = parent ? node.nextSibling : nodeList[idx + 1]; - if (!sibling || sibling.nodeType !== NODE_TYPE_TEXT) { - break; - } - - node.nodeValue = node.nodeValue + sibling.nodeValue; - - if (sibling.parentNode) { - sibling.parentNode.removeChild(sibling); - } - if (notLiveList && sibling === nodeList[idx + 1]) { - nodeList.splice(idx + 1, 1); - } - } - } - function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { @@ -8982,7 +8879,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { attr = nAttrs[j]; name = attr.name; - value = attr.value; + value = trim(attr.value); // support ngAttr attribute binding ngAttrName = directiveNormalize(name); @@ -9038,6 +8935,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } break; case NODE_TYPE_TEXT: /* Text Node */ + if (msie === 11) { + // Workaround for #11781 + while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { + node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; + node.parentNode.removeChild(node.nextSibling); + } + } addTextInterpolateDirective(directives, node.nodeValue); break; case NODE_TYPE_COMMENT: /* Comment */ @@ -9310,9 +9214,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var slots = createMap(); - if (!isObject(directiveValue)) { - $template = jqLite(jqLiteClone(compileNode)).contents(); - } else { + $template = jqLite(jqLiteClone(compileNode)).contents(); + + if (isObject(directiveValue)) { // We have transclusion slots, // collect them up, compile them and store their transclusion functions @@ -9864,11 +9768,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { forEach(dst, function(value, key) { if (key.charAt(0) !== '$') { if (src[key] && src[key] !== value) { - if (value.length) { - value += (key === 'style' ? ';' : ' ') + src[key]; - } else { - value = src[key]; - } + value += (key === 'style' ? ';' : ' ') + src[key]; } dst.$set(key, value, true, srcAttr[key]); } @@ -9987,11 +9887,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { childBoundTranscludeFn); } linkQueue = null; - }).catch(function(error) { - if (error instanceof Error) { - $exceptionHandler(error); - } - }).catch(noop); + }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { var childBoundTranscludeFn = boundTranscludeFn; @@ -10091,9 +9987,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } // maction[xlink:href] can source SVG. It's not limited to . } else if (attrNormalizedName === 'xlinkHref' || - (tag === 'form' && attrNormalizedName === 'action') || - // links can be stylesheets or imports, which can run script in the current origin - (tag === 'link' && attrNormalizedName === 'href') + (tag === 'form' && attrNormalizedName === 'action') ) { return $sce.RESOURCE_URL; } @@ -10116,12 +10010,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { 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.'); - } - directives.push({ priority: 100, compile: function() { @@ -10129,6 +10017,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { pre: function attrInterpolatePreLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = createMap())); + 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.'); + } + // If the attribute has changed since last $interpolate()ed var newValue = attr[name]; if (newValue !== value) { @@ -10437,16 +10331,12 @@ SimpleChange.prototype.isFirstChange = function() { return this.previousValue == var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; -var SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g; - /** * Converts all accepted directives format into proper directive name. * @param name Name to normalize */ function directiveNormalize(name) { - return name - .replace(PREFIX_REGEXP, '') - .replace(SPECIAL_CHARS_REGEXP, fnCamelCaseReplace); + return camelCase(name.replace(PREFIX_REGEXP, '')); } /** @@ -10601,12 +10491,13 @@ function $ControllerProvider() { /** * @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. + * + * @description If called, allows `$controller` to find controller constructors on `window` * */ this.allowGlobals = function() { globals = true; @@ -10627,7 +10518,7 @@ function $ControllerProvider() { * * 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) + * `window` object (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 @@ -10766,33 +10657,6 @@ function $DocumentProvider() { }]; } - -/** - * @private - * @this - * Listens for document visibility change and makes the current status accessible. - */ -function $$IsDocumentHiddenProvider() { - this.$get = ['$document', '$rootScope', function($document, $rootScope) { - var doc = $document[0]; - var hidden = doc && doc.hidden; - - $document.on('visibilitychange', changeListener); - - $rootScope.$on('$destroy', function() { - $document.off('visibilitychange', changeListener); - }); - - function changeListener() { - hidden = doc.hidden; - } - - return function() { - return hidden; - }; - }]; -} - /** * @ngdoc service * @name $exceptionHandler @@ -10877,6 +10741,11 @@ var JSON_ENDS = { }; var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/; var $httpMinErr = minErr('$http'); +var $httpMinErrLegacyFn = function(method) { + return function() { + throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); + }; +}; function serializeValue(v) { if (isObject(v)) { @@ -11149,10 +11018,6 @@ function $HttpProvider() { * 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'`. - * **/ var defaults = this.defaults = { // transform incoming response data @@ -11176,9 +11041,7 @@ function $HttpProvider() { xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', - paramSerializer: '$httpParamSerializer', - - jsonpCallbackParam: 'callback' + paramSerializer: '$httpParamSerializer' }; var useApplyAsync = false; @@ -11209,6 +11072,35 @@ function $HttpProvider() { return useApplyAsync; }; + var useLegacyPromise = true; + /** + * @ngdoc method + * @name $httpProvider#useLegacyPromiseExtensions + * @description + * + * @deprecated + * sinceVersion="v1.4.4" + * removeVersion="v1.6.0" + * This method will be removed in v1.6.0 along with the legacy promise methods. + * + * Configure `$http` service to return promises without the shorthand methods `success` and `error`. + * This should be used to make sure that applications work without these methods. + * + * Defaults to true. If no value is specified, returns the current configured value. + * + * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. + * + * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. + * otherwise, returns the current configured value. + **/ + this.useLegacyPromiseExtensions = function(value) { + if (isDefined(value)) { + useLegacyPromise = !!value; + return this; + } + return useLegacyPromise; + }; + /** * @ngdoc property * @name $httpProvider#interceptors @@ -11224,8 +11116,8 @@ function $HttpProvider() { **/ var interceptorFactories = this.interceptors = []; - this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce', - function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) { + this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', + function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { var defaultCache = $cacheFactory('$http'); @@ -11342,6 +11234,15 @@ function $HttpProvider() { * $httpBackend.flush(); * ``` * + * ## Deprecation Notice + *
+ * The `$http` legacy promise methods `success` and `error` have been deprecated and will be + * removed in v1.6.0. + * Use the standard `then` method instead. + * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to + * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. + *
+ * * ## Setting HTTP Headers * * The $http service will automatically add certain HTTP headers to all requests. These defaults @@ -11639,8 +11540,7 @@ function $HttpProvider() { * processed. The object has following properties: * * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) - * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested; - * or an object created by a call to `$sce.trustAsResourceUrl(url)`. + * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. * - **params** – `{Object.}` – Map of strings or objects which will be serialized * with the `paramSerializer` and appended as GET parameters. * - **data** – `{string|Object}` – Data to be sent as the request message data. @@ -11706,11 +11606,11 @@ function $HttpProvider() {
http status code: {{status}}
@@ -11719,13 +11619,6 @@ function $HttpProvider() { 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([ - 'self', - 'https://angularjs.org/**' - ]); - }]) .controller('FetchController', ['$scope', '$http', '$templateCache', function($scope, $http, $templateCache) { $scope.method = 'GET'; @@ -11793,16 +11686,15 @@ function $HttpProvider() { throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); } - if (!isString($sce.valueOf(requestConfig.url))) { - throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url); + if (!isString(requestConfig.url)) { + throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); } var config = extend({ method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse, - paramSerializer: defaults.paramSerializer, - jsonpCallbackParam: defaults.jsonpCallbackParam + paramSerializer: defaults.paramSerializer }, requestConfig); config.headers = mergeHeaders(requestConfig); @@ -11810,11 +11702,9 @@ function $HttpProvider() { config.paramSerializer = isString(config.paramSerializer) ? $injector.get(config.paramSerializer) : config.paramSerializer; - $browser.$$incOutstandingRequestCount(); - var requestInterceptors = []; var responseInterceptors = []; - var promise = $q.resolve(config); + var promise = $q.when(config); // apply interceptors forEach(reversedInterceptors, function(interceptor) { @@ -11829,7 +11719,29 @@ function $HttpProvider() { promise = chainInterceptors(promise, requestInterceptors); promise = promise.then(serverRequest); promise = chainInterceptors(promise, responseInterceptors); - promise = promise.finally(completeOutstandingRequest); + + if (useLegacyPromise) { + promise.success = function(fn) { + assertArgFn(fn, 'fn'); + + promise.then(function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function(fn) { + assertArgFn(fn, 'fn'); + + promise.then(null, function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + } else { + promise.success = $httpMinErrLegacyFn('success'); + promise.error = $httpMinErrLegacyFn('error'); + } return promise; @@ -11847,10 +11759,6 @@ function $HttpProvider() { return promise; } - function completeOutstandingRequest() { - $browser.$$completeOutstandingRequest(noop); - } - function executeHeaderFns(headers, config) { var headerContent, processedHeaders = {}; @@ -11934,8 +11842,7 @@ function $HttpProvider() { * @description * Shortcut method to perform `GET` request. * - * @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 {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -11947,8 +11854,7 @@ function $HttpProvider() { * @description * Shortcut method to perform `DELETE` request. * - * @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 {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -11960,8 +11866,7 @@ function $HttpProvider() { * @description * Shortcut method to perform `HEAD` request. * - * @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 {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -11972,34 +11877,11 @@ function $HttpProvider() { * * @description * Shortcut method to perform `JSONP` request. - * - * 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 - * by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}. - * - * 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. - * - * ``` - * $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'}) - * ``` - * - * You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`. - * Initially this is set to `'callback'`. - * - *
- * You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback - * parameter value should go. - *
- * - * If you would like to customise where and how the callbacks are stored then try overriding + * If you would like to customize where and how the callbacks are stored then try overriding * or decorating the {@link $jsonpCallbacks} service. * - * @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 {string} url Relative or absolute URL specifying the destination of the request. + * The name of the callback should be the string `JSON_CALLBACK`. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -12098,28 +11980,12 @@ function $HttpProvider() { cache, cachedResp, reqHeaders = config.headers, - isJsonp = lowercase(config.method) === 'jsonp', - url = config.url; - - if (isJsonp) { - // JSONP is a pretty sensitive operation where we're allowing a script to have full access to - // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL. - url = $sce.getTrustedResourceUrl(url); - } else if (!isString(url)) { - // If it is not a string then the URL must be a $sce trusted object - url = $sce.valueOf(url); - } - - url = buildUrl(url, config.paramSerializer(config.params)); - - if (isJsonp) { - // Check the url and add the JSONP callback placeholder - url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam); - } + url = buildUrl(config.url, config.paramSerializer(config.params)); $http.pendingRequests.push(config); promise.then(removePendingReq, removePendingReq); + if ((config.cache || defaults.cache) && config.cache !== false && (config.method === 'GET' || config.method === 'JSONP')) { cache = isObject(config.cache) ? config.cache @@ -12251,24 +12117,6 @@ function $HttpProvider() { } 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); - } - - // Add in the JSON_CALLBACK callback param value - url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK'; - - return url; - } }]; } @@ -12329,6 +12177,7 @@ function $HttpBackendProvider() { function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { // TODO(vojta): fix the signature return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { + $browser.$$incOutstandingRequestCount(); url = url || $browser.url(); if (lowercase(method) === 'jsonp') { @@ -12440,6 +12289,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc jsonpDone = xhr = null; callback(status, response, headersString, statusText); + $browser.$$completeOutstandingRequest(noop); } }; @@ -12454,8 +12304,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc script.async = true; callback = function(event) { - script.removeEventListener('load', callback); - script.removeEventListener('error', callback); + removeEventListenerFn(script, 'load', callback); + removeEventListenerFn(script, 'error', callback); rawDocument.body.removeChild(script); script = null; var status = -1; @@ -12474,8 +12324,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc } }; - script.addEventListener('load', callback); - script.addEventListener('error', callback); + addEventListenerFn(script, 'load', callback); + addEventListenerFn(script, 'error', callback); rawDocument.body.appendChild(script); return callback; } @@ -12554,8 +12404,9 @@ function $InterpolateProvider() { if (value) { startSymbol = value; return this; + } else { + return startSymbol; } - return startSymbol; }; /** @@ -12571,8 +12422,9 @@ function $InterpolateProvider() { if (value) { endSymbol = value; return this; + } else { + return endSymbol; } - return endSymbol; }; @@ -12591,6 +12443,23 @@ function $InterpolateProvider() { replace(escapedEndRegexp, endSymbol); } + function stringify(value) { + if (value == null) { // null || undefined + return ''; + } + switch (typeof value) { + case 'string': + break; + case 'number': + value = '' + value; + break; + default: + value = toJson(value); + } + + return value; + } + // TODO: this is the same as the constantWatchDelegate in parse.js function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { var unwatch = scope.$watch(function constantInterpolateWatch(scope) { @@ -13056,8 +12925,6 @@ 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); intervals[promise.$$intervalId].reject('canceled'); $window.clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; @@ -13299,8 +13166,6 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' - - this.$$urlUpdatedByLocation = true; }; this.$$parseLinkUrl = function(url, relHref) { @@ -13434,8 +13299,6 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); - - this.$$urlUpdatedByLocation = true; }; this.$$parseLinkUrl = function(url, relHref) { @@ -13493,8 +13356,6 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' this.$$absUrl = appBase + hashPrefix + this.$$url; - - this.$$urlUpdatedByLocation = true; }; } @@ -13824,7 +13685,6 @@ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], fun // 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; }; @@ -13887,7 +13747,7 @@ function locationGetterSetter(property, preprocess) { * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ function $LocationProvider() { - var hashPrefix = '!', + var hashPrefix = '', html5Mode = { enabled: false, requireBase: true, @@ -13898,7 +13758,7 @@ function $LocationProvider() { * @ngdoc method * @name $locationProvider#hashPrefix * @description - * The default value for the prefix is `'!'`. + * The default value for the prefix is `''`. * @param {string=} prefix Prefix for hash part (containing path and search) * @returns {*} current value if used as getter or itself (chaining) if used as setter */ @@ -14102,7 +13962,7 @@ function $LocationProvider() { // update $location when $browser url changes $browser.onUrlChange(function(newUrl, newState) { - if (!startsWith(newUrl, appBaseNoFile)) { + if (isUndefined(stripBaseUrl(appBaseNoFile, newUrl))) { // If we are navigating outside of the app then force a reload $window.location.href = newUrl; return; @@ -14137,40 +13997,36 @@ function $LocationProvider() { // update browser $rootScope.$watch(function $locationWatch() { - if (initializing || $location.$$urlUpdatedByLocation) { - $location.$$urlUpdatedByLocation = false; + 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); - 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 || urlOrStateChanged) { + initializing = false; - if (initializing || urlOrStateChanged) { - initializing = false; - - $rootScope.$evalAsync(function() { - var newUrl = $location.absUrl(); - var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - $location.$$state, oldState).defaultPrevented; + $rootScope.$evalAsync(function() { + var newUrl = $location.absUrl(); + var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + $location.$$state, oldState).defaultPrevented; - // if the location was changed by a `$locationChangeStart` handler then stop - // processing this location change - if ($location.absUrl() !== newUrl) return; + // 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); + if (defaultPrevented) { + $location.$$parse(oldUrl); + $location.$$state = oldState; + } else { + if (urlOrStateChanged) { + setBrowserUrlWithFallback(newUrl, currentReplace, + oldState === $location.$$state ? null : $location.$$state); } - }); - } + afterLocationChange(oldUrl, oldState); + } + }); } $location.$$replace = false; @@ -14366,23 +14222,60 @@ function $LogProvider() { var $parseMinErr = minErr('$parse'); -var objectValueOf = {}.constructor.prototype.valueOf; +var ARRAY_CTOR = [].constructor; +var BOOLEAN_CTOR = (false).constructor; +var FUNCTION_CTOR = Function.constructor; +var NUMBER_CTOR = (0).constructor; +var OBJECT_CTOR = {}.constructor; +var STRING_CTOR = ''.constructor; +var ARRAY_CTOR_PROTO = ARRAY_CTOR.prototype; +var BOOLEAN_CTOR_PROTO = BOOLEAN_CTOR.prototype; +var FUNCTION_CTOR_PROTO = FUNCTION_CTOR.prototype; +var NUMBER_CTOR_PROTO = NUMBER_CTOR.prototype; +var OBJECT_CTOR_PROTO = OBJECT_CTOR.prototype; +var STRING_CTOR_PROTO = STRING_CTOR.prototype; + +var CALL = FUNCTION_CTOR_PROTO.call; +var APPLY = FUNCTION_CTOR_PROTO.apply; +var BIND = FUNCTION_CTOR_PROTO.bind; + +var objectValueOf = OBJECT_CTOR_PROTO.valueOf; // Sandboxing Angular Expressions // ------------------------------ -// Angular 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. +// Angular expressions are generally considered safe because these expressions only have direct +// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by +// obtaining a reference to native JS functions such as the Function constructor. // // As an example, consider the following Angular expression: // // {}.toString.constructor('alert("evil JS code")') // -// It is important to realize that if you create an expression from a string that contains user provided -// content then it is possible that your application contains a security vulnerability to an XSS style attack. +// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits +// against the expression language, but not to prevent exploits that were enabled by exposing +// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good +// practice and therefore we are not even trying to protect against interaction with an object +// explicitly exposed in this way. +// +// In general, it is not possible to access a Window object from an angular expression unless a +// window or some DOM object that has a reference to window is published onto a Scope. +// Similarly we prevent invocations of function known to be dangerous, as well as assignments to +// native objects. // // See https://docs.angularjs.org/guide/security +function ensureSafeMemberName(name, fullExpression) { + if (name === '__defineGetter__' || name === '__defineSetter__' + || name === '__lookupGetter__' || name === '__lookupSetter__' + || name === '__proto__') { + throw $parseMinErr('isecfld', + 'Attempting to access a disallowed field in Angular expressions! ' + + 'Expression: {0}', fullExpression); + } + return name; +} + function getStringValue(name) { // Property names must be strings. This means that non-string objects cannot be used // as keys in an object. Any non-string object, including a number, is typecasted @@ -14401,6 +14294,67 @@ function getStringValue(name) { return name + ''; } +function ensureSafeObject(obj, fullExpression) { + // nifty check if obj is Function that is fast and works across iframes and other contexts + if (obj) { + if (obj.constructor === obj) { + throw $parseMinErr('isecfn', + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isWindow(obj) + obj.window === obj) { + throw $parseMinErr('isecwindow', + 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isElement(obj) + obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { + throw $parseMinErr('isecdom', + 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// block Object so that we can't get hold of dangerous Object.* methods + obj === Object) { + throw $parseMinErr('isecobj', + 'Referencing Object in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + } + return obj; +} + +function ensureSafeFunction(obj, fullExpression) { + if (obj) { + if (obj.constructor === obj) { + throw $parseMinErr('isecfn', + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (obj === CALL || obj === APPLY || obj === BIND) { + throw $parseMinErr('isecff', + 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + } +} + +function ensureSafeAssignContext(obj, fullExpression) { + if (obj) { + if (obj === ARRAY_CTOR || + obj === BOOLEAN_CTOR || + obj === FUNCTION_CTOR || + obj === NUMBER_CTOR || + obj === OBJECT_CTOR || + obj === STRING_CTOR || + obj === ARRAY_CTOR_PROTO || + obj === BOOLEAN_CTOR_PROTO || + obj === FUNCTION_CTOR_PROTO || + obj === NUMBER_CTOR_PROTO || + obj === OBJECT_CTOR_PROTO || + obj === STRING_CTOR_PROTO) { + throw $parseMinErr('isecaf', + 'Assigning to a constructor or its prototype is disallowed! Expression: {0}', + fullExpression); + } + } +} var OPERATORS = createMap(); forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); @@ -15121,12 +15075,13 @@ function ASTCompiler(astBuilder, $filter) { } ASTCompiler.prototype = { - compile: function(expression) { + compile: function(expression, expensiveChecks) { var self = this; var ast = this.astBuilder.ast(expression); this.state = { nextId: 0, filters: {}, + expensiveChecks: expensiveChecks, fn: {vars: [], body: [], own: {}}, assign: {vars: [], body: [], own: {}}, inputs: [] @@ -15169,14 +15124,24 @@ ASTCompiler.prototype = { // eslint-disable-next-line no-new-func var fn = (new Function('$filter', + 'ensureSafeMemberName', + 'ensureSafeObject', + 'ensureSafeFunction', 'getStringValue', + 'ensureSafeAssignContext', 'ifDefined', 'plus', + 'text', fnString))( this.$filter, + ensureSafeMemberName, + ensureSafeObject, + ensureSafeFunction, getStringValue, + ensureSafeAssignContext, ifDefined, - plusFn); + plusFn, + expression); this.state = this.stage = undefined; fn.literal = isLiteral(ast); fn.constant = isConstant(ast); @@ -15250,7 +15215,7 @@ ASTCompiler.prototype = { case AST.Literal: expression = this.escape(ast.value); this.assign(intoId, expression); - recursionFn(intoId || expression); + recursionFn(expression); break; case AST.UnaryExpression: this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); @@ -15290,18 +15255,22 @@ ASTCompiler.prototype = { nameId.computed = false; nameId.name = ast.name; } + ensureSafeMemberName(ast.name); self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), function() { self.if_(self.stage === 'inputs' || 's', function() { if (create && create !== 1) { self.if_( - self.isNull(self.nonComputedMember('s', ast.name)), + self.not(self.nonComputedMember('s', ast.name)), self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); } self.assign(intoId, self.nonComputedMember('s', ast.name)); }); }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) ); + if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { + self.addEnsureSafeObject(intoId); + } recursionFn(intoId); break; case AST.MemberExpression: @@ -15309,24 +15278,32 @@ ASTCompiler.prototype = { intoId = intoId || this.nextId(); self.recurse(ast.object, left, undefined, function() { self.if_(self.notNull(left), function() { + if (create && create !== 1) { + self.addEnsureSafeAssignContext(left); + } if (ast.computed) { right = self.nextId(); self.recurse(ast.property, right); self.getStringValue(right); + self.addEnsureSafeMemberName(right); if (create && create !== 1) { self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); } - expression = self.computedMember(left, right); + expression = self.ensureSafeObject(self.computedMember(left, right)); self.assign(intoId, expression); if (nameId) { nameId.computed = true; nameId.name = right; } } else { + ensureSafeMemberName(ast.property.name); if (create && create !== 1) { - self.if_(self.isNull(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); + self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); } expression = self.nonComputedMember(left, ast.property.name); + if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { + expression = self.ensureSafeObject(expression); + } self.assign(intoId, expression); if (nameId) { nameId.computed = false; @@ -15358,16 +15335,21 @@ ASTCompiler.prototype = { args = []; self.recurse(ast.callee, right, left, function() { self.if_(self.notNull(right), function() { + self.addEnsureSafeFunction(right); forEach(ast.arguments, function(expr) { - self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { - args.push(argument); + self.recurse(expr, self.nextId(), undefined, function(argument) { + args.push(self.ensureSafeObject(argument)); }); }); if (left.name) { + if (!self.state.expensiveChecks) { + self.addEnsureSafeObject(left.context); + } expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; } else { expression = right + '(' + args.join(',') + ')'; } + expression = self.ensureSafeObject(expression); self.assign(intoId, expression); }, function() { self.assign(intoId, 'undefined'); @@ -15382,6 +15364,8 @@ ASTCompiler.prototype = { this.recurse(ast.left, undefined, left, function() { self.if_(self.notNull(left.context), function() { self.recurse(ast.right, right); + self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); + self.addEnsureSafeAssignContext(left.context); expression = self.member(left.context, left.name, left.computed) + ast.operator + right; self.assign(intoId, expression); recursionFn(intoId || expression); @@ -15391,13 +15375,13 @@ ASTCompiler.prototype = { case AST.ArrayExpression: args = []; forEach(ast.elements, function(expr) { - self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) { + self.recurse(expr, self.nextId(), undefined, function(argument) { args.push(argument); }); }); expression = '[' + args.join(',') + ']'; this.assign(intoId, expression); - recursionFn(intoId || expression); + recursionFn(expression); break; case AST.ObjectExpression: args = []; @@ -15439,15 +15423,15 @@ ASTCompiler.prototype = { break; case AST.ThisExpression: this.assign(intoId, 's'); - recursionFn(intoId || 's'); + recursionFn('s'); break; case AST.LocalsExpression: this.assign(intoId, 'l'); - recursionFn(intoId || 'l'); + recursionFn('l'); break; case AST.NGValueParameter: this.assign(intoId, 'v'); - recursionFn(intoId || 'v'); + recursionFn('v'); break; } }, @@ -15506,10 +15490,6 @@ ASTCompiler.prototype = { return '!(' + expression + ')'; }, - isNull: function(expression) { - return expression + '==null'; - }, - notNull: function(expression) { return expression + '!=null'; }, @@ -15533,13 +15513,45 @@ ASTCompiler.prototype = { return this.nonComputedMember(left, right); }, - getStringValue: function(item) { - this.assign(item, 'getStringValue(' + item + ')'); + addEnsureSafeObject: function(item) { + this.current().body.push(this.ensureSafeObject(item), ';'); }, - lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { - var self = this; - return function() { + addEnsureSafeMemberName: function(item) { + this.current().body.push(this.ensureSafeMemberName(item), ';'); + }, + + addEnsureSafeFunction: function(item) { + this.current().body.push(this.ensureSafeFunction(item), ';'); + }, + + addEnsureSafeAssignContext: function(item) { + this.current().body.push(this.ensureSafeAssignContext(item), ';'); + }, + + ensureSafeObject: function(item) { + return 'ensureSafeObject(' + item + ',text)'; + }, + + ensureSafeMemberName: function(item) { + return 'ensureSafeMemberName(' + item + ',text)'; + }, + + ensureSafeFunction: function(item) { + return 'ensureSafeFunction(' + item + ',text)'; + }, + + getStringValue: function(item) { + this.assign(item, 'getStringValue(' + item + ')'); + }, + + ensureSafeAssignContext: function(item) { + return 'ensureSafeAssignContext(' + item + ',text)'; + }, + + lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { + var self = this; + return function() { self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); }; }, @@ -15588,9 +15600,11 @@ function ASTInterpreter(astBuilder, $filter) { } ASTInterpreter.prototype = { - compile: function(expression) { + compile: function(expression, expensiveChecks) { var self = this; var ast = this.astBuilder.ast(expression); + this.expression = expression; + this.expensiveChecks = expensiveChecks; findConstantAndWatchExpressions(ast, self.$filter); var assignable; var assign; @@ -15661,16 +15675,20 @@ ASTInterpreter.prototype = { context ); case AST.Identifier: - return self.identifier(ast.name, context, create); + ensureSafeMemberName(ast.name, self.expression); + return self.identifier(ast.name, + self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), + context, create, self.expression); case AST.MemberExpression: left = this.recurse(ast.object, false, !!create); if (!ast.computed) { + ensureSafeMemberName(ast.property.name, self.expression); right = ast.property.name; } if (ast.computed) right = this.recurse(ast.property); return ast.computed ? - this.computedMember(left, right, context, create) : - this.nonComputedMember(left, right, context, create); + this.computedMember(left, right, context, create, self.expression) : + this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); case AST.CallExpression: args = []; forEach(ast.arguments, function(expr) { @@ -15691,11 +15709,13 @@ ASTInterpreter.prototype = { var rhs = right(scope, locals, assign, inputs); var value; if (rhs.value != null) { + ensureSafeObject(rhs.context, self.expression); + ensureSafeFunction(rhs.value, self.expression); var values = []; for (var i = 0; i < args.length; ++i) { - values.push(args[i](scope, locals, assign, inputs)); + values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); } - value = rhs.value.apply(rhs.context, values); + value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); } return context ? {value: value} : value; }; @@ -15705,6 +15725,8 @@ ASTInterpreter.prototype = { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); var rhs = right(scope, locals, assign, inputs); + ensureSafeObject(lhs.value, self.expression); + ensureSafeAssignContext(lhs.context); lhs.context[lhs.name] = rhs; return context ? {value: rhs} : rhs; }; @@ -15780,7 +15802,7 @@ ASTInterpreter.prototype = { if (isDefined(arg)) { arg = -arg; } else { - arg = -0; + arg = 0; } return context ? {value: arg} : arg; }; @@ -15896,13 +15918,16 @@ ASTInterpreter.prototype = { value: function(value, context) { return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; }, - identifier: function(name, context, create) { + identifier: function(name, expensiveChecks, context, create, expression) { return function(scope, locals, assign, inputs) { var base = locals && (name in locals) ? locals : scope; - if (create && create !== 1 && base && base[name] == null) { + if (create && create !== 1 && base && !(base[name])) { base[name] = {}; } var value = base ? base[name] : undefined; + if (expensiveChecks) { + ensureSafeObject(value, expression); + } if (context) { return {context: base, name: name, value: value}; } else { @@ -15910,7 +15935,7 @@ ASTInterpreter.prototype = { } }; }, - computedMember: function(left, right, context, create) { + computedMember: function(left, right, context, create, expression) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); var rhs; @@ -15918,12 +15943,15 @@ ASTInterpreter.prototype = { if (lhs != null) { rhs = right(scope, locals, assign, inputs); rhs = getStringValue(rhs); + ensureSafeMemberName(rhs, expression); if (create && create !== 1) { + ensureSafeAssignContext(lhs); if (lhs && !(lhs[rhs])) { lhs[rhs] = {}; } } value = lhs[rhs]; + ensureSafeObject(value, expression); } if (context) { return {context: lhs, name: rhs, value: value}; @@ -15932,15 +15960,19 @@ ASTInterpreter.prototype = { } }; }, - nonComputedMember: function(left, right, context, create) { + nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); if (create && create !== 1) { - if (lhs && lhs[right] == null) { + ensureSafeAssignContext(lhs); + if (lhs && !(lhs[right])) { lhs[right] = {}; } } var value = lhs != null ? lhs[right] : undefined; + if (expensiveChecks || isPossiblyDangerousMemberName(right)) { + ensureSafeObject(value, expression); + } if (context) { return {context: lhs, name: right, value: value}; } else { @@ -15972,10 +16004,14 @@ Parser.prototype = { constructor: Parser, parse: function(text) { - return this.astCompiler.compile(text); + return this.astCompiler.compile(text, this.options.expensiveChecks); } }; +function isPossiblyDangerousMemberName(name) { + return name === 'constructor'; +} + function getValueOf(value) { return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); } @@ -16033,7 +16069,8 @@ function getValueOf(value) { * service. */ function $ParseProvider() { - var cache = createMap(); + var cacheDefault = createMap(); + var cacheExpensive = createMap(); var literals = { 'true': true, 'false': false, @@ -16091,20 +16128,37 @@ function $ParseProvider() { var noUnsafeEval = csp().noUnsafeEval; var $parseOptions = { csp: noUnsafeEval, + expensiveChecks: false, + literals: copy(literals), + isIdentifierStart: isFunction(identStart) && identStart, + isIdentifierContinue: isFunction(identContinue) && identContinue + }, + $parseOptionsExpensive = { + csp: noUnsafeEval, + expensiveChecks: true, literals: copy(literals), isIdentifierStart: isFunction(identStart) && identStart, isIdentifierContinue: isFunction(identContinue) && identContinue }; + var runningChecksEnabled = false; + + $parse.$$runningExpensiveChecks = function() { + return runningChecksEnabled; + }; + return $parse; - function $parse(exp, interceptorFn) { + function $parse(exp, interceptorFn, expensiveChecks) { var parsedExpression, oneTime, cacheKey; + expensiveChecks = expensiveChecks || runningChecksEnabled; + switch (typeof exp) { case 'string': exp = exp.trim(); cacheKey = exp; + var cache = (expensiveChecks ? cacheExpensive : cacheDefault); parsedExpression = cache[cacheKey]; if (!parsedExpression) { @@ -16112,8 +16166,9 @@ function $ParseProvider() { oneTime = true; exp = exp.substring(2); } - var lexer = new Lexer($parseOptions); - var parser = new Parser(lexer, $filter, $parseOptions); + var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; + var lexer = new Lexer(parseOptions); + var parser = new Parser(lexer, $filter, parseOptions); parsedExpression = parser.parse(exp); if (parsedExpression.constant) { parsedExpression.$$watchDelegate = constantWatchDelegate; @@ -16123,6 +16178,9 @@ function $ParseProvider() { } else if (parsedExpression.inputs) { parsedExpression.$$watchDelegate = inputsWatchDelegate; } + if (expensiveChecks) { + parsedExpression = expensiveChecksInterceptor(parsedExpression); + } cache[cacheKey] = parsedExpression; } return addInterceptor(parsedExpression, interceptorFn); @@ -16135,6 +16193,30 @@ function $ParseProvider() { } } + function expensiveChecksInterceptor(fn) { + if (!fn) return fn; + expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; + expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); + expensiveCheckFn.constant = fn.constant; + expensiveCheckFn.literal = fn.literal; + for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { + fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); + } + expensiveCheckFn.inputs = fn.inputs; + + return expensiveCheckFn; + + function expensiveCheckFn(scope, locals, assign, inputs) { + var expensiveCheckOldValue = runningChecksEnabled; + runningChecksEnabled = true; + try { + return fn(scope, locals, assign, inputs); + } finally { + runningChecksEnabled = expensiveCheckOldValue; + } + } + } + function expressionInputDirtyCheck(newValue, oldValueOfValue) { if (newValue == null || oldValueOfValue == null) { // null/undefined @@ -16204,22 +16286,14 @@ function $ParseProvider() { }, listener, objectEquality, prettyPrintExpression); } - function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { + function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch, lastValue; - if (parsedExpression.inputs) { - unwatch = inputsWatchDelegate(scope, oneTimeListener, objectEquality, parsedExpression, prettyPrintExpression); - } else { - unwatch = scope.$watch(oneTimeWatch, oneTimeListener, objectEquality); - } - return unwatch; - - function oneTimeWatch(scope) { + unwatch = scope.$watch(function oneTimeWatch(scope) { return parsedExpression(scope); - } - function oneTimeListener(value, old, scope) { + }, /** @this */ function oneTimeListener(value, old, scope) { lastValue = value; if (isFunction(listener)) { - listener(value, old, scope); + listener.apply(this, arguments); } if (isDefined(value)) { scope.$$postDigest(function() { @@ -16228,17 +16302,18 @@ function $ParseProvider() { } }); } - } + }, objectEquality); + return unwatch; } function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch, lastValue; unwatch = scope.$watch(function oneTimeWatch(scope) { return parsedExpression(scope); - }, function oneTimeListener(value, old, scope) { + }, /** @this */ function oneTimeListener(value, old, scope) { lastValue = value; if (isFunction(listener)) { - listener(value, old, scope); + listener.call(this, value, old, scope); } if (isAllDefined(value)) { scope.$$postDigest(function() { @@ -16287,15 +16362,14 @@ function $ParseProvider() { }; // 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; + useInputs = !parsedExpression.inputs; fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; } @@ -16308,13 +16382,14 @@ function $ParseProvider() { * @ngdoc service * @name $q * @requires $rootScope + * @this * * @description * A service that helps you run functions asynchronously, and use their return values (or exceptions) * when they are done processing. * - * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred - * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * This is an implementation of promises/deferred objects inspired by + * [Kris Kowal's Q](https://github.com/kriskowal/q). * * $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. @@ -16521,61 +16596,22 @@ function $ParseProvider() { * * @returns {Promise} The newly created promise. */ -/** - * @ngdoc provider - * @name $qProvider - * @this - * - * @description - */ function $QProvider() { - var errorOnUnhandledRejections = true; + this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { return qFactory(function(callback) { $rootScope.$evalAsync(callback); - }, $exceptionHandler, errorOnUnhandledRejections); + }, $exceptionHandler); }]; - - /** - * @ngdoc method - * @name $qProvider#errorOnUnhandledRejections - * @kind function - * - * @description - * Retrieves or overrides whether to generate an error when a rejected promise is not handled. - * This feature is enabled by default. - * - * @param {boolean=} value Whether to generate an error when a rejected promise is not handled. - * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for - * chaining otherwise. - */ - this.errorOnUnhandledRejections = function(value) { - if (isDefined(value)) { - errorOnUnhandledRejections = value; - return this; - } else { - return errorOnUnhandledRejections; - } - }; } /** @this */ function $$QProvider() { - var errorOnUnhandledRejections = true; this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { return qFactory(function(callback) { $browser.defer(callback); - }, $exceptionHandler, errorOnUnhandledRejections); + }, $exceptionHandler); }]; - - this.errorOnUnhandledRejections = function(value) { - if (isDefined(value)) { - errorOnUnhandledRejections = value; - return this; - } else { - return errorOnUnhandledRejections; - } - }; } /** @@ -16584,14 +16620,10 @@ 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 - * promises rejections. * @returns {object} Promise manager. */ -function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { +function qFactory(nextTick, exceptionHandler) { var $qMinErr = minErr('$q', TypeError); - var queueSize = 0; - var checkQueue = []; /** * @ngdoc method @@ -16604,18 +16636,14 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { * @returns {Deferred} Returns a new instance of deferred. */ function defer() { - return new Deferred(); - } - - function Deferred() { - var promise = this.promise = new Promise(); - //Non prototype methods necessary to support unbound execution :/ - this.resolve = function(val) { resolvePromise(promise, val); }; - this.reject = function(reason) { rejectPromise(promise, reason); }; - this.notify = function(progress) { notifyPromise(promise, progress); }; + var d = new Deferred(); + //Necessary to support unbound execution :/ + d.resolve = simpleBind(d, d.resolve); + d.reject = simpleBind(d, d.reject); + d.notify = simpleBind(d, d.notify); + return d; } - function Promise() { this.$$state = { status: 0 }; } @@ -16625,13 +16653,13 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { return this; } - var result = new Promise(); + var result = new Deferred(); this.$$state.pending = this.$$state.pending || []; this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); - return result; + return result.promise; }, 'catch': function(callback) { @@ -16647,140 +16675,122 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { } }); + //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native + function simpleBind(context, fn) { + return function(value) { + fn.call(context, value); + }; + } + function processQueue(state) { - var fn, promise, pending; + var fn, deferred, pending; pending = state.pending; state.processScheduled = false; state.pending = undefined; - try { - for (var i = 0, ii = pending.length; i < ii; ++i) { - state.pur = true; - promise = pending[i][0]; - fn = pending[i][state.status]; - try { - if (isFunction(fn)) { - resolvePromise(promise, fn(state.value)); - } else if (state.status === 1) { - resolvePromise(promise, state.value); - } else { - rejectPromise(promise, state.value); - } - } catch (e) { - rejectPromise(promise, e); - } - } - } finally { - --queueSize; - if (errorOnUnhandledRejections && queueSize === 0) { - nextTick(processChecks); - } - } - } - - function processChecks() { - // eslint-disable-next-line no-unmodified-loop-condition - while (!queueSize && checkQueue.length) { - var toCheck = checkQueue.shift(); - if (!toCheck.pur) { - toCheck.pur = true; - var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value); - if (toCheck.value instanceof Error) { - exceptionHandler(toCheck.value, errorMessage); + for (var i = 0, ii = pending.length; i < ii; ++i) { + deferred = pending[i][0]; + fn = pending[i][state.status]; + try { + if (isFunction(fn)) { + deferred.resolve(fn(state.value)); + } else if (state.status === 1) { + deferred.resolve(state.value); } else { - exceptionHandler(errorMessage); + deferred.reject(state.value); } + } catch (e) { + deferred.reject(e); + exceptionHandler(e); } } } function scheduleProcessQueue(state) { - if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !state.pur) { - if (queueSize === 0 && checkQueue.length === 0) { - nextTick(processChecks); - } - checkQueue.push(state); - } if (state.processScheduled || !state.pending) return; state.processScheduled = true; - ++queueSize; nextTick(function() { processQueue(state); }); } - function resolvePromise(promise, val) { - if (promise.$$state.status) return; - if (val === promise) { - $$reject(promise, $qMinErr( - 'qcycle', - 'Expected promise to be resolved with value other than itself \'{0}\'', - val)); - } else { - $$resolve(promise, val); - } - + function Deferred() { + this.promise = new Promise(); } - function $$resolve(promise, val) { - var then; - var done = false; - try { - if (isObject(val) || isFunction(val)) then = val.then; - if (isFunction(then)) { - promise.$$state.status = -1; - then.call(val, doResolve, doReject, doNotify); + extend(Deferred.prototype, { + resolve: function(val) { + if (this.promise.$$state.status) return; + if (val === this.promise) { + this.$$reject($qMinErr( + 'qcycle', + 'Expected promise to be resolved with value other than itself \'{0}\'', + val)); } else { - promise.$$state.value = val; - promise.$$state.status = 1; - scheduleProcessQueue(promise.$$state); + this.$$resolve(val); } - } catch (e) { - doReject(e); - } - function doResolve(val) { - if (done) return; - done = true; - $$resolve(promise, val); - } - function doReject(val) { - if (done) return; - done = true; - $$reject(promise, val); - } - function doNotify(progress) { - notifyPromise(promise, progress); - } - } + }, - function rejectPromise(promise, reason) { - if (promise.$$state.status) return; - $$reject(promise, reason); - } + $$resolve: function(val) { + var then; + var that = this; + var done = false; + try { + if ((isObject(val) || isFunction(val))) then = val && val.then; + if (isFunction(then)) { + this.promise.$$state.status = -1; + then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); + } else { + this.promise.$$state.value = val; + this.promise.$$state.status = 1; + scheduleProcessQueue(this.promise.$$state); + } + } catch (e) { + rejectPromise(e); + exceptionHandler(e); + } - function $$reject(promise, reason) { - promise.$$state.value = reason; - promise.$$state.status = 2; - scheduleProcessQueue(promise.$$state); - } + function resolvePromise(val) { + if (done) return; + done = true; + that.$$resolve(val); + } + function rejectPromise(val) { + if (done) return; + done = true; + that.$$reject(val); + } + }, - function notifyPromise(promise, progress) { - var callbacks = promise.$$state.pending; + reject: function(reason) { + if (this.promise.$$state.status) return; + this.$$reject(reason); + }, - if ((promise.$$state.status <= 0) && callbacks && callbacks.length) { - nextTick(function() { - var callback, result; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - result = callbacks[i][0]; - callback = callbacks[i][3]; - try { - notifyPromise(result, isFunction(callback) ? callback(progress) : progress); - } catch (e) { - exceptionHandler(e); + $$reject: function(reason) { + this.promise.$$state.value = reason; + this.promise.$$state.status = 2; + scheduleProcessQueue(this.promise.$$state); + }, + + notify: function(progress) { + var callbacks = this.promise.$$state.pending; + + if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { + nextTick(function() { + var callback, result; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + result = callbacks[i][0]; + callback = callbacks[i][3]; + try { + result.notify(isFunction(callback) ? callback(progress) : progress); + } catch (e) { + exceptionHandler(e); + } } - } - }); + }); + } } - } + }); /** * @ngdoc method @@ -16819,9 +16829,9 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. */ function reject(reason) { - var result = new Promise(); - rejectPromise(result, reason); - return result; + var result = new Deferred(); + result.reject(reason); + return result.promise; } function handleCallback(value, resolver, callback) { @@ -16859,9 +16869,9 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { function when(value, callback, errback, progressBack) { - var result = new Promise(); - resolvePromise(result, value); - return result.then(callback, errback, progressBack); + var result = new Deferred(); + result.resolve(value); + return result.promise.then(callback, errback, progressBack); } /** @@ -16897,7 +16907,7 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { */ function all(promises) { - var result = new Promise(), + var deferred = new Deferred(), counter = 0, results = isArray(promises) ? [] : {}; @@ -16905,17 +16915,17 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { counter++; when(promise).then(function(value) { results[key] = value; - if (!(--counter)) resolvePromise(result, results); + if (!(--counter)) deferred.resolve(results); }, function(reason) { - rejectPromise(result, reason); + deferred.reject(reason); }); }); if (counter === 0) { - resolvePromise(result, results); + deferred.resolve(results); } - return result; + return deferred.promise; } /** @@ -16947,19 +16957,19 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); } - var promise = new Promise(); + var deferred = new Deferred(); function resolveFn(value) { - resolvePromise(promise, value); + deferred.resolve(value); } function rejectFn(reason) { - rejectPromise(promise, reason); + deferred.reject(reason); } resolver(resolveFn, rejectFn); - return promise; + return deferred.promise; } // Let's make the instanceof operator work for promises, so that @@ -17112,7 +17122,6 @@ function $RootScopeProvider() { function cleanUpScope($scope) { - // Support: IE 9 only if (msie === 9) { // There is a memory leak in IE9 if all child scopes are not disconnected // completely when a scope is destroyed. So this code will recurse up through @@ -17304,8 +17313,6 @@ function $RootScopeProvider() { * 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`}. * - 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. @@ -17883,10 +17890,6 @@ function $RootScopeProvider() { } } 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(); }, @@ -18517,13 +18520,6 @@ var SCE_CONTEXTS = { // Helper functions follow. -var UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g; - -function snakeToCamel(name) { - return name - .replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace); -} - function adjustMatcher(matcher) { if (matcher === 'self') { return matcher; @@ -18717,7 +18713,7 @@ function $SceDelegateProvider() { function matchUrl(matcher, parsedUrl) { if (matcher === 'self') { - return urlIsSameOrigin(parsedUrl) || urlIsSameOriginAsBaseUrl(parsedUrl); + return urlIsSameOrigin(parsedUrl); } else { // definitely a regex. See adjustMatchers() return !!matcher.exec(parsedUrl.href); @@ -19109,8 +19105,8 @@ function $SceDelegateProvider() { * .controller('AppController', ['$http', '$templateCache', '$sce', * function AppController($http, $templateCache, $sce) { * var self = this; - * $http.get('test_data.json', {cache: $templateCache}).then(function(response) { - * self.userComments = response.data; + * $http.get('test_data.json', {cache: $templateCache}).success(function(userComments) { + * self.userComments = userComments; * }); * self.explicitlyTrustedHtml = $sce.trustAsHtml( * '` 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()); -} - -/** - * Determines 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()`. - * @return {boolean} True if both URLs have the same origin, and false otherwise. - */ -function urlsAreSameOrigin(url1, url2) { - url1 = (isString(url1)) ? urlResolve(url1) : url1; - url2 = (isString(url2)) ? urlResolve(url2) : url2; - - return (url1.protocol === url2.protocol && - url1.host === url2.host); -} - -/** - * Returns the current document base URL. - * @return {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; + var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); } /** @@ -20695,7 +20649,7 @@ var ZERO_CHAR = '0';

default currency symbol ($): {{amount | currency}}
- custom currency identifier (USD$): {{amount | currency:"USD$"}}
+ custom currency identifier (USD$): {{amount | currency:"USD$"}} no fractions (0): {{amount | currency:"USD$":0}}
@@ -22670,11 +22624,10 @@ forEach(['src', 'srcset', 'href'], function(attrName) { attr.$set(name, value); - // Support: IE 9-11 only - // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist + // 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 can sanitize the url. if (msie && propName) element.prop(propName, attr[name]); }); } @@ -22682,7 +22635,7 @@ forEach(['src', 'srcset', 'href'], function(attrName) { }; }); -/* global -nullFormCtrl, -PENDING_CLASS, -SUBMITTED_CLASS +/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true */ var nullFormCtrl = { $addControl: noop, @@ -22693,7 +22646,6 @@ var nullFormCtrl = { $setPristine: noop, $setSubmitted: noop }, -PENDING_CLASS = 'ng-pending', SUBMITTED_CLASS = 'ng-submitted'; function nullFormRenameControl(control, name) { @@ -22744,28 +22696,22 @@ function nullFormRenameControl(control, name) { */ //asks for $scope to fool the BC controller module FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; -function FormController($element, $attrs, $scope, $animate, $interpolate) { - this.$$controls = []; +function FormController(element, attrs, $scope, $animate, $interpolate) { + var form = this, + controls = []; // init state - this.$error = {}; - this.$$success = {}; - this.$pending = undefined; - this.$name = $interpolate($attrs.name || $attrs.ngForm || '')($scope); - this.$dirty = false; - this.$pristine = true; - this.$valid = true; - this.$invalid = false; - this.$submitted = false; - this.$$parentForm = nullFormCtrl; - - this.$$element = $element; - this.$$animate = $animate; - - setupValidity(this); -} + form.$error = {}; + form.$$success = {}; + form.$pending = undefined; + form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); + form.$dirty = false; + form.$pristine = true; + form.$valid = true; + form.$invalid = false; + form.$submitted = false; + form.$$parentForm = nullFormCtrl; -FormController.prototype = { /** * @ngdoc method * @name form.FormController#$rollbackViewValue @@ -22777,11 +22723,11 @@ FormController.prototype = { * event defined in `ng-model-options`. This method is typically needed by the reset button of * a form that uses `ng-model-options` to pend updates. */ - $rollbackViewValue: function() { - forEach(this.$$controls, function(control) { + form.$rollbackViewValue = function() { + forEach(controls, function(control) { control.$rollbackViewValue(); }); - }, + }; /** * @ngdoc method @@ -22794,11 +22740,11 @@ FormController.prototype = { * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ - $commitViewValue: function() { - forEach(this.$$controls, function(control) { + form.$commitViewValue = function() { + forEach(controls, function(control) { control.$commitViewValue(); }); - }, + }; /** * @ngdoc method @@ -22821,29 +22767,29 @@ FormController.prototype = { * For example, if an input control is added that is already `$dirty` and has `$error` properties, * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. */ - $addControl: function(control) { + form.$addControl = function(control) { // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored // and not added to the scope. Now we throw an error. assertNotHasOwnProperty(control.$name, 'input'); - this.$$controls.push(control); + controls.push(control); if (control.$name) { - this[control.$name] = control; + form[control.$name] = control; } - control.$$parentForm = this; - }, + control.$$parentForm = form; + }; // Private API: rename a form control - $$renameControl: function(control, newName) { + form.$$renameControl = function(control, newName) { var oldName = control.$name; - if (this[oldName] === control) { - delete this[oldName]; + if (form[oldName] === control) { + delete form[oldName]; } - this[newName] = control; + form[newName] = control; control.$name = newName; - }, + }; /** * @ngdoc method @@ -22861,26 +22807,60 @@ FormController.prototype = { * different from case to case. For example, removing the only `$dirty` control from a form may or * may not mean that the form is still `$dirty`. */ - $removeControl: function(control) { - if (control.$name && this[control.$name] === control) { - delete this[control.$name]; - } - forEach(this.$pending, function(value, name) { - // eslint-disable-next-line no-invalid-this - this.$setValidity(name, null, control); - }, this); - forEach(this.$error, function(value, name) { - // eslint-disable-next-line no-invalid-this - this.$setValidity(name, null, control); - }, this); - forEach(this.$$success, function(value, name) { - // eslint-disable-next-line no-invalid-this - this.$setValidity(name, null, control); - }, this); - - arrayRemove(this.$$controls, control); + form.$removeControl = function(control) { + if (control.$name && form[control.$name] === control) { + delete form[control.$name]; + } + forEach(form.$pending, function(value, name) { + form.$setValidity(name, null, control); + }); + forEach(form.$error, function(value, name) { + form.$setValidity(name, null, control); + }); + forEach(form.$$success, function(value, name) { + form.$setValidity(name, null, control); + }); + + arrayRemove(controls, control); control.$$parentForm = nullFormCtrl; - }, + }; + + + /** + * @ngdoc method + * @name form.FormController#$setValidity + * + * @description + * Sets the validity of a form control. + * + * This method will also propagate to parent forms. + */ + addSetValidityMethod({ + ctrl: this, + $element: element, + set: function(object, property, controller) { + var list = object[property]; + if (!list) { + object[property] = [controller]; + } else { + var index = list.indexOf(controller); + if (index === -1) { + list.push(controller); + } + } + }, + unset: function(object, property, controller) { + var list = object[property]; + if (!list) { + return; + } + arrayRemove(list, controller); + if (list.length === 0) { + delete object[property]; + } + }, + $animate: $animate + }); /** * @ngdoc method @@ -22892,13 +22872,13 @@ FormController.prototype = { * This method can be called to add the 'ng-dirty' class and set the form to a dirty * state (ng-dirty class). This method will also propagate to parent forms. */ - $setDirty: function() { - this.$$animate.removeClass(this.$$element, PRISTINE_CLASS); - this.$$animate.addClass(this.$$element, DIRTY_CLASS); - this.$dirty = true; - this.$pristine = false; - this.$$parentForm.$setDirty(); - }, + form.$setDirty = function() { + $animate.removeClass(element, PRISTINE_CLASS); + $animate.addClass(element, DIRTY_CLASS); + form.$dirty = true; + form.$pristine = false; + form.$$parentForm.$setDirty(); + }; /** * @ngdoc method @@ -22916,15 +22896,15 @@ FormController.prototype = { * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ - $setPristine: function() { - this.$$animate.setClass(this.$$element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); - this.$dirty = false; - this.$pristine = true; - this.$submitted = false; - forEach(this.$$controls, function(control) { + form.$setPristine = function() { + $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); + form.$dirty = false; + form.$pristine = true; + form.$submitted = false; + forEach(controls, function(control) { control.$setPristine(); }); - }, + }; /** * @ngdoc method @@ -22939,11 +22919,11 @@ FormController.prototype = { * Setting a form controls back to their untouched state is often useful when setting the form * back to its pristine state. */ - $setUntouched: function() { - forEach(this.$$controls, function(control) { + form.$setUntouched = function() { + forEach(controls, function(control) { control.$setUntouched(); }); - }, + }; /** * @ngdoc method @@ -22952,56 +22932,22 @@ FormController.prototype = { * @description * Sets the form to its submitted state. */ - $setSubmitted: function() { - this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); - this.$submitted = true; - this.$$parentForm.$setSubmitted(); - } -}; + form.$setSubmitted = function() { + $animate.addClass(element, SUBMITTED_CLASS); + form.$submitted = true; + form.$$parentForm.$setSubmitted(); + }; +} /** - * @ngdoc method - * @name form.FormController#$setValidity + * @ngdoc directive + * @name ngForm + * @restrict EAC * * @description - * Sets the validity of a form control. - * - * This method will also propagate to parent forms. - */ -addSetValidityMethod({ - clazz: FormController, - set: function(object, property, controller) { - var list = object[property]; - if (!list) { - object[property] = [controller]; - } else { - var index = list.indexOf(controller); - if (index === -1) { - list.push(controller); - } - } - }, - unset: function(object, property, controller) { - var list = object[property]; - if (!list) { - return; - } - arrayRemove(list, controller); - if (list.length === 0) { - delete object[property]; - } - } -}); - -/** - * @ngdoc directive - * @name ngForm - * @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. + * 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. * * Note: the purpose of `ngForm` is to group controls, * but not to be a replacement for the `
` tag with all of its capabilities @@ -23188,13 +23134,13 @@ var formDirectiveFactory = function(isNgForm) { event.preventDefault(); }; - formElement[0].addEventListener('submit', handleFormSubmission); + addEventListenerFn(formElement[0], 'submit', handleFormSubmission); // unregister the preventDefault listener so that we don't not leak memory but in a // way that will achieve the prevention of the default action. formElement.on('$destroy', function() { $timeout(function() { - formElement[0].removeEventListener('submit', handleFormSubmission); + removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); }, 0, false); }); } @@ -23239,111 +23185,6 @@ var formDirectiveFactory = function(isNgForm) { var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); - - -// helper methods -function setupValidity(instance) { - instance.$$classCache = {}; - instance.$$classCache[INVALID_CLASS] = !(instance.$$classCache[VALID_CLASS] = instance.$$element.hasClass(VALID_CLASS)); -} -function addSetValidityMethod(context) { - var clazz = context.clazz, - set = context.set, - unset = context.unset; - - clazz.prototype.$setValidity = function(validationErrorKey, state, controller) { - if (isUndefined(state)) { - createAndSet(this, '$pending', validationErrorKey, controller); - } else { - unsetAndCleanup(this, '$pending', validationErrorKey, controller); - } - if (!isBoolean(state)) { - unset(this.$error, validationErrorKey, controller); - unset(this.$$success, validationErrorKey, controller); - } else { - if (state) { - unset(this.$error, validationErrorKey, controller); - set(this.$$success, validationErrorKey, controller); - } else { - set(this.$error, validationErrorKey, controller); - unset(this.$$success, validationErrorKey, controller); - } - } - if (this.$pending) { - cachedToggleClass(this, PENDING_CLASS, true); - this.$valid = this.$invalid = undefined; - toggleValidationCss(this, '', null); - } else { - cachedToggleClass(this, PENDING_CLASS, false); - this.$valid = isObjectEmpty(this.$error); - this.$invalid = !this.$valid; - toggleValidationCss(this, '', this.$valid); - } - - // re-read the state as the set/unset methods could have - // combined state in this.$error[validationError] (used for forms), - // where setting/unsetting only increments/decrements the value, - // and does not replace it. - var combinedState; - if (this.$pending && this.$pending[validationErrorKey]) { - combinedState = undefined; - } else if (this.$error[validationErrorKey]) { - combinedState = false; - } else if (this.$$success[validationErrorKey]) { - combinedState = true; - } else { - combinedState = null; - } - - toggleValidationCss(this, validationErrorKey, combinedState); - this.$$parentForm.$setValidity(validationErrorKey, combinedState, this); - }; - - function createAndSet(ctrl, name, value, controller) { - if (!ctrl[name]) { - ctrl[name] = {}; - } - set(ctrl[name], value, controller); - } - - function unsetAndCleanup(ctrl, name, value, controller) { - if (ctrl[name]) { - unset(ctrl[name], value, controller); - } - if (isObjectEmpty(ctrl[name])) { - ctrl[name] = undefined; - } - } - - function cachedToggleClass(ctrl, className, switchValue) { - if (switchValue && !ctrl.$$classCache[className]) { - ctrl.$$animate.addClass(ctrl.$$element, className); - ctrl.$$classCache[className] = true; - } else if (!switchValue && ctrl.$$classCache[className]) { - ctrl.$$animate.removeClass(ctrl.$$element, className); - ctrl.$$classCache[className] = false; - } - } - - function toggleValidationCss(ctrl, validationErrorKey, isValid) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - - cachedToggleClass(ctrl, VALID_CLASS + validationErrorKey, isValid === true); - cachedToggleClass(ctrl, INVALID_CLASS + validationErrorKey, isValid === false); - } -} - -function isObjectEmpty(obj) { - if (obj) { - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - return false; - } - } - } - return true; -} - /* global VALID_CLASS: false, INVALID_CLASS: false, @@ -24023,17 +23864,7 @@ var inputType = { * @param {string} ngModel Assignable angular 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. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * Can be interpolated. - * @param {string=} ngMin Like `min`, sets the `min` validation error key if the value entered is less than `ngMin`, - * but does not trigger HTML5 native validation. Takes an expression. - * @param {string=} ngMax Like `max`, sets the `max` validation error key if the value entered is greater than `ngMax`, - * but does not trigger HTML5 native validation. Takes an expression. - * @param {string=} step Sets the `step` validation error key if the value entered does not fit the `step` constraint. - * Can be interpolated. - * @param {string=} ngStep Like `step`, sets the `step` validation error key if the value entered does not fit the `ngStep` constraint, - * but does not trigger HTML5 native validation. Takes an expression. * @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 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of @@ -24388,6 +24219,28 @@ var inputType = { * @description * Native range input with validation and transformation. * + *
+ *

+ * In v1.5.9+, in order to avoid interfering with already existing, custom directives for + * `input[range]`, you need to let Angular know that you want to enable its built-in support. + * You can do this by adding the `ng-input-range` attribute to the input element. E.g.: + * `` + *


+ *

+ * Input elements without the `ng-input-range` attibute will continue to be treated the same + * as in previous versions (e.g. their model value will be a string not a number and Angular + * will not take `min`/`max`/`step` attributes and properties into account). + *


+ *

+ * **Note:** From v1.6.x onwards, the support for `input[range]` will be always enabled and + * the `ng-input-range` attribute will have no effect. + *


+ *

+ * This documentation page refers to elements which have the built-in support enabled; i.e. + * elements _with_ the `ng-input-range` attribute. + *

+ *
+ * * The model for the range input must always be a `Number`. * * IE9 and other browsers that do not support the `range` type fall back @@ -24409,7 +24262,7 @@ var inputType = { * * 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 ``, + * 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. * @@ -24428,10 +24281,12 @@ var inputType = { * instead may set the `stepMismatch` error. If that's the case, the Angular 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 + * 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 ngInputRange The presense of this attribute enables the built-in support for + * `input[range]`. * @param {string} ngModel Assignable angular 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 @@ -24459,7 +24314,7 @@ var inputType = { - Model as range: + Model as range:
Model as number:
Min:
@@ -24485,7 +24340,7 @@ var inputType = { }]); - Model as range: + Model as range:
Model as number:
Min:
@@ -24771,7 +24626,7 @@ function createDateInputType(type, regexp, parseDate, format) { return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { badInputChecker(scope, element, attr, ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var timezone = ctrl && ctrl.$options.getOption('timezone'); + var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; var previousDate; ctrl.$$parserName = type; @@ -24909,27 +24764,15 @@ function isValidForStep(viewValue, stepBase, step) { // 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 (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); + if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) { + var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step)); 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; @@ -24937,8 +24780,8 @@ function isValidForStep(viewValue, stepBase, step) { function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { badInputChecker(scope, element, attr, ctrl); - numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + numberFormatterParser(ctrl); var minVal; var maxVal; @@ -24966,20 +24809,6 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { ctrl.$validate(); }); } - - if (isDefined(attr.step) || attr.ngStep) { - var stepVal; - ctrl.$validators.step = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || - isValidForStep(viewValue, minVal || 0, stepVal); - }; - - attr.$observe('step', function(val) { - stepVal = parseNumberAttrVal(val); - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - }); - } } function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { @@ -25143,20 +24972,14 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { } function radioInputType(scope, element, attr, ctrl) { - var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false'; // make the name unique, if not defined if (isUndefined(attr.name)) { element.attr('name', nextUid()); } var listener = function(ev) { - var value; if (element[0].checked) { - value = attr.value; - if (doTrim) { - value = trim(value); - } - ctrl.$setViewValue(value, ev && ev.type); + ctrl.$setViewValue(attr.value, ev && ev.type); } }; @@ -25164,10 +24987,9 @@ function radioInputType(scope, element, attr, ctrl) { ctrl.$render = function() { var value = attr.value; - if (doTrim) { - value = trim(value); - } - element[0].checked = (value === ctrl.$viewValue); + // We generally use strict comparison. This is behavior we cannot change without a BC. + // eslint-disable-next-line eqeqeq + element[0].checked = (value == ctrl.$viewValue); }; attr.$observe('value', ctrl.$render); @@ -25412,7 +25234,11 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', link: { pre: function(scope, element, attr, ctrls) { if (ctrls[0]) { - (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, + var type = lowercase(attr.type); + if ((type === 'range') && !attr.hasOwnProperty('ngInputRange')) { + type = 'text'; + } + (inputType[type] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, $browser, $filter, $parse); } } @@ -25428,19 +25254,21 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; * @name ngValue * * @description - * Binds the given expression to the value of the element. + * Binds the given expression to the value of `