Fix #11743: Don't mask script errors in jQuery.ajax, closes gh-795.

This commit is contained in:
Richard Gibson 2012-05-31 08:31:13 -07:00 committed by Dave Methvin
parent 2d37b6ccb8
commit 742872984e
5 changed files with 123 additions and 87 deletions

View File

@ -319,6 +319,7 @@ jQuery.extend({
username: null, username: null,
password: null, password: null,
cache: null, cache: null,
throws: false,
traditional: false, traditional: false,
headers: {}, headers: {},
*/ */
@ -480,6 +481,8 @@ jQuery.extend({
// It is defined here because jslint complains if it is declared // It is defined here because jslint complains if it is declared
// at the end of the function (which would be more logical and readable) // at the end of the function (which would be more logical and readable)
function done( status, nativeStatusText, responses, headers ) { function done( status, nativeStatusText, responses, headers ) {
var isSuccess, success, error, response, modified,
statusText = nativeStatusText;
// Called once // Called once
if ( state === 2 ) { if ( state === 2 ) {
@ -504,13 +507,10 @@ jQuery.extend({
// Set readyState // Set readyState
jqXHR.readyState = status > 0 ? 4 : 0; jqXHR.readyState = status > 0 ? 4 : 0;
var isSuccess, // Get response data
success, if ( responses ) {
error, response = ajaxHandleResponses( s, jqXHR, responses );
statusText = nativeStatusText, }
response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
lastModified,
etag;
// If successful, handle type chaining // If successful, handle type chaining
if ( status >= 200 && status < 300 || status === 304 ) { if ( status >= 200 && status < 300 || status === 304 ) {
@ -518,11 +518,13 @@ jQuery.extend({
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) { if ( s.ifModified ) {
if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { modified = jqXHR.getResponseHeader("Last-Modified");
jQuery.lastModified[ ifModifiedKey ] = lastModified; if ( modified ) {
jQuery.lastModified[ ifModifiedKey ] = modified;
} }
if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { modified = jqXHR.getResponseHeader("Etag");
jQuery.etag[ ifModifiedKey ] = etag; if ( modified ) {
jQuery.etag[ ifModifiedKey ] = modified;
} }
} }
@ -535,15 +537,11 @@ jQuery.extend({
// If we have data // If we have data
} else { } else {
try { isSuccess = ajaxConvert( s, response );
success = ajaxConvert( s, response ); statusText = isSuccess.state;
statusText = "success"; success = isSuccess.data;
isSuccess = true; error = isSuccess.error;
} catch(e) { isSuccess = !error;
// We have a parsererror
statusText = "parsererror";
error = e;
}
} }
} else { } else {
// We extract error from statusText // We extract error from statusText
@ -913,86 +911,87 @@ function ajaxHandleResponses( s, jqXHR, responses ) {
// Chain conversions given the request and the original response // Chain conversions given the request and the original response
function ajaxConvert( s, response ) { function ajaxConvert( s, response ) {
var conv, conv2, current, tmp,
// Work with a copy of dataTypes in case we need to modify it for conversion
dataTypes = s.dataTypes.slice(),
prev = dataTypes[ 0 ],
converters = {},
i = 0;
// Apply the dataFilter if provided // Apply the dataFilter if provided
if ( s.dataFilter ) { if ( s.dataFilter ) {
response = s.dataFilter( response, s.dataType ); response = s.dataFilter( response, s.dataType );
} }
var dataTypes = s.dataTypes, // Create converters map with lowercased keys
converters = {}, if ( dataTypes[ 1 ] ) {
i, for ( conv in s.converters ) {
key, converters[ conv.toLowerCase() ] = s.converters[ conv ];
length = dataTypes.length,
tmp,
// Current and previous dataTypes
current = dataTypes[ 0 ],
prev,
// Conversion expression
conversion,
// Conversion function
conv,
// Conversion functions (transitive conversion)
conv1,
conv2;
// For each dataType in the chain
for ( i = 1; i < length; i++ ) {
// Create converters map
// with lowercased keys
if ( i === 1 ) {
for ( key in s.converters ) {
if ( typeof key === "string" ) {
converters[ key.toLowerCase() ] = s.converters[ key ];
}
}
} }
}
// Get the dataTypes // Convert to each sequential dataType, tolerating list modification
prev = current; for ( ; (current = dataTypes[++i]); ) {
current = dataTypes[ i ];
// If current is auto dataType, update it to prev // There's only work to do if current dataType is non-auto
if ( current === "*" ) { if ( current !== "*" ) {
current = prev;
// If no auto and dataTypes are actually different
} else if ( prev !== "*" && prev !== current ) {
// Get the converter // Convert response if prev dataType is non-auto and differs from current
conversion = prev + " " + current; if ( prev !== "*" && prev !== current ) {
conv = converters[ conversion ] || converters[ "* " + current ];
// If there is no direct converter, search transitively // Seek a direct converter
if ( !conv ) { conv = converters[ prev + " " + current ] || converters[ "* " + current ];
conv2 = undefined;
for ( conv1 in converters ) { // If none found, seek a pair
tmp = conv1.split( " " ); if ( !conv ) {
if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { for ( conv2 in converters ) {
conv2 = converters[ tmp[1] + " " + current ];
if ( conv2 ) { // If conv2 outputs current
conv1 = converters[ conv1 ]; tmp = conv2.split(" ");
if ( conv1 === true ) { if ( tmp[ 1 ] === current ) {
conv = conv2;
} else if ( conv2 === true ) { // If prev can be converted to accepted input
conv = conv1; conv = converters[ prev + " " + tmp[ 0 ] ] ||
converters[ "* " + tmp[ 0 ] ];
if ( conv ) {
// Condense equivalence converters
if ( conv === true ) {
conv = converters[ conv2 ];
// Otherwise, insert the intermediate dataType
} else if ( converters[ conv2 ] !== true ) {
current = tmp[ 0 ];
dataTypes.splice( i--, 0, current );
}
break;
} }
break; }
}
}
// Apply converter (if not an equivalence)
if ( conv !== true ) {
// Unless errors are allowed to bubble, catch and return them
if ( conv && s.throws ) {
response = conv( response );
} else {
try {
response = conv( response );
} catch ( e ) {
return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
} }
} }
} }
} }
// If we found no converter, dispatch an error
if ( !( conv || conv2 ) ) { // Update prev for next iteration
jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); prev = current;
}
// If found converter is not an equivalence
if ( conv !== true ) {
// Convert with 1 or 2 converters accordingly
response = conv ? conv( response ) : conv2( conv1(response) );
}
} }
} }
return response;
return { state: "success", data: response };
} }
})( jQuery ); })( jQuery );

View File

@ -343,11 +343,12 @@ jQuery.fn.extend({
jQuery.each( scripts, function( i, elem ) { jQuery.each( scripts, function( i, elem ) {
if ( elem.src ) { if ( elem.src ) {
jQuery.ajax({ jQuery.ajax({
type: "GET",
global: false,
url: elem.src, url: elem.src,
type: "GET",
dataType: "script",
async: false, async: false,
dataType: "script" global: false,
throws: true
}); });
} else { } else {
jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) ); jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );

View File

@ -1 +1 @@
{bad: 1} {bad: toTheBone}

View File

@ -1746,6 +1746,28 @@ test("jQuery.ajax() - malformed JSON", function() {
}); });
}); });
test("jQuery.ajax() - script, throws exception (#11743)", function() {
expect(1);
raises(function() {
jQuery.ajax({
url: "data/badjson.js",
dataType: "script",
throws: true,
// TODO find a way to test this asynchronously, too
async: false,
// Global events get confused by the exception
global: false,
success: function() {
ok( false, "Success." );
},
error: function() {
ok( false, "Error." );
}
});
}, "exception bubbled" );
});
test("jQuery.ajax() - script by content-type", function() { test("jQuery.ajax() - script by content-type", function() {
expect(2); expect(2);

View File

@ -1796,3 +1796,17 @@ test("Ensure oldIE creates a new set on appendTo (#8894)", function() {
strictEqual( jQuery("<bdi/>").clone().addClass("test").appendTo("<div/>").end().hasClass("test"), false, "Check jQuery.fn.appendTo after clone html5 element" ); strictEqual( jQuery("<bdi/>").clone().addClass("test").appendTo("<div/>").end().hasClass("test"), false, "Check jQuery.fn.appendTo after clone html5 element" );
strictEqual( jQuery("<p/>").appendTo("<div/>").end().length, jQuery("<p>test</p>").appendTo("<div/>").end().length, "Elements created with createElement and with createDocumentFragment should be treated alike" ); strictEqual( jQuery("<p/>").appendTo("<div/>").end().length, jQuery("<p>test</p>").appendTo("<div/>").end().length, "Elements created with createElement and with createDocumentFragment should be treated alike" );
}); });
test("html() - script exceptions bubble (#11743)", function() {
expect(2);
raises(function() {
jQuery("#qunit-fixture").html("<script>undefined(); ok( false, 'error not thrown' );</script>");
ok( false, "error ignored" );
}, "exception bubbled from inline script" );
raises(function() {
jQuery("#qunit-fixture").html("<script src='data/badjson.js'></script>");
ok( false, "error ignored" );
}, "exception bubbled from remote script" );
});