Commit 8ab23aec authored by jaubourg's avatar jaubourg

Fixes #2994. Not finding a transport now fires the error callbacks and doesn't...

Fixes #2994. Not finding a transport now fires the error callbacks and doesn't make ajax return false. Had to revise how jsonp and script prefilters & transports work (better separation of concerns). Also took the opportunity to revise jXHR getRequestHeader and abort methods and enabled early transport garbage collection when the request completes.
parent d9cb6987
...@@ -306,30 +306,35 @@ jQuery.extend({ ...@@ -306,30 +306,35 @@ jQuery.extend({
// (match is used internally) // (match is used internally)
getResponseHeader: function( key , match ) { getResponseHeader: function( key , match ) {
if ( state !== 2 ) { if ( state === 2 ) {
return null;
}
if ( responseHeaders === undefined ) { if ( responseHeaders === undefined ) {
responseHeaders = {}; responseHeaders = {};
if ( typeof responseHeadersString === "string" ) { if ( typeof responseHeadersString === "string" ) {
while( ( match = rheaders.exec( responseHeadersString ) ) ) { while( ( match = rheaders.exec( responseHeadersString ) ) ) {
responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
}
} }
} }
match = responseHeaders[ key.toLowerCase() ];
} else {
match = null;
} }
return responseHeaders[ key.toLowerCase() ];
return match;
}, },
// Cancel the request // Cancel the request
abort: function( statusText ) { abort: function( statusText ) {
if ( transport && state !== 2 ) { if ( transport ) {
transport.abort( statusText || "abort" ); transport.abort( statusText || "abort" );
done( 0 , statusText );
} }
done( 0 , statusText );
return this; return this;
} }
}; };
...@@ -347,6 +352,10 @@ jQuery.extend({ ...@@ -347,6 +352,10 @@ jQuery.extend({
// State is "done" now // State is "done" now
state = 2; state = 2;
// Dereference transport for early garbage collection
// (no matter how long the jXHR transport will be used
transport = 0;
// Set readyState // Set readyState
jXHR.readyState = status ? 4 : 0; jXHR.readyState = status ? 4 : 0;
...@@ -599,84 +608,87 @@ jQuery.extend({ ...@@ -599,84 +608,87 @@ jQuery.extend({
s.data = jQuery.param( s.data , s.traditional ); s.data = jQuery.param( s.data , s.traditional );
} }
// Get transport // Apply prefilters
transport = jQuery.ajaxPrefilter( s , options ).ajaxTransport( s ); jQuery.ajaxPrefilter( s , options );
// Watch for a new set of requests // Watch for a new set of requests
if ( s.global && jQuery.active++ === 0 ) { if ( s.global && jQuery.active++ === 0 ) {
jQuery.event.trigger( "ajaxStart" ); jQuery.event.trigger( "ajaxStart" );
} }
// If no transport, we auto-abort // More options handling for requests with no content
if ( ! transport ) { 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 // Add anti-cache in url if needed
if ( ! s.hasContent ) { if ( s.cache === false ) {
// If data is available, append data to url var ts = jQuery.now(),
if ( s.data ) { // try replacing _= if it is there
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; ret = s.url.replace( rts , "$1_=" + ts );
}
// Add anti-cache in url if needed // if nothing was replaced, add timestamp to the end
if ( s.cache === false ) { s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "");
}
}
var ts = jQuery.now(), // Set the correct header, if data is being sent
// try replacing _= if it is there if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
ret = s.url.replace( rts , "$1_=" + ts ); requestHeaders[ "content-type" ] = s.contentType;
}
// if nothing was replaced, add timestamp to the end // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); if ( s.ifModified ) {
} if ( jQuery_lastModified[ s.url ] ) {
requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ];
} }
if ( jQuery_etag[ s.url ] ) {
// Set the correct header, if data is being sent requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ];
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
requestHeaders[ "content-type" ] = s.contentType;
} }
}
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. // Set the Accepts header for the server, depending on the dataType
if ( s.ifModified ) { requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
if ( jQuery_lastModified[ s.url ] ) { s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; s.accepts[ "*" ];
}
if ( jQuery_etag[ s.url ] ) { // Check for headers option
requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; for ( i in s.headers ) {
} requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
} }
// Set the Accepts header for the server, depending on the dataType // Allow custom headers/mimetypes and early abort
requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) {
s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
s.accepts[ "*" ];
// Check for headers option // Abort if not done already
for ( i in s.headers ) { done( 0 , "abort" );
requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
// 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 // Get transport
if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { transport = jQuery.ajaxTransport( s );
// Abort if not done already // If no transport, we auto-abort
done( 0 , "abort" ); if ( ! transport ) {
jXHR = false;
done( 0 , "notransport" );
} else { } else {
// Set state as sending // Set state as sending
state = 1; state = jXHR.readyState = 1;
jXHR.readyState = 1;
// Install callbacks on deferreds
for ( i in { success:1, error:1, complete:1 } ) {
jXHR[ i ]( s[ i ] );
}
// Send global event // Send global event
if ( s.global ) { if ( s.global ) {
......
...@@ -11,9 +11,7 @@ jQuery.ajaxSetup({ ...@@ -11,9 +11,7 @@ jQuery.ajaxSetup({
return "jsonp" + jsc++; return "jsonp" + jsc++;
} }
// Normalize jsonp queries // Detect, normalize options and install callbacks for jsonp requests
// 1) put callback parameter in url or data
// 2) sneakily ensure transportDataType is always jsonp for jsonp requests
}).ajaxPrefilter("json jsonp", function(s, originalSettings) { }).ajaxPrefilter("json jsonp", function(s, originalSettings) {
if ( s.dataTypes[ 0 ] === "jsonp" || if ( s.dataTypes[ 0 ] === "jsonp" ||
...@@ -22,8 +20,10 @@ jQuery.ajaxSetup({ ...@@ -22,8 +20,10 @@ jQuery.ajaxSetup({
jsre.test(s.url) || jsre.test(s.url) ||
typeof(s.data) === "string" && jsre.test(s.data) ) { typeof(s.data) === "string" && jsre.test(s.data) ) {
var jsonpCallback = s.jsonpCallback = var responseContainer,
jsonpCallback = s.jsonpCallback =
jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
previous = window[ jsonpCallback ],
url = s.url.replace(jsre, "$1" + jsonpCallback + "$2"), url = s.url.replace(jsre, "$1" + jsonpCallback + "$2"),
data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "$1" + jsonpCallback + "$2") : s.data; data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "$1" + jsonpCallback + "$2") : s.data;
...@@ -33,51 +33,42 @@ jQuery.ajaxSetup({ ...@@ -33,51 +33,42 @@ jQuery.ajaxSetup({
s.url = url; s.url = url;
s.data = data; s.data = data;
s.dataTypes[ 0 ] = "jsonp";
}
// Bind transport to jsonp dataType
}).ajaxTransport("jsonp", function(s) {
// Put callback in place window [ jsonpCallback ] = function( response ) {
var responseContainer, responseContainer = [response];
jsonpCallback = s.jsonpCallback, };
previous = window[ jsonpCallback ];
window [ jsonpCallback ] = function( response ) { s.complete = [function() {
responseContainer = [response];
};
s.complete = [function() { // Set callback back to previous value
window[ jsonpCallback ] = previous;
// Set callback back to previous value // Call if it was a function and we have a response
window[ jsonpCallback ] = previous; if ( previous) {
if ( responseContainer && jQuery.isFunction ( previous ) ) {
// Call if it was a function and we have a response window[ jsonpCallback ] ( responseContainer[0] );
if ( previous) { }
if ( responseContainer && jQuery.isFunction ( previous ) ) { } else {
window[ jsonpCallback ] ( responseContainer[0] ); // 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 // Use data converter to retrieve json after script execution
s.dataTypes[ 0 ] = "json"; 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 // force json dataType
s.converters["script json"] = function() { s.dataTypes[ 0 ] = "json";
if ( ! responseContainer ) {
jQuery.error( jsonpCallback + " was not called" );
}
return responseContainer[ 0 ];
};
// Delegate to script transport // Delegate to script
return "script"; return "script";
}
}); });
})( jQuery ); })( jQuery );
...@@ -15,18 +15,22 @@ jQuery.ajaxSetup({ ...@@ -15,18 +15,22 @@ jQuery.ajaxSetup({
"text script": jQuery.globalEval "text script": jQuery.globalEval
} }
// Bind script tag hack transport // Handle cache's special case and global
}).ajaxTransport("script", function(s) { }).ajaxPrefilter("script", function(s) {
// Handle cache special case
if ( s.cache === undefined ) { if ( s.cache === undefined ) {
s.cache = false; s.cache = false;
} }
// This transport only deals with cross domain get requests if ( s.crossDomain ) {
if ( s.crossDomain && s.async && ( s.type === "GET" || ! s.data ) ) {
s.global = false; 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, var script,
head = document.getElementsByTagName("head")[0] || document.documentElement; head = document.getElementsByTagName("head")[0] || document.documentElement;
......
...@@ -1865,25 +1865,19 @@ test("jQuery ajax - failing cross-domain", function() { ...@@ -1865,25 +1865,19 @@ test("jQuery ajax - failing cross-domain", function() {
var i = 2; var i = 2;
if ( jQuery.ajax({ jQuery.ajax({
url: 'http://somewebsitethatdoesnotexist-67864863574657654.com', url: 'http://somewebsitethatdoesnotexist-67864863574657654.com',
success: function(){ ok( false , "success" ); }, success: function(){ ok( false , "success" ); },
error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); }, error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); },
complete: function() { if ( ! --i ) start(); } complete: function() { if ( ! --i ) start(); }
}) === false ) { });
ok( true , "no transport" );
if ( ! --i ) start();
}
if ( jQuery.ajax({ jQuery.ajax({
url: 'http://www.google.com', url: 'http://www.google.com',
success: function(){ ok( false , "success" ); }, success: function(){ ok( false , "success" ); },
error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); }, error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); },
complete: function() { if ( ! --i ) start(); } complete: function() { if ( ! --i ) start(); }
}) === false ) { });
ok( true , "no transport" );
if ( ! --i ) start();
}
}); });
...@@ -1937,7 +1931,7 @@ test( "jQuery.ajax - statusCode" , function() { ...@@ -1937,7 +1931,7 @@ test( "jQuery.ajax - statusCode" , function() {
404: function() { 404: function() {
ok( ! isSuccess , name ); ok( ! isSuccess , name );
} }
} };
} }
jQuery.each( { jQuery.each( {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment