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.

This commit is contained in:
jaubourg 2011-01-16 02:57:39 +01:00
parent d9cb69873c
commit 8ab23aec2c
4 changed files with 129 additions and 128 deletions

View file

@ -306,30 +306,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;
}
};
@ -347,6 +352,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;
@ -599,84 +608,87 @@ jQuery.extend({
s.data = jQuery.param( s.data , s.traditional );
}
// Get transport
transport = jQuery.ajaxPrefilter( s , options ).ajaxTransport( s );
// Apply prefilters
jQuery.ajaxPrefilter( s , options );
// 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;
// If data is available, append data to url
if ( s.data ) {
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
}
// Add anti-cache in url if needed
if ( s.cache === false ) {
var ts = jQuery.now(),
// try replacing _= if it is there
ret = s.url.replace( rts , "$1_=" + ts );
// if nothing was replaced, add timestamp to the end
s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + 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;
}
// 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 ];
}
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) {
// Abort if not done already
done( 0 , "abort" );
// Return false
jXHR = false;
} else {
// More options handling for requests with no content
if ( ! s.hasContent ) {
// If data is available, append data to url
if ( s.data ) {
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
}
// Add anti-cache in url if needed
if ( s.cache === false ) {
var ts = jQuery.now(),
// try replacing _= if it is there
ret = s.url.replace( rts , "$1_=" + ts );
// if nothing was replaced, add timestamp to the end
s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "");
}
// Install callbacks on deferreds
for ( i in { success:1, error:1, complete:1 } ) {
jXHR[ i ]( s[ i ] );
}
// Set the correct header, if data is being sent
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
requestHeaders[ "content-type" ] = s.contentType;
}
// Get transport
transport = jQuery.ajaxTransport( s );
// 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 ];
}
}
// If no transport, we auto-abort
if ( ! transport ) {
// 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 ];
}
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) {
// Abort if not done already
done( 0 , "abort" );
jXHR = false;
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 ) {

View file

@ -11,9 +11,7 @@ jQuery.ajaxSetup({
return "jsonp" + jsc++;
}
// Normalize jsonp queries
// 1) put callback parameter in url or data
// 2) sneakily ensure transportDataType is always jsonp for jsonp requests
// Detect, normalize options and install callbacks for jsonp requests
}).ajaxPrefilter("json jsonp", function(s, originalSettings) {
if ( s.dataTypes[ 0 ] === "jsonp" ||
@ -22,8 +20,10 @@ jQuery.ajaxSetup({
jsre.test(s.url) ||
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,
previous = window[ jsonpCallback ],
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;
@ -33,51 +33,42 @@ jQuery.ajaxSetup({
s.url = url;
s.data = data;
s.dataTypes[ 0 ] = "jsonp";
}
// Bind transport to jsonp dataType
}).ajaxTransport("jsonp", function(s) {
window [ jsonpCallback ] = function( response ) {
responseContainer = [response];
};
// Put callback in place
var responseContainer,
jsonpCallback = s.jsonpCallback,
previous = window[ jsonpCallback ];
s.complete = [function() {
window [ jsonpCallback ] = function( response ) {
responseContainer = [response];
};
// Set callback back to previous value
window[ jsonpCallback ] = previous;
s.complete = [function() {
// 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 );

View file

@ -15,18 +15,22 @@ jQuery.ajaxSetup({
"text script": jQuery.globalEval
}
// Bind script tag hack transport
}).ajaxTransport("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.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;

View file

@ -1865,25 +1865,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();
}
});
});
@ -1937,7 +1931,7 @@ test( "jQuery.ajax - statusCode" , function() {
404: function() {
ok( ! isSuccess , name );
}
}
};
}
jQuery.each( {