# HG changeset patch # User rougeronj # Date 1421921780 -3600 # Node ID c0034b35c44e744d98bcf932e900590f1fa9966c # Parent 9fcb0e0991ed67959b3d094100e15dc9492d26fd merge + gulp + correction of superposition category-text diff -r 9fcb0e0991ed -r c0034b35c44e annot-server/static/js/app.js --- a/annot-server/static/js/app.js Thu Jan 22 10:30:36 2015 +0100 +++ b/annot-server/static/js/app.js Thu Jan 22 11:16:20 2015 +0100 @@ -219,7 +219,7 @@ user : $scope.username }; sock.send(JSON.stringify(new_annot)); - if(context.logging===true){ + if(context.logging===true){ log('Sent: ' + JSON.stringify(new_annot)); } if(typeof c==='undefined'){ diff -r 9fcb0e0991ed -r c0034b35c44e annot-server/static/js/lib.js --- a/annot-server/static/js/lib.js Thu Jan 22 10:30:36 2015 +0100 +++ b/annot-server/static/js/lib.js Thu Jan 22 11:16:20 2015 +0100 @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v2.1.1 + * jQuery JavaScript Library v2.1.3 * http://jquery.com/ * * Includes Sizzle.js @@ -9,19 +9,19 @@ * Released under the MIT license * http://jquery.org/license * - * Date: 2014-05-01T17:11Z + * Date: 2014-12-18T15:11Z */ (function( global, factory ) { if ( typeof module === "object" && typeof module.exports === "object" ) { - // For CommonJS and CommonJS-like environments where a proper window is present, - // execute the factory and get jQuery - // For environments that do not inherently posses a window with a document - // (such as Node.js), expose a jQuery-making factory as module.exports - // This accentuates the need for the creation of a real window + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info + // See ticket #14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { @@ -37,10 +37,10 @@ // Pass this if window is not defined yet }(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { -// Can't do this because several apps including ASP.NET trace +// Support: Firefox 18+ +// Can't be in strict mode, several libs including ASP.NET trace // the stack via arguments.caller.callee and Firefox dies if // you try to trace through "use strict" call chains. (#13335) -// Support: Firefox 18+ // var arr = []; @@ -67,7 +67,7 @@ // Use the correct document accordingly with window argument (sandbox) document = window.document, - version = "2.1.1", + version = "2.1.3", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -185,7 +185,7 @@ if ( typeof target === "boolean" ) { deep = target; - // skip the boolean and the target + // Skip the boolean and the target target = arguments[ i ] || {}; i++; } @@ -195,7 +195,7 @@ target = {}; } - // extend jQuery itself if only one argument is passed + // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; @@ -252,9 +252,6 @@ noop: function() {}, - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, @@ -269,7 +266,8 @@ // parseFloat NaNs numeric-cast false positives (null|true|false|"") // ...but misinterprets leading-number strings, particularly hex literals ("0x...") // subtraction forces infinities to NaN - return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0; + // adding 1 corrects loss of precision from parseFloat (#15100) + return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; }, isPlainObject: function( obj ) { @@ -303,7 +301,7 @@ if ( obj == null ) { return obj + ""; } - // Support: Android < 4.0, iOS < 6 (functionish RegExp) + // Support: Android<4.0, iOS<6 (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call(obj) ] || "object" : typeof obj; @@ -333,6 +331,7 @@ }, // Convert dashed to camelCase; used by the css and data modules + // Support: IE9-11+ // Microsoft forgot to hump their vendor prefix (#9572) camelCase: function( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); @@ -548,14 +547,14 @@ } var Sizzle = /*! - * Sizzle CSS Selector Engine v1.10.19 + * Sizzle CSS Selector Engine v2.2.0-pre * http://sizzlejs.com/ * - * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2014-04-18 + * Date: 2014-12-16 */ (function( window ) { @@ -582,7 +581,7 @@ contains, // Instance-specific data - expando = "sizzle" + -(new Date()), + expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, @@ -597,7 +596,6 @@ }, // General-purpose constants - strundefined = typeof undefined, MAX_NEGATIVE = 1 << 31, // Instance methods @@ -607,12 +605,13 @@ push_native = arr.push, push = arr.push, slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function( elem ) { + // Use a stripped-down indexOf as it's faster than native + // http://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { var i = 0, - len = this.length; + len = list.length; for ( ; i < len; i++ ) { - if ( this[i] === elem ) { + if ( list[i] === elem ) { return i; } } @@ -652,6 +651,7 @@ ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), @@ -703,6 +703,14 @@ String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); }; // Optimize for push.apply( _, NodeList ) @@ -745,19 +753,18 @@ context = context || document; results = results || []; - - if ( !selector || typeof selector !== "string" ) { + nodeType = context.nodeType; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + return results; } - if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { - return []; - } - - if ( documentIsHTML && !seed ) { - - // Shortcuts - if ( (match = rquickExpr.exec( selector )) ) { + if ( !seed && documentIsHTML ) { + + // Try to shortcut find operations when possible (e.g., not under DocumentFragment) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // Speed-up: Sizzle("#ID") if ( (m = match[1]) ) { if ( nodeType === 9 ) { @@ -789,7 +796,7 @@ return results; // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + } else if ( (m = match[3]) && support.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } @@ -799,7 +806,7 @@ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { nid = old = expando; newContext = context; - newSelector = nodeType === 9 && selector; + newSelector = nodeType !== 1 && selector; // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root @@ -986,7 +993,7 @@ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { - return context && typeof context.getElementsByTagName !== strundefined && context; + return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience @@ -1010,9 +1017,8 @@ * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, - doc = node ? node.ownerDocument || node : preferredDoc, - parent = doc.defaultView; + var hasCompare, parent, + doc = node ? node.ownerDocument || node : preferredDoc; // If no document and documentElement is available, return if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { @@ -1022,9 +1028,7 @@ // Set our document document = doc; docElem = doc.documentElement; - - // Support tests - documentIsHTML = !isXML( doc ); + parent = doc.defaultView; // Support: IE>8 // If iframe document is assigned to "document" variable and if iframe has been reloaded, @@ -1033,21 +1037,22 @@ if ( parent && parent !== parent.top ) { // IE11 does not have attachEvent, so all must suffer if ( parent.addEventListener ) { - parent.addEventListener( "unload", function() { - setDocument(); - }, false ); + parent.addEventListener( "unload", unloadHandler, false ); } else if ( parent.attachEvent ) { - parent.attachEvent( "onunload", function() { - setDocument(); - }); - } - } + parent.attachEvent( "onunload", unloadHandler ); + } + } + + /* Support tests + ---------------------------------------------------------------------- */ + documentIsHTML = !isXML( doc ); /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) support.attributes = assert(function( div ) { div.className = "i"; return !div.getAttribute("className"); @@ -1062,17 +1067,8 @@ return !div.getElementsByTagName("*").length; }); - // Check if getElementsByClassName can be trusted - support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { - div.innerHTML = "
"; - - // Support: Safari<4 - // Catch class over-caching - div.firstChild.className = "i"; - // Support: Opera<10 - // Catch gEBCN failure to find non-leading classes - return div.getElementsByClassName("i").length === 2; - }); + // Support: IE<9 + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name @@ -1086,7 +1082,7 @@ // ID find and filter if ( support.getById ) { Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var m = context.getElementById( id ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 @@ -1107,7 +1103,7 @@ Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; @@ -1116,14 +1112,20 @@ // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); } } : + function( tag, context ) { var elem, tmp = [], i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments @@ -1141,7 +1143,7 @@ // Class Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + if ( documentIsHTML ) { return context.getElementsByClassName( className ); } }; @@ -1170,13 +1172,15 @@ // setting a boolean content attribute, // since its presence should be enough // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; + docElem.appendChild( div ).innerHTML = "" + + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowclip^='']").length ) { + if ( div.querySelectorAll("[msallowcapture^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } @@ -1186,12 +1190,24 @@ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } + // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ + if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !div.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibing-combinator selector` fails + if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } }); assert(function( div ) { @@ -1308,7 +1324,7 @@ // Maintain original order return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } @@ -1335,7 +1351,7 @@ aup ? -1 : bup ? 1 : sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check @@ -1398,7 +1414,7 @@ elem.document && elem.document.nodeType !== 11 ) { return ret; } - } catch(e) {} + } catch (e) {} } return Sizzle( expr, document, null, [ elem ] ).length > 0; @@ -1617,7 +1633,7 @@ return pattern || (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); }); }, @@ -1639,7 +1655,7 @@ operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; @@ -1759,7 +1775,7 @@ matched = fn( seed, argument ), i = matched.length; while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); + idx = indexOf( seed, matched[i] ); seed[ idx ] = !( matches[ idx ] = matched[i] ); } }) : @@ -1798,6 +1814,8 @@ function( elem, context, xml ) { input[0] = elem; matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; return !results.pop(); }; }), @@ -1809,6 +1827,7 @@ }), "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; }; @@ -2230,7 +2249,7 @@ i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } @@ -2265,13 +2284,16 @@ return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; + return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( (checkContext = context).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; } ]; for ( ; i < len; i++ ) { @@ -2521,7 +2543,7 @@ // Sort stability support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; -// Support: Chrome<14 +// Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; @@ -2730,7 +2752,7 @@ if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; - // scripts is true for back-compat + // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[1], @@ -2758,8 +2780,8 @@ } else { elem = document.getElementById( match[2] ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 + // Support: Blackberry 4.6 + // gEBID returns nodes no longer in the document (#6963) if ( elem && elem.parentNode ) { // Inject the element directly into the jQuery object this.length = 1; @@ -2812,7 +2834,7 @@ var rparentsprev = /^(?:parents|prev(?:Until|All))/, - // methods guaranteed to produce a unique set when starting from a unique set + // Methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, @@ -2892,8 +2914,7 @@ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); }, - // Determine the position of an element within - // the matched set of elements + // Determine the position of an element within the set index: function( elem ) { // No argument, return index in parent @@ -2901,7 +2922,7 @@ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; } - // index in selector + // Index in selector if ( typeof elem === "string" ) { return indexOf.call( jQuery( elem ), this[ 0 ] ); } @@ -3317,7 +3338,7 @@ progressValues, progressContexts, resolveContexts; - // add listeners to Deferred subordinates; treat others as resolved + // Add listeners to Deferred subordinates; treat others as resolved if ( length > 1 ) { progressValues = new Array( length ); progressContexts = new Array( length ); @@ -3334,7 +3355,7 @@ } } - // if we're not waiting on anything, resolve the master + // If we're not waiting on anything, resolve the master if ( !remaining ) { deferred.resolveWith( resolveContexts, resolveValues ); } @@ -3413,7 +3434,7 @@ readyList = jQuery.Deferred(); // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one + // We once tried to use readyState "interactive" here, but it caused issues like the one // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready @@ -3507,7 +3528,7 @@ function Data() { - // Support: Android < 4, + // Support: Android<4, // Old WebKit does not have Object.preventExtensions/freeze method, // return new empty object instead with no [[set]] accessor Object.defineProperty( this.cache = {}, 0, { @@ -3516,7 +3537,7 @@ } }); - this.expando = jQuery.expando + Math.random(); + this.expando = jQuery.expando + Data.uid++; } Data.uid = 1; @@ -3544,7 +3565,7 @@ descriptor[ this.expando ] = { value: unlock }; Object.defineProperties( owner, descriptor ); - // Support: Android < 4 + // Support: Android<4 // Fallback to a less secure definition } catch ( e ) { descriptor[ this.expando ] = unlock; @@ -3684,17 +3705,16 @@ -/* - Implementation Summary - - 1. Enforce API surface and semantic compatibility with 1.9.x branch - 2. Improve the module's maintainability by reducing the storage - paths to a single mechanism. - 3. Use the same single mechanism to support "private" and "user" data. - 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) - 5. Avoid exposing implementation details on user objects (eg. expando properties) - 6. Provide a clear path for implementation upgrade to WeakMap in 2014 -*/ +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /([A-Z])/g; @@ -3899,7 +3919,7 @@ queue.unshift( "inprogress" ); } - // clear up the last queue stop function + // Clear up the last queue stop function delete hooks.stop; fn.call( elem, next, hooks ); } @@ -3909,7 +3929,7 @@ } }, - // not intended for public consumption - generates a queueHooks object, or returns the current one + // Not public - generate a queueHooks object, or return the current one _queueHooks: function( elem, type ) { var key = type + "queueHooks"; return data_priv.get( elem, key ) || data_priv.access( elem, key, { @@ -3939,7 +3959,7 @@ this.each(function() { var queue = jQuery.queue( this, type, data ); - // ensure a hooks for this queue + // Ensure a hooks for this queue jQuery._queueHooks( this, type ); if ( type === "fx" && queue[0] !== "inprogress" ) { @@ -4006,21 +4026,22 @@ div = fragment.appendChild( document.createElement( "div" ) ), input = document.createElement( "input" ); - // #11217 - WebKit loses check when the name is after the checked attribute + // Support: Safari<=5.1 + // Check state lost if the name is set (#11217) // Support: Windows Web Apps (WWA) - // `name` and `type` need .setAttribute for WWA + // `name` and `type` must use .setAttribute for WWA (#14901) input.setAttribute( "type", "radio" ); input.setAttribute( "checked", "checked" ); input.setAttribute( "name", "t" ); div.appendChild( input ); - // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 - // old WebKit doesn't clone checked state correctly in fragments + // Support: Safari<=5.1, Android<4.2 + // Older WebKit doesn't clone checked state correctly in fragments support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + // Support: IE<=11+ // Make sure textarea (and checkbox) defaultValue is properly cloned - // Support: IE9-IE11+ div.innerHTML = ""; support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; })(); @@ -4398,8 +4419,8 @@ j = 0; while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { event.handleObj = handleObj; @@ -4549,7 +4570,7 @@ event.target = document; } - // Support: Safari 6.0+, Chrome < 28 + // Support: Safari 6.0+, Chrome<28 // Target should not be a text node (#504, #13143) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; @@ -4654,7 +4675,7 @@ // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && - // Support: Android < 4.0 + // Support: Android<4.0 src.returnValue === false ? returnTrue : returnFalse; @@ -4744,8 +4765,8 @@ }; }); +// Support: Firefox, Chrome, Safari // Create "bubbling" focus and blur events -// Support: Firefox, Chrome, Safari if ( !support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { @@ -4898,7 +4919,7 @@ // We have to close these tags to support XHTML (#13200) wrapMap = { - // Support: IE 9 + // Support: IE9 option: [ 1, "" ], thead: [ 1, "", "
" ], @@ -4909,7 +4930,7 @@ _default: [ 0, "", "" ] }; -// Support: IE 9 +// Support: IE9 wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; @@ -4999,7 +5020,7 @@ ret; } -// Support: IE >= 9 +// Fix IE bugs, see support tests function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); @@ -5019,8 +5040,7 @@ clone = elem.cloneNode( true ), inPage = jQuery.contains( elem.ownerDocument, elem ); - // Support: IE >= 9 - // Fix Cloning issues + // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { @@ -5071,8 +5091,8 @@ // Add nodes directly if ( jQuery.type( elem ) === "object" ) { - // Support: QtWebKit - // jQuery.merge because push.apply(_, arraylike) throws + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node @@ -5094,15 +5114,14 @@ tmp = tmp.lastChild; } - // Support: QtWebKit - // jQuery.merge because push.apply(_, arraylike) throws + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; - // Fixes #12346 - // Support: Webkit, IE + // Ensure the created nodes are orphaned (#12392) tmp.textContent = ""; } } @@ -5464,7 +5483,7 @@ // getDefaultComputedStyle might be reliably used only on attached element display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? - // Use of this method is a temporary fix (more like optmization) until something better comes along, + // Use of this method is a temporary fix (more like optimization) until something better comes along, // since it was removed from specification and supported only in FF style.display : jQuery.css( elem[ 0 ], "display" ); @@ -5514,7 +5533,14 @@ var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { - return elem.ownerDocument.defaultView.getComputedStyle( elem, null ); + // Support: IE<=11+, Firefox<=30+ (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + if ( elem.ownerDocument.defaultView.opener ) { + return elem.ownerDocument.defaultView.getComputedStyle( elem, null ); + } + + return window.getComputedStyle( elem, null ); }; @@ -5526,7 +5552,7 @@ computed = computed || getStyles( elem ); // Support: IE9 - // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + // getPropertyValue is only needed for .css('filter') (#12537) if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; } @@ -5572,15 +5598,13 @@ return { get: function() { if ( conditionFn() ) { - // Hook not needed (or it's not possible to use it due to missing dependency), - // remove it. - // Since there are no other hooks for marginRight, remove the whole object. + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. delete this.get; return; } // Hook needed; redefine it so that the support test is not executed again. - return (this.get = hookFn).apply( this, arguments ); } }; @@ -5597,6 +5621,8 @@ return; } + // Support: IE9-11+ + // Style of cloned element affects source element cloned (#8908) div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; @@ -5629,6 +5655,7 @@ if ( window.getComputedStyle ) { jQuery.extend( support, { pixelPosition: function() { + // This test is executed only once but we still do memoizing // since we can use the boxSizingReliable pre-computing. // No need to check if the test was already performed, though. @@ -5642,6 +5669,7 @@ return boxSizingReliableVal; }, reliableMarginRight: function() { + // Support: Android 2.3 // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. (#3333) @@ -5663,6 +5691,7 @@ ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight ); docElem.removeChild( container ); + div.removeChild( marginDiv ); return ret; } @@ -5694,8 +5723,8 @@ var - // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" - // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + // Swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ), rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ), @@ -5708,15 +5737,15 @@ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; -// return a css property mapped to a potentially vendor prefixed property +// Return a css property mapped to a potentially vendor prefixed property function vendorPropName( style, name ) { - // shortcut for names that are not vendor prefixed + // Shortcut for names that are not vendor prefixed if ( name in style ) { return name; } - // check for vendor prefixed names + // Check for vendor prefixed names var capName = name[0].toUpperCase() + name.slice(1), origName = name, i = cssPrefixes.length; @@ -5749,7 +5778,7 @@ val = 0; for ( ; i < 4; i += 2 ) { - // both box models exclude margin, so add it if we want it + // Both box models exclude margin, so add it if we want it if ( extra === "margin" ) { val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); } @@ -5760,15 +5789,15 @@ val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } - // at this point, extra isn't border nor margin, so remove border + // At this point, extra isn't border nor margin, so remove border if ( extra !== "margin" ) { val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } else { - // at this point, extra isn't content, so add padding + // At this point, extra isn't content, so add padding val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - // at this point, extra isn't content nor padding, so add border + // At this point, extra isn't content nor padding, so add border if ( extra !== "padding" ) { val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } @@ -5786,7 +5815,7 @@ styles = getStyles( elem ), isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - // some non-html elements return undefined for offsetWidth, so check for null/undefined + // Some non-html elements return undefined for offsetWidth, so check for null/undefined // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 if ( val <= 0 || val == null ) { @@ -5801,7 +5830,7 @@ return val; } - // we need the check for style in case a browser which returns unreliable values + // Check for style in case a browser which returns unreliable values // for getComputedStyle silently falls back to the reliable elem.style valueIsBorderBox = isBorderBox && ( support.boxSizingReliable() || val === elem.style[ name ] ); @@ -5810,7 +5839,7 @@ val = parseFloat( val ) || 0; } - // use the active box-sizing model to add/subtract irrelevant styles + // Use the active box-sizing model to add/subtract irrelevant styles return ( val + augmentWidthOrHeight( elem, @@ -5874,12 +5903,14 @@ } jQuery.extend({ + // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { + // We should always get a number back from opacity var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; @@ -5907,12 +5938,12 @@ // Add in properties whose names you wish to fix before // setting or getting the value cssProps: { - // normalize float css property "float": "cssFloat" }, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; @@ -5925,33 +5956,32 @@ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); - // gets hook for the prefixed version - // followed by the unprefixed version + // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; - // convert relative number strings (+= or -=) to relative numbers. #7345 + // Convert "+=" or "-=" to relative numbers (#7345) if ( type === "string" && (ret = rrelNum.exec( value )) ) { value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); // Fixes bug #9237 type = "number"; } - // Make sure that null and NaN values aren't set. See: #7116 + // Make sure that null and NaN values aren't set (#7116) if ( value == null || value !== value ) { return; } - // If a number was passed in, add 'px' to the (except for certain CSS properties) + // If a number, add 'px' to the (except for certain CSS properties) if ( type === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; } - // Fixes #8908, it can be done more correctly by specifying setters in cssHooks, - // but it would mean to define eight (for every problematic property) identical functions + // Support: IE9-11+ + // background-* props affect original clone's values if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { style[ name ] = "inherit"; } @@ -5979,8 +6009,7 @@ // Make sure that we're working with the right name name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); - // gets hook for the prefixed version - // followed by the unprefixed version + // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // If a hook was provided get the computed value from there @@ -5993,12 +6022,12 @@ val = curCSS( elem, name, styles ); } - //convert "normal" to computed value + // Convert "normal" to computed value if ( val === "normal" && name in cssNormalTransform ) { val = cssNormalTransform[ name ]; } - // Return, converting to number if forced or a qualifier was provided and val looks numeric + // Make numeric if forced or a qualifier was provided and val looks numeric if ( extra === "" || extra ) { num = parseFloat( val ); return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; @@ -6011,8 +6040,9 @@ jQuery.cssHooks[ name ] = { get: function( elem, computed, extra ) { if ( computed ) { - // certain elements can have dimension info if we invisibly show them - // however, it must have a current display style that would benefit from this + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ? jQuery.swap( elem, cssShow, function() { return getWidthOrHeight( elem, name, extra ); @@ -6040,8 +6070,6 @@ jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight, function( elem, computed ) { if ( computed ) { - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - // Work around by temporarily setting element display to inline-block return jQuery.swap( elem, { "display": "inline-block" }, curCSS, [ elem, "marginRight" ] ); } @@ -6059,7 +6087,7 @@ var i = 0, expanded = {}, - // assumes a single number if not a string + // Assumes a single number if not a string parts = typeof value === "string" ? value.split(" ") : [ value ]; for ( ; i < 4; i++ ) { @@ -6182,17 +6210,18 @@ return tween.elem[ tween.prop ]; } - // passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails - // so, simple values such as "10px" are parsed to Float. - // complex values such as "rotate(1rad)" are returned as is. + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. result = jQuery.css( tween.elem, tween.prop, "" ); // Empty strings, null, undefined and "auto" are converted to 0. return !result || result === "auto" ? 0 : result; }, set: function( tween ) { - // use step hook for back compat - use cssHook if its there - use .style if its - // available and use plain properties where available + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { @@ -6206,7 +6235,6 @@ // Support: IE9 // Panic based approach to setting things on disconnected nodes - Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { set: function( tween ) { if ( tween.elem.nodeType && tween.elem.parentNode ) { @@ -6262,16 +6290,16 @@ start = +target || 1; do { - // If previous iteration zeroed out, double until we get *something* - // Use a string for doubling factor so we don't accidentally see scale as unchanged below + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below scale = scale || ".5"; // Adjust and apply start = start / scale; jQuery.style( tween.elem, prop, start + unit ); - // Update scale, tolerating zero or NaN from tween.cur() - // And breaking the loop if scale is unchanged or perfect, or if we've just had enough + // Update scale, tolerating zero or NaN from tween.cur(), + // break the loop if scale is unchanged or perfect, or if we've just had enough } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); } @@ -6303,8 +6331,8 @@ i = 0, attrs = { height: type }; - // if we include width, step value is 1 to do all cssExpand values, - // if we don't include width, step value is 2 to skip over Left and Right + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right includeWidth = includeWidth ? 1 : 0; for ( ; i < 4 ; i += 2 - includeWidth ) { which = cssExpand[ i ]; @@ -6326,7 +6354,7 @@ for ( ; index < length; index++ ) { if ( (tween = collection[ index ].call( animation, prop, value )) ) { - // we're done with this property + // We're done with this property return tween; } } @@ -6341,7 +6369,7 @@ hidden = elem.nodeType && isHidden( elem ), dataShow = data_priv.get( elem, "fxshow" ); - // handle queue: false promises + // Handle queue: false promises if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { @@ -6356,8 +6384,7 @@ hooks.unqueued++; anim.always(function() { - // doing this makes sure that the complete handler will be called - // before this completes + // Ensure the complete handler is called before this completes anim.always(function() { hooks.unqueued--; if ( !jQuery.queue( elem, "fx" ).length ) { @@ -6367,7 +6394,7 @@ }); } - // height/width overflow pass + // Height/width overflow pass if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE9-10 do not @@ -6429,7 +6456,7 @@ dataShow = data_priv.access( elem, "fxshow", {} ); } - // store state if its toggle - enables .stop().toggle() to "reverse" + // Store state if its toggle - enables .stop().toggle() to "reverse" if ( toggle ) { dataShow.hidden = !hidden; } @@ -6489,8 +6516,8 @@ value = hooks.expand( value ); delete props[ name ]; - // not quite $.extend, this wont overwrite keys already present. - // also - reusing 'index' from above because we have the correct "name" + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" for ( index in value ) { if ( !( index in props ) ) { props[ index ] = value[ index ]; @@ -6509,7 +6536,7 @@ index = 0, length = animationPrefilters.length, deferred = jQuery.Deferred().always( function() { - // don't match elem in the :animated selector + // Don't match elem in the :animated selector delete tick.elem; }), tick = function() { @@ -6518,7 +6545,8 @@ } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) + // Support: Android 2.3 + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, @@ -6554,7 +6582,7 @@ }, stop: function( gotoEnd ) { var index = 0, - // if we are going to the end, we want to run all the tweens + // If we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; if ( stopped ) { @@ -6565,8 +6593,7 @@ animation.tweens[ index ].run( 1 ); } - // resolve when we played the last frame - // otherwise, reject + // Resolve when we played the last frame; otherwise, reject if ( gotoEnd ) { deferred.resolveWith( elem, [ animation, gotoEnd ] ); } else { @@ -6648,7 +6675,7 @@ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; - // normalize opt.queue - true/undefined/null -> "fx" + // Normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } @@ -6672,10 +6699,10 @@ jQuery.fn.extend({ fadeTo: function( speed, to, easing, callback ) { - // show any hidden elements after setting opacity to 0 + // Show any hidden elements after setting opacity to 0 return this.filter( isHidden ).css( "opacity", 0 ).show() - // animate to the value specified + // Animate to the value specified .end().animate({ opacity: to }, speed, easing, callback ); }, animate: function( prop, speed, easing, callback ) { @@ -6738,9 +6765,9 @@ } } - // start the next in the queue if the last step wasn't forced - // timers currently will call their complete callbacks, which will dequeue - // but only if they were gotoEnd + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } @@ -6758,17 +6785,17 @@ timers = jQuery.timers, length = queue ? queue.length : 0; - // enable finishing flag on private data + // Enable finishing flag on private data data.finish = true; - // empty the queue first + // Empty the queue first jQuery.queue( this, type, [] ); if ( hooks && hooks.stop ) { hooks.stop.call( this, true ); } - // look for any active animations, and finish them + // Look for any active animations, and finish them for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && timers[ index ].queue === type ) { timers[ index ].anim.stop( true ); @@ -6776,14 +6803,14 @@ } } - // look for any animations in the old queue and finish them + // Look for any animations in the old queue and finish them for ( index = 0; index < length; index++ ) { if ( queue[ index ] && queue[ index ].finish ) { queue[ index ].finish.call( this ); } } - // turn off finishing flag + // Turn off finishing flag delete data.finish; }); } @@ -6886,21 +6913,21 @@ input.type = "checkbox"; - // Support: iOS 5.1, Android 4.x, Android 2.3 - // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere) + // Support: iOS<=5.1, Android<=4.2+ + // Default value for a checkbox should be "on" support.checkOn = input.value !== ""; - // Must access the parent to make an option select properly - // Support: IE9, IE10 + // Support: IE<=11+ + // Must access selectedIndex to make default options select support.optSelected = opt.selected; - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) + // Support: Android<=2.3 + // Options inside disabled selects are incorrectly marked as disabled select.disabled = true; support.optDisabled = !opt.disabled; - // Check if an input maintains its value after becoming a radio - // Support: IE9, IE10 + // Support: IE<=11+ + // An input loses its value after becoming a radio input = document.createElement( "input" ); input.value = "t"; input.type = "radio"; @@ -6997,8 +7024,6 @@ set: function( elem, value ) { if ( !support.radioValue && value === "radio" && jQuery.nodeName( elem, "input" ) ) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to default in case type is set after value during creation var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { @@ -7068,7 +7093,7 @@ var ret, hooks, notxml, nType = elem.nodeType; - // don't get/set properties on text, comment and attribute nodes + // Don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } @@ -7104,8 +7129,6 @@ } }); -// Support: IE9+ -// Selectedness for an option in an optgroup can be inaccurate if ( !support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { @@ -7213,7 +7236,7 @@ } } - // only assign if different to avoid unneeded rendering. + // Only assign if different to avoid unneeded rendering. finalValue = value ? jQuery.trim( cur ) : ""; if ( elem.className !== finalValue ) { elem.className = finalValue; @@ -7240,14 +7263,14 @@ return this.each(function() { if ( type === "string" ) { - // toggle individual class names + // Toggle individual class names var className, i = 0, self = jQuery( this ), classNames = value.match( rnotwhite ) || []; while ( (className = classNames[ i++ ]) ) { - // check each className given, space separated list + // Check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { @@ -7262,7 +7285,7 @@ data_priv.set( this, "__className__", this.className ); } - // If the element has a class name or if we're passed "false", + // If the element has a class name or if we're passed `false`, // then remove the whole classname (if there was one, the above saved it). // Otherwise bring back whatever was previously saved (if anything), // falling back to the empty string if nothing was stored. @@ -7306,9 +7329,9 @@ ret = elem.value; return typeof ret === "string" ? - // handle most common string cases + // Handle most common string cases ret.replace(rreturn, "") : - // handle cases where value is null/undef or number + // Handle cases where value is null/undef or number ret == null ? "" : ret; } @@ -7416,7 +7439,7 @@ } } - // force browsers to behave consistently when non-matching value is set + // Force browsers to behave consistently when non-matching value is set if ( !optionSet ) { elem.selectedIndex = -1; } @@ -7437,8 +7460,6 @@ }; if ( !support.checkOn ) { jQuery.valHooks[ this ].get = function( elem ) { - // Support: Webkit - // "" is returned instead of "on" if a value isn't specified return elem.getAttribute("value") === null ? "on" : elem.value; }; } @@ -7520,10 +7541,6 @@ var - // Document location - ajaxLocParts, - ajaxLocation, - rhash = /#.*$/, rts = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, @@ -7552,22 +7569,13 @@ transports = {}, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat("*"); - -// #8138, IE may throw an exception when accessing -// a field from window.location if document.domain has been set -try { - ajaxLocation = location.href; -} catch( e ) { - // Use the href attribute of an A element - // since IE will modify it given document.location - ajaxLocation = document.createElement( "a" ); - ajaxLocation.href = ""; - ajaxLocation = ajaxLocation.href; -} - -// Segment location into parts -ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + allTypes = "*/".concat( "*" ), + + // Document location + ajaxLocation = window.location.href, + + // Segment location into parts + ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { @@ -8046,7 +8054,8 @@ } // We can fire global events as of now if asked to - fireGlobals = s.global; + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { @@ -8119,7 +8128,7 @@ return jqXHR.abort(); } - // aborting is no longer a cancellation + // Aborting is no longer a cancellation strAbort = "abort"; // Install callbacks on deferreds @@ -8231,8 +8240,7 @@ isSuccess = !error; } } else { - // We extract error from statusText - // then normalize statusText and status for non-aborts + // Extract error from statusText and normalize for non-aborts error = statusText; if ( status || !statusText ) { statusText = "error"; @@ -8288,7 +8296,7 @@ jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { - // shift arguments if data argument was omitted + // Shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; @@ -8305,13 +8313,6 @@ }; }); -// Attach a bunch of functions for handling common AJAX events -jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) { - jQuery.fn[ type ] = function( fn ) { - return this.on( type, fn ); - }; -}); - jQuery._evalUrl = function( url ) { return jQuery.ajax({ @@ -8529,8 +8530,9 @@ // Support: IE9 // Open requests must be manually aborted on unload (#5280) -if ( window.ActiveXObject ) { - jQuery( window ).on( "unload", function() { +// See https://support.microsoft.com/kb/2856746 for more info +if ( window.attachEvent ) { + window.attachEvent( "onunload", function() { for ( var key in xhrCallbacks ) { xhrCallbacks[ key ](); } @@ -8883,6 +8885,16 @@ +// Attach a bunch of functions for handling common AJAX events +jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) { + jQuery.fn[ type ] = function( fn ) { + return this.on( type, fn ); + }; +}); + + + + jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { return elem === fn.elem; @@ -8919,7 +8931,8 @@ calculatePosition = ( position === "absolute" || position === "fixed" ) && ( curCSSTop + curCSSLeft ).indexOf("auto") > -1; - // Need to be able to calculate position if either top or left is auto and position is either absolute or fixed + // Need to be able to calculate position if either + // top or left is auto and position is either absolute or fixed if ( calculatePosition ) { curPosition = curElem.position(); curTop = curPosition.top; @@ -8976,8 +8989,8 @@ return box; } + // Support: BlackBerry 5, iOS 3 (original iPhone) // If we don't have gBCR, just use 0,0 rather than error - // BlackBerry 5, iOS 3 (original iPhone) if ( typeof elem.getBoundingClientRect !== strundefined ) { box = elem.getBoundingClientRect(); } @@ -8999,7 +9012,7 @@ // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent if ( jQuery.css( elem, "position" ) === "fixed" ) { - // We assume that getBoundingClientRect is available when computed position is fixed + // Assume getBoundingClientRect is there when computed position is fixed offset = elem.getBoundingClientRect(); } else { @@ -9062,16 +9075,18 @@ }; }); +// Support: Safari<7+, Chrome<37+ // Add the top/left cssHooks using jQuery.fn.position // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 -// getComputedStyle returns percent when specified for top/left/bottom/right -// rather than make the css module depend on the offset module, we just check for it here +// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280 +// getComputedStyle returns percent when specified for top/left/bottom/right; +// rather than make the css module depend on the offset module, just check for it here jQuery.each( [ "top", "left" ], function( i, prop ) { jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, function( elem, computed ) { if ( computed ) { computed = curCSS( elem, prop ); - // if curCSS returns percentage, fallback to offset + // If curCSS returns percentage, fallback to offset return rnumnonpx.test( computed ) ? jQuery( elem ).position()[ prop ] + "px" : computed; @@ -9084,7 +9099,7 @@ // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { - // margin is only for outerHeight, outerWidth + // Margin is only for outerHeight, outerWidth jQuery.fn[ funcName ] = function( margin, value ) { var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); @@ -9175,8 +9190,8 @@ return jQuery; }; -// Expose jQuery and $ identifiers, even in -// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557) +// Expose jQuery and $ identifiers, even in AMD +// (#7102#comment:10, https://github.com/jquery/jquery/pull/557) // and CommonJS for browser emulators (#13566) if ( typeof noGlobal === strundefined ) { window.jQuery = window.$ = jQuery; @@ -9190,7 +9205,7 @@ })); /** - * @license AngularJS v1.3.0-rc.5 + * @license AngularJS v1.3.8 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -9228,45 +9243,28 @@ function minErr(module, ErrorConstructor) { ErrorConstructor = ErrorConstructor || Error; - return function () { + return function() { var code = arguments[0], prefix = '[' + (module ? module + ':' : '') + code + '] ', template = arguments[1], templateArgs = arguments, - stringify = function (obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else if (typeof obj !== 'string') { - return JSON.stringify(obj); - } - return obj; - }, + message, i; - message = prefix + template.replace(/\{\d+\}/g, function (match) { + message = prefix + template.replace(/\{\d+\}/g, function(match) { var index = +match.slice(1, -1), arg; if (index + 2 < templateArgs.length) { - arg = templateArgs[index + 2]; - if (typeof arg === 'function') { - return arg.toString().replace(/ ?\{[\s\S]*$/, ''); - } else if (typeof arg === 'undefined') { - return 'undefined'; - } else if (typeof arg !== 'string') { - return toJson(arg); - } - return arg; + return toDebugString(templateArgs[index + 2]); } return match; }); - message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.5/' + + message = message + '\nhttp://errors.angularjs.org/1.3.8/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { - message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + - encodeURIComponent(stringify(arguments[i])); + message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + + encodeURIComponent(toDebugString(arguments[i])); } return new ErrorConstructor(message); }; @@ -9317,16 +9315,16 @@ isWindow: true, isScope: true, isFile: true, + isFormData: true, isBlob: true, isBoolean: true, isPromiseLike: true, trim: true, + escapeForRegexp: true, isElement: true, makeMap: true, - size: true, includes: true, arrayRemove: true, - isLeafNode: true, copy: true, shallowCopy: true, equals: true, @@ -9396,7 +9394,7 @@ * @param {string} string String to be converted to lowercase. * @returns {string} Lowercased string. */ -var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; +var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; var hasOwnProperty = Object.prototype.hasOwnProperty; /** @@ -9409,7 +9407,7 @@ * @param {string} string String to be converted to uppercase. * @returns {string} Uppercased string. */ -var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; +var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; var manualLowercase = function(s) { @@ -9435,8 +9433,8 @@ } -var /** holds major version number for IE or NaN for real browsers */ - msie, +var + msie, // holds major version number for IE, or NaN if UA is not IE. jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding slice = [].slice, @@ -9493,6 +9491,11 @@ * It is worth noting that `.forEach` does not iterate over inherited properties because it filters * using the `hasOwnProperty` method. * + * Unlike ES262's + * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), + * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just + * return the value provided. + * ```js var values = {name: 'misko', gender: 'male'}; var log = []; @@ -9540,18 +9543,12 @@ } function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys.sort(); + return Object.keys(obj).sort(); } function forEachSorted(obj, iterator, context) { var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys.length; i++) { iterator.call(context, obj[keys[i]], keys[i]); } return keys; @@ -9604,7 +9601,9 @@ * * @description * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) - * to `dst`. You can specify multiple `src` objects. + * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so + * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. + * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy). * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). @@ -9634,7 +9633,7 @@ function inherit(parent, extra) { - return extend(new (extend(function() {}, {prototype:parent}))(), extra); + return extend(Object.create(parent), extra); } /** @@ -9672,6 +9671,8 @@ return (transformationFn || angular.identity)(value); }; ``` + * @param {*} value to be returned. + * @returns {*} the value passed in. */ function identity($) {return $;} identity.$inject = []; @@ -9691,7 +9692,7 @@ * @param {*} value Reference to check. * @returns {boolean} True if `value` is undefined. */ -function isUndefined(value){return typeof value === 'undefined';} +function isUndefined(value) {return typeof value === 'undefined';} /** @@ -9706,7 +9707,7 @@ * @param {*} value Reference to check. * @returns {boolean} True if `value` is defined. */ -function isDefined(value){return typeof value !== 'undefined';} +function isDefined(value) {return typeof value !== 'undefined';} /** @@ -9722,7 +9723,7 @@ * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Object` but not `null`. */ -function isObject(value){ +function isObject(value) { // http://jsperf.com/isobject4 return value !== null && typeof value === 'object'; } @@ -9740,7 +9741,7 @@ * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `String`. */ -function isString(value){return typeof value === 'string';} +function isString(value) {return typeof value === 'string';} /** @@ -9755,7 +9756,7 @@ * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Number`. */ -function isNumber(value){return typeof value === 'number';} +function isNumber(value) {return typeof value === 'number';} /** @@ -9801,7 +9802,7 @@ * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Function`. */ -function isFunction(value){return typeof value === 'function';} +function isFunction(value) {return typeof value === 'function';} /** @@ -9838,6 +9839,11 @@ } +function isFormData(obj) { + return toString.call(obj) === '[object FormData]'; +} + + function isBlob(obj) { return toString.call(obj) === '[object Blob]'; } @@ -9857,6 +9863,14 @@ return isString(value) ? value.trim() : value; }; +// Copied from: +// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 +// Prereq: s is a string. +var escapeForRegexp = function(s) { + return s.replace(/([-()\[\]{}+?*.$\^|,:#=0) + if (index >= 0) array.splice(index, 1); return value; } -function isLeafNode (node) { - if (node) { - switch (nodeName_(node)) { - case "option": - case "pre": - case "title": - return true; - } - } - return false; -} - /** * @ngdoc function * @name angular.copy @@ -9953,7 +9927,7 @@ * Creates a deep copy of `source`, which should be an object or an array. * * * If no destination is supplied, a copy of the object or array is created. - * * If a destination is provided, all of its elements (for array) or properties (for objects) + * * If a destination is provided, all of its elements (for arrays) or properties (for objects) * are deleted and then all elements/properties from the source are copied to it. * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. * * If `source` is identical to 'destination' an exception will be thrown. @@ -10040,7 +10014,7 @@ var result; if (isArray(source)) { destination.length = 0; - for ( var i = 0; i < source.length; i++) { + for (var i = 0; i < source.length; i++) { result = copy(source[i], null, stackSource, stackDest); if (isObject(source[i])) { stackSource.push(source[i]); @@ -10057,8 +10031,8 @@ delete destination[key]; }); } - for ( var key in source) { - if(source.hasOwnProperty(key)) { + for (var key in source) { + if (source.hasOwnProperty(key)) { result = copy(source[key], null, stackSource, stackDest); if (isObject(source[key])) { stackSource.push(source[key]); @@ -10139,7 +10113,7 @@ if (isArray(o1)) { if (!isArray(o2)) return false; if ((length = o1.length) == o2.length) { - for(key=0; key').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : elemHtml. match(/^(<[^>]+>)/)[1]. replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); - } catch(e) { + } catch (e) { return lowercase(elemHtml); } @@ -10332,7 +10310,7 @@ function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); - } catch(e) { + } catch (e) { // Ignore any invalid uri component } } @@ -10345,14 +10323,14 @@ function parseKeyValue(/**string*/keyValue) { var obj = {}, key_value, key; forEach((keyValue || "").split('&'), function(keyValue) { - if ( keyValue ) { + if (keyValue) { key_value = keyValue.replace(/\+/g,'%20').split('='); key = tryDecodeURIComponent(key_value[0]); - if ( isDefined(key) ) { + if (isDefined(key)) { var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; if (!hasOwnProperty.call(obj, key)) { obj[key] = val; - } else if(isArray(obj[key])) { + } else if (isArray(obj[key])) { obj[key].push(val); } else { obj[key] = [obj[key],val]; @@ -10425,7 +10403,7 @@ function getNgAttribute(element, ngAttr) { var attr, i, ii = ngAttrPrefixes.length; element = jqLite(element); - for (i=0; i @@ -10635,8 +10613,8 @@ * @param {Object=} config an object for defining configuration options for the application. The * following keys are supported: * - * - `strictDi`: disable automatic function annotation for the application. This is meant to - * assist in finding bugs which break minified code. + * * `strictDi` - disable automatic function annotation for the application. This is meant to + * assist in finding bugs which break minified code. Defaults to `false`. * * @returns {auto.$injector} Returns the newly created injector for this app. */ @@ -10728,7 +10706,12 @@ * @param {DOMElement} element DOM element which is the root of angular application. */ function getTestability(rootElement) { - return angular.element(rootElement).injector().get('$$testability'); + var injector = angular.element(rootElement).injector(); + if (!injector) { + throw ngMinErr('test', + 'no injector found for element argument to getTestability'); + } + return injector.get('$$testability'); } var SNAKE_CASE_REGEXP = /[A-Z]/g; @@ -11115,7 +11098,7 @@ * }) * ``` * - * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * See {@link ng.$animateProvider#register $animateProvider.register()} and * {@link ngAnimate ngAnimate module} for more information. */ animation: invokeLater('$animateProvider', 'register'), @@ -11165,7 +11148,7 @@ * @description * Use this method to register work which needs to be performed on module loading. * For more about how to configure services, see - * {@link providers#providers_provider-recipe Provider Recipe}. + * {@link providers#provider-recipe Provider Recipe}. */ config: config, @@ -11189,7 +11172,7 @@ config(configFn); } - return moduleInstance; + return moduleInstance; /** * @param {string} provider @@ -11210,6 +11193,34 @@ } +/* global: toDebugString: true */ + +function serializeObject(obj) { + var seen = []; + + return JSON.stringify(obj, function(key, val) { + val = toJsonReplacer(key, val); + if (isObject(val)) { + + if (seen.indexOf(val) >= 0) return '<>'; + + seen.push(val); + } + return val; + }); +} + +function toDebugString(obj) { + if (typeof obj === 'function') { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (typeof obj === 'undefined') { + return 'undefined'; + } else if (typeof obj !== 'string') { + return serializeObject(obj); + } + return obj; +} + /* global angularModule: true, version: true, @@ -11293,7 +11304,8 @@ $TimeoutProvider, $$RAFProvider, $$AsyncCallbackProvider, - $WindowProvider + $WindowProvider, + $$jqLiteProvider */ @@ -11312,15 +11324,15 @@ * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.3.0-rc.5', // all of these placeholder strings will be replaced by grunt's + full: '1.3.8', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, - dot: 0, - codeName: 'impossible-choreography' -}; - - -function publishExternalAPI(angular){ + dot: 8, + codeName: 'prophetic-narwhal' +}; + + +function publishExternalAPI(angular) { extend(angular, { 'bootstrap': bootstrap, 'copy': copy, @@ -11446,7 +11458,8 @@ $timeout: $TimeoutProvider, $window: $WindowProvider, $$rAF: $$RAFProvider, - $$asyncCallback : $$AsyncCallbackProvider + $$asyncCallback: $$AsyncCallbackProvider, + $$jqLite: $$jqLiteProvider }); } ]); @@ -11491,12 +11504,12 @@ * - [`addClass()`](http://api.jquery.com/addClass/) * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) - * - [`attr()`](http://api.jquery.com/attr/) + * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters * - [`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/) - * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()` + * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()` * - [`data()`](http://api.jquery.com/data/) * - [`detach()`](http://api.jquery.com/detach/) * - [`empty()`](http://api.jquery.com/empty/) @@ -11539,10 +11552,12 @@ * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current - * element or its parent. + * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to + * be enabled. * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the * current element. This getter should be used only on elements that contain a directive which starts a new isolate * scope. Calling `scope()` on this element always returns the original non-isolate scope. + * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * @@ -11574,7 +11589,7 @@ var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; -var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"}; +var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; var jqLiteMinErr = minErr('jqLite'); /** @@ -11703,7 +11718,7 @@ return element.cloneNode(true); } -function jqLiteDealoc(element, onlyDescendants){ +function jqLiteDealoc(element, onlyDescendants) { if (!onlyDescendants) jqLiteRemoveData(element); if (element.querySelectorAll) { @@ -11726,18 +11741,22 @@ if (!type) { for (type in events) { if (type !== '$destroy') { - removeEventListenerFn(element, type, events[type]); + removeEventListenerFn(element, type, handle); } delete events[type]; } } else { forEach(type.split(' '), function(type) { - if (isUndefined(fn)) { - removeEventListenerFn(element, type, events[type]); - delete events[type]; - } else { - arrayRemove(events[type] || [], fn); - } + if (isDefined(fn)) { + var listenerFns = events[type]; + arrayRemove(listenerFns || [], fn); + if (listenerFns && listenerFns.length > 0) { + return; + } + } + + removeEventListenerFn(element, type, handle); + delete events[type]; }); } } @@ -11806,7 +11825,7 @@ function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf( " " + selector + " " ) > -1); + indexOf(" " + selector + " ") > -1); } function jqLiteRemoveClass(element, cssClasses) { @@ -11865,13 +11884,13 @@ function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); + return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); } function jqLiteInheritedData(element, name, value) { // if element is the document object work with the html element instead // this makes $(document).scope() possible - if(element.nodeType == NODE_TYPE_DOCUMENT) { + if (element.nodeType == NODE_TYPE_DOCUMENT) { element = element.documentElement; } var names = isArray(name) ? name : [name]; @@ -11901,6 +11920,20 @@ if (parent) parent.removeChild(element); } + +function jqLiteDocumentLoaded(action, win) { + win = win || window; + if (win.document.readyState === 'complete') { + // Force the action to be run async for consistent behaviour + // from the action's point of view + // i.e. it will definitely not be in a $apply + win.setTimeout(action); + } else { + // No need to unbind this handler as load is only ever called once + jqLite(win).on('load', action); + } +} + ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// @@ -11915,7 +11948,7 @@ } // check if document is already loaded - if (document.readyState === 'complete'){ + if (document.readyState === 'complete') { setTimeout(trigger); } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 @@ -11923,12 +11956,11 @@ // jshint -W064 JQLite(window).on('load', trigger); // fallback to window.onload for others // jshint +W064 - this.on('DOMContentLoaded', trigger); } }, toString: function() { var value = []; - forEach(this, function(e){ value.push('' + e);}); + forEach(this, function(e) { value.push('' + e);}); return '[' + value.join(', ') + ']'; }, @@ -11956,11 +11988,11 @@ BOOLEAN_ELEMENTS[value] = true; }); var ALIASED_ATTR = { - 'ngMinlength' : 'minlength', - 'ngMaxlength' : 'maxlength', - 'ngMin' : 'min', - 'ngMax' : 'max', - 'ngPattern' : 'pattern' + 'ngMinlength': 'minlength', + 'ngMaxlength': 'maxlength', + 'ngMin': 'min', + 'ngMax': 'max', + 'ngPattern': 'pattern' }; function getBooleanAttrName(element, name) { @@ -12019,7 +12051,7 @@ } }, - attr: function(element, name, value){ + attr: function(element, name, value) { var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { @@ -12032,7 +12064,7 @@ } } else { return (element[name] || - (element.attributes.getNamedItem(name)|| noop).specified) + (element.attributes.getNamedItem(name) || noop).specified) ? lowercasedName : undefined; } @@ -12072,7 +12104,7 @@ if (isUndefined(value)) { if (element.multiple && nodeName_(element) === 'select') { var result = []; - forEach(element.options, function (option) { + forEach(element.options, function(option) { if (option.selected) { result.push(option.value || option.text); } @@ -12093,7 +12125,7 @@ }, empty: jqLiteEmpty -}, function(fn, name){ +}, function(fn, name) { /** * Properties: writes return selection, reads return first value */ @@ -12145,7 +12177,7 @@ }); function createEventHandler(element, events) { - var eventHandler = function (event, type) { + var eventHandler = function(event, type) { // jQuery specific api event.isDefaultPrevented = function() { return event.defaultPrevented; @@ -12201,7 +12233,7 @@ forEach({ removeData: jqLiteRemoveData, - on: function jqLiteOn(element, type, fn, unsupported){ + on: function jqLiteOn(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); // Do not add event handlers to non-elements because they will not be cleaned up. @@ -12237,7 +12269,7 @@ var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !target.contains(related)) ){ + if (!related || (related !== target && !target.contains(related))) { handle(event, type); } }); @@ -12271,7 +12303,7 @@ replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node){ + forEach(new JQLite(replaceNode), function(node) { if (index) { parent.insertBefore(node, index.nextSibling); } else { @@ -12283,7 +12315,7 @@ children: function(element) { var children = []; - forEach(element.childNodes, function(element){ + forEach(element.childNodes, function(element) { if (element.nodeType === NODE_TYPE_ELEMENT) children.push(element); }); @@ -12309,7 +12341,7 @@ prepend: function(element, node) { if (element.nodeType === NODE_TYPE_ELEMENT) { var index = element.firstChild; - forEach(new JQLite(node), function(child){ + forEach(new JQLite(node), function(child) { element.insertBefore(child, index); }); } @@ -12346,7 +12378,7 @@ toggleClass: function(element, selector, condition) { if (selector) { - forEach(selector.split(' '), function(className){ + forEach(selector.split(' '), function(className) { var classCondition = condition; if (isUndefined(classCondition)) { classCondition = !jqLiteHasClass(element, className); @@ -12411,14 +12443,14 @@ }); } } -}, function(fn, name){ +}, function(fn, name) { /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; - for(var i = 0, ii = this.length; i < ii; i++) { + for (var i = 0, ii = this.length; i < ii; i++) { if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); if (isDefined(value)) { @@ -12437,6 +12469,27 @@ JQLite.prototype.unbind = JQLite.prototype.off; }); + +// Provider for private $$jqLite service +function $$jqLiteProvider() { + this.$get = function $$jqLite() { + return extend(JQLite, { + hasClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteHasClass(node, classes); + }, + addClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteAddClass(node, classes); + }, + removeClass: function(node, classes) { + if (node.attr) node = node[0]; + return jqLiteRemoveClass(node, classes); + } + }); + }; +} + /** * Computes a hash of an 'obj'. * Hash of a: @@ -12520,10 +12573,11 @@ * Creates an injector object that can be used for retrieving services as well as for * dependency injection (see {@link guide/di dependency injection}). * - * @param {Array.} modules A list of module functions or their aliases. See - * {@link angular.module}. The `ng` module must be explicitly added. - * @returns {function()} Injector object. See {@link auto.$injector $injector}. + * {@link angular.module}. The `ng` module must be explicitly added. + * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which + * disallows argument name annotation inference. + * @returns {injector} Injector object. See {@link auto.$injector $injector}. * * @example * Typical usage @@ -12668,8 +12722,10 @@ * ## Inference * * In JavaScript calling `toString()` on a function returns the function definition. The definition - * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with - * minification, and obfuscation tools since these tools change the argument names. + * can then be parsed and the function arguments can be extracted. This method of discovering + * annotations is disallowed when the injector is in strict mode. + * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the + * argument names. * * ## `$inject` Annotation * By adding an `$inject` property onto a function the injection parameters can be specified. @@ -12686,6 +12742,7 @@ * Return an instance of the service. * * @param {string} name The name of the instance to retrieve. + * @param {string} caller An optional string to provide the origin of the function call for error messages. * @return {*} The instance. */ @@ -12754,6 +12811,8 @@ * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * + * You can disallow this method by using strict injection mode. + * * This method does not work with code minification / obfuscation. For this reason the following * annotation strategies are supported. * @@ -12806,6 +12865,8 @@ * @param {Function|Array.} fn Function for which dependent service names need to * be retrieved as described above. * + * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. + * * @returns {Array.} The names of the services which the function requires. */ @@ -13132,14 +13193,17 @@ } }, providerInjector = (providerCache.$injector = - createInternalInjector(providerCache, function() { + createInternalInjector(providerCache, function(serviceName, caller) { + if (angular.isString(caller)) { + path.push(caller); + } throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); })), instanceCache = {}, instanceInjector = (instanceCache.$injector = - createInternalInjector(instanceCache, function(servicename) { - var provider = providerInjector.get(servicename + providerSuffix); - return instanceInjector.invoke(provider.$get, provider, undefined, servicename); + createInternalInjector(instanceCache, function(serviceName, caller) { + var provider = providerInjector.get(serviceName + providerSuffix, caller); + return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); @@ -13174,7 +13238,7 @@ function enforceReturnValue(name, factory) { return function enforcedReturnValue() { - var result = instanceInjector.invoke(factory); + var result = instanceInjector.invoke(factory, this); if (isUndefined(result)) { throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); } @@ -13215,7 +13279,7 @@ //////////////////////////////////// // Module Loading //////////////////////////////////// - function loadModules(modulesToLoad){ + function loadModules(modulesToLoad) { var runBlocks = [], moduleFn; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; @@ -13223,7 +13287,7 @@ function runInvokeQueue(queue) { var i, ii; - for(i = 0, ii = queue.length; i < ii; i++) { + for (i = 0, ii = queue.length; i < ii; i++) { var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); @@ -13269,7 +13333,7 @@ function createInternalInjector(cache, factory) { - function getService(serviceName) { + function getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', @@ -13280,7 +13344,7 @@ try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; - return cache[serviceName] = factory(serviceName); + return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; @@ -13303,7 +13367,7 @@ length, i, key; - for(i = 0, length = $inject.length; i < length; i++) { + for (i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', @@ -13312,7 +13376,7 @@ args.push( locals && locals.hasOwnProperty(key) ? locals[key] - : getService(key) + : getService(key, serviceName) ); } if (isArray(fn)) { @@ -13325,14 +13389,11 @@ } function instantiate(Type, locals, serviceName) { - var Constructor = function() {}, - instance, returnedValue; - // Check if Type is annotated and use just the given function at n-1 as parameter // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); - Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; - instance = new Constructor(); - returnedValue = invoke(Type, instance, locals, serviceName); + // Object creation: http://jsperf.com/create-constructor/2 + var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype); + var returnedValue = invoke(Type, instance, locals, serviceName); return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; } @@ -13352,93 +13413,251 @@ createInjector.$$annotate = annotate; /** - * @ngdoc service - * @name $anchorScroll - * @kind function - * @requires $window - * @requires $location - * @requires $rootScope - * - * @description - * When called, it checks current value of `$location.hash()` and scrolls to the related element, - * according to rules specified in - * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). - * - * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. - * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. - * - * @example - - -
- Go to bottom - You're at the bottom! -
-
- - angular.module('anchorScrollExample', []) - .controller('ScrollController', ['$scope', '$location', '$anchorScroll', - function ($scope, $location, $anchorScroll) { - $scope.gotoBottom = function() { - // set the location.hash to the id of - // the element you wish to scroll to. - $location.hash('bottom'); - - // call $anchorScroll() - $anchorScroll(); - }; - }]); - - - #scrollArea { - height: 350px; - overflow: auto; - } - - #bottom { - display: block; - margin-top: 2000px; - } - -
+ * @ngdoc provider + * @name $anchorScrollProvider + * + * @description + * Use `$anchorScrollProvider` to disable automatic scrolling whenever + * {@link ng.$location#hash $location.hash()} changes. */ function $AnchorScrollProvider() { var autoScrollingEnabled = true; + /** + * @ngdoc method + * @name $anchorScrollProvider#disableAutoScrolling + * + * @description + * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to + * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.
+ * Use this method to disable automatic scrolling. + * + * If automatic scrolling is disabled, one must explicitly call + * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the + * current hash. + */ this.disableAutoScrolling = function() { autoScrollingEnabled = false; }; + /** + * @ngdoc service + * @name $anchorScroll + * @kind function + * @requires $window + * @requires $location + * @requires $rootScope + * + * @description + * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and + * scrolls to the related element, according to the rules specified in the + * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). + * + * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to + * match any anchor whenever it changes. This can be disabled by calling + * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. + * + * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a + * vertical scroll-offset (either fixed or dynamic). + * + * @property {(number|function|jqLite)} yOffset + * If set, specifies a vertical scroll-offset. This is often useful when there are fixed + * positioned elements at the top of the page, such as navbars, headers etc. + * + * `yOffset` can be specified in various ways: + * - **number**: A fixed number of pixels to be used as offset.

+ * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return + * a number representing the offset (in pixels).

+ * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from + * the top of the page to the element's bottom will be used as offset.
+ * **Note**: The element will be taken into account only as long as its `position` is set to + * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust + * their height and/or positioning according to the viewport's size. + * + *
+ *
+ * In order for `yOffset` to work properly, scrolling should take place on the document's root and + * not some child element. + *
+ * + * @example + + +
+ Go to bottom + You're at the bottom! +
+
+ + angular.module('anchorScrollExample', []) + .controller('ScrollController', ['$scope', '$location', '$anchorScroll', + function ($scope, $location, $anchorScroll) { + $scope.gotoBottom = function() { + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); + + // call $anchorScroll() + $anchorScroll(); + }; + }]); + + + #scrollArea { + height: 280px; + overflow: auto; + } + + #bottom { + display: block; + margin-top: 2000px; + } + +
+ * + *
+ * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). + * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. + * + * @example + + + +
+ Anchor {{x}} of 5 +
+
+ + angular.module('anchorScrollOffsetExample', []) + .run(['$anchorScroll', function($anchorScroll) { + $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels + }]) + .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', + function ($anchorScroll, $location, $scope) { + $scope.gotoAnchor = function(x) { + var newHash = 'anchor' + x; + if ($location.hash() !== newHash) { + // set the $location.hash to `newHash` and + // $anchorScroll will automatically scroll to it + $location.hash('anchor' + x); + } else { + // call $anchorScroll() explicitly, + // since $location.hash hasn't changed + $anchorScroll(); + } + }; + } + ]); + + + body { + padding-top: 50px; + } + + .anchor { + border: 2px dashed DarkOrchid; + padding: 10px 10px 200px 10px; + } + + .fixed-header { + background-color: rgba(0, 0, 0, 0.2); + height: 50px; + position: fixed; + top: 0; left: 0; right: 0; + } + + .fixed-header > a { + display: inline-block; + margin: 5px 15px; + } + +
+ */ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { var document = $window.document; - // helper function to get first anchor from a NodeList - // can't use filter.filter, as it accepts only instances of Array - // and IE can't convert NodeList to an array using [].slice - // TODO(vojta): use filter if we change it to accept lists as well + // Helper function to get first anchor from a NodeList + // (using `Array#some()` instead of `angular#forEach()` since it's more performant + // and working in all supported browsers.) function getFirstAnchor(list) { var result = null; - forEach(list, function(element) { - if (!result && nodeName_(element) === 'a') result = element; + Array.prototype.some.call(list, function(element) { + if (nodeName_(element) === 'a') { + result = element; + return true; + } }); return result; } + function getYOffset() { + + var offset = scroll.yOffset; + + if (isFunction(offset)) { + offset = offset(); + } else if (isElement(offset)) { + var elem = offset[0]; + var style = $window.getComputedStyle(elem); + if (style.position !== 'fixed') { + offset = 0; + } else { + offset = elem.getBoundingClientRect().bottom; + } + } else if (!isNumber(offset)) { + offset = 0; + } + + return offset; + } + + function scrollTo(elem) { + if (elem) { + elem.scrollIntoView(); + + var offset = getYOffset(); + + if (offset) { + // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. + // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the + // top of the viewport. + // + // IF the number of pixels from the top of `elem` to the end of the page's content is less + // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some + // way down the page. + // + // This is often the case for elements near the bottom of the page. + // + // In such cases we do not need to scroll the whole `offset` up, just the difference between + // the top of the element and the offset, which is enough to align the top of `elem` at the + // desired position. + var elemTop = elem.getBoundingClientRect().top; + $window.scrollBy(0, elemTop - offset); + } + } else { + $window.scrollTo(0, 0); + } + } + function scroll() { var hash = $location.hash(), elm; // empty hash, scroll to the top of the page - if (!hash) $window.scrollTo(0, 0); + if (!hash) scrollTo(null); // element with given id - else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); + else if ((elm = document.getElementById(hash))) scrollTo(elm); // first anchor with given name :-D - else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); + else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); // no element and hash == 'top', scroll to the top of the page - else if (hash === 'top') $window.scrollTo(0, 0); + else if (hash === 'top') scrollTo(null); } // does not scroll when user clicks on anchor link that is currently on @@ -13449,7 +13668,9 @@ // skip the initial scroll if $location.hash is empty if (newVal === oldVal && newVal === '') return; - $rootScope.$evalAsync(scroll); + jqLiteDocumentLoaded(function() { + $rootScope.$evalAsync(scroll); + }); }); } @@ -13532,7 +13753,7 @@ * @return {RegExp} The current CSS className expression value. If null then there is no expression value */ this.classNameFilter = function(expression) { - if(arguments.length === 1) { + if (arguments.length === 1) { this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; } return this.$$classNameFilter; @@ -13557,7 +13778,7 @@ return defer.promise; } - function resolveElementClasses(element, cache) { + function resolveElementClasses(element, classes) { var toAdd = [], toRemove = []; var hasClasses = createMap(); @@ -13565,7 +13786,7 @@ hasClasses[className] = true; }); - forEach(cache.classes, function(status, className) { + forEach(classes, function(status, className) { var hasClass = hasClasses[className]; // If the most recent class manipulation (via $animate) was to remove the class, and the @@ -13579,7 +13800,8 @@ } }); - return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove]; + return (toAdd.length + toRemove.length) > 0 && + [toAdd.length ? toAdd : null, toRemove.length ? toRemove : null]; } function cachedClassManipulation(cache, classes, op) { @@ -13601,6 +13823,13 @@ return currentDefer.promise; } + function applyStyles(element, options) { + if (angular.isObject(options)) { + var styles = extend(options.from || {}, options.to || {}); + element.css(styles); + } + } + /** * * @ngdoc service @@ -13619,6 +13848,10 @@ * page}. */ return { + animate: function(element, from, to) { + applyStyles(element, { from: from, to: to }); + return asyncPromise(); + }, /** * @@ -13633,9 +13866,11 @@ * a child (if the after element is not present) * @param {DOMElement} after the sibling element which will append the element * after itself + * @param {object=} options an optional collection of styles that will be applied to the element. * @return {Promise} the animation callback promise */ - enter : function(element, parent, after) { + enter: function(element, parent, after, options) { + applyStyles(element, options); after ? after.after(element) : parent.prepend(element); return asyncPromise(); @@ -13649,9 +13884,10 @@ * @description Removes the element from the DOM. When the function is called a promise * is returned that will be resolved at a later time. * @param {DOMElement} element the element which will be removed from the DOM + * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - leave : function(element) { + leave: function(element, options) { element.remove(); return asyncPromise(); }, @@ -13671,12 +13907,13 @@ * inserted into (if the after element is not present) * @param {DOMElement} after the sibling element where the element will be * positioned next to + * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - move : function(element, parent, after) { + move: function(element, parent, after, options) { // Do not remove element before insert. Removing will cause data associated with the // element to be dropped. Insert will implicitly do the remove. - return this.enter(element, parent, after); + return this.enter(element, parent, after, options); }, /** @@ -13689,20 +13926,23 @@ * @param {DOMElement} element the element which will have the className value * added to it * @param {string} className the CSS class which will be added to the element + * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - addClass : function(element, className) { - return this.setClass(element, className, []); + addClass: function(element, className, options) { + return this.setClass(element, className, [], options); }, - $$addClassImmediately : function addClassImmediately(element, className) { + $$addClassImmediately: function(element, className, options) { element = jqLite(element); className = !isString(className) ? (isArray(className) ? className.join(' ') : '') : className; - forEach(element, function (element) { + forEach(element, function(element) { jqLiteAddClass(element, className); }); + applyStyles(element, options); + return asyncPromise(); }, /** @@ -13715,20 +13955,22 @@ * @param {DOMElement} element the element which will have the className value * removed from it * @param {string} className the CSS class which will be removed from the element + * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - removeClass : function(element, className) { - return this.setClass(element, [], className); + removeClass: function(element, className, options) { + return this.setClass(element, [], className, options); }, - $$removeClassImmediately : function removeClassImmediately(element, className) { + $$removeClassImmediately: function(element, className, options) { element = jqLite(element); className = !isString(className) ? (isArray(className) ? className.join(' ') : '') : className; - forEach(element, function (element) { + forEach(element, function(element) { jqLiteRemoveClass(element, className); }); + applyStyles(element, options); return asyncPromise(); }, @@ -13743,28 +13985,24 @@ * removed from it * @param {string} add the CSS classes which will be added to the element * @param {string} remove the CSS class which will be removed from the element + * @param {object=} options an optional collection of options that will be applied to the element. * @return {Promise} the animation callback promise */ - setClass : function(element, add, remove, runSynchronously) { + setClass: function(element, add, remove, options) { var self = this; var STORAGE_KEY = '$$animateClasses'; var createdCache = false; element = jqLite(element); - if (runSynchronously) { - // TODO(@caitp/@matsko): Remove undocumented `runSynchronously` parameter, and always - // perform DOM manipulation asynchronously or in postDigest. - self.$$addClassImmediately(element, add); - self.$$removeClassImmediately(element, remove); - return asyncPromise(); - } - var cache = element.data(STORAGE_KEY); if (!cache) { cache = { - classes: {} + classes: {}, + options: options }; createdCache = true; + } else if (options && cache.options) { + cache.options = angular.extend(cache.options || {}, options); } var classes = cache.classes; @@ -13779,11 +14017,14 @@ var cache = element.data(STORAGE_KEY); element.removeData(STORAGE_KEY); - var classes = cache && resolveElementClasses(element, cache); - - if (classes) { - if (classes[0]) self.$$addClassImmediately(element, classes[0]); - if (classes[1]) self.$$removeClassImmediately(element, classes[1]); + // in the event that the element is removed before postDigest + // is run then the cache will be undefined and there will be + // no need anymore to add or remove and of the element classes + if (cache) { + var classes = resolveElementClasses(element, cache.classes); + if (classes) { + self.$$setClassImmediately(element, classes[0], classes[1], cache.options); + } } done(); @@ -13794,13 +14035,20 @@ return cache.promise; }, - enabled : noop, - cancel : noop + $$setClassImmediately: function(element, add, remove, options) { + add && this.$$addClassImmediately(element, add); + remove && this.$$removeClassImmediately(element, remove); + applyStyles(element, options); + return asyncPromise(); + }, + + enabled: noop, + cancel: noop }; }]; }]; -function $$AsyncCallbackProvider(){ +function $$AsyncCallbackProvider() { this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { return $$rAF.supported ? function(fn) { return $$rAF(fn); } @@ -13830,8 +14078,7 @@ /** * @param {object} window The global window object. * @param {object} document jQuery wrapped document. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. + * @param {object} $log window.console or an object with the same interface. * @param {object} $sniffer $sniffer service */ function Browser(window, document, $log, $sniffer) { @@ -13862,7 +14109,7 @@ } finally { outstandingRequestCount--; if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { + while (outstandingRequestCallbacks.length) { try { outstandingRequestCallbacks.pop()(); } catch (e) { @@ -13873,6 +14120,11 @@ } } + function getHash(url) { + var index = url.indexOf('#'); + return index === -1 ? '' : url.substr(index + 1); + } + /** * @private * Note: this method is used only by scenario runner @@ -13883,7 +14135,7 @@ // force browser to execute all pollFns - this is needed so that cookies and other pollers fire // at some deterministic time in respect to the test runner's actions. Leaving things up to the // regular poller would result in flaky tests. - forEach(pollFns, function(pollFn){ pollFn(); }); + forEach(pollFns, function(pollFn) { pollFn(); }); if (outstandingRequestCount === 0) { callback(); @@ -13925,7 +14177,7 @@ */ function startPoller(interval, setTimeout) { (function check() { - forEach(pollFns, function(pollFn){ pollFn(); }); + forEach(pollFns, function(pollFn) { pollFn(); }); pollTimeout = setTimeout(check, interval); })(); } @@ -13934,11 +14186,14 @@ // URL API ////////////////////////////////////////////////////////////// - var lastBrowserUrl = location.href, - lastHistoryState = history.state, + var cachedState, lastHistoryState, + lastBrowserUrl = location.href, baseElement = document.find('base'), reloadLocation = null; + cacheState(); + lastHistoryState = cachedState; + /** * @name $browser#url * @@ -13973,29 +14228,36 @@ // setter if (url) { + var sameState = lastHistoryState === state; + // Don't change anything if previous and current URLs and states match. This also prevents // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. // See https://github.com/angular/angular.js/commit/ffb2701 - if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) { - return; + if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { + return self; } var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); lastBrowserUrl = url; + lastHistoryState = state; // Don't use history API if only the hash changed // due to a bug in IE10/IE11 which leads // to not firing a `hashchange` nor `popstate` event // in some cases (see #9143). - if ($sniffer.history && (!sameBase || history.state !== state)) { + if ($sniffer.history && (!sameBase || !sameState)) { history[replace ? 'replaceState' : 'pushState'](state, '', url); - lastHistoryState = history.state; + cacheState(); + // Do the assignment again so that those two variables are referentially identical. + lastHistoryState = cachedState; } else { if (!sameBase) { reloadLocation = url; } if (replace) { location.replace(url); + } else if (!sameBase) { + location.href = url; } else { - location.href = url; + location.hash = getHash(url); } } return self; @@ -14019,20 +14281,40 @@ * @returns {object} state */ self.state = function() { - return isUndefined(history.state) ? null : history.state; + return cachedState; }; var urlChangeListeners = [], urlChangeInit = false; + function cacheStateAndFireUrlChange() { + cacheState(); + fireUrlChange(); + } + + // This variable should be used *only* inside the cacheState function. + var lastCachedState = null; + function cacheState() { + // This should be the only place in $browser where `history.state` is read. + cachedState = window.history.state; + cachedState = isUndefined(cachedState) ? null : cachedState; + + // Prevent callbacks fo fire twice if both hashchange & popstate were fired. + if (equals(cachedState, lastCachedState)) { + cachedState = lastCachedState; + } + lastCachedState = cachedState; + } + function fireUrlChange() { - if (lastBrowserUrl === self.url() && lastHistoryState === history.state) { + if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { return; } lastBrowserUrl = self.url(); + lastHistoryState = cachedState; forEach(urlChangeListeners, function(listener) { - listener(self.url(), history.state); + listener(self.url(), cachedState); }); } @@ -14065,9 +14347,9 @@ // changed by push/replaceState // html5 history api - popstate event - if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); + if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); // hashchange event - jqLite(window).on('hashchange', fireUrlChange); + jqLite(window).on('hashchange', cacheStateAndFireUrlChange); urlChangeInit = true; } @@ -14108,6 +14390,14 @@ var lastCookieString = ''; var cookiePath = self.baseHref(); + function safeDecodeURIComponent(str) { + try { + return decodeURIComponent(str); + } catch (e) { + return str; + } + } + /** * @name $browser#cookies * @@ -14145,8 +14435,8 @@ // - 20 cookies per unique domain // - 4096 bytes per cookie if (cookieLength > 4096) { - $log.warn("Cookie '"+ name + - "' possibly not set or overflowed because it was too large ("+ + $log.warn("Cookie '" + name + + "' possibly not set or overflowed because it was too large (" + cookieLength + " > 4096 bytes)!"); } } @@ -14161,12 +14451,12 @@ cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies - name = decodeURIComponent(cookie.substring(0, index)); + name = safeDecodeURIComponent(cookie.substring(0, index)); // the first value that is seen for a cookie is the most // specific one. values for the same cookie name that // follow are for less specific paths. if (lastCookies[name] === undefined) { - lastCookies[name] = decodeURIComponent(cookie.substring(index + 1)); + lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); } } } @@ -14224,9 +14514,9 @@ } -function $BrowserProvider(){ +function $BrowserProvider() { this.$get = ['$window', '$log', '$sniffer', '$document', - function( $window, $log, $sniffer, $document){ + function($window, $log, $sniffer, $document) { return new Browser($window, $document, $log, $sniffer); }]; } @@ -14600,7 +14890,8 @@ * ``` * * **Note:** the `script` tag containing the template does not need to be included in the `head` of - * the document, but it must be below the `ng-app` definition. + * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, + * element with ng-app attribute), otherwise the template will be ignored. * * Adding via the $templateCache service: * @@ -14691,6 +14982,7 @@ * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, * transclude: false, * restrict: 'A', + * templateNamespace: 'html', * scope: false, * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, * controllerAs: 'stringAlias', @@ -14744,9 +15036,9 @@ * #### `multiElement` * When this property is set to true, the HTML compiler will collect DOM nodes between * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them - * together as the directive elements. It is recomended that this feature be used on directives - * which are not strictly behavioural (such as {@link api/ng.directive:ngClick ngClick}), and which - * do not manipulate or replace child nodes (such as {@link api/ng.directive:ngInclude ngInclude}). + * together as the directive elements. It is recommended that this feature be used on directives + * which are not strictly behavioural (such as {@link ngClick}), and which + * do not manipulate or replace child nodes (such as {@link ngInclude}). * * #### `priority` * When there are multiple directives defined on a single DOM element, sometimes it @@ -14759,7 +15051,8 @@ * #### `terminal` * If set to true then the current `priority` will be the last set of directives * which will execute (any directives at the current priority will still execute - * as the order of execution on same `priority` is undefined). + * as the order of execution on same `priority` is undefined). Note that expressions + * and other directives used in the directive's template will also be excluded from execution. * * #### `scope` * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the @@ -14792,7 +15085,9 @@ * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If + * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use + * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). * * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. * If no `attr` name is specified then the attribute name is assumed to be the same as the @@ -14806,7 +15101,7 @@ * * * #### `bindToController` - * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController` will + * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will * allow a component to have its properties bound to the controller, rather than to scope. When the controller * is instantiated, the initial values of the isolate scope bindings are already available. * @@ -14906,7 +15201,7 @@ * You can specify `templateUrl` as a string representing the URL or as a function which takes two * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns * a string value representing the url. In either case, the template URL is passed through {@link - * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. + * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. * * * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) @@ -14916,7 +15211,7 @@ * * `false` - the template will replace the contents of the directive's element. * * The replacement process migrates all of the attributes / classes from the old element to the new - * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * one. See the {@link guide/directive#template-expanding-directive * Directives Guide} for an example. * * There are very few scenarios where element replacement is required for the application function, @@ -15071,7 +15366,7 @@ * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery * object that contains the compiled DOM, which is linked to the correct transclusion scope. * - * When you call a transclusion function you can pass in a **clone attach function**. This function is accepts + * When you call a transclusion function you can pass in a **clone attach function**. This function accepts * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded * content and the `scope` is the newly created transclusion scope, to which the clone is bound. * @@ -15248,10 +15543,17 @@ * * * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. + * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. + * + *
+ * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it + * e.g. will not use the right outer scope. Please pass the transclude function as a + * `parentBoundTranscludeFn` to the link function instead. + *
+ * * @param {number} maxPriority only apply directives lower than given priority (Only effects the * root element(s), not their children) - * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template + * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. @@ -15263,6 +15565,19 @@ * * `clonedElement` - is a clone of the original `element` passed into the compiler. * * `scope` - is the current scope with which the linking function is working with. * + * * `options` - An optional object hash with linking options. If `options` is provided, then the following + * keys may be used to control linking behavior: + * + * * `parentBoundTranscludeFn` - the transclude function made available to + * directives; if given, it will be passed through to the link functions of + * directives found in `element` during compilation. + * * `transcludeControllers` - an object hash with keys that map controller names + * to controller instances; if given, it will make the controllers + * available to directives. + * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add + * the cloned elements; only needed for transcludes that are allowed to contain non html + * elements (e.g. SVG elements). See also the directive.controller property. + * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * @@ -15308,8 +15623,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/, + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; @@ -15319,7 +15634,7 @@ var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; function parseIsolateBindings(scope, directiveName) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; + var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; var bindings = {}; @@ -15334,9 +15649,10 @@ } bindings[scopeName] = { - attrName: match[3] || scopeName, - mode: match[1], - optional: match[2] === '?' + mode: match[1][0], + collection: match[2] === '*', + optional: match[3] === '?', + attrName: match[4] || scopeName }; }); @@ -15408,7 +15724,7 @@ * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * The sanitization is a security measure aimed at preventing XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` @@ -15475,14 +15791,14 @@ * * `ng-binding` CSS class * * `$binding` data property containing an array of the binding expressions * - * You may want to use this in production for a significant performance boost. See + * You may want to disable this in production for a significant performance boost. See * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. * * The default value is true. */ var debugInfoEnabled = true; this.debugInfoEnabled = function(enabled) { - if(isDefined(enabled)) { + if (isDefined(enabled)) { debugInfoEnabled = enabled; return this; } @@ -15512,6 +15828,21 @@ }; Attributes.prototype = { + /** + * @ngdoc method + * @name $compile.directive.Attributes#$normalize + * @kind function + * + * @description + * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or + * `data-`) to its normalized, camelCase form. + * + * Also there is special case for Moz prefix starting with upper case letter. + * + * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} + * + * @param {string} name Name to normalize + */ $normalize: directiveNormalize, @@ -15526,8 +15857,8 @@ * * @param {string} classVal The className value that will be added to the element */ - $addClass : function(classVal) { - if(classVal && classVal.length > 0) { + $addClass: function(classVal) { + if (classVal && classVal.length > 0) { $animate.addClass(this.$$element, classVal); } }, @@ -15543,8 +15874,8 @@ * * @param {string} classVal The className value that will be removed from the element */ - $removeClass : function(classVal) { - if(classVal && classVal.length > 0) { + $removeClass: function(classVal) { + if (classVal && classVal.length > 0) { $animate.removeClass(this.$$element, classVal); } }, @@ -15561,7 +15892,7 @@ * @param {string} newClasses The current CSS className value * @param {string} oldClasses The former CSS className value */ - $updateClass : function(newClasses, oldClasses) { + $updateClass: function(newClasses, oldClasses) { var toAdd = tokenDifference(newClasses, oldClasses); if (toAdd && toAdd.length) { $animate.addClass(this.$$element, toAdd); @@ -15591,13 +15922,12 @@ booleanKey = getBooleanAttrName(node, key), aliasedKey = getAliasedAttrName(node, key), observer = key, - normalizedVal, nodeName; if (booleanKey) { this.$$element.prop(key, value); attrName = booleanKey; - } else if(aliasedKey) { + } else if (aliasedKey) { this[aliasedKey] = value; observer = aliasedKey; } @@ -15635,22 +15965,22 @@ // for each tuples var nbrUrisWith2parts = Math.floor(rawUris.length / 2); - for (var i=0; i - forEach($compileNodes, function(node, index){ + forEach($compileNodes, function(node, index) { if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { $compileNodes[index] = jqLite(node).wrap('').parent()[0]; } @@ -15781,8 +16111,22 @@ maxPriority, ignoreDirective, previousCompileContext); compile.$$addScopeClass($compileNodes); var namespace = null; - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){ + return function publicLinkFn(scope, cloneConnectFn, options) { assertArg(scope, 'scope'); + + options = options || {}; + var parentBoundTranscludeFn = options.parentBoundTranscludeFn, + transcludeControllers = options.transcludeControllers, + futureParentElement = options.futureParentElement; + + // When `parentBoundTranscludeFn` is passed, it is a + // `controllersBoundTransclude` function (it was previously passed + // as `transclude` to directive.link) so we must unwrap it to get + // its `boundTranscludeFn` + if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { + parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; + } + if (!namespace) { namespace = detectNamespaceForChildElements(futureParentElement); } @@ -15824,7 +16168,7 @@ if (!node) { return 'html'; } else { - return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg': 'html'; + return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; } } @@ -15906,7 +16250,7 @@ stableNodeList = nodeList; } - for(i = 0, ii = linkFns.length; i < ii;) { + for (i = 0, ii = linkFns.length; i < ii;) { node = stableNodeList[linkFns[i++]]; nodeLinkFn = linkFns[i++]; childLinkFn = linkFns[i++]; @@ -15919,7 +16263,7 @@ childScope = scope; } - if ( nodeLinkFn.transcludeOnThisElement ) { + if (nodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn( scope, nodeLinkFn.transclude, parentBoundTranscludeFn, nodeLinkFn.elementTranscludeOnThisElement); @@ -15952,7 +16296,11 @@ transcludedScope.$$transcluded = true; } - return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement); + return transcludeFn(transcludedScope, cloneFn, { + parentBoundTranscludeFn: previousBoundTranscludeFn, + transcludeControllers: controllers, + futureParentElement: futureParentElement + }); }; return boundTranscludeFn; @@ -15974,7 +16322,7 @@ match, className; - switch(nodeType) { + switch (nodeType) { case NODE_TYPE_ELEMENT: /* Element */ // use the node name: addDirective(directives, @@ -15993,7 +16341,10 @@ // support ngAttr attribute binding ngAttrName = directiveNormalize(name); if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { - name = snake_case(ngAttrName.substr(6), '-'); + name = name.replace(PREFIX_REGEXP, '') + .substr(8).replace(/_(.)/g, function(match, letter) { + return letter.toUpperCase(); + }); } var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); @@ -16066,7 +16417,6 @@ var nodes = []; var depth = 0; if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { - var startNode = node; do { if (!node) { throw $compileMinErr('uterdir', @@ -16150,7 +16500,7 @@ directiveValue; // executes all directives on the current element - for(var i = 0, ii = directives.length; i < ii; i++) { + for (var i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; var attrStart = directive.$$start; var attrEnd = directive.$$end; @@ -16394,7 +16744,7 @@ "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName); } - return value; + return value || null; } else if (isArray(require)) { value = []; forEach(require, function(require) { @@ -16421,7 +16771,13 @@ isolateScope = scope.$new(true); } - transcludeFn = boundTranscludeFn && controllersBoundTransclude; + if (boundTranscludeFn) { + // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` + // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` + transcludeFn = controllersBoundTransclude; + transcludeFn.$$boundTransclude = boundTranscludeFn; + } + if (controllerDirectives) { // TODO: merge `controllers` and `elementControllers` into single object. controllers = {}; @@ -16456,8 +16812,6 @@ } if (newIsolateScopeDirective) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || templateDirective === newIsolateScopeDirective.$$originalDirective))); compile.$$addScopeClass($element, true); @@ -16483,7 +16837,7 @@ isolateBindingContext[scopeName] = value; }); attrs.$$observers[attrName].$$scope = scope; - if( attrs[attrName] ) { + if (attrs[attrName]) { // If the attribute has been provided then we trigger an interpolation to ensure // the value is there for use in the link fn isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope); @@ -16498,7 +16852,7 @@ if (parentGet.literal) { compare = equals; } else { - compare = function(a,b) { return a === b || (a !== a && b !== b); }; + compare = function(a, b) { return a === b || (a !== a && b !== b); }; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest @@ -16522,7 +16876,12 @@ return lastValue = parentValue; }; parentValueWatch.$stateful = true; - var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); + var unwatch; + if (definition.collection) { + unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); + } else { + unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); + } isolateScope.$on('$destroy', unwatch); break; @@ -16543,7 +16902,7 @@ } // PRELINKING - for(i = 0, ii = preLinkFns.length; i < ii; i++) { + for (i = 0, ii = preLinkFns.length; i < ii; i++) { linkFn = preLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, @@ -16564,7 +16923,7 @@ childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); // POSTLINKING - for(i = postLinkFns.length - 1; i >= 0; i--) { + for (i = postLinkFns.length - 1; i >= 0; i--) { linkFn = postLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, @@ -16624,11 +16983,11 @@ if (name === ignoreDirective) return null; var match = null; if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i directive.priority) && + if ((maxPriority === undefined || maxPriority > directive.priority) && directive.restrict.indexOf(location) != -1) { if (startAttrName) { directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); @@ -16636,7 +16995,7 @@ tDirectives.push(directive); match = directive; } - } catch(e) { $exceptionHandler(e); } + } catch (e) { $exceptionHandler(e); } } } return match; @@ -16653,8 +17012,8 @@ */ function directiveIsMultiElement(name) { if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i'+template+''; + wrapper.innerHTML = '<' + type + '>' + template + ''; return wrapper.childNodes[0].childNodes; default: return template; @@ -16897,7 +17256,10 @@ function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { - var interpolateFn = $interpolate(value, true); + var trustedContext = getTrustedContext(node, name); + allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; + + var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); // no interpolation found -> ignore if (!interpolateFn) return; @@ -16922,16 +17284,16 @@ "ng- versions (such as ng-click instead of onclick) instead."); } - // If the attribute was removed, then we are done - if (!attr[name]) { - return; + // If the attribute has changed since last $interpolate()ed + var newValue = attr[name]; + if (newValue !== value) { + // we need to interpolate again since the attribute value has been updated + // (e.g. by another directive's compile function) + // ensure unset/empty values make interpolateFn falsy + interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); + value = newValue; } - // we need to interpolate again, in case the attribute value has been updated - // (e.g. by another directive's compile function) - interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name), - ALL_OR_NOTHING_ATTRS[name] || allOrNothing); - // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; @@ -16950,7 +17312,7 @@ //skip animations when the first digest occurs (when //both the new and the old values are the same) since //the CSS classes are the non-interpolated values - if(name === 'class' && newValue != oldValue) { + if (name === 'class' && newValue != oldValue) { attr.$updateClass(newValue, oldValue); } else { attr.$set(name, newValue); @@ -16980,7 +17342,7 @@ i, ii; if ($rootElement) { - for(i = 0, ii = $rootElement.length; i < ii; i++) { + for (i = 0, ii = $rootElement.length; i < ii; i++) { if ($rootElement[i] == firstElementToRemove) { $rootElement[i++] = newNode; for (var j = i, j2 = j + removeCount - 1, @@ -17055,23 +17417,16 @@ function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { try { linkFn(scope, $element, attrs, controllers, transcludeFn); - } catch(e) { + } catch (e) { $exceptionHandler(e, startingTag($element)); } } }]; } -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; /** * Converts all accepted directives format into proper directive name. - * All of these will become 'myDirective': - * my:Directive - * my-directive - * x-my-directive - * data-my:directive - * - * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function directiveNormalize(name) { @@ -17128,7 +17483,7 @@ /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn -){} +) {} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, @@ -17136,7 +17491,7 @@ /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn -){} +) {} function tokenDifference(str1, str2) { var values = '', @@ -17144,10 +17499,10 @@ tokens2 = str2.split(/\s+/); outer: - for(var i = 0; i < tokens1.length; i++) { + for (var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; + for (var j = 0; j < tokens2.length; j++) { + if (token == tokens2[j]) continue outer; } values += (values.length > 0 ? ' ' : '') + token; } @@ -17230,6 +17585,10 @@ * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global * `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 + * to work correctly. + * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * @@ -17253,7 +17612,7 @@ identifier = ident; } - if(isString(expression)) { + if (isString(expression)) { match = expression.match(CNTRL_REG), constructor = match[1], identifier = identifier || match[3]; @@ -17275,10 +17634,10 @@ // // This feature is not intended for use by applications, and is thus not documented // publicly. - var Constructor = function() {}; - Constructor.prototype = (isArray(expression) ? + // Object creation: http://jsperf.com/create-constructor/2 + var controllerPrototype = (isArray(expression) ? expression[expression.length - 1] : expression).prototype; - instance = new Constructor(); + instance = Object.create(controllerPrototype); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); @@ -17339,8 +17698,8 @@
*/ -function $DocumentProvider(){ - this.$get = ['$window', function(window){ +function $DocumentProvider() { + this.$get = ['$window', function(window) { return jqLite(window.document); }]; } @@ -17361,8 +17720,8 @@ * ## Example: * * ```js - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { - * return function (exception, cause) { + * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { + * return function(exception, cause) { * exception.message += ' (caused by "' + cause + '")'; * throw exception; * }; @@ -17372,6 +17731,14 @@ * This example will override the normal action of `$exceptionHandler`, to make angular * exceptions fail hard when they happen, instead of just logging to the console. * + *
+ * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` + * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} + * (unless executed during a digest). + * + * If you wish, you can manually delegate exceptions, e.g. + * `try { ... } catch(e) { $exceptionHandler(e); }` + * * @param {Error} exception Exception associated with the error. * @param {string=} cause optional information about the context in which * the error was thrown. @@ -17385,6 +17752,36 @@ }]; } +var APPLICATION_JSON = 'application/json'; +var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; +var JSON_START = /^\[|^\{(?!\{)/; +var JSON_ENDS = { + '[': /]$/, + '{': /}$/ +}; +var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; + +function defaultHttpResponseTransform(data, headers) { + if (isString(data)) { + // Strip json vulnerability protection prefix and trim whitespace + var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); + + if (tempData) { + var contentType = headers('Content-Type'); + if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { + data = fromJson(tempData); + } + } + } + + return data; +} + +function isJsonLike(str) { + var jsonStart = str.match(JSON_START); + return jsonStart && JSON_ENDS[jsonStart[0]].test(str); +} + /** * Parse headers into key value object * @@ -17392,7 +17789,7 @@ * @returns {Object} Parsed headers as key value object */ function parseHeaders(headers) { - var parsed = {}, key, val, i; + var parsed = createMap(), key, val, i; if (!headers) return parsed; @@ -17429,7 +17826,11 @@ if (!headersObj) headersObj = parseHeaders(headers); if (name) { - return headersObj[lowercase(name)] || null; + var value = headersObj[lowercase(name)]; + if (value === void 0) { + value = null; + } + return value; } return headersObj; @@ -17443,16 +17844,17 @@ * This function is used for both request and response transforming * * @param {*} data Data to transform. - * @param {function(string=)} headers Http headers getter fn. + * @param {function(string=)} headers HTTP headers getter fn. + * @param {number} status HTTP status code of the response. * @param {(Function|Array.)} fns Function or an array of functions. * @returns {*} Transformed data. */ -function transformData(data, headers, fns) { +function transformData(data, headers, status, fns) { if (isFunction(fns)) - return fns(data, headers); + return fns(data, headers, status); forEach(fns, function(fn) { - data = fn(data, headers); + data = fn(data, headers, status); }); return data; @@ -17471,12 +17873,6 @@ * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. * */ function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/, - APPLICATION_JSON = 'application/json', - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; - /** * @ngdoc property * @name $httpProvider#defaults @@ -17484,6 +17880,11 @@ * * Object containing default values for all {@link ng.$http $http} requests. * + * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} + * that will provide the cache for all requests who set their `cache` property to `true`. + * If you set the `default.cache = false` then only requests that specify their own custom + * cache object will be cached. See {@link $http#caching $http Caching} for more information. + * * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. * Defaults value is `'XSRF-TOKEN'`. * @@ -17497,25 +17898,15 @@ * - **`defaults.headers.post`** * - **`defaults.headers.put`** * - **`defaults.headers.patch`** + * **/ var defaults = this.defaults = { // transform incoming response data - transformResponse: [function defaultHttpResponseTransform(data, headers) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - var contentType = headers('Content-Type'); - if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) || - (JSON_START.test(data) && JSON_END.test(data))) { - data = fromJson(data); - } - } - return data; - }], + transformResponse: [defaultHttpResponseTransform], // transform outgoing request data transformRequest: [function(d) { - return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; + return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; }], // default headers @@ -17539,7 +17930,7 @@ * @description * * Configure $http service to combine processing of multiple http responses received at around - * the same time via {@link ng.$rootScope#applyAsync $rootScope.$applyAsync}. This can result in + * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in * significant performance improvement for bigger applications that make many HTTP requests * concurrently (common during application bootstrap). * @@ -17561,9 +17952,18 @@ }; /** - * Are ordered by request, i.e. they are applied in the same order as the + * @ngdoc property + * @name $httpProvider#interceptors + * @description + * + * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} + * pre-processing of request or postprocessing of responses. + * + * These service factories are ordered by request, i.e. they are applied in the same order as the * array, on request, but reverse order, on response. - */ + * + * {@link ng.$http#interceptors Interceptors detailed info} + **/ var interceptorFactories = this.interceptors = []; this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', @@ -17615,7 +18015,8 @@ * with two $http specific methods: `success` and `error`. * * ```js - * $http({method: 'GET', url: '/someUrl'}). + * // Simple GET request example : + * $http.get('/someUrl'). * success(function(data, status, headers, config) { * // this callback will be called asynchronously * // when the response is available @@ -17626,6 +18027,20 @@ * }); * ``` * + * ```js + * // Simple POST request example (passing data) : + * $http.post('/someUrl', {msg:'hello word!'}). + * success(function(data, status, headers, config) { + * // this callback will be called asynchronously + * // when the response is available + * }). + * error(function(data, status, headers, config) { + * // called asynchronously if an error occurs + * // or server returns response with an error status. + * }); + * ``` + * + * * Since the returned value of calling the $http function is a `promise`, you can also use * the `then` method to register callbacks, and these callbacks will receive a single argument – * an object representing the response. See the API signature and type info below for more @@ -17698,12 +18113,27 @@ * In addition, you can supply a `headers` property in the config object passed when * calling `$http(config)`, which overrides the defaults without changing them globally. * + * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, + * Use the `headers` property, setting the desired header to `undefined`. For example: + * + * ```js + * var req = { + * method: 'POST', + * url: 'http://example.com', + * headers: { + * 'Content-Type': undefined + * }, + * data: { test: 'test' }, + * } + * + * $http(req).success(function(){...}).error(function(){...}); + * ``` * * ## Transforming Requests and Responses * * Both requests and responses can be transformed using transformation functions: `transformRequest` * and `transformResponse`. These properties can be a single function that returns - * the transformed value (`{function(data, headersGetter)`) or an array of such transformation functions, + * the transformed value (`{function(data, headersGetter, status)`) or an array of such transformation functions, * which allows you to `push` or `unshift` a new transformation function into the transformation chain. * * ### Default Transformations @@ -17778,7 +18208,7 @@ * * You can change the default cache to a new object (built with * {@link ng.$cacheFactory `$cacheFactory`}) by updating the - * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set + * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set * their `cache` property to `true` will now use this cache object. * * If you set the default cache to `false` then only requests that specify their own custom @@ -17944,12 +18374,14 @@ * `{function(data, headersGetter)|Array.}` – * transform function or an array of such functions. The transform function takes the http * request body and headers and returns its transformed (typically serialized) version. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} * - **transformResponse** – - * `{function(data, headersGetter)|Array.}` – + * `{function(data, headersGetter, status)|Array.}` – * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations} + * response body, headers and status and returns its transformed (typically deserialized) version. + * See {@link ng.$http#overriding-the-default-transformations-per-request + * Overriding the Default Transformations} * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the * GET request, otherwise if a cache instance built with * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for @@ -18070,20 +18502,23 @@ */ function $http(requestConfig) { - var config = { + + if (!angular.isObject(requestConfig)) { + throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); + } + + var config = extend({ method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse - }; - var headers = mergeHeaders(requestConfig); - - extend(config, requestConfig); - config.headers = headers; + }, requestConfig); + + config.headers = mergeHeaders(requestConfig); config.method = uppercase(config.method); var serverRequest = function(config) { - headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + var headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); // strip content-type if data is undefined if (isUndefined(reqData)) { @@ -18099,7 +18534,7 @@ } // send request - return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + return sendReq(config, reqData).then(transformResponse, transformResponse); }; var chain = [serverRequest, undefined]; @@ -18115,7 +18550,7 @@ } }); - while(chain.length) { + while (chain.length) { var thenFn = chain.shift(); var rejectFn = chain.shift(); @@ -18140,14 +18575,34 @@ function transformResponse(response) { // make a copy since the response must be cacheable - var resp = extend({}, response, { - data: transformData(response.data, response.headers, config.transformResponse) - }); + var resp = extend({}, response); + if (!response.data) { + resp.data = response.data; + } else { + resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); + } return (isSuccess(response.status)) ? resp : $q.reject(resp); } + function executeHeaderFns(headers) { + var headerContent, processedHeaders = {}; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + processedHeaders[header] = headerContent; + } + } else { + processedHeaders[header] = headerFn; + } + }); + + return processedHeaders; + } + function mergeHeaders(config) { var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), @@ -18170,23 +18625,7 @@ } // execute if header value is a function for merged headers - execHeaders(reqHeaders); - return reqHeaders; - - function execHeaders(headers) { - var headerContent; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(); - if (headerContent != null) { - headers[header] = headerContent; - } else { - delete headers[header]; - } - } - }); - } + return executeHeaderFns(reqHeaders); } } @@ -18329,11 +18768,12 @@ * !!! ACCESSES CLOSURE VARS: * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests */ - function sendReq(config, reqData, reqHeaders) { + function sendReq(config, reqData) { var deferred = $q.defer(), promise = deferred.promise, cache, cachedResp, + reqHeaders = config.headers, url = buildUrl(config.url, config.params); $http.pendingRequests.push(config); @@ -18352,8 +18792,7 @@ if (isDefined(cachedResp)) { if (isPromiseLike(cachedResp)) { // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; + cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); } else { // serving from cache if (isArray(cachedResp)) { @@ -18427,10 +18866,13 @@ status: status, headers: headersGetter(headers), config: config, - statusText : statusText - }); - } - + statusText: statusText + }); + } + + function resolvePromiseWithResult(result) { + resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); + } function removePendingReq() { var idx = $http.pendingRequests.indexOf(config); @@ -18448,7 +18890,7 @@ forEach(value, function(v) { if (isObject(v)) { - if (isDate(v)){ + if (isDate(v)) { v = v.toISOString(); } else { v = toJson(v); @@ -18458,7 +18900,7 @@ encodeUriQuery(v)); }); }); - if(parts.length > 0) { + if (parts.length > 0) { url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } return url; @@ -18545,7 +18987,7 @@ statusText); }; - var requestError = function () { + var requestError = function() { // The response is always empty // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error completeRequest(callback, -1, null, null, ''); @@ -18592,7 +19034,9 @@ function completeRequest(callback, status, response, headersString, statusText) { // cancel timeout and subsequent timeout promise resolution - timeoutId && $browserDefer.cancel(timeoutId); + if (timeoutId !== undefined) { + $browserDefer.cancel(timeoutId); + } jsonpDone = xhr = null; callback(status, response, headersString, statusText); @@ -18687,7 +19131,7 @@ * @param {string=} value new value to set the starting symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ - this.startSymbol = function(value){ + this.startSymbol = function(value) { if (value) { startSymbol = value; return this; @@ -18705,7 +19149,7 @@ * @param {string=} value new value to set the ending symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ - this.endSymbol = function(value){ + this.endSymbol = function(value) { if (value) { endSymbol = value; return this; @@ -18831,9 +19275,9 @@ concat = [], expressionPositions = []; - while(index < textLength) { - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { + while (index < textLength) { + if (((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { if (index !== startIndex) { concat.push(unescapeText(text.substring(index, startIndex))); } @@ -18867,34 +19311,31 @@ if (!mustHaveExpression || expressions.length) { var compute = function(values) { - for(var i = 0, ii = expressions.length; i < ii; i++) { + for (var i = 0, ii = expressions.length; i < ii; i++) { if (allOrNothing && isUndefined(values[i])) return; concat[expressionPositions[i]] = values[i]; } return concat.join(''); }; - var getValue = function (value) { + var getValue = function(value) { return trustedContext ? $sce.getTrusted(trustedContext, value) : $sce.valueOf(value); }; - var stringify = function (value) { + var stringify = function(value) { if (value == null) { // null || undefined return ''; } switch (typeof value) { - case 'string': { + case 'string': break; - } - case 'number': { + case 'number': value = '' + value; break; - } - default: { + default: value = toJson(value); - } } return value; @@ -18911,7 +19352,7 @@ } return compute(values); - } catch(err) { + } catch (err) { var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); $exceptionHandler(newErr); @@ -18921,7 +19362,7 @@ // all of these properties are undocumented for now exp: text, //just for compatibility with regular watchers created via $watch expressions: expressions, - $$watchDelegate: function (scope, listener, objectEquality) { + $$watchDelegate: function(scope, listener, objectEquality) { var lastValue; return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { var currValue = compute(values); @@ -18941,8 +19382,9 @@ function parseStringifyInterceptor(value) { try { - return stringify(getValue(value)); - } catch(err) { + value = getValue(value); + return allOrNothing && !isDefined(value) ? value : stringify(value); + } catch (err) { var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); $exceptionHandler(newErr); @@ -19042,33 +19484,33 @@ * // Don't start a new fight if we are already fighting * if ( angular.isDefined(stop) ) return; * - * stop = $interval(function() { - * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { - * $scope.blood_1 = $scope.blood_1 - 3; - * $scope.blood_2 = $scope.blood_2 - 4; - * } else { - * $scope.stopFight(); - * } - * }, 100); - * }; + * stop = $interval(function() { + * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { + * $scope.blood_1 = $scope.blood_1 - 3; + * $scope.blood_2 = $scope.blood_2 - 4; + * } else { + * $scope.stopFight(); + * } + * }, 100); + * }; * - * $scope.stopFight = function() { - * if (angular.isDefined(stop)) { - * $interval.cancel(stop); - * stop = undefined; - * } - * }; + * $scope.stopFight = function() { + * if (angular.isDefined(stop)) { + * $interval.cancel(stop); + * stop = undefined; + * } + * }; * - * $scope.resetFight = function() { - * $scope.blood_1 = 100; - * $scope.blood_2 = 120; - * }; + * $scope.resetFight = function() { + * $scope.blood_1 = 100; + * $scope.blood_2 = 120; + * }; * - * $scope.$on('$destroy', function() { - * // Make sure that the interval is destroyed too - * $scope.stopFight(); - * }); - * }]) + * $scope.$on('$destroy', function() { + * // Make sure that the interval is destroyed too + * $scope.stopFight(); + * }); + * }]) * // Register the 'myCurrentTime' directive factory method. * // We inject $interval and dateFilter service since the factory method is DI. * .directive('myCurrentTime', ['$interval', 'dateFilter', @@ -19181,7 +19623,7 @@ * * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ -function $LocaleProvider(){ +function $LocaleProvider() { this.$get = function() { return { id: 'en-us', @@ -19224,7 +19666,7 @@ SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), AMPMS: ['AM','PM'], medium: 'MMM d, y h:mm:ss a', - short: 'M/d/yy h:mm a', + 'short': 'M/d/yy h:mm a', fullDate: 'EEEE, MMMM d, y', longDate: 'MMMM d, y', mediumDate: 'MMM d, y', @@ -19265,8 +19707,8 @@ return segments.join('/'); } -function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { - var parsedUrl = urlResolve(absoluteUrl, appBase); +function parseAbsoluteUrl(absoluteUrl, locationObj) { + var parsedUrl = urlResolve(absoluteUrl); locationObj.$$protocol = parsedUrl.protocol; locationObj.$$host = parsedUrl.hostname; @@ -19274,12 +19716,12 @@ } -function parseAppUrl(relativeUrl, locationObj, appBase) { +function parseAppUrl(relativeUrl, locationObj) { var prefixed = (relativeUrl.charAt(0) !== '/'); if (prefixed) { relativeUrl = '/' + relativeUrl; } - var match = urlResolve(relativeUrl, appBase); + var match = urlResolve(relativeUrl); locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname); locationObj.$$search = parseKeyValue(match.search); @@ -19311,6 +19753,10 @@ return index == -1 ? url : url.substr(0, index); } +function trimEmptyHash(url) { + return url.replace(/(#.+)|#$/, '$1'); +} + function stripFile(url) { return url.substr(0, stripHash(url).lastIndexOf('/') + 1); @@ -19334,12 +19780,12 @@ this.$$html5 = true; basePrefix = basePrefix || ''; var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); + parseAbsoluteUrl(appBase, this); /** * Parse given html5 (regular) url string into properties - * @param {string} newAbsoluteUrl HTML5 url + * @param {string} url HTML5 url * @private */ this.$$parse = function(url) { @@ -19349,7 +19795,7 @@ appBaseNoFile); } - parseAppUrl(pathUrl, this, appBase); + parseAppUrl(pathUrl, this); if (!this.$$path) { this.$$path = '/'; @@ -19380,14 +19826,14 @@ var appUrl, prevAppUrl; var rewrittenUrl; - if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { + if ((appUrl = beginsWith(appBase, url)) !== undefined) { prevAppUrl = appUrl; - if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { + if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); } else { rewrittenUrl = appBase + prevAppUrl; } - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { + } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { rewrittenUrl = appBaseNoFile + appUrl; } else if (appBaseNoFile == url + '/') { rewrittenUrl = appBaseNoFile; @@ -19412,7 +19858,7 @@ function LocationHashbangUrl(appBase, hashPrefix) { var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); + parseAbsoluteUrl(appBase, this); /** @@ -19422,17 +19868,26 @@ */ this.$$parse = function(url) { var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); - var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' - ? beginsWith(hashPrefix, withoutBaseUrl) - : (this.$$html5) - ? withoutBaseUrl - : ''; - - if (!isString(withoutHashUrl)) { - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, - hashPrefix); - } - parseAppUrl(withoutHashUrl, this, appBase); + var withoutHashUrl; + + if (withoutBaseUrl.charAt(0) === '#') { + + // The rest of the url starts with a hash so we have + // got either a hashbang path or a plain hash fragment + withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); + if (isUndefined(withoutHashUrl)) { + // There was no hashbang prefix so we just have a hash fragment + withoutHashUrl = withoutBaseUrl; + } + + } else { + // There was no hashbang path nor hash fragment: + // If we are in HTML5 mode we use what is left as the path; + // Otherwise we ignore what is left + withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; + } + + parseAppUrl(withoutHashUrl, this); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); @@ -19449,7 +19904,7 @@ * Inside of Angular, we're always using pathnames that * do not include drive names for routing. */ - function removeWindowsDriveName (path, url, base) { + function removeWindowsDriveName(path, url, base) { /* Matches paths for file protocol on windows, such as /C:/foo/bar, and captures only /foo/bar. @@ -19486,7 +19941,7 @@ }; this.$$parseLinkUrl = function(url, relHref) { - if(stripHash(appBase) == stripHash(url)) { + if (stripHash(appBase) == stripHash(url)) { this.$$parse(url); return true; } @@ -19521,11 +19976,11 @@ var rewrittenUrl; var appUrl; - if ( appBase == stripHash(url) ) { + if (appBase == stripHash(url)) { rewrittenUrl = url; - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { + } else if ((appUrl = beginsWith(appBaseNoFile, url))) { rewrittenUrl = appBase + hashPrefix + appUrl; - } else if ( appBaseNoFile === url + '/') { + } else if (appBaseNoFile === url + '/') { rewrittenUrl = appBaseNoFile; } if (rewrittenUrl) { @@ -19570,6 +20025,13 @@ * Return full url representation with all segments encoded according to rules specified in * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var absUrl = $location.absUrl(); + * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" + * ``` + * * @return {string} full url */ absUrl: locationGetter('$$absUrl'), @@ -19585,6 +20047,13 @@ * * Change path, search and hash, when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var url = $location.url(); + * // => "/some/path?foo=bar&baz=xoxo" + * ``` + * * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) * @return {string} url */ @@ -19593,8 +20062,8 @@ return this.$$url; var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); + if (match[1] || url === '') this.path(decodeURIComponent(match[1])); + if (match[2] || match[1] || url === '') this.search(match[3] || ''); this.hash(match[5] || ''); return this; @@ -19609,6 +20078,13 @@ * * Return protocol of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var protocol = $location.protocol(); + * // => "http" + * ``` + * * @return {string} protocol of current url */ protocol: locationGetter('$$protocol'), @@ -19622,6 +20098,13 @@ * * Return host of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var host = $location.host(); + * // => "example.com" + * ``` + * * @return {string} host of current url. */ host: locationGetter('$$host'), @@ -19635,6 +20118,13 @@ * * Return port of current url. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var port = $location.port(); + * // => 80 + * ``` + * * @return {Number} port */ port: locationGetter('$$port'), @@ -19653,6 +20143,13 @@ * Note: Path should always begin with forward slash (/), this method will add the forward slash * if it is missing. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo + * var path = $location.path(); + * // => "/some/path" + * ``` + * * @param {(string|number)=} path New path * @return {string} path */ @@ -19678,10 +20175,9 @@ * var searchObject = $location.search(); * // => {foo: 'bar', baz: 'xoxo'} * - * * // set foo to 'yipee' * $location.search('foo', 'yipee'); - * // => $location + * // $location.search() => {foo: 'yipee', baz: 'xoxo'} * ``` * * @param {string|Object.|Object.>} search New search params - string or @@ -19716,6 +20212,7 @@ search = search.toString(); this.$$search = parseKeyValue(search); } else if (isObject(search)) { + search = copy(search, {}); // remove object undefined or null properties forEach(search, function(value, key) { if (value == null) delete search[key]; @@ -19750,6 +20247,13 @@ * * Change hash fragment when called with parameter and return `$location`. * + * + * ```js + * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue + * var hash = $location.hash(); + * // => "hashValue" + * ``` + * * @param {(string|number)=} hash New hash fragment * @return {string} hash */ @@ -19771,7 +20275,7 @@ } }; -forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) { +forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { Location.prototype = Object.create(locationPrototype); /** @@ -19863,7 +20367,7 @@ * @description * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ -function $LocationProvider(){ +function $LocationProvider() { var hashPrefix = '', html5Mode = { enabled: false, @@ -19901,8 +20405,8 @@ * whether or not a tag is required to be present. If `enabled` and `requireBase` are * true, and a base tag is not present, an error will be thrown when `$location` is injected. * See the {@link guide/$location $location guide for more information} - * - **rewriteLinks** - `{boolean}` - (default: `false`) When html5Mode is enabled, disables - * url rewriting for relative linksTurns off url rewriting for relative links. + * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, + * enables/disables url rewriting for relative links. * * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter */ @@ -19913,7 +20417,7 @@ } else if (isObject(mode)) { if (isBoolean(mode.enabled)) { - html5Mode.enabled = mode.enabled; + html5Mode.enabled = mode.enabled; } if (isBoolean(mode.requireBase)) { @@ -19921,7 +20425,7 @@ } if (isBoolean(mode.rewriteLinks)) { - html5Mode.rewriteLinks = mode.rewriteLinks; + html5Mode.rewriteLinks = mode.rewriteLinks; } return this; @@ -19940,7 +20444,7 @@ * This change can be prevented by calling * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more * details about event object. Upon successful change - * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. + * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. * * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when * the browser supports the HTML5 History API. @@ -19969,8 +20473,8 @@ * @param {string=} oldState History state object that was before it was changed. */ - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function( $rootScope, $browser, $sniffer, $rootElement) { + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', + function($rootScope, $browser, $sniffer, $rootElement, $window) { var $location, LocationMode, baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' @@ -20052,7 +20556,7 @@ if ($location.absUrl() != $browser.url()) { $rootScope.$apply(); // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; + $window.angular['ff-684208-preventDefault'] = true; } } } @@ -20071,11 +20575,19 @@ $rootScope.$evalAsync(function() { var oldUrl = $location.absUrl(); var oldState = $location.$$state; + var defaultPrevented; $location.$$parse(newUrl); $location.$$state = newState; - if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, - newState, oldState).defaultPrevented) { + + defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, + newState, oldState).defaultPrevented; + + // 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; setBrowserUrlWithFallback(oldUrl, false, oldState); @@ -20089,22 +20601,33 @@ // update browser $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); + var oldUrl = trimEmptyHash($browser.url()); + var newUrl = trimEmptyHash($location.absUrl()); var oldState = $browser.state(); var currentReplace = $location.$$replace; - - if (initializing || oldUrl !== $location.absUrl() || - ($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) { + var urlOrStateChanged = oldUrl !== newUrl || + ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); + + if (initializing || urlOrStateChanged) { initializing = false; $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl, - $location.$$state, oldState).defaultPrevented) { + 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 (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; } else { - setBrowserUrlWithFallback($location.absUrl(), currentReplace, - oldState === $location.$$state ? null : $location.$$state); + if (urlOrStateChanged) { + setBrowserUrlWithFallback(newUrl, currentReplace, + oldState === $location.$$state ? null : $location.$$state); + } afterLocationChange(oldUrl, oldState); } }); @@ -20168,7 +20691,7 @@ * @description * Use the `$logProvider` to configure how the application logs messages */ -function $LogProvider(){ +function $LogProvider() { var debug = true, self = this; @@ -20188,7 +20711,7 @@ } }; - this.$get = ['$window', function($window){ + this.$get = ['$window', function($window) { return { /** * @ngdoc method @@ -20233,7 +20756,7 @@ * @description * Write a debug message */ - debug: (function () { + debug: (function() { var fn = consoleLog('debug'); return function() { @@ -20292,7 +20815,7 @@ // Sandboxing Angular Expressions // ------------------------------ // 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 +// 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: @@ -20301,7 +20824,7 @@ // // 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 +// 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. // @@ -20309,6 +20832,8 @@ // 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) { @@ -20317,7 +20842,7 @@ || name === "__proto__") { throw $parseMinErr('isecfld', 'Attempting to access a disallowed field in Angular expressions! ' - +'Expression: {0}', fullExpression); + + 'Expression: {0}', fullExpression); } return name; } @@ -20386,8 +20911,7 @@ //Operators - will be wrapped by binaryFn/unaryFn/assignment/filter var OPERATORS = extend(createMap(), { - /* jshint bitwise : false */ - '+':function(self, locals, a,b){ + '+':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); if (isDefined(a)) { if (isDefined(b)) { @@ -20395,33 +20919,30 @@ } return a; } - return isDefined(b)?b:undefined;}, - '-':function(self, locals, a,b){ + return isDefined(b) ? b : undefined;}, + '-':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); - return (isDefined(a)?a:0)-(isDefined(b)?b:0); + return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); }, - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, - '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, - '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, - '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, - '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, - '!':function(self, locals, a){return !a(self, locals);}, + '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, + '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, + '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, + '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, + '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, + '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, + '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, + '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, + '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, + '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, + '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, + '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, + '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, + '!':function(self, locals, a) {return !a(self, locals);}, //Tokenized as operators but parsed as assignment/filters '=':true, '|':true }); -/* jshint bitwise: true */ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; @@ -20431,54 +20952,41 @@ /** * @constructor */ -var Lexer = function (options) { +var Lexer = function(options) { this.options = options; }; Lexer.prototype = { constructor: Lexer, - lex: function (text) { + lex: function(text) { this.text = text; this.index = 0; - this.ch = undefined; this.tokens = []; while (this.index < this.text.length) { - this.ch = this.text.charAt(this.index); - if (this.is('"\'')) { - this.readString(this.ch); - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + var ch = this.text.charAt(this.index); + if (ch === '"' || ch === "'") { + this.readString(ch); + } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { this.readNumber(); - } else if (this.isIdent(this.ch)) { + } else if (this.isIdent(ch)) { this.readIdent(); - } else if (this.is('(){}[].,;:?')) { - this.tokens.push({ - index: this.index, - text: this.ch - }); - this.index++; - } else if (this.isWhitespace(this.ch)) { + } else if (this.is(ch, '(){}[].,;:?')) { + this.tokens.push({index: this.index, text: ch}); this.index++; - } else { - var ch2 = this.ch + this.peek(); + } else if (this.isWhitespace(ch)) { + this.index++; + } else { + var ch2 = ch + this.peek(); var ch3 = ch2 + this.peek(2); - var fn = OPERATORS[this.ch]; - var fn2 = OPERATORS[ch2]; - var fn3 = OPERATORS[ch3]; - if (fn3) { - this.tokens.push({index: this.index, text: ch3, fn: fn3}); - this.index += 3; - } else if (fn2) { - this.tokens.push({index: this.index, text: ch2, fn: fn2}); - this.index += 2; - } else if (fn) { - this.tokens.push({ - index: this.index, - text: this.ch, - fn: fn - }); - this.index += 1; + var op1 = OPERATORS[ch]; + var op2 = OPERATORS[ch2]; + var op3 = OPERATORS[ch3]; + if (op1 || op2 || op3) { + var token = op3 ? ch3 : (op2 ? ch2 : ch); + this.tokens.push({index: this.index, text: token, operator: true}); + this.index += token.length; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } @@ -20487,8 +20995,8 @@ return this.tokens; }, - is: function(chars) { - return chars.indexOf(this.ch) !== -1; + is: function(ch, chars) { + return chars.indexOf(ch) !== -1; }, peek: function(i) { @@ -20497,7 +21005,7 @@ }, isNumber: function(ch) { - return ('0' <= ch && ch <= '9'); + return ('0' <= ch && ch <= '9') && typeof ch === "string"; }, isWhitespace: function(ch) { @@ -20550,79 +21058,28 @@ } this.index++; } - number = 1 * number; this.tokens.push({ index: start, text: number, constant: true, - fn: function() { return number; } + value: Number(number) }); }, readIdent: function() { - var expression = this.text; - - var ident = ''; var start = this.index; - - var lastDot, peekIndex, methodName, ch; - while (this.index < this.text.length) { - ch = this.text.charAt(this.index); - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { - if (ch === '.') lastDot = this.index; - ident += ch; - } else { + var ch = this.text.charAt(this.index); + if (!(this.isIdent(ch) || this.isNumber(ch))) { break; } this.index++; } - - //check if the identifier ends with . and if so move back one char - if (lastDot && ident[ident.length - 1] === '.') { - this.index--; - ident = ident.slice(0, -1); - lastDot = ident.lastIndexOf('.'); - if (lastDot === -1) { - lastDot = undefined; - } - } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = this.index; - while (peekIndex < this.text.length) { - ch = this.text.charAt(peekIndex); - if (ch === '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - this.index = peekIndex; - break; - } - if (this.isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - this.tokens.push({ index: start, - text: ident, - fn: CONSTANTS[ident] || getterFn(ident, this.options, expression) - }); - - if (methodName) { - this.tokens.push({ - index: lastDot, - text: '.' - }); - this.tokens.push({ - index: lastDot + 1, - text: methodName - }); - } + text: this.text.slice(start, this.index), + identifier: true + }); }, readString: function(quote) { @@ -20653,9 +21110,8 @@ this.tokens.push({ index: start, text: rawString, - string: string, constant: true, - fn: function() { return string; } + value: string }); return; } else { @@ -20675,13 +21131,13 @@ /** * @constructor */ -var Parser = function (lexer, $filter, options) { +var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; }; -Parser.ZERO = extend(function () { +Parser.ZERO = extend(function() { return 0; }, { sharedGetter: true, @@ -20691,7 +21147,7 @@ Parser.prototype = { constructor: Parser, - parse: function (text) { + parse: function(text) { this.text = text; this.tokens = this.lexer.lex(text); @@ -20707,7 +21163,7 @@ return value; }, - primary: function () { + primary: function() { var primary; if (this.expect('(')) { primary = this.filterChain(); @@ -20716,16 +21172,14 @@ primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); - } else { - var token = this.expect(); - primary = token.fn; - if (!primary) { - this.throwError('not a primary expression', token); - } - if (token.constant) { - primary.constant = true; - primary.literal = true; - } + } else if (this.peek().identifier && this.peek().text in CONSTANTS) { + primary = CONSTANTS[this.consume().text]; + } else if (this.peek().identifier) { + primary = this.identifier(); + } else if (this.peek().constant) { + primary = this.constant(); + } else { + this.throwError('not a primary expression', this.peek()); } var next, context; @@ -20759,8 +21213,11 @@ }, peek: function(e1, e2, e3, e4) { - if (this.tokens.length > 0) { - var token = this.tokens[0]; + return this.peekAhead(0, e1, e2, e3, e4); + }, + peekAhead: function(i, e1, e2, e3, e4) { + if (this.tokens.length > i) { + var token = this.tokens[i]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { @@ -20770,7 +21227,7 @@ return false; }, - expect: function(e1, e2, e3, e4){ + expect: function(e1, e2, e3, e4) { var token = this.peek(e1, e2, e3, e4); if (token) { this.tokens.shift(); @@ -20779,13 +21236,20 @@ return false; }, - consume: function(e1){ - if (!this.expect(e1)) { + consume: function(e1) { + if (this.tokens.length === 0) { + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + } + + var token = this.expect(e1); + if (!token) { this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } - }, - - unaryFn: function(fn, right) { + return token; + }, + + unaryFn: function(op, right) { + var fn = OPERATORS[op]; return extend(function $parseUnaryFn(self, locals) { return fn(self, locals, right); }, { @@ -20794,7 +21258,8 @@ }); }, - binaryFn: function(left, fn, right, isBranching) { + binaryFn: function(left, op, right, isBranching) { + var fn = OPERATORS[op]; return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right); }, { @@ -20803,6 +21268,28 @@ }); }, + identifier: function() { + var id = this.consume().text; + + //Continue reading each `.identifier` unless it is a method invocation + while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { + id += this.consume().text + this.consume().text; + } + + return getterFn(id, this.options, this.text); + }, + + constant: function() { + var value = this.consume().value; + + return extend(function $parseConstant() { + return value; + }, { + constant: true, + literal: true + }); + }, + statements: function() { var statements = []; while (true) { @@ -20834,8 +21321,7 @@ }, filter: function(inputFn) { - var token = this.expect(); - var fn = this.$filter(token.text); + var fn = this.$filter(this.consume().text); var argsFn; var args; @@ -20898,17 +21384,14 @@ var token; if ((token = this.expect('?'))) { middle = this.assignment(); - if ((token = this.expect(':'))) { + if (this.consume(':')) { var right = this.assignment(); - return extend(function $parseTernary(self, locals){ + return extend(function $parseTernary(self, locals) { return left(self, locals) ? middle(self, locals) : right(self, locals); }, { constant: left.constant && middle.constant && right.constant }); - - } else { - this.throwError('expected :', token); } } @@ -20919,7 +21402,7 @@ var left = this.logicalAND(); var token; while ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND(), true); + left = this.binaryFn(left, token.text, this.logicalAND(), true); } return left; }, @@ -20927,8 +21410,8 @@ logicalAND: function() { var left = this.equality(); var token; - if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND(), true); + while ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.text, this.equality(), true); } return left; }, @@ -20936,8 +21419,8 @@ equality: function() { var left = this.relational(); var token; - if ((token = this.expect('==','!=','===','!=='))) { - left = this.binaryFn(left, token.fn, this.equality()); + while ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.text, this.relational()); } return left; }, @@ -20945,8 +21428,8 @@ relational: function() { var left = this.additive(); var token; - if ((token = this.expect('<', '>', '<=', '>='))) { - left = this.binaryFn(left, token.fn, this.relational()); + while ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.text, this.additive()); } return left; }, @@ -20955,7 +21438,7 @@ var left = this.multiplicative(); var token; while ((token = this.expect('+','-'))) { - left = this.binaryFn(left, token.fn, this.multiplicative()); + left = this.binaryFn(left, token.text, this.multiplicative()); } return left; }, @@ -20964,7 +21447,7 @@ var left = this.unary(); var token; while ((token = this.expect('*','/','%'))) { - left = this.binaryFn(left, token.fn, this.unary()); + left = this.binaryFn(left, token.text, this.unary()); } return left; }, @@ -20974,26 +21457,25 @@ if (this.expect('+')) { return this.primary(); } else if ((token = this.expect('-'))) { - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + return this.binaryFn(Parser.ZERO, token.text, this.unary()); } else if ((token = this.expect('!'))) { - return this.unaryFn(token.fn, this.unary()); + return this.unaryFn(token.text, this.unary()); } else { return this.primary(); } }, fieldAccess: function(object) { - var expression = this.text; - var field = this.expect().text; - var getter = getterFn(field, this.options, expression); + var getter = this.identifier(); return extend(function $parseFieldAccess(scope, locals, self) { - return getter(self || object(scope, locals)); + var o = self || object(scope, locals); + return (o == null) ? undefined : getter(o); }, { assign: function(scope, value, locals) { var o = object(scope, locals); if (!o) object.assign(scope, o = {}); - return setter(o, field, value, expression); + return getter.assign(o, value); } }); }, @@ -21038,7 +21520,7 @@ var args = argsFn.length ? [] : null; return function $parseFunctionCall(scope, locals) { - var context = contextGetter ? contextGetter(scope, locals) : scope; + var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; var fn = fnGetter(scope, locals, context) || noop; if (args) { @@ -21051,17 +21533,17 @@ ensureSafeObject(context, expressionText); ensureSafeFunction(fn, expressionText); - // IE stupidity! (IE doesn't have apply for some native functions) + // IE doesn't have apply for some native functions var v = fn.apply ? fn.apply(context, args) : fn(args[0], args[1], args[2], args[3], args[4]); return ensureSafeObject(v, expressionText); - }; + }; }, // This is used with json array declaration - arrayDeclaration: function () { + arrayDeclaration: function() { var elementFns = []; if (this.peekToken().text !== ']') { do { @@ -21069,8 +21551,7 @@ // Support trailing commas per ES5.1. break; } - var elementFn = this.expression(); - elementFns.push(elementFn); + elementFns.push(this.expression()); } while (this.expect(',')); } this.consume(']'); @@ -21088,7 +21569,7 @@ }); }, - object: function () { + object: function() { var keys = [], valueFns = []; if (this.peekToken().text !== '}') { do { @@ -21096,11 +21577,16 @@ // Support trailing commas per ES5.1. break; } - var token = this.expect(); - keys.push(token.string || token.text); + var token = this.consume(); + if (token.constant) { + keys.push(token.value); + } else if (token.identifier) { + keys.push(token.text); + } else { + this.throwError("invalid key", token); + } this.consume(':'); - var value = this.expression(); - valueFns.push(value); + valueFns.push(this.expression()); } while (this.expect(',')); } this.consume('}'); @@ -21143,64 +21629,85 @@ return setValue; } -var getterFnCache = createMap(); +var getterFnCacheDefault = createMap(); +var getterFnCacheExpensive = createMap(); + +function isPossiblyDangerousMemberName(name) { + return name == 'constructor'; +} /** * Implementation of the "Black Hole" variant from: * - http://jsperf.com/angularjs-parse-getter/4 * - http://jsperf.com/path-evaluation-simplified/7 */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) { ensureSafeMemberName(key0, fullExp); ensureSafeMemberName(key1, fullExp); ensureSafeMemberName(key2, fullExp); ensureSafeMemberName(key3, fullExp); ensureSafeMemberName(key4, fullExp); + var eso = function(o) { + return ensureSafeObject(o, fullExp); + }; + var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity; + var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity; + var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity; + var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity; + var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity; return function cspSafeGetter(scope, locals) { var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; if (pathVal == null) return pathVal; - pathVal = pathVal[key0]; + pathVal = eso0(pathVal[key0]); if (!key1) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key1]; + pathVal = eso1(pathVal[key1]); if (!key2) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key2]; + pathVal = eso2(pathVal[key2]); if (!key3) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key3]; + pathVal = eso3(pathVal[key3]); if (!key4) return pathVal; if (pathVal == null) return undefined; - pathVal = pathVal[key4]; + pathVal = eso4(pathVal[key4]); return pathVal; }; } +function getterFnWithEnsureSafeObject(fn, fullExpression) { + return function(s, l) { + return fn(s, l, ensureSafeObject, fullExpression); + }; +} + function getterFn(path, options, fullExp) { + var expensiveChecks = options.expensiveChecks; + var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault); var fn = getterFnCache[path]; - if (fn) return fn; + var pathKeys = path.split('.'), pathKeysLength = pathKeys.length; // http://jsperf.com/angularjs-parse-getter/6 if (options.csp) { if (pathKeysLength < 6) { - fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks); } else { fn = function cspSafeGetter(scope, locals) { var i = 0, val; do { val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], - pathKeys[i++], fullExp)(scope, locals); + pathKeys[i++], fullExp, expensiveChecks)(scope, locals); locals = undefined; // clear after first iteration scope = val; @@ -21210,22 +21717,33 @@ } } else { var code = ''; + if (expensiveChecks) { + code += 's = eso(s, fe);\nl = eso(l, fe);\n'; + } + var needsEnsureSafeObject = expensiveChecks; forEach(pathKeys, function(key, index) { ensureSafeMemberName(key, fullExp); - code += 'if(s == null) return undefined;\n' + - 's='+ (index + var lookupJs = (index // we simply dereference 's' on any .dot notation ? 's' // but if we are first then we check locals first, and if so read it first - : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n'; + : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key; + if (expensiveChecks || isPossiblyDangerousMemberName(key)) { + lookupJs = 'eso(' + lookupJs + ', fe)'; + needsEnsureSafeObject = true; + } + code += 'if(s == null) return undefined;\n' + + 's=' + lookupJs + ';\n'; }); code += 'return s;'; /* jshint -W054 */ - var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals + var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject /* jshint +W054 */ evaledFnGetter.toString = valueFn(code); - + if (needsEnsureSafeObject) { + evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp); + } fn = evaledFnGetter; } @@ -21237,6 +21755,12 @@ return fn; } +var objectValueOf = Object.prototype.valueOf; + +function getValueOf(value) { + return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); +} + /////////////////////////////////// /** @@ -21289,15 +21813,20 @@ * service. */ function $ParseProvider() { - var cache = createMap(); - - var $parseOptions = { - csp: false - }; + var cacheDefault = createMap(); + var cacheExpensive = createMap(); + this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { - $parseOptions.csp = $sniffer.csp; + var $parseOptions = { + csp: $sniffer.csp, + expensiveChecks: false + }, + $parseOptionsExpensive = { + csp: $sniffer.csp, + expensiveChecks: true + }; function wrapSharedExpression(exp) { var wrapped = exp; @@ -21314,13 +21843,14 @@ return wrapped; } - return function $parse(exp, interceptorFn) { + return function $parse(exp, interceptorFn, expensiveChecks) { var parsedExpression, oneTime, cacheKey; switch (typeof exp) { case 'string': cacheKey = exp = exp.trim(); + var cache = (expensiveChecks ? cacheExpensive : cacheDefault); parsedExpression = cache[cacheKey]; if (!parsedExpression) { @@ -21329,8 +21859,9 @@ 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) { @@ -21383,7 +21914,7 @@ // attempt to convert the value to a primitive type // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can // be cheaply dirty-checked - newValue = newValue.valueOf(); + newValue = getValueOf(newValue); if (typeof newValue === 'object') { // objects/arrays are not supported - deep-watching them would be too expensive @@ -21410,7 +21941,7 @@ var newInputValue = inputExpressions(scope); if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) { lastResult = parsedExpression(scope); - oldInputValue = newInputValue && newInputValue.valueOf(); + oldInputValue = newInputValue && getValueOf(newInputValue); } return lastResult; }, listener, objectEquality); @@ -21427,7 +21958,7 @@ for (var i = 0, ii = inputExpressions.length; i < ii; i++) { var newInputValue = inputExpressions[i](scope); if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { - oldInputValueOfValues[i] = newInputValue && newInputValue.valueOf(); + oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); } } @@ -21449,7 +21980,7 @@ listener.apply(this, arguments); } if (isDefined(value)) { - scope.$$postDigest(function () { + scope.$$postDigest(function() { if (isDefined(lastValue)) { unwatch(); } @@ -21459,23 +21990,24 @@ } function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { - var unwatch; + var unwatch, lastValue; return unwatch = scope.$watch(function oneTimeWatch(scope) { return parsedExpression(scope); }, function oneTimeListener(value, old, scope) { + lastValue = value; if (isFunction(listener)) { listener.call(this, value, old, scope); } if (isAllDefined(value)) { - scope.$$postDigest(function () { - if(isAllDefined(value)) unwatch(); + scope.$$postDigest(function() { + if (isAllDefined(lastValue)) unwatch(); }); } }, objectEquality); function isAllDefined(value) { var allDefined = true; - forEach(value, function (val) { + forEach(value, function(val) { if (!isDefined(val)) allDefined = false; }); return allDefined; @@ -21496,8 +22028,16 @@ function addInterceptor(parsedExpression, interceptorFn) { if (!interceptorFn) return parsedExpression; - - var fn = function interceptedExpression(scope, locals) { + var watchDelegate = parsedExpression.$$watchDelegate; + + var regularWatch = + watchDelegate !== oneTimeLiteralWatchDelegate && + watchDelegate !== oneTimeWatchDelegate; + + var fn = regularWatch ? function regularInterceptedExpression(scope, locals) { + var value = parsedExpression(scope, locals); + return interceptorFn(value, scope, locals); + } : function oneTimeInterceptedExpression(scope, locals) { var value = parsedExpression(scope, locals); var result = interceptorFn(value, scope, locals); // we only return the interceptor's result if the @@ -21527,7 +22067,11 @@ * @requires $rootScope * * @description - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * A service that helps you run functions asynchronously, and use their return values (or exceptions) + * when they are done processing. + * + * 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 promises to some degree. @@ -21544,24 +22088,27 @@ * It can be used like so: * * ```js - * return $q(function(resolve, reject) { - * // perform some asynchronous operation, resolve or reject the promise when appropriate. - * setInterval(function() { - * if (pollStatus > 0) { - * resolve(polledValue); - * } else if (pollStatus < 0) { - * reject(polledValue); - * } else { - * pollStatus = pollAgain(function(value) { - * polledValue = value; - * }); - * } - * }, 10000); - * }). - * then(function(value) { - * // handle success + * // for the purpose of this example let's assume that variables `$q` and `okToGreet` + * // are available in the current lexical scope (they could have been injected or passed in). + * + * function asyncGreet(name) { + * // perform some asynchronous operation, resolve or reject the promise when appropriate. + * return $q(function(resolve, reject) { + * setTimeout(function() { + * if (okToGreet(name)) { + * resolve('Hello, ' + name + '!'); + * } else { + * reject('Greeting ' + name + ' is not allowed.'); + * } + * }, 1000); + * }); + * } + * + * var promise = asyncGreet('Robin Hood'); + * promise.then(function(greeting) { + * alert('Success: ' + greeting); * }, function(reason) { - * // handle failure + * alert('Failed: ' + reason); * }); * ``` * @@ -21577,7 +22124,7 @@ * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. * * ```js - * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` + * // for the purpose of this example let's assume that variables `$q` and `okToGreet` * // are available in the current lexical scope (they could have been injected or passed in). * * function asyncGreet(name) { @@ -21660,16 +22207,12 @@ * * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, * but to do so without modifying the final value. This is useful to release resources or do some * clean-up that needs to be done whether the promise was rejected or resolved. See the [full * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 and Android 2.x compatible. - * * # Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily @@ -21836,7 +22379,7 @@ } else { promise.reject(state.value); } - } catch(e) { + } catch (e) { promise.reject(e); exceptionHandler(e); } @@ -21886,7 +22429,7 @@ this.promise.$$state.status = 1; scheduleProcessQueue(this.promise.$$state); } - } catch(e) { + } catch (e) { fns[1](e); exceptionHandler(e); } @@ -21914,7 +22457,7 @@ callback = callbacks[i][3]; try { result.notify(isFunction(callback) ? callback(progress) : progress); - } catch(e) { + } catch (e) { exceptionHandler(e); } } @@ -21979,7 +22522,7 @@ var callbackOutput = null; try { if (isFunction(callback)) callbackOutput = callback(); - } catch(e) { + } catch (e) { return makePromise(e, false); } if (isPromiseLike(callbackOutput)) { @@ -22087,15 +22630,13 @@ return $Q; } -function $$RAFProvider(){ //rAF +function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - $window.mozRequestAnimationFrame; + $window.webkitRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || - $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; var rafSupported = !!requestAnimationFrame; @@ -22186,7 +22727,7 @@ * They also provide an event emission/broadcast and subscription facility. See the * {@link guide/scope developer guide on scopes}. */ -function $RootScopeProvider(){ +function $RootScopeProvider() { var TTL = 10; var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; @@ -22200,7 +22741,7 @@ }; this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function( $injector, $exceptionHandler, $parse, $browser) { + function($injector, $exceptionHandler, $parse, $browser) { /** * @ngdoc type @@ -22224,7 +22765,6 @@ var child = parent.$new(); parent.salutation = "Hello"; - child.name = "World"; expect(child.salutation).toEqual('Hello'); child.salutation = "Welcome"; @@ -22232,6 +22772,10 @@ expect(parent.salutation).toEqual('Hello'); * ``` * + * When interacting with `Scope` in tests, additional helper methods are available on the + * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional + * details. + * * * @param {Object.=} providers Map of service factory which need to be * provided for the current scope. Defaults to {@link ng}. @@ -22543,7 +23087,7 @@ if (!watchExpressions.length) { // No expressions means we call the listener ASAP var shouldCall = true; - self.$evalAsync(function () { + self.$evalAsync(function() { if (shouldCall) listener(newValues, newValues, self); }); return function deregisterWatchGroup() { @@ -22560,7 +23104,7 @@ }); } - forEach(watchExpressions, function (expr, i) { + forEach(watchExpressions, function(expr, i) { var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { newValues[i] = value; oldValues[i] = oldValue; @@ -22670,6 +23214,9 @@ newValue = _value; var newLength, key, bothNaN, newItem, oldItem; + // If the new value is undefined, then return undefined as the watch may be a one-time watch + if (isUndefined(newValue)) return; + if (!isObject(newValue)) { // if primitive if (oldValue !== newValue) { oldValue = newValue; @@ -22732,7 +23279,7 @@ if (oldLength > newLength) { // we used to have more keys, need to find them and destroy them. changeDetected++; - for(key in oldValue) { + for (key in oldValue) { if (!newValue.hasOwnProperty(key)) { oldLength--; delete oldValue[key]; @@ -22852,10 +23399,10 @@ dirty = false; current = target; - while(asyncQueue.length) { + while (asyncQueue.length) { try { asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression); + asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); } catch (e) { $exceptionHandler(e); } @@ -22885,11 +23432,11 @@ if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); + watchLog[logIdx].push({ + msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, + newVal: value, + oldVal: last + }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers @@ -22909,7 +23456,7 @@ // this piece should be kept in sync with the traversal in $broadcast if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { + while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } @@ -22917,19 +23464,19 @@ // `break traverseScopesLoop;` takes us to here - if((dirty || asyncQueue.length) && !(ttl--)) { + if ((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', - TTL, toJson(watchLog)); + TTL, watchLog); } } while (dirty || asyncQueue.length); clearPhase(); - while(postDigestQueue.length) { + while (postDigestQueue.length) { try { postDigestQueue.shift()(); } catch (e) { @@ -23070,8 +23617,9 @@ * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. */ - $evalAsync: function(expr) { + $evalAsync: function(expr, locals) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush if (!$rootScope.$$phase && !asyncQueue.length) { @@ -23082,10 +23630,10 @@ }); } - asyncQueue.push({scope: this, expression: expr}); + asyncQueue.push({scope: this, expression: expr, locals: locals}); }, - $$postDigest : function(fn) { + $$postDigest: function(fn) { postDigestQueue.push(fn); }, @@ -23222,8 +23770,11 @@ var self = this; return function() { - namedListeners[namedListeners.indexOf(listener)] = null; - decrementListenerCount(self, 1, name); + var indexOfListener = namedListeners.indexOf(listener); + if (indexOfListener !== -1) { + namedListeners[indexOfListener] = null; + decrementListenerCount(self, 1, name); + } }; }, @@ -23270,7 +23821,7 @@ do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; - for (i=0, length=namedListeners.length; i to learn more about them. * You can ensure your document is in standards mode and not quirks mode by adding `` @@ -23964,7 +24506,7 @@ * * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link @@ -24180,7 +24722,7 @@ * @description * Enables/disables SCE and returns the current value. */ - this.enabled = function (value) { + this.enabled = function(value) { if (arguments.length) { enabled = !!value; } @@ -24234,13 +24776,13 @@ * sce.js and sceSpecs.js would need to be aware of this detail. */ - this.$get = ['$parse', '$sniffer', '$sceDelegate', function( - $parse, $sniffer, $sceDelegate) { - // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows + this.$get = ['$parse', '$sceDelegate', function( + $parse, $sceDelegate) { + // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow // the "expression(javascript expression)" syntax which is insecure. - if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { + if (enabled && msie < 8) { throw $sceMinErr('iequirks', - 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + + 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 'mode. You can fix this by adding the text to the top of your HTML ' + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); } @@ -24258,7 +24800,7 @@ * @description * Returns a boolean indicating if SCE is enabled. */ - sce.isEnabled = function () { + sce.isEnabled = function() { return enabled; }; sce.trustAs = $sceDelegate.trustAs; @@ -24294,7 +24836,7 @@ if (parsed.literal && parsed.constant) { return parsed; } else { - return $parse(expr, function (value) { + return $parse(expr, function(value) { return sce.getTrusted(type, value); }); } @@ -24463,7 +25005,7 @@ * * @description * Shorthand method. `$sce.parseAsHtml(expression string)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -24480,7 +25022,7 @@ * * @description * Shorthand method. `$sce.parseAsCss(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -24497,7 +25039,7 @@ * * @description * Shorthand method. `$sce.parseAsUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -24514,7 +25056,7 @@ * * @description * Shorthand method. `$sce.parseAsResourceUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -24531,7 +25073,7 @@ * * @description * Shorthand method. `$sce.parseAsJs(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} + * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: @@ -24547,15 +25089,15 @@ getTrusted = sce.getTrusted, trustAs = sce.trustAs; - forEach(SCE_CONTEXTS, function (enumValue, name) { + forEach(SCE_CONTEXTS, function(enumValue, name) { var lName = lowercase(name); - sce[camelCase("parse_as_" + lName)] = function (expr) { + sce[camelCase("parse_as_" + lName)] = function(expr) { return parse(enumValue, expr); }; - sce[camelCase("get_trusted_" + lName)] = function (value) { + sce[camelCase("get_trusted_" + lName)] = function(value) { return getTrusted(enumValue, value); }; - sce[camelCase("trust_as_" + lName)] = function (value) { + sce[camelCase("trust_as_" + lName)] = function(value) { return trustAs(enumValue, value); }; }); @@ -24585,31 +25127,30 @@ int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), boxee = /Boxee/i.test(($window.navigator || {}).userAgent), document = $document[0] || {}, - documentMode = document.documentMode, vendorPrefix, - vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, + vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, bodyStyle = document.body && document.body.style, transitions = false, animations = false, match; if (bodyStyle) { - for(var prop in bodyStyle) { - if(match = vendorRegex.exec(prop)) { + for (var prop in bodyStyle) { + if (match = vendorRegex.exec(prop)) { vendorPrefix = match[0]; vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); break; } } - if(!vendorPrefix) { + if (!vendorPrefix) { vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; } transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - if (android && (!transitions||!animations)) { + if (android && (!transitions || !animations)) { transitions = isString(document.body.style.webkitTransition); animations = isString(document.body.style.webkitAnimation); } @@ -24632,7 +25173,9 @@ // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have // it. In particular the event is not fired when backspace or delete key are pressed or // when cut operation is performed. - if (event == 'input' && msie == 9) return false; + // IE10+ implements 'input' event but it erroneously fires under various situations, + // e.g. when placeholder changes, or a form is focused. + if (event === 'input' && msie <= 11) return false; if (isUndefined(eventSupport[event])) { var divElm = document.createElement('div'); @@ -24643,11 +25186,9 @@ }, csp: csp(), vendorPrefix: vendorPrefix, - transitions : transitions, - animations : animations, - android: android, - msie : msie, - msieDocumentMode: documentMode + transitions: transitions, + animations: animations, + android: android }; }]; } @@ -24661,7 +25202,7 @@ * @description * The `$templateRequest` service downloads the provided template using `$http` and, upon success, * stores the contents inside of `$templateCache`. If the HTTP request fails or the response data - * of the HTTP request is empty then a `$compile` error will be thrown (the exception can be thwarted + * of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted * by setting the 2nd parameter of the function to true). * * @param {string} tpl The HTTP request template URL @@ -24677,24 +25218,33 @@ var self = handleRequestFn; self.totalPendingRequests++; - return $http.get(tpl, { cache : $templateCache }) + var transformResponse = $http.defaults && $http.defaults.transformResponse; + + if (isArray(transformResponse)) { + transformResponse = transformResponse.filter(function(transformer) { + return transformer !== defaultHttpResponseTransform; + }); + } else if (transformResponse === defaultHttpResponseTransform) { + transformResponse = null; + } + + var httpOptions = { + cache: $templateCache, + transformResponse: transformResponse + }; + + return $http.get(tpl, httpOptions) .then(function(response) { - var html = response.data; - if(!html || html.length === 0) { - return handleError(); - } - self.totalPendingRequests--; - $templateCache.put(tpl, html); - return html; + return response.data; }, handleError); - function handleError() { + function handleError(resp) { self.totalPendingRequests--; if (!ignoreRequestError) { throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl); } - return $q.reject(); + return $q.reject(resp); } } @@ -24737,7 +25287,7 @@ if (dataBinding) { forEach(dataBinding, function(bindingName) { if (opt_exactMatch) { - var matcher = new RegExp('(^|\\s)' + expression + '(\\s|\\||$)'); + var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); if (matcher.test(bindingName)) { matches.push(binding); } @@ -24859,7 +25409,7 @@ timeoutId = $browser.defer(function() { try { deferred.resolve(fn()); - } catch(e) { + } catch (e) { deferred.reject(e); $exceptionHandler(e); } @@ -24910,7 +25460,7 @@ // exactly the behavior needed here. There is little value is mocking these out for this // service. var urlParsingNode = document.createElement("a"); -var originUrl = urlResolve(window.location.href, true); +var originUrl = urlResolve(window.location.href); /** @@ -24965,7 +25515,7 @@ * | pathname | The pathname, beginning with "/" * */ -function urlResolve(url, base) { +function urlResolve(url) { var href = url; if (msie) { @@ -25025,7 +25575,7 @@
-
+

{{text}}
@@ -33727,7 +34467,6 @@ compile: function(element, attr) { if (attr.type == 'text/ng-template') { var templateUrl = attr.id, - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent text = element[0].text; $templateCache.put(templateUrl, text); @@ -33749,33 +34488,74 @@ * * The `ngOptions` attribute can be used to dynamically generate a list of `