From: Colin Snover Date: Mon, 17 Jan 2011 21:31:43 +0000 (-0600) Subject: Merge in data_nocollide branch. Fixes #6968, improves unit testing framework checks... X-Git-Url: http://git.asbjorn.biz/?p=jquery.git;a=commitdiff_plain;h=e78d3a7e2d47e9796f87c18b76f8178b0bee42c5;hp=57cc182a40e909868d41f9b1bb405b06138f6cae Merge in data_nocollide branch. Fixes #6968, improves unit testing framework checks for leaky stuff. --- diff --git a/src/ajax.js b/src/ajax.js index dbea8d5..871481d 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -11,6 +11,7 @@ var r20 = /%20/g, rselectTextarea = /^(?:select|textarea)/i, rts = /([?&])_=[^&]*/, rurl = /^(\w+:)?\/\/([^\/?#:]+)(?::(\d+))?/, + rCRLF = /\r?\n/g, // Slice function sliceFunc = Array.prototype.slice, @@ -60,26 +61,34 @@ jQuery.fn.extend({ type: type, dataType: "html", data: params, - complete: function( res, status ) { + // Complete callback (responseText is used internally) + complete: function( jXHR, status, responseText ) { + // Store the response as specified by the jXHR object + responseText = jXHR.responseText; // If successful, inject the HTML into all the matched elements - if ( status === "success" || status === "notmodified" ) { + if ( jXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jXHR.done(function( r ) { + responseText = r; + }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("
") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE - .append(res.responseText.replace(rscript, "")) + .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result - res.responseText ); + responseText ); } if ( callback ) { - self.each( callback, [res.responseText, status, res] ); + self.each( callback, [responseText, status, jXHR] ); } } }); @@ -107,9 +116,9 @@ jQuery.fn.extend({ null : jQuery.isArray(val) ? jQuery.map( val, function(val, i){ - return {name: elem.name, value: val.replace(/\r?\n/g, "\r\n")}; + return { name: elem.name, value: val.replace(rCRLF, "\r\n") }; }) : - {name: elem.name, value: val.replace(/\r?\n/g, "\r\n")}; + { name: elem.name, value: val.replace(rCRLF, "\r\n") }; }).get(); } }); @@ -151,7 +160,8 @@ jQuery.extend({ }, ajaxSetup: function( settings ) { - jQuery.extend( jQuery.ajaxSettings, settings ); + jQuery.extend( true, jQuery.ajaxSettings, settings ); + return this; }, ajaxSettings: { @@ -165,11 +175,12 @@ jQuery.extend({ timeout: 0, data: null, dataType: null, - dataTypes: null, username: null, password: null, cache: null, traditional: false, + headers: {}, + crossDomain: null, */ xhr: function() { return new window.XMLHttpRequest(); @@ -256,6 +267,8 @@ jQuery.extend({ // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery._Deferred(), + // Status-dependent callbacks + statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, // Response headers @@ -265,6 +278,9 @@ jQuery.extend({ transport, // timeout handle timeoutTimer, + // Cross-domain detection vars + loc = document.location, + parts, // The jXHR state state = 0, // Loop variable @@ -291,30 +307,35 @@ jQuery.extend({ // (match is used internally) getResponseHeader: function( key , match ) { - if ( state !== 2 ) { - return null; - } + if ( state === 2 ) { - if ( responseHeaders === undefined ) { + if ( responseHeaders === undefined ) { - responseHeaders = {}; + responseHeaders = {}; - if ( typeof responseHeadersString === "string" ) { + if ( typeof responseHeadersString === "string" ) { - while( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } } } + match = responseHeaders[ key.toLowerCase() ]; + + } else { + + match = null; } - return responseHeaders[ key.toLowerCase() ]; + + return match; }, // Cancel the request abort: function( statusText ) { - if ( transport && state !== 2 ) { + if ( transport ) { transport.abort( statusText || "abort" ); - done( 0 , statusText ); } + done( 0 , statusText ); return this; } }; @@ -332,6 +353,10 @@ jQuery.extend({ // State is "done" now state = 2; + // Dereference transport for early garbage collection + // (no matter how long the jXHR transport will be used + transport = 0; + // Set readyState jXHR.readyState = status ? 4 : 0; @@ -353,17 +378,10 @@ jQuery.extend({ // Stored success success, // Stored error - error = statusText; - - // If not timeout, force a jQuery-compliant status text - if ( statusText != "timeout" ) { - statusText = ( status >= 200 && status < 300 ) ? - "success" : - ( status === 304 ? "notmodified" : "error" ); - } + error; // If successful, handle type chaining - if ( statusText === "success" || statusText === "notmodified" ) { + if ( status >= 200 && status < 300 || status === 304 ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { @@ -379,91 +397,124 @@ jQuery.extend({ } } - if ( s.ifModified && statusText === "notmodified" ) { + // If not modified + if ( status === 304 ) { - success = null; + // Set the statusText accordingly + statusText = "notmodified"; + // Mark as a success isSuccess = 1; + // If we have data } else { + + // Set the statusText accordingly + statusText = "success"; + // Chain data conversions and determine the final value // (if an exception is thrown in the process, it'll be notified as an error) try { var i, + // Current dataType current, + // Previous dataType prev, - checker, + // Conversion function conv, + // Conversion functions (when text is used in-between) conv1, conv2, - convertion, + // Local references to dataTypes & converters dataTypes = s.dataTypes, converters = s.converters, + // DataType to responseXXX field mapping responses = { "xml": "XML", "text": "Text" }; + // For each dataType in the chain for( i = 0 ; i < dataTypes.length ; i++ ) { current = dataTypes[ i ]; + // If a responseXXX field for this dataType exists + // and if it hasn't been set yet if ( responses[ current ] ) { + // Set it jXHR[ "response" + responses[ current ] ] = response; + // Mark it as set responses[ current ] = 0; } + // If this is not the first element if ( i ) { + // Get the dataType to convert from prev = dataTypes[ i - 1 ]; + // If no catch-all and dataTypes are actually different if ( prev !== "*" && current !== "*" && prev !== current ) { - conv = converters[ ( conversion = prev + " " + current ) ] || + // Get the converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; conv1 = conv2 = 0; + // If there is no direct converter and none of the dataTypes is text if ( ! conv && prev !== "text" && current !== "text" ) { + // Try with text in-between conv1 = converters[ prev + " text" ] || converters[ "* text" ]; conv2 = converters[ "text " + current ]; + // Revert back to a single converter + // if one of the converter is an equivalence if ( conv1 === true ) { conv = conv2; } else if ( conv2 === true ) { conv = conv1; } } - + // If we found no converter, dispatch an error if ( ! ( conv || conv1 && conv2 ) ) { throw conversion; } - + // If found converter is not an equivalence if ( conv !== true ) { + // Convert with 1 or 2 converters accordingly response = conv ? conv( response ) : conv2( conv1( response ) ); } } + // If it is the first element of the chain + // and we have a dataFilter } else if ( s.dataFilter ) { - - response = s.dataFilter( response ); + // Apply the dataFilter + response = s.dataFilter( response , current ); + // Get dataTypes again in case the filter changed them dataTypes = s.dataTypes; } } + // End of loop // We have a real success success = response; isSuccess = 1; + // If an exception was thrown } catch(e) { + // We have a parsererror statusText = "parsererror"; error = "" + e; } } - } else { // if not success, mark it as an error + // if not success, mark it as an error + } else { - error = error || statusText; + error = statusText = statusText || "error"; // Set responseText if needed if ( response ) { @@ -482,6 +533,9 @@ jQuery.extend({ deferred.fireReject( callbackContext , [ jXHR , statusText , error ] ); } + // Status-dependent callbacks + jXHR.statusCode( statusCode ); + if ( s.global ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , [ jXHR , s , isSuccess ? success : error ] ); @@ -505,23 +559,37 @@ jQuery.extend({ jXHR.error = jXHR.fail; jXHR.complete = completeDeferred.done; + // Status-dependent callbacks + jXHR.statusCode = function( map ) { + if ( map ) { + var resolved = jXHR.isResolved(), + tmp; + if ( resolved || jXHR.isRejected() ) { + tmp = map[ jXHR.status ]; + if ( tmp ) { + if ( map === statusCode ) { + delete statusCode[ jXHR.status ]; + } + jXHR[ resolved ? "done" : "fail" ]( tmp ); + } + } else { + for( tmp in map ) { + statusCode[ tmp ] = [ statusCode[ tmp ] , map[ tmp ] ]; + } + } + } + return this; + }; + // Remove hash character (#7531: and string promotion) s.url = ( "" + s.url ).replace( rhash , "" ); - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = ! rnoContent.test( s.type ); - // Extract dataTypes list s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( /\s+/ ); // Determine if a cross-domain request is in order - var parts = rurl.exec( s.url.toLowerCase() ), - loc = location; - if ( ! s.crossDomain ) { + parts = rurl.exec( s.url.toLowerCase() ); s.crossDomain = !!( parts && ( parts[ 1 ] && parts[ 1 ] != loc.protocol || @@ -531,88 +599,97 @@ jQuery.extend({ } // Convert data if not already a string - if ( s.data && s.processData && typeof s.data != "string" ) { + if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data , s.traditional ); } - // Get transport - transport = jQuery.ajax.prefilter( s , options ).transport( s ); + // Apply prefilters + jQuery.ajaxPrefilter( s , options ); + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = ! rnoContent.test( s.type ); // Watch for a new set of requests if ( s.global && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } - // If no transport, we auto-abort - if ( ! transport ) { + // More options handling for requests with no content + if ( ! s.hasContent ) { - done( 0 , "transport not found" ); - jXHR = false; - - } else { + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + } - // More options handling for requests with no content - if ( ! s.hasContent ) { + // Add anti-cache in url if needed + if ( s.cache === false ) { - // If data is available, append data to url - if ( s.data ) { - s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; - } + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts , "$1_=" + ts ); - // Add anti-cache in url if needed - if ( s.cache === false ) { + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); + } + } - var ts = jQuery.now(), - // try replacing _= if it is there - ret = s.url.replace( rts , "$1_=" + ts ); + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + requestHeaders[ "content-type" ] = s.contentType; + } - // if nothing was replaced, add timestamp to the end - s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); - } + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery_lastModified[ s.url ] ) { + requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - requestHeaders[ "content-type" ] = s.contentType; + if ( jQuery_etag[ s.url ] ) { + requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; } + } - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery_lastModified[ s.url ] ) { - requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; - } - if ( jQuery_etag[ s.url ] ) { - requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; - } - } + // Set the Accepts header for the server, depending on the dataType + requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ]; + + // Check for headers option + for ( i in s.headers ) { + requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + } - // Set the Accepts header for the server, depending on the dataType - requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : - s.accepts[ "*" ]; + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { - // Check for headers option - for ( i in s.headers ) { - requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + // Abort if not done already + done( 0 , "abort" ); + + // Return false + jXHR = false; + + } else { + + // Install callbacks on deferreds + for ( i in { success:1, error:1, complete:1 } ) { + jXHR[ i ]( s[ i ] ); } - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { + // Get transport + transport = jQuery.ajaxTransport( s ); - // Abort if not done already - done( 0 , "abort" ); - jXHR = false; + // If no transport, we auto-abort + if ( ! transport ) { + + done( 0 , "notransport" ); } else { // Set state as sending - state = 1; - jXHR.readyState = 1; - - // Install callbacks on deferreds - for ( i in { success:1, error:1, complete:1 } ) { - jXHR[ i ]( s[ i ] ); - } + state = jXHR.readyState = 1; // Send global event if ( s.global ) { @@ -786,7 +863,7 @@ function ajax_selectOrExecute( structure , s ) { } } - return noSelect ? jQuery.ajax : selected; + return noSelect ? jQuery : selected; } // Add an element to one of the structures in ajaxSettings @@ -845,13 +922,13 @@ function ajax_addElement( structure , args ) { } } - return jQuery.ajax; + return jQuery; } // Install prefilter & transport methods -jQuery.each( [ "prefilter" , "transport" ] , function( _ , name ) { - _ = name + "s"; - jQuery.ajax[ name ] = function() { +jQuery.each( [ "Prefilter" , "Transport" ] , function( _ , name ) { + _ = name.toLowerCase() + "s"; + jQuery[ "ajax" + name ] = function() { return ajax_addElement( _ , arguments ); }; } ); diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js index f4b324e..883876f 100644 --- a/src/ajax/jsonp.js +++ b/src/ajax/jsonp.js @@ -1,8 +1,7 @@ (function( jQuery ) { var jsc = jQuery.now(), - jsre = /\=(?:\?|%3F)(&|$)/i, - rquery_jsonp = /\?/; + jsre = /(\=)(?:\?|%3F)(&|$)|()(?:\?\?|%3F%3F)()/i; // Default jsonp settings jQuery.ajaxSetup({ @@ -10,75 +9,78 @@ jQuery.ajaxSetup({ jsonpCallback: function() { return "jsonp" + jsc++; } -}); -// Normalize jsonp queries -// 1) put callback parameter in url or data -// 2) sneakily ensure transportDataType is always jsonp for jsonp requests -jQuery.ajax.prefilter("json jsonp", function(s, originalSettings) { +// Detect, normalize options and install callbacks for jsonp requests +// (dataIsString is used internally) +}).ajaxPrefilter("json jsonp", function(s, originalSettings, dataIsString) { + + dataIsString = ( typeof(s.data) === "string" ); if ( s.dataTypes[ 0 ] === "jsonp" || - originalSettings.jsonp || originalSettings.jsonpCallback || - jsre.test(s.url) || - typeof(s.data) === "string" && jsre.test(s.data) ) { + originalSettings.jsonp != null || + s.jsonp !== false && ( jsre.test( s.url ) || + dataIsString && jsre.test( s.data ) ) ) { - var jsonpCallback = s.jsonpCallback = + var responseContainer, + jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, - url = s.url.replace(jsre, "=" + jsonpCallback + "$1"), - data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "=" + jsonpCallback + "$1") : s.data; - - if ( url === s.url && data === s.data ) { - url += (rquery_jsonp.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + previous = window[ jsonpCallback ], + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( dataIsString ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } } s.url = url; s.data = data; - s.dataTypes[ 0 ] = "jsonp"; - } - -// Bind transport to jsonp dataType -}).transport("jsonp", function(s) { - // Put callback in place - var responseContainer, - jsonpCallback = s.jsonpCallback, - previous = window[ jsonpCallback ]; + window [ jsonpCallback ] = function( response ) { + responseContainer = [response]; + }; - window [ jsonpCallback ] = function( response ) { - responseContainer = [response]; - }; + s.complete = [function() { - s.complete = [function() { + // Set callback back to previous value + window[ jsonpCallback ] = previous; - // Set callback back to previous value - window[ jsonpCallback ] = previous; - - // Call if it was a function and we have a response - if ( previous) { - if ( responseContainer && jQuery.isFunction ( previous ) ) { - window[ jsonpCallback ] ( responseContainer[0] ); + // Call if it was a function and we have a response + if ( previous) { + if ( responseContainer && jQuery.isFunction ( previous ) ) { + window[ jsonpCallback ] ( responseContainer[0] ); + } + } else { + // else, more memory leak avoidance + try{ delete window[ jsonpCallback ]; } catch(e){} } - } else { - // else, more memory leak avoidance - try{ delete window[ jsonpCallback ]; } catch(e){} - } - }, s.complete ]; + }, s.complete ]; - // Sneakily ensure this will be handled as json - s.dataTypes[ 0 ] = "json"; + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( ! responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; - // Use data converter to retrieve json after script execution - s.converters["script json"] = function() { - if ( ! responseContainer ) { - jQuery.error( jsonpCallback + " was not called" ); - } - return responseContainer[ 0 ]; - }; + // force json dataType + s.dataTypes[ 0 ] = "json"; - // Delegate to script transport - return "script"; + // Delegate to script + return "script"; + } }); })( jQuery ); diff --git a/src/ajax/script.js b/src/ajax/script.js index 0db0de6..b0e576f 100644 --- a/src/ajax/script.js +++ b/src/ajax/script.js @@ -1,7 +1,7 @@ (function( jQuery ) { -// Install text to script executor -jQuery.extend( true, jQuery.ajaxSettings , { +// Install script dataType +jQuery.ajaxSetup({ accepts: { script: "text/javascript, application/javascript" @@ -14,20 +14,24 @@ jQuery.extend( true, jQuery.ajaxSettings , { converters: { "text script": jQuery.globalEval } -} ); -// Bind script tag hack transport -jQuery.ajax.transport("script", function(s) { +// Handle cache's special case and global +}).ajaxPrefilter("script", function(s) { - // Handle cache special case if ( s.cache === undefined ) { s.cache = false; } - // This transport only deals with cross domain get requests - if ( s.crossDomain && s.async && ( s.type === "GET" || ! s.data ) ) { - + if ( s.crossDomain ) { + s.type = "GET"; s.global = false; + } + +// Bind script tag hack transport +}).ajaxTransport("script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { var script, head = document.getElementsByTagName("head")[0] || document.documentElement; @@ -47,7 +51,7 @@ jQuery.ajax.transport("script", function(s) { script.src = s.url; // Attach handlers for all browsers - script.onload = script.onreadystatechange = function( _ , statusText) { + script.onload = script.onreadystatechange = function( _ , isAbort ) { if ( ! script.readyState || /loaded|complete/.test( script.readyState ) ) { @@ -59,10 +63,13 @@ jQuery.ajax.transport("script", function(s) { head.removeChild( script ); } + // Dereference the script script = 0; - // Callback - callback( statusText ? 0 : 200, statusText || "success" ); + // Callback if not abort + if ( ! isAbort ) { + callback( 200, "success" ); + } } }; // Use insertBefore instead of appendChild to circumvent an IE6 bug. @@ -70,9 +77,9 @@ jQuery.ajax.transport("script", function(s) { head.insertBefore( script, head.firstChild ); }, - abort: function(statusText) { + abort: function() { if ( script ) { - script.onload( 0 , statusText ); + script.onload(0,1); } } }; diff --git a/src/ajax/xhr.js b/src/ajax/xhr.js index 26e91ce..34aa832 100644 --- a/src/ajax/xhr.js +++ b/src/ajax/xhr.js @@ -1,19 +1,16 @@ (function( jQuery ) { -var // Next fake timer id - xhrPollingId = jQuery.now(), +var // Next active xhr id + xhrId = jQuery.now(), - // Callbacks hashtable + // active xhrs xhrs = {}, - // XHR pool - xhrPool = [], + // #5280: see below + xhrUnloadAbortInstalled; - // #5280: see end of file - xhrUnloadAbortMarker; - -jQuery.ajax.transport( function( s , determineDataType ) { +jQuery.ajaxTransport( function( s , determineDataType ) { // Cross domain only allowed if supported through XMLHttpRequest if ( ! s.crossDomain || jQuery.support.cors ) { @@ -25,26 +22,24 @@ jQuery.ajax.transport( function( s , determineDataType ) { send: function(headers, complete) { // #5280: we need to abort on unload or IE will keep connections alive - if ( ! xhrUnloadAbortMarker ) { + if ( ! xhrUnloadAbortInstalled ) { - xhrUnloadAbortMarker = []; + xhrUnloadAbortInstalled = 1; jQuery(window).bind( "unload" , function() { // Abort all pending requests jQuery.each(xhrs, function(_, xhr) { if ( xhr.onreadystatechange ) { - xhr.onreadystatechange( xhrUnloadAbortMarker ); + xhr.onreadystatechange( 1 ); } }); - // Reset polling structure to be safe - xhrs = {}; - }); } - var xhr = xhrPool.pop() || s.xhr(), + // Get a new xhr + var xhr = s.xhr(), handle; // Open the socket @@ -58,7 +53,7 @@ jQuery.ajax.transport( function( s , determineDataType ) { // Requested-With header // Not set for crossDomain requests with no content // (see why at http://trac.dojotoolkit.org/ticket/9486) - // Won't change header if already provided in beforeSend + // Won't change header if already provided if ( ! ( s.crossDomain && ! s.hasContent ) && ! headers["x-requested-with"] ) { headers["x-requested-with"] = "XMLHttpRequest"; } @@ -76,49 +71,40 @@ jQuery.ajax.transport( function( s , determineDataType ) { try { xhr.send( ( s.hasContent && s.data ) || null ); } catch(e) { - // Store back in pool - xhrPool.push( xhr ); complete(0, "error", "" + e); return; } // Listener - callback = function ( abortStatusText ) { + callback = function( _ , isAbort ) { // Was never called and is aborted or complete - if ( callback && ( abortStatusText || xhr.readyState === 4 ) ) { + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = 0; - // Do not listen anymore - // and Store back in pool + // Do not keep as active anymore + // and store back into pool if (handle) { xhr.onreadystatechange = jQuery.noop; delete xhrs[ handle ]; - handle = undefined; - xhrPool.push( xhr ); } - callback = 0; - - // Get info - var status, statusText, response, responseHeaders; - - if ( abortStatusText ) { + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed if ( xhr.readyState !== 4 ) { xhr.abort(); } - - // Stop here if unloadAbort - if ( abortStatusText === xhrUnloadAbortMarker ) { - return; - } - - status = 0; - statusText = abortStatusText; - } else { - status = xhr.status; + // Get info + var status = xhr.status, + statusText, + response, + responseHeaders = xhr.getAllResponseHeaders(); try { // Firefox throws an exception when accessing statusText for faulty cross-domain requests @@ -130,8 +116,6 @@ jQuery.ajax.transport( function( s , determineDataType ) { } - responseHeaders = xhr.getAllResponseHeaders(); - // Filter status for non standard behaviours // (so many they seem to be the actual "standard") status = @@ -164,10 +148,10 @@ jQuery.ajax.transport( function( s , determineDataType ) { xhr.getResponseHeader("content-type"), xhr.responseText, xhr.responseXML ); - } - // Call complete - complete(status,statusText,response,responseHeaders); + // Call complete + complete(status,statusText,response,responseHeaders); + } } }; @@ -180,18 +164,16 @@ jQuery.ajax.transport( function( s , determineDataType ) { } else { - // Listener is externalized to handle abort on unload - handle = xhrPollingId++; + // Add to list of active xhrs + handle = xhrId++; xhrs[ handle ] = xhr; - xhr.onreadystatechange = function() { - callback(); - }; + xhr.onreadystatechange = callback; } }, - abort: function(statusText) { + abort: function() { if ( callback ) { - callback(statusText); + callback(0,1); } } }; diff --git a/src/core.js b/src/core.js index b9e6d81..fbf6491 100644 --- a/src/core.js +++ b/src/core.js @@ -3,7 +3,7 @@ var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); + return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite @@ -63,6 +63,9 @@ var jQuery = function( selector, context ) { // The deferred used on DOM ready readyList, + // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".split( " " ), + // The ready event handler DOMContentLoaded, @@ -78,7 +81,8 @@ var jQuery = function( selector, context ) { class2type = {}; jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) @@ -112,6 +116,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; doc = (context ? context.ownerDocument || context : document); // If a single string is passed in and it's a single tag @@ -129,7 +134,7 @@ jQuery.fn = jQuery.prototype = { } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + selector = (ret.cacheable ? jQuery(ret.fragment).clone()[0] : ret.fragment).childNodes; } return jQuery.merge( this, selector ); @@ -171,7 +176,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { - return jQuery( context ).find( selector ); + return this.constructor( context ).find( selector ); } // HANDLE: $(function) @@ -222,7 +227,7 @@ jQuery.fn = jQuery.prototype = { // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set - var ret = jQuery(); + var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); @@ -287,7 +292,7 @@ jQuery.fn = jQuery.prototype = { }, end: function() { - return this.prevObject || jQuery(null); + return this.prevObject || this.constructor(null); }, // For internal use only. @@ -896,9 +901,10 @@ jQuery.extend({ Deferred: function( func ) { var deferred = jQuery._Deferred(), - failDeferred = jQuery._Deferred(); + failDeferred = jQuery._Deferred(), + promise; - // Add errorDeferred methods and redefine cancel + // Add errorDeferred methods, then and promise jQuery.extend( deferred , { then: function( doneCallbacks , failCallbacks ) { @@ -911,14 +917,18 @@ jQuery.extend({ isRejected: failDeferred.isResolved, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - obj = obj || {}; - jQuery.each( "then done fail isResolved isRejected".split( " " ) , function( _ , method ) { - obj[ method ] = deferred[ method ]; - }); - obj.promise = function() { - return obj; - }; + // (i is used internally) + promise: function( obj , i ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ]; + } return obj; } @@ -940,10 +950,32 @@ jQuery.extend({ // Deferred helper when: function( object ) { - object = object && jQuery.isFunction( object.promise ) ? - object : - jQuery.Deferred().resolve( object ); - return object.promise(); + var args = arguments, + length = args.length, + deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(), + resolveArray; + + if ( length > 1 ) { + resolveArray = new Array( length ); + jQuery.each( args, function( index, element, args ) { + jQuery.when( element ).done( function( value ) { + args = arguments; + resolveArray[ index ] = args.length > 1 ? slice.call( args , 0 ) : value; + if( ! --length ) { + deferred.fire( promise, resolveArray ); + } + }).fail( function() { + deferred.fireReject( promise, arguments ); + }); + return !deferred.isRejected(); + }); + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; }, // Use of jQuery.browser is frowned upon. @@ -960,6 +992,25 @@ jQuery.extend({ return { browser: match[1] || "", version: match[2] || "0" }; }, + subclass: function(){ + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if (context && context instanceof jQuery && !(context instanceof jQuerySubclass)){ + context = jQuerySubclass(context); + } + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, + browser: {} }); diff --git a/src/css.js b/src/css.js index 8a83c60..19c6342 100644 --- a/src/css.js +++ b/src/css.js @@ -263,8 +263,9 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, rsLeft, + var left, ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; // From the awesome hack by Dean Edwards @@ -275,16 +276,19 @@ if ( document.documentElement.currentStyle ) { if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; - rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; - elem.runtimeStyle.left = rsLeft; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } } return ret === "" ? "auto" : ret; diff --git a/src/event.js b/src/event.js index 7fa5488..2ddf288 100644 --- a/src/event.js +++ b/src/event.js @@ -607,7 +607,7 @@ jQuery.Event = function( src ) { // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // Event type diff --git a/src/manipulation.js b/src/manipulation.js index 206476c..596a457 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -427,15 +427,29 @@ function cloneFixAttributes(src, dest) { if ( nodeName === "object" ) { dest.outerHTML = src.outerHTML; - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button - } else if ( nodeName === "input" && src.checked ) { - dest.defaultChecked = dest.checked = src.checked; + } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + if ( src.checked ) { + dest.defaultChecked = dest.checked = src.checked; + } + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } // IE6-8 fails to return the selected option to the default selected // state when cloning options } else if ( nodeName === "option" ) { dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; } // Event data gets referenced instead of copied if the expando @@ -624,6 +638,11 @@ jQuery.extend({ jQuery.removeEvent( elem, type, data.handle ); } } + + // Null the DOM reference to avoid IE6/7/8 leak (#7054) + if ( data.handle ) { + data.handle.elem = null; + } } if ( deleteExpando ) { diff --git a/src/traversing.js b/src/traversing.js index 689e901..b36ce3d 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -6,7 +6,14 @@ var runtil = /Until$/, rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, - POS = jQuery.expr.match.POS; + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; jQuery.fn.extend({ find: function( selector ) { @@ -196,7 +203,8 @@ jQuery.each({ } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); + var ret = jQuery.map( this, fn, until ), + args = slice.call(arguments); if ( !runtil.test( name ) ) { selector = until; @@ -206,13 +214,13 @@ jQuery.each({ ret = jQuery.filter( selector, ret ); } - ret = this.length > 1 ? jQuery.unique( ret ) : ret; + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } - return this.pushStack( ret, name, slice.call(arguments).join(",") ); + return this.pushStack( ret, name, args.join(",") ); }; }); diff --git a/test/data/jsonp.php b/test/data/jsonp.php index 9ae1d84..6c13d72 100644 --- a/test/data/jsonp.php +++ b/test/data/jsonp.php @@ -1,6 +1,10 @@ ").load(url("data/name.html"), function(responseText) { + strictEqual( div.html(), "Hello World" , "Test div was filled with filtered data" ); + strictEqual( responseText, "Hello World" , "Test callback receives filtered data" ); + jQuery.ajaxSetup({ dataFilter: 0 }); + start(); + }); +}); + test("load(String, Object, Function)", function() { expect(2); stop(); @@ -1080,233 +1114,249 @@ test("jQuery.getScript(String, Function) - no callback", function() { }); }); -test("jQuery.ajax() - JSONP, Local", function() { - expect(10); - - var count = 0; - function plus(){ if ( ++count == 9 ) start(); } - - stop(); - - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, no callback)" ); - plus(); - } - }); +jQuery.each( [ "Same Domain", "Cross Domain" ] , function( crossDomain , label ) { - jQuery.ajax({ - url: "data/jsonp.php?callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url callback)" ); - plus(); - } - }); + test("jQuery.ajax() - JSONP, " + label, function() { + expect(17); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - data: "callback=?", - success: function(data){ - ok( data.data, "JSON results returned (GET, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data callback)" ); - plus(); - } - }); + var count = 0; + function plus(){ if ( ++count == 17 ) start(); } - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - data: { - callback: "?" - }, - success: function(data){ - ok( data.data, "JSON results returned (GET, processed data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, processed data callback)" ); - plus(); - } - }); + stop(); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonp: "callback", - success: function(data){ - ok( data.data, "JSON results returned (GET, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, no callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, no callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonpCallback: "jsonpResults", - success: function(data){ - ok( data.data, "JSON results returned (GET, custom callback name)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, custom callback name)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, url callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, url callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=?", + success: function(data){ + ok( data.data, "JSON results returned (GET, data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - data: "callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (POST, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, url context-free callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, url context-free callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - jsonp: "callback", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (POST, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=??", + success: function(data){ + ok( data.data, "JSON results returned (GET, data context-free callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data context-free callback)" ); + plus(); + } + }); - //#7578 - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - beforeSend: function(){ - strictEqual( this.cache, false, "cache must be false on JSON request" ); - plus(); - return false; - } - }); -}); + jQuery.ajax({ + url: "data/jsonp.php/??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, REST-like)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, REST-like)" ); + plus(); + } + }); -test("jQuery.ajax() - JSONP - Custom JSONP Callback", function() { - expect(1); - stop(); + jQuery.ajax({ + url: "data/jsonp.php/???json=1", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + strictEqual( jQuery.type(data), "array", "JSON results returned (GET, REST-like with param)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, REST-like with param)" ); + plus(); + } + }); - window.jsonpResults = function(data) { - ok( data.data, "JSON results returned (GET, custom callback function)" ); - window.jsonpResults = undefined; - start(); - }; + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: { + callback: "?" + }, + success: function(data){ + ok( data.data, "JSON results returned (GET, processed data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, processed data callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonpCallback: "jsonpResults" - }); -}); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + jsonp: "callback", + success: function(data){ + ok( data.data, "JSON results returned (GET, data obj callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data obj callback)" ); + plus(); + } + }); -test("jQuery.ajax() - JSONP, Remote", function() { - expect(4); + window.jsonpResults = function(data) { + ok( data.data, "JSON results returned (GET, custom callback function)" ); + window.jsonpResults = undefined; + plus(); + }; - var count = 0; - function plus(){ if ( ++count == 4 ) start(); } + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + jsonpCallback: "jsonpResults", + success: function(data){ + ok( data.data, "JSON results returned (GET, custom callback name)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, custom callback name)" ); + plus(); + } + }); - var base = window.location.href.replace(/[^\/]*$/, ""); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, no callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data obj callback)" ); + plus(); + } + }); - stop(); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + data: "callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (POST, data callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, no callback)" ); - plus(); - } - }); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + jsonp: "callback", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, data obj callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (POST, data obj callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php?callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url callback)" ); - plus(); - } - }); + //#7578 + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + beforeSend: function(){ + strictEqual( this.cache, false, "cache must be false on JSON request" ); + plus(); + return false; + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - data: "callback=?", - success: function(data){ - ok( data.data, "JSON results returned (GET, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=XXX", + dataType: "jsonp", + jsonp: false, + jsonpCallback: "XXX", + crossDomain: crossDomain, + beforeSend: function() { + ok( /^data\/jsonp.php\?callback=XXX&_=\d+$/.test( this.url ) , + "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); + plus(); + }, + success: function(data){ + ok( data.data, "JSON results returned (GET, custom callback name with no url manipulation)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, custom callback name with no url manipulation)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - jsonp: "callback", - success: function(data){ - ok( data.data, "JSON results returned (GET, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } }); }); @@ -1778,25 +1828,19 @@ test("jQuery ajax - failing cross-domain", function() { var i = 2; - if ( jQuery.ajax({ + jQuery.ajax({ url: 'http://somewebsitethatdoesnotexist-67864863574657654.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }) === false ) { - ok( true , "no transport" ); - if ( ! --i ) start(); - } + }); - if ( jQuery.ajax({ + jQuery.ajax({ url: 'http://www.google.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }) === false ) { - ok( true , "no transport" ); - if ( ! --i ) start(); - } + }); }); @@ -1828,6 +1872,76 @@ test( "jQuery.ajax - Location object as url (#7531)", 1, function () { ok( success, "document.location did not generate exception" ); }); +test( "jQuery.ajax - statusCode" , function() { + + var count = 10; + + expect( 16 ); + stop(); + + function countComplete() { + if ( ! --count ) { + start(); + } + } + + function createStatusCodes( name , isSuccess ) { + name = "Test " + name + " " + ( isSuccess ? "success" : "error" ); + return { + 200: function() { + ok( isSuccess , name ); + }, + 404: function() { + ok( ! isSuccess , name ); + } + }; + } + + jQuery.each( { + "data/name.html": true, + "data/someFileThatDoesNotExist.html": false + } , function( uri , isSuccess ) { + + jQuery.ajax( url( uri ) , { + statusCode: createStatusCodes( "in options" , isSuccess ), + complete: countComplete + }); + + jQuery.ajax( url( uri ) , { + complete: countComplete + }).statusCode( createStatusCodes( "immediately with method" , isSuccess ) ); + + jQuery.ajax( url( uri ) , { + complete: function(jXHR) { + jXHR.statusCode( createStatusCodes( "on complete" , isSuccess ) ); + countComplete(); + } + }); + + jQuery.ajax( url( uri ) , { + complete: function(jXHR) { + setTimeout( function() { + jXHR.statusCode( createStatusCodes( "very late binding" , isSuccess ) ); + countComplete(); + } , 100 ); + } + }); + + jQuery.ajax( url( uri ) , { + statusCode: createStatusCodes( "all (options)" , isSuccess ), + complete: function(jXHR) { + jXHR.statusCode( createStatusCodes( "all (on complete)" , isSuccess ) ); + setTimeout( function() { + jXHR.statusCode( createStatusCodes( "all (very late binding)" , isSuccess ) ); + countComplete(); + } , 100 ); + } + }).statusCode( createStatusCodes( "all (immediately with method)" , isSuccess ) ); + + }); + +}); + } //} \ No newline at end of file diff --git a/test/unit/core.js b/test/unit/core.js index 6231b1d..8e57edf 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -12,7 +12,7 @@ test("Basic requirements", function() { }); test("jQuery()", function() { - expect(23); + expect(24); // Basic constructor's behavior @@ -21,7 +21,7 @@ test("jQuery()", function() { equals( jQuery(null).length, 0, "jQuery(null) === jQuery([])" ); equals( jQuery("").length, 0, "jQuery('') === jQuery([])" ); - var obj = jQuery("div") + var obj = jQuery("div"); equals( jQuery(obj).selector, "div", "jQuery(jQueryObj) == jQueryObj" ); // can actually yield more than one, when iframes are included, the window is an array as well @@ -87,6 +87,14 @@ test("jQuery()", function() { // manually clean up detached elements elem.remove(); + + for ( var i = 0; i < 3; ++i ) { + elem = jQuery(""); + } + equals( elem[0].defaultValue, "TEST", "Ensure cached nodes are cloned properly (Bug #6655)" ); + + // manually clean up detached elements + elem.remove(); }); test("selector state", function() { @@ -1001,7 +1009,7 @@ test("jQuery._Deferred()", function() { test("jQuery.Deferred()", function() { - expect( 4 ); + expect( 10 ); jQuery.Deferred( function( defer ) { strictEqual( this , defer , "Defer passed as this & first argument" ); @@ -1021,11 +1029,35 @@ test("jQuery.Deferred()", function() { }, function() { ok( true , "Error on reject" ); }); + + ( new jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument (new)" ); + this.resolve( "done" ); + }) ).then( function( value ) { + strictEqual( value , "done" , "Passed function executed (new)" ); + }); + + ( new jQuery.Deferred() ).resolve().then( function() { + ok( true , "Success on resolve (new)" ); + }, function() { + ok( false , "Error on resolve (new)" ); + }); + + ( new jQuery.Deferred() ).reject().then( function() { + ok( false , "Success on reject (new)" ); + }, function() { + ok( true , "Error on reject (new)" ); + }); + + var tmp = jQuery.Deferred(); + + strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); + strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); }); test("jQuery.when()", function() { - expect( 21 ); + expect( 23 ); // Some other objects jQuery.each( { @@ -1048,6 +1080,10 @@ test("jQuery.when()", function() { } ); + ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); + } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); + var cache, i; for( i = 1 ; i < 4 ; i++ ) { @@ -1061,3 +1097,106 @@ test("jQuery.when()", function() { }); } }); + +test("jQuery.when() - joined", function() { + + expect(8); + + jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); + strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); + strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - non-observables"); + }); + + var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), + errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); + + jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); + same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); + strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - resolved observable"); + }); + + jQuery.when( 1 , errorDeferred , 3 ).done( function() { + ok( false , "Test the created deferred was rejected - rejected observable"); + }).fail( function( error , errorParam ) { + strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); + strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); + }); +}); + +test("jQuery.subclass", function(){ + expect(378); + + var Subclass = jQuery.subclass(), + SubclassSubclass = Subclass.subclass(), + jQueryDocument = jQuery(document), + selectors, contexts, methods, method, arg, description; + + jQueryDocument.toString = function(){ return 'jQueryDocument'; }; + + Subclass.fn.subclassMethod = function(){}; + SubclassSubclass.fn.subclassSubclassMethod = function(){}; + + selectors = [ + 'body', + 'html, body', + '
' + ]; + + methods = [ // all methods that return a new jQuery instance + ['eq', 1], + ['add', document], + ['end'], + ['has'], + ['closest', 'div'], + ['filter', document], + ['find', 'div'] + ]; + + contexts = [undefined, document, jQueryDocument]; + + jQuery.each(selectors, function(i, selector){ + + jQuery.each(methods, function(){ + method = this[0]; + arg = this[1]; + + jQuery.each(contexts, function(i, context){ + + description = '("'+selector+'", '+context+').'+method+'('+(arg||'')+')'; + + same( + jQuery(selector, context)[method](arg).subclassMethod, undefined, + 'jQuery'+description+' doesnt have Subclass methods' + ); + same( + jQuery(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'jQuery'+description+' doesnt have SubclassSubclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'Subclass'+description+' has Subclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'Subclass'+description+' doesnt have SubclassSubclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'SubclassSubclass'+description+' has Subclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassSubclassMethod, SubclassSubclass.fn.subclassSubclassMethod, + 'SubclassSubclass'+description+' has SubclassSubclass methods' + ); + + }); + }); + }); + +}); diff --git a/test/unit/css.js b/test/unit/css.js index 0637071..1b25987 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -320,3 +320,25 @@ test(":visible selector works properly on children with a hidden parent (bug #45 jQuery('#table').css('display', 'none').html('cellcell'); equals(jQuery('#table td:visible').length, 0, "hidden cell children not perceived as visible"); }); + +test("internal ref to elem.runtimeStyle (bug #7608)", function () { + expect(1); + + var result = true, + val = 10; + + jQuery('
' + + '
 
').appendTo("#main"); + + try { + // the bug is located within src/css.js + jQuery("#bug7608 #test").animate( { width: val }, 1000); + + } catch (e) { + result = false; + } + + ok( result, "elem.runtimeStyle does not throw exception" ); + + jQuery("#bug7608").remove(); +}); diff --git a/test/unit/event.js b/test/unit/event.js index a6d8cb6..02824a9 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -304,15 +304,15 @@ test("live/delegate immediate propagation", function() { $p.undelegate( "click" ); }); -test("bind/delegate bubbling, isDefaultPrevented (Bug #7793)", function() { +test("bind/delegate bubbling, isDefaultPrevented", function() { expect(2); var $anchor2 = jQuery( "#anchor2" ), $main = jQuery( "#main" ), fakeClick = function($jq) { // Use a native click so we don't get jQuery simulated bubbling if ( document.createEvent ) { - var e = document.createEvent( "MouseEvents" ); - e.initEvent( "click", true, true ); + var e = document.createEvent( 'MouseEvents' ); + e.initEvent( "click", true, true ); $jq[0].dispatchEvent(e); } else if ( $jq[0].click ) { @@ -323,7 +323,15 @@ test("bind/delegate bubbling, isDefaultPrevented (Bug #7793)", function() { e.preventDefault(); }); $main.delegate("#foo", "click", function(e) { - equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + var orig = e.originalEvent; + + if ( typeof(orig.defaultPrevented) === "boolean" || typeof(orig.returnValue) === "boolean" || orig.getPreventDefault ) { + equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + + } else { + // Opera < 11 doesn't implement any interface we can use, so give it a pass + ok( true, "isDefaultPrevented not supported by this browser, test skipped" ); + } }); fakeClick( $anchor2 ); $anchor2.unbind( "click" ); diff --git a/test/unit/manipulation.js b/test/unit/manipulation.js index de84144..37234d8 100644 --- a/test/unit/manipulation.js +++ b/test/unit/manipulation.js @@ -402,7 +402,7 @@ test("append(Function) with incoming value", function() { }); test("append the same fragment with events (Bug #6997, 5566)", function () { - expect(4 + (document.fireEvent ? 1 : 0)); + expect(2 + (document.fireEvent ? 1 : 0)); stop(1000); var element; @@ -437,14 +437,6 @@ test("append the same fragment with events (Bug #6997, 5566)", function () { jQuery("#listWithTabIndex li").before(element); jQuery("#listWithTabIndex li.test6997").eq(1).click(); - - element = jQuery(""); - - equals( element.clone().find("option:selected").val(), element.find("option:selected").val(), "Selected option cloned correctly" ); - - element = jQuery("").attr('checked', 'checked'); - - equals( element.clone().is(":checked"), element.is(":checked"), "Checked input cloned correctly" ); }); test("appendTo(String|Element|Array<Element>|jQuery)", function() { @@ -976,6 +968,28 @@ test("clone()", function() { equal( jQuery("body").clone().children()[0].id, "qunit-header", "Make sure cloning body works" ); }); +test("clone(form element) (Bug #3879, #6655)", function() { + expect(6); + element = jQuery(""); + + equals( element.clone().find("option:selected").val(), element.find("option:selected").val(), "Selected option cloned correctly" ); + + element = jQuery("").attr('checked', 'checked'); + clone = element.clone(); + + equals( clone.is(":checked"), element.is(":checked"), "Checked input cloned correctly" ); + equals( clone[0].defaultValue, "foo", "Checked input defaultValue cloned correctly" ); + equals( clone[0].defaultChecked, !jQuery.support.noCloneEvent, "Checked input defaultChecked cloned correctly" ); + + element = jQuery(""); + clone = element.clone(); + equals( clone[0].defaultValue, "foo", "Text input defaultValue cloned correctly" ); + + element = jQuery(""); + clone = element.clone(); + equals( clone[0].defaultValue, "foo", "Textarea defaultValue cloned correctly" ); +}); + if (!isLocal) { test("clone() on XML nodes", function() { expect(2); diff --git a/version.txt b/version.txt index 7810cd6..574f968 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.4.5pre \ No newline at end of file +1.5pre \ No newline at end of file