Rewrite of the Ajax module by Julian Aubourg. Some (dated) details can be found here: http://oksoclap.com/6Y26bm1ZsB more details are forthcoming. Fixes #7195.

This commit is contained in:
jaubourg 2010-12-09 13:34:28 -05:00 committed by jeresig
parent 29616e60c8
commit ab3ba4a812
12 changed files with 1997 additions and 469 deletions

View file

@ -23,7 +23,11 @@ BASE_FILES = ${SRC_DIR}/core.js\
${SRC_DIR}/traversing.js\
${SRC_DIR}/manipulation.js\
${SRC_DIR}/css.js\
${SRC_DIR}/ajax.js\
${SRC_DIR}/ajax.js\
${SRC_DIR}/xhr.js\
${SRC_DIR}/transports/jsonp.js\
${SRC_DIR}/transports/script.js\
${SRC_DIR}/transports/xhr.js\
${SRC_DIR}/effects.js\
${SRC_DIR}/offset.js\
${SRC_DIR}/dimensions.js

View file

@ -1,17 +1,11 @@
(function( jQuery ) {
var jsc = jQuery.now(),
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
var rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
rselectTextarea = /^(?:select|textarea)/i,
rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
rnoContent = /^(?:GET|HEAD)$/,
rbracket = /\[\]$/,
jsre = /\=\?(&|$)/,
rquery = /\?/,
rts = /([?&])_=[^&]*/,
rurl = /^(\w+:)?\/\/([^\/?#]+)/,
r20 = /%20/g,
rhash = /#.*$/,
// Keep a copy of the old load method
_load = jQuery.fn.load;
@ -49,9 +43,9 @@ jQuery.fn.extend({
type = "POST";
}
}
var self = this;
// Request the remote document
jQuery.ajax({
url: url,
@ -90,36 +84,37 @@ jQuery.fn.extend({
},
serializeArray: function() {
return this.map(function() {
return this.map(function(){
return this.elements ? jQuery.makeArray(this.elements) : this;
})
.filter(function() {
.filter(function(){
return this.name && !this.disabled &&
(this.checked || rselectTextarea.test(this.nodeName) ||
rinput.test(this.type));
})
.map(function( i, elem ) {
.map(function(i, elem){
var val = jQuery(this).val();
return val == null ?
null :
jQuery.isArray(val) ?
jQuery.map( val, function( val, i ) {
return { name: elem.name, value: val };
jQuery.map( val, function(val, i){
return {name: elem.name, value: val};
}) :
{ name: elem.name, value: val };
{name: elem.name, value: val};
}).get();
}
});
// Attach a bunch of functions for handling common AJAX events
jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) {
jQuery.fn[o] = function( f ) {
jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function(i,o){
jQuery.fn[o] = function(f){
return this.bind(o, f);
};
});
jQuery.extend({
get: function( url, data, callback, type ) {
// shift arguments if data argument was omited
if ( jQuery.isFunction( data ) ) {
@ -176,342 +171,120 @@ jQuery.extend({
/*
timeout: 0,
data: null,
dataType: null,
dataTypes: null,
username: null,
password: null,
cache: null,
traditional: false,
*/
// This function can be overriden by calling jQuery.ajaxSetup
xhr: function() {
return new window.XMLHttpRequest();
},
xhrResponseFields: {
xml: "XML",
text: "Text",
json: "JSON"
},
accepts: {
xml: "application/xml, text/xml",
html: "text/html",
script: "text/javascript, application/javascript",
json: "application/json, text/javascript",
text: "text/plain",
_default: "*/*"
json: "application/json, text/javascript",
"*": "*/*"
},
autoDataType: {
xml: /xml/,
html: /html/,
json: /json/
},
// Prefilters
// 1) They are useful to introduce custom dataTypes (see transport/jsonp for an example)
// 2) These are called:
// * BEFORE asking for a transport
// * AFTER param serialization (s.data is a string if s.processData is true)
// 3) They MUST be order agnostic
prefilters: [],
// Transports bindings
// 1) key is the dataType
// 2) the catchall symbol "*" can be used
// 3) selection will start with transport dataType and THEN go to "*" if needed
transports: {
},
// Checkers
// 1) key is dataType
// 2) they are called to control successful response
// 3) error throws is used as error data
dataCheckers: {
// Check if data is a string
"text": function(data) {
if ( typeof data != "string" ) {
jQuery.error("typeerror");
}
},
// Check if xml has been properly parsed
"xml": function(data) {
var documentElement = data ? data.documentElement : data;
if ( ! documentElement || ! documentElement.nodeName ) {
jQuery.error("typeerror");
}
if ( documentElement.nodeName == "parsererror" ) {
jQuery.error("parsererror");
}
}
},
// List of data converters
// 1) key format is "source_type => destination_type" (spaces required)
// 2) the catchall symbol "*" can be used for source_type
dataConverters: {
// Convert anything to text
"* => text": function(data) {
return "" + data;
},
// Text to html (no transformation)
"text => html": function(data) {
return data;
},
// Evaluate text as a json expression
"text => json": jQuery.parseJSON,
// Parse text as xml
"text => xml": function(data) {
var xml, parser;
if ( window.DOMParser ) { // Standard
parser = new DOMParser();
xml = parser.parseFromString(data,"text/xml");
} else { // IE
xml = new ActiveXObject("Microsoft.XMLDOM");
xml.async="false";
xml.loadXML(data);
}
return xml;
}
}
},
ajax: function( origSettings ) {
// IE8 leaks a lot when we've set abort, and IE6-8 a little
// when we have set onreadystatechange. Bug #6242
// XXX IE7 still leaks when abort is called, no matter what
// we do
function cleanup() {
// IE6 will throw an error setting xhr.abort
try {
xhr.abort = xhr.onreadystatechange = jQuery.noop;
} catch(e) {}
// Main method
ajax: function( url , s ) {
if ( arguments.length === 1 ) {
s = url;
url = s ? s.url : undefined;
}
var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type);
// toString fixes people passing a window.location or
// document.location to $.ajax, which worked in 1.4.2 and
// earlier (bug #7531). It should be removed in 1.5.
s.url = ("" + s.url).replace( rhash, "" );
// Use original (not extended) context object if it was provided
s.context = origSettings && origSettings.context != null ? origSettings.context : s;
// convert data if not already a string
if ( s.data && s.processData && typeof s.data !== "string" ) {
s.data = jQuery.param( s.data, s.traditional );
}
// Handle JSONP Parameter Callbacks
if ( s.dataType === "jsonp" ) {
if ( type === "GET" ) {
if ( !jsre.test( s.url ) ) {
s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
}
} else if ( !s.data || !jsre.test(s.data) ) {
s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
}
s.dataType = "json";
}
// Build temporary JSONP function
if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
jsonp = s.jsonpCallback || ("jsonp" + jsc++);
// Replace the =? sequence both in the query string and the data
if ( s.data ) {
s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
}
s.url = s.url.replace(jsre, "=" + jsonp + "$1");
// We need to make sure
// that a JSONP style response is executed properly
s.dataType = "script";
// Handle JSONP-style loading
var customJsonp = window[ jsonp ];
window[ jsonp ] = function( tmp ) {
if ( jQuery.isFunction( customJsonp ) ) {
customJsonp( tmp );
} else {
// Garbage collect
window[ jsonp ] = undefined;
try {
delete window[ jsonp ];
} catch( jsonpError ) {}
}
data = tmp;
jQuery.handleSuccess( s, xhr, status, data );
jQuery.handleComplete( s, xhr, status, data );
if ( head ) {
head.removeChild( script );
}
};
}
if ( s.dataType === "script" && s.cache === undefined ) {
s.cache = false;
}
if ( s.cache === false && noContent ) {
var ts = jQuery.now();
// try replacing _= if it is there
var 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 : "");
}
// If data is available, append data to url for GET/HEAD requests
if ( s.data && noContent ) {
s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
}
// Watch for a new set of requests
if ( s.global && jQuery.active++ === 0 ) {
jQuery.event.trigger( "ajaxStart" );
}
// Matches an absolute URL, and saves the domain
var parts = rurl.exec( s.url ),
remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host);
// If we're requesting a remote document
// and trying to load JSON or Script with a GET
if ( s.dataType === "script" && type === "GET" && remote ) {
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
if ( s.scriptCharset ) {
script.charset = s.scriptCharset;
}
script.src = s.url;
// Handle Script loading
if ( !jsonp ) {
var done = false;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function() {
if ( !done && (!this.readyState ||
this.readyState === "loaded" || this.readyState === "complete") ) {
done = true;
jQuery.handleSuccess( s, xhr, status, data );
jQuery.handleComplete( s, xhr, status, data );
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
if ( head && script.parentNode ) {
head.removeChild( script );
}
}
};
}
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );
// We handle everything using the script element injection
return undefined;
}
var requestDone = false;
// Create the request object
var xhr = s.xhr();
if ( !xhr ) {
return;
}
// Open the socket
// Passing null username, generates a login popup on Opera (#2865)
if ( s.username ) {
xhr.open(type, s.url, s.async, s.username, s.password);
} else {
xhr.open(type, s.url, s.async);
}
// Need an extra try/catch for cross domain requests in Firefox 3
try {
// Set content-type if data specified and content-body is valid for this type
if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) {
xhr.setRequestHeader("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] ) {
xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]);
}
if ( jQuery.etag[s.url] ) {
xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]);
}
}
// Set header so the called script knows that it's an XMLHttpRequest
// Only send the header if it's not a remote XHR
if ( !remote ) {
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
// Set the Accepts header for the server, depending on the dataType
xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
s.accepts[ s.dataType ] + ", */*; q=0.01" :
s.accepts._default );
} catch( headerError ) {}
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) {
// Handle the global AJAX counter
if ( s.global && jQuery.active-- === 1 ) {
jQuery.event.trigger( "ajaxStop" );
}
// close opended socket
xhr.abort();
return false;
}
if ( s.global ) {
jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] );
}
// Wait for a response to come back
var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) {
// The request was aborted
if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) {
// Opera doesn't call onreadystatechange before this point
// so we simulate the call
if ( !requestDone ) {
jQuery.handleComplete( s, xhr, status, data );
}
requestDone = true;
if ( xhr ) {
cleanup();
}
// The transfer is complete and the data is available, or the request timed out
} else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) {
requestDone = true;
status = isTimeout === "timeout" ?
"timeout" :
!jQuery.httpSuccess( xhr ) ?
"error" :
s.ifModified && jQuery.httpNotModified( xhr, s.url ) ?
"notmodified" :
"success";
var errMsg;
if ( status === "success" ) {
// Watch for, and catch, XML document parse errors
try {
// process the data (runs the xml through httpData regardless of callback)
data = jQuery.httpData( xhr, s.dataType, s );
} catch( parserError ) {
status = "parsererror";
errMsg = parserError;
}
}
// Make sure that the request was successful or notmodified
if ( status === "success" || status === "notmodified" ) {
// JSONP handles its own success callback
if ( !jsonp ) {
jQuery.handleSuccess( s, xhr, status, data );
}
} else {
jQuery.handleError( s, xhr, status, errMsg );
}
// Fire the complete handlers
if ( !jsonp ) {
jQuery.handleComplete( s, xhr, status, data );
}
if ( isTimeout === "timeout" ) {
xhr.abort();
}
cleanup();
}
};
// Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK)
// Opera doesn't fire onreadystatechange at all on abort
try {
var oldAbort = xhr.abort;
xhr.abort = function() {
if ( xhr ) {
// oldAbort has no call property in IE7 so
// just do it this way, which works in all
// browsers
Function.prototype.call.call( oldAbort, xhr );
}
onreadystatechange( "abort" );
};
} catch( abortError ) {}
// Timeout checker
if ( s.async && s.timeout > 0 ) {
setTimeout(function() {
// Check to see if the request is still happening
if ( xhr && !requestDone ) {
onreadystatechange( "timeout" );
}
}, s.timeout);
}
// Send the data
try {
xhr.send( noContent || s.data == null ? null : s.data );
} catch( sendError ) {
jQuery.handleError( s, xhr, null, sendError );
// Fire the complete handlers
jQuery.handleComplete( s, xhr, status, data );
}
// firefox 1.5 doesn't fire statechange for sync requests
if ( !s.async ) {
onreadystatechange();
}
// return XMLHttpRequest to allow aborting the request etc.
return xhr;
return jQuery.xhr().open( s ? s.type : undefined , url ).send( undefined , s );
},
// Serialize an array of form elements or a set of
@ -595,110 +368,7 @@ jQuery.extend({
// Last-Modified header cache for next request
lastModified: {},
etag: {},
handleError: function( s, xhr, status, e ) {
// If a local callback was specified, fire it
if ( s.error ) {
s.error.call( s.context, xhr, status, e );
}
// Fire the global callback
if ( s.global ) {
jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] );
}
},
handleSuccess: function( s, xhr, status, data ) {
// If a local callback was specified, fire it and pass it the data
if ( s.success ) {
s.success.call( s.context, data, status, xhr );
}
// Fire the global callback
if ( s.global ) {
jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] );
}
},
handleComplete: function( s, xhr, status ) {
// Process result
if ( s.complete ) {
s.complete.call( s.context, xhr, status );
}
// The request was completed
if ( s.global ) {
jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] );
}
// Handle the global AJAX counter
if ( s.global && jQuery.active-- === 1 ) {
jQuery.event.trigger( "ajaxStop" );
}
},
triggerGlobal: function( s, type, args ) {
(s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args);
},
// Determines if an XMLHttpRequest was successful or not
httpSuccess: function( xhr ) {
try {
// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
return !xhr.status && location.protocol === "file:" ||
xhr.status >= 200 && xhr.status < 300 ||
xhr.status === 304 || xhr.status === 1223;
} catch(e) {}
return false;
},
// Determines if an XMLHttpRequest returns NotModified
httpNotModified: function( xhr, url ) {
var lastModified = xhr.getResponseHeader("Last-Modified"),
etag = xhr.getResponseHeader("Etag");
if ( lastModified ) {
jQuery.lastModified[url] = lastModified;
}
if ( etag ) {
jQuery.etag[url] = etag;
}
return xhr.status === 304;
},
httpData: function( xhr, type, s ) {
var ct = xhr.getResponseHeader("content-type") || "",
xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
data = xml ? xhr.responseXML : xhr.responseText;
if ( xml && data.documentElement.nodeName === "parsererror" ) {
jQuery.error( "parsererror" );
}
// Allow a pre-filtering function to sanitize the response
// s is checked to keep backwards compatibility
if ( s && s.dataFilter ) {
data = s.dataFilter( data, type );
}
// The filter can actually parse the response
if ( typeof data === "string" ) {
// Get the JavaScript object, if JSON is used.
if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
data = jQuery.parseJSON( data );
// If the type is "script", eval it in global context
} else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
jQuery.globalEval( data );
}
}
return data;
}
etag: {}
});
@ -711,19 +381,24 @@ jQuery.extend({
*/
if ( window.ActiveXObject ) {
jQuery.ajaxSettings.xhr = function() {
if ( window.location.protocol !== "file:" ) {
try {
return new window.XMLHttpRequest();
} catch(xhrError) {}
}
if ( window.location.protocol !== "file:" ) {
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch(activeError) {}
return new window.XMLHttpRequest();
} catch( xhrError ) {}
}
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch( activeError ) {}
};
}
// Does this browser support XHR requests?
jQuery.support.ajax = !!jQuery.ajaxSettings.xhr();
var testXHR = jQuery.ajaxSettings.xhr();
})( jQuery );
// Does this browser support XHR requests?
jQuery.support.ajax = !!testXHR;
// Does this browser support crossDomain XHR requests
jQuery.support.cors = testXHR && "withCredentials" in testXHR;
})(jQuery);

89
src/transports/jsonp.js Normal file
View file

@ -0,0 +1,89 @@
(function( jQuery ) {
var jsc = jQuery.now(),
jsre = /\=\?(&|$)/,
rquery = /\?/;
// Default jsonp callback name
jQuery.ajaxSettings.jsonpCallback = function() {
return "jsonp" + jsc++;
};
// Normalize jsonp queries
// 1) put callback parameter in url or data
// 2) ensure transportDataType is json
// 3) ensure options jsonp is always provided so that jsonp requests are always
// json request with the jsonp option set
jQuery.xhr.prefilter( function(s) {
var transportDataType = s.dataTypes[0];
if ( s.jsonp ||
transportDataType === "jsonp" ||
transportDataType === "json" && ( jsre.test(s.url) || typeof(s.data) === "string" && jsre.test(s.data) ) ) {
var jsonp = s.jsonp = s.jsonp || "callback",
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 = url += (rquery.test( url ) ? "&" : "?") + jsonp + "=" + jsonpCallback;
}
s.url = url;
s.data = data;
s.dataTypes[0] = "json";
}
});
// Bind transport to json dataType
jQuery.xhr.bindTransport("json", function(s) {
if ( s.jsonp ) {
// Put callback in place
var responseContainer,
jsonpCallback = s.jsonpCallback,
previous = window[ jsonpCallback ];
window [ jsonpCallback ] = function( response ) {
responseContainer = [response];
};
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] );
}
} else {
// else, more memory leak avoidance
try{ delete window[ jsonpCallback ]; } catch(e){}
}
}, s.complete ];
// Use data converter to retrieve json after script execution
s.dataConverters["script => json"] = function() {
if ( ! responseContainer ) {
jQuery.error("Callback '" + jsonpCallback + "' was not called");
}
return responseContainer[ 0 ];
};
// Delegate to script transport
return "script";
}
});
})(jQuery);

83
src/transports/script.js Normal file
View file

@ -0,0 +1,83 @@
(function( jQuery ) {
// Install text to script executor
jQuery.extend( true, jQuery.ajaxSettings , {
accepts: {
script: "text/javascript, application/javascript"
},
autoDataType: {
script: /javascript/
},
dataConverters: {
"text => script": jQuery.globalEval
}
} );
// Bind script tag hack transport
jQuery.xhr.bindTransport("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 ) ) {
s.global = false;
var script,
head = document.getElementsByTagName("head")[0] || document.documentElement;
return {
send: function(_, callback) {
script = document.createElement("script");
script.async = "async";
if ( s.scriptCharset ) {
script.charset = s.scriptCharset;
}
script.src = s.url;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function(statusText) {
if ( (!script.readyState ||
script.readyState === "loaded" || script.readyState === "complete") ) {
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
// Remove the script
if ( head && script.parentNode ) {
head.removeChild( script );
}
script = undefined;
// Callback & dereference
callback(statusText ? 0 : 200, statusText || "success");
}
};
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );
},
abort: function(statusText) {
if ( script ) {
script.onload(statusText);
}
}
};
}
});
})(jQuery);

191
src/transports/xhr.js Normal file
View file

@ -0,0 +1,191 @@
(function( jQuery ) {
var // Next fake timer id
xhrPollingId = jQuery.now(),
// Callbacks hashtable
xhrs = {},
// #5280: see end of file
xhrUnloadAbortMarker = [];
jQuery.xhr.bindTransport( function( s , determineDataType ) {
// Cross domain only allowed if supported through XMLHttpRequest
if ( ! s.crossDomain || jQuery.support.cors ) {
var callback;
return {
send: function(headers, complete) {
var xhr = s.xhr(),
handle;
// Open the socket
// Passing null username, generates a login popup on Opera (#2865)
if ( s.username ) {
xhr.open(s.type, s.url, s.async, s.username, s.password);
} else {
xhr.open(s.type, s.url, s.async);
}
// 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
if ( ! ( s.crossDomain && ! s.hasContent ) && ! headers["x-requested-with"] ) {
headers["x-requested-with"] = "XMLHttpRequest";
}
// Need an extra try/catch for cross domain requests in Firefox 3
try {
jQuery.each(headers, function(key,value) {
xhr.setRequestHeader(key,value);
});
} catch(_) {}
// Do send the request
try {
xhr.send( ( s.hasContent && s.data ) || null );
} catch(e) {
complete(0, "error", "" + e);
return;
}
// Listener
callback = function ( abortStatusText ) {
// Was never called and is aborted or complete
if ( callback && ( abortStatusText || xhr.readyState === 4 ) ) {
// Do not listen anymore
if (handle) {
xhr.onreadystatechange = jQuery.noop;
delete xhrs[ handle ];
handle = undefined;
}
callback = 0;
// Get info
var status, statusText, response, responseHeaders;
if ( abortStatusText ) {
if ( xhr.readyState !== 4 ) {
xhr.abort();
}
// Stop here if unloadAbort
if ( abortStatusText === xhrUnloadAbortMarker ) {
return;
}
status = 0;
statusText = abortStatusText;
} else {
status = xhr.status;
try { // Firefox throws an exception when accessing statusText for faulty cross-domain requests
statusText = xhr.statusText;
} catch( e ) {
statusText = ""; // We normalize with Webkit giving an empty statusText
}
responseHeaders = xhr.getAllResponseHeaders();
// Filter status for non standard behaviours
// (so many they seem to be the actual "standard")
status =
// Opera returns 0 when it should be 304
// Webkit returns 0 for failing cross-domain no matter the real status
status === 0 ?
(
! s.crossDomain || statusText ? // Webkit, Firefox: filter out faulty cross-domain requests
(
responseHeaders ? // Opera: filter out real aborts #6060
304
:
0
)
:
302 // We assume 302 but could be anything cross-domain related
)
:
(
status == 1223 ? // IE sometimes returns 1223 when it should be 204 (see #1450)
204
:
status
);
// Guess response if needed & update datatype accordingly
if ( status >= 200 && status < 300 ) {
response =
determineDataType(
s,
xhr.getResponseHeader("content-type"),
xhr.responseText,
xhr.responseXML );
}
}
// Call complete
complete(status,statusText,response,responseHeaders);
}
};
// if we're in sync mode
// or it's in cache and has been retrieved directly (IE6 & IE7)
// we need to manually fire the callback
if ( ! s.async || xhr.readyState === 4 ) {
callback();
} else {
// Listener is externalized to handle abort on unload
handle = xhrPollingId++;
xhrs[ handle ] = xhr;
xhr.onreadystatechange = function() {
callback();
};
}
},
abort: function(statusText) {
if ( callback ) {
callback(statusText);
}
}
};
}
});
// #5280: we need to abort on unload or IE will keep connections alive
jQuery(window).bind( "unload" , function() {
// Abort all pending requests
jQuery.each(xhrs, function(_, xhr) {
if ( xhr.onreadystatechange ) {
xhr.onreadystatechange( xhrUnloadAbortMarker );
}
});
// Resest polling structure to be safe
xhrs = {};
});
})(jQuery);

941
src/xhr.js Normal file
View file

@ -0,0 +1,941 @@
(function( jQuery , undefined ) {
var rquery = /\?/,
rhash = /#.*$/,
rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
rnoContent = /^(?:GET|HEAD)$/,
rts = /([?&])_=[^&]*/,
rurl = /^(\w+:)?\/\/([^\/?#]+)/,
slice = Array.prototype.slice,
isFunction = jQuery.isFunction;
// Creates a jQuery xhr object
jQuery.xhr = function( _native ) {
if ( _native ) {
return jQuery.ajaxSettings.xhr();
}
function reset(force) {
// We only need to reset if we went through the init phase
// (with the exception of object creation)
if ( force || internal ) {
// Reset callbacks lists
callbacksLists = {
success: createCBList(),
error: createCBList(),
complete: createCBList()
};
// Reset private variables
requestHeaders = {};
responseHeadersString = responseHeaders = internal = done = timeoutTimer = s = undefined;
// Reset state
xhr.readyState = 0;
sendFlag = 0;
// Remove responseX fields
for ( var name in xhr ) {
if ( /^response/.test(name) ) {
delete xhr[name];
}
}
}
}
function init() {
var // Options extraction
// Remove hash character (#7531: first for string promotion)
url = s.url = ( "" + s.url ).replace( rhash , "" ),
// Uppercase the type
type = s.type = s.type.toUpperCase(),
// Determine if request has content
hasContent = s.hasContent = ! rnoContent.test( type ),
// Extract dataTypes list
dataType = s.dataType,
dataTypes = s.dataTypes = dataType ? jQuery.trim(dataType).toLowerCase().split(/\s+/) : ["*"],
// Determine if a cross-domain request is in order
parts = rurl.exec( url.toLowerCase() ),
loc = location,
crossDomain = s.crossDomain = !!( parts && ( parts[1] && parts[1] != loc.protocol || parts[2] != loc.host ) ),
// Get other options locally
data = s.data,
originalContentType = s.contentType,
prefilters = s.prefilters,
accepts = s.accepts,
headers = s.headers,
// Other Variables
transportDataType,
i;
// Convert data if not already a string
if ( data && s.processData && typeof data != "string" ) {
data = s.data = jQuery.param( data , s.traditional );
}
// Apply option prefilters
for (i in prefilters) {
prefilters[i](s);
}
// Get internal
internal = selectTransport( s );
// Re-actualize url & data
url = s.url;
data = s.data;
// If internal was found
if ( internal ) {
// Get transportDataType
transportDataType = dataTypes[0];
// More options handling for requests with no content
if ( ! hasContent ) {
// If data is available, append data to url
if ( data ) {
url += (rquery.test(url) ? "&" : "?") + data;
}
// Add anti-cache in url if needed
if ( s.cache === false ) {
var ts = jQuery.now(),
// try replacing _= if it is there
ret = url.replace(rts, "$1_=" + ts );
// if nothing was replaced, add timestamp to the end
url = ret + ((ret == url) ? (rquery.test(url) ? "&" : "?") + "_=" + ts : "");
}
s.url = url;
}
// Set the correct header, if data is being sent
if ( ( data && hasContent ) || originalContentType ) {
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[url] ) {
requestHeaders["if-modified-since"] = jQuery_lastModified[url];
}
if ( jQuery_etag[url] ) {
requestHeaders["if-none-match"] = jQuery_etag[url];
}
}
// Set the Accepts header for the server, depending on the dataType
requestHeaders.accept = transportDataType && accepts[ transportDataType ] ?
accepts[ transportDataType ] + ( transportDataType !== "*" ? ", */*; q=0.01" : "" ) :
accepts[ "*" ];
// Check for headers option
if ( headers ) {
xhr.setRequestHeaders( headers );
}
}
callbackContext = s.context || s;
globalEventContext = s.context ? jQuery(s.context) : jQuery.event;
for ( i in callbacksLists ) {
callbacksLists[i].bind(s[i]);
}
// Watch for a new set of requests
if ( s.global && jQuery.active++ === 0 ) {
jQuery.event.trigger( "ajaxStart" );
}
done = whenDone;
}
function whenDone(status, statusText, response, headers) {
// Called once
done = undefined;
// Reset sendFlag
sendFlag = 0;
// Cache response headers
responseHeadersString = headers || "";
// Clear timeout if it exists
if ( timeoutTimer ) {
clearTimeout(timeoutTimer);
}
var // Reference url
url = s.url,
// and ifModified status
ifModified = s.ifModified,
// Is it a success?
isSuccess = 0,
// 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" );
}
// If successful, handle type chaining
if ( statusText === "success" || statusText === "notmodified" ) {
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( ifModified ) {
var lastModified = xhr.getResponseHeader("Last-Modified"),
etag = xhr.getResponseHeader("Etag");
if (lastModified) {
jQuery_lastModified[url] = lastModified;
}
if (etag) {
jQuery_etag[url] = etag;
}
}
if ( ifModified && statusText === "notmodified" ) {
success = null;
isSuccess = 1;
} else {
// Chain data conversions and determine the final value
// (if an exception is thrown in the process, it'll be notified as an error)
try {
function checkData(data) {
if ( data !== undefined ) {
var testFunction = s.dataCheckers[srcDataType];
if ( isFunction( testFunction ) ) {
testFunction(data);
}
}
}
function convertData (data) {
var conversionFunction = dataConverters[srcDataType+" => "+destDataType] ||
dataConverters["* => "+destDataType],
noFunction = ! isFunction( conversionFunction );
if ( noFunction ) {
if ( srcDataType != "text" && destDataType != "text" ) {
// We try to put text inbetween
var first = dataConverters[srcDataType+" => text"] ||
dataConverters["* => text"],
second = dataConverters["text => "+destDataType] ||
dataConverters["* => "+destDataType],
areFunctions = isFunction( first ) && isFunction( second );
if ( areFunctions ) {
conversionFunction = function (data) {
return second( first ( data ) );
};
}
noFunction = ! areFunctions;
}
if ( noFunction ) {
jQuery.error( "no data converter between " + srcDataType + " and " + destDataType );
}
}
return conversionFunction(data);
}
var dataTypes = s.dataTypes,
i,
length,
data = response,
dataConverters = s.dataConverters,
srcDataType,
destDataType,
responseTypes = s.xhrResponseFields;
for ( i = 0, length = dataTypes.length ; i < length ; i++ ) {
destDataType = dataTypes[i];
if ( !srcDataType ) { // First time
// Copy type
srcDataType = destDataType;
// Check
checkData(data);
// Apply dataFilter
if ( isFunction( s.dataFilter ) ) {
data = s.dataFilter(data, s.dataType);
// Recheck data
checkData(data);
}
} else { // Subsequent times
// handle auto
// JULIAN: for reasons unknown to me === doesn't work here
if (destDataType == "*") {
destDataType = srcDataType;
} else if ( srcDataType != destDataType ) {
// Convert
data = convertData(data);
// Copy type & check
srcDataType = destDataType;
checkData(data);
}
}
// Copy response into the xhr if it hasn't been already
var responseDataType,
responseType = responseTypes[srcDataType];
if ( responseType ) {
responseDataType = srcDataType;
} else {
responseType = responseTypes[ responseDataType = "text" ];
}
if ( responseType !== 1 ) {
xhr[ "response" + responseType ] = data;
responseTypes[ responseType ] = 1;
}
}
// We have a real success
success = data;
isSuccess = 1;
} catch(e) {
statusText = "parsererror";
error = "" + e;
}
}
} else { // if not success, mark it as an error
error = error || statusText;
}
// Set data for the fake xhr object
xhr.status = status;
xhr.statusText = statusText;
// Keep local copies of vars in case callbacks re-use the xhr
var _s = s,
_callbacksLists = callbacksLists,
_callbackContext = callbackContext,
_globalEventContext = globalEventContext;
// Set state if the xhr hasn't been re-used
function _setState( value ) {
if ( xhr.readyState && s === _s ) {
setState( value );
}
}
// Really completed?
if ( status && s.async ) {
setState( 2 );
_setState( 3 );
}
// We're done
_setState( 4 );
// Success
_callbacksLists.success.fire( isSuccess , _callbackContext , success, statusText, xhr);
if ( isSuccess && _s.global ) {
_globalEventContext.trigger( "ajaxSuccess", [xhr, _s, success] );
}
// Error
_callbacksLists.error.fire( ! isSuccess , _callbackContext , xhr, statusText, error);
if ( !isSuccess && _s.global ) {
_globalEventContext.trigger( "ajaxError", [xhr, _s, error] );
}
// Complete
_callbacksLists.complete.fire( 1 , _callbackContext, xhr, statusText);
if ( _s.global ) {
_globalEventContext.trigger( "ajaxComplete", [xhr, _s] );
// Handle the global AJAX counter
if ( ! --jQuery.active ) {
jQuery.event.trigger( "ajaxStop" );
}
}
}
// Ready state control
function checkState( expected , test ) {
if ( expected !== true && ( expected === false || test === false || xhr.readyState !== expected ) ) {
jQuery.error("INVALID_STATE_ERR");
}
}
// Ready state change
function setState( value ) {
xhr.readyState = value;
if ( isFunction( xhr.onreadystatechange ) ) {
xhr.onreadystatechange();
}
}
var // jQuery lists
jQuery_lastModified = jQuery.lastModified,
jQuery_etag = jQuery.etag,
// Options object
s,
// Callback stuff
callbackContext,
globalEventContext,
callbacksLists,
// Headers (they are sent all at once)
requestHeaders,
// Response headers
responseHeadersString,
responseHeaders,
// Done callback
done,
// transport
internal,
// timeout handle
timeoutTimer,
// The send flag
sendFlag,
// Fake xhr
xhr = {
// state
readyState: 0,
// Callback
onreadystatechange: null,
// Open
open: function(type, url, async, username, password) {
xhr.abort();
reset();
s = {
type: type,
url: url,
async: async,
username: username,
password: password
};
setState(1);
return xhr;
},
// Send
send: function(data, moreOptions) {
checkState(1 , !sendFlag);
s.data = data;
s = jQuery.extend( true,
{},
jQuery.ajaxSettings,
s,
moreOptions || ( moreOptions === false ? { global: false } : {} ) );
if ( moreOptions ) {
// We force the original context
// (plain objects used as context get extended)
s.context = moreOptions.context;
}
init();
// If not internal, abort
if ( ! internal ) {
done( 0 , "transport not found" );
return false;
}
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend ) {
var _s = s;
if ( s.beforeSend.call(callbackContext, xhr, s) === false || ! xhr.readyState || _s !== s ) {
// Abort if not done
if ( xhr.readyState && _s === s ) {
xhr.abort();
}
// Handle the global AJAX counter
if ( _s.global && ! --jQuery.active ) {
jQuery.event.trigger( "ajaxStop" );
}
return false;
}
}
sendFlag = 1;
// Send global event
if ( s.global ) {
globalEventContext.trigger("ajaxSend", [xhr, s]);
}
// Timeout
if ( s.async && s.timeout > 0 ) {
timeoutTimer = setTimeout(function(){
xhr.abort("timeout");
}, s.timeout);
}
if ( s.async ) {
setState(1);
}
try {
internal.send(requestHeaders, done);
return xhr;
} catch (e) {
if ( done ) {
done(0, "error", "" + e);
} else {
jQuery.error(e);
}
}
return false;
},
// Caches the header
setRequestHeader: function(name,value) {
checkState(1, !sendFlag);
requestHeaders[ name.toLowerCase() ] = value;
return xhr;
},
// Ditto with an s
setRequestHeaders: function(map) {
checkState(1, !sendFlag);
for ( var name in map ) {
requestHeaders[ name.toLowerCase() ] = map[name];
}
return xhr;
},
// Utility method to get headers set
getRequestHeader: function(name) {
checkState(1, !sendFlag);
return requestHeaders[ name.toLowerCase() ];
},
// Raw string
getAllResponseHeaders: function() {
return xhr.readyState <= 1 ? "" : responseHeadersString;
},
// Builds headers hashtable if needed
getResponseHeader: function( key ) {
if ( xhr.readyState <= 1 ) {
return null;
}
if ( responseHeaders === undefined ) {
responseHeaders = {};
if ( typeof responseHeadersString === "string" ) {
var match;
while( ( match = rheaders.exec( responseHeadersString ) ) ) {
responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
}
}
}
return responseHeaders[ key.toLowerCase() ];
},
// Cancel the request
abort: function(statusText) {
if (internal) {
internal.abort( statusText || "abort" );
}
xhr.readyState = 0;
}
};
// Init data (so that we can bind callbacks early
reset(1);
// Install callbacks related methods
jQuery.each(["bind","unbind"], function(_, name) {
xhr[name] = function(type) {
var functors = slice.call(arguments,1),
list;
jQuery.each(type.split(/\s+/g), function() {
list = callbacksLists[this];
if ( list ) {
list[name].apply(list, functors );
}
});
return this;
};
});
jQuery.each(callbacksLists, function(name) {
var list;
xhr[name] = function() {
list = callbacksLists[name];
if ( list ) {
list.bind.apply(list, arguments );
}
return this;
};
});
// Return the xhr emulation
return xhr;
};
// Create a callback list
function createCBList() {
var functors = [],
autoFire = 0,
fireArgs,
list = {
fire: function( flag , context ) {
// Save info for later bindings
fireArgs = arguments;
// Remove autoFire to keep bindings in order
autoFire = 0;
var args = slice.call( fireArgs , 2 );
// Execute callbacks
while ( flag && functors.length ) {
flag = functors.shift().apply( context , args ) !== false;
}
// Clean if asked to stop
if ( ! flag ) {
clean();
}
// Set autoFire
autoFire = 1;
},
bind: function() {
var args = arguments,
i = 0,
length = args.length,
func;
for ( ; i < length ; i++ ) {
func = args[ i ];
if ( jQuery.isArray(func) ) {
list.bind.apply( list , func );
} else if ( isFunction(func) ) {
// Add if not already in
if ( ! pos( func ) ) {
functors.push( func );
}
}
}
if ( autoFire ) {
list.fire.apply( list , fireArgs );
}
},
unbind: function() {
var i = 0,
args = arguments,
length = args.length,
func,
position;
if ( length ) {
for( ; i < length ; i++ ) {
func = args[i];
if ( jQuery.isArray(func) ) {
list.unbind.apply(list,func);
} else if ( isFunction(func) ) {
position = pos(func);
if ( position ) {
functors.splice(position-1,1);
}
}
}
} else {
functors = [];
}
}
};
// Get the index of the functor in the list (1-based)
function pos( func ) {
for (var i = 0, length = functors.length; i < length && functors[i] !== func; i++) {
}
return i < length ? ( i + 1 ) : 0;
}
// Clean the object
function clean() {
// Empty callbacks list
functors = [];
// Inhibit methods
for (var i in list) {
list[i] = jQuery.noop;
}
}
return list;
}
jQuery.extend(jQuery.xhr, {
// Add new prefilter
prefilter: function (functor) {
if ( isFunction(functor) ) {
jQuery.ajaxSettings.prefilters.push( functor );
}
return this;
},
// Bind a transport to one or more dataTypes
bindTransport: function () {
var args = arguments,
i,
start = 0,
length = args.length,
dataTypes = [ "*" ],
functors = [],
functor,
first,
append,
list,
transports = jQuery.ajaxSettings.transports;
if ( length ) {
if ( ! isFunction( args[ 0 ] ) ) {
dataTypes = args[ 0 ].toLowerCase().split(/\s+/);
start = 1;
}
if ( dataTypes.length && start < length ) {
for ( i = start; i < length; i++ ) {
functor = args[i];
if ( isFunction(functor) ) {
functors.push( functor );
}
}
if ( functors.length ) {
jQuery.each ( dataTypes, function( _ , dataType ) {
first = /^\+/.test( dataType );
if (first) {
dataType = dataType.substr(1);
}
if ( dataType !== "" ) {
append = Array.prototype[ first ? "unshift" : "push" ];
list = transports[ dataType ];
jQuery.each ( functors, function( _ , functor ) {
if ( ! list ) {
list = transports[ dataType ] = [ functor ];
} else {
append.call( list , functor );
}
} );
}
} );
}
}
}
return this;
}
});
// Select a transport given options
function selectTransport( s ) {
var dataTypes = s.dataTypes,
transportDataType,
transportsList,
transport,
i,
length,
checked = {},
flag;
function initSearch( dataType ) {
flag = transportDataType !== dataType && ! checked[ dataType ];
if ( flag ) {
checked[ dataType ] = 1;
transportDataType = dataType;
transportsList = s.transports[ dataType ];
i = -1;
length = transportsList ? transportsList.length : 0 ;
}
return flag;
}
initSearch( dataTypes[ 0 ] );
for ( i = 0 ; ! transport && i <= length ; i++ ) {
if ( i === length ) {
initSearch( "*" );
} else {
transport = transportsList[ i ]( s , determineDataType );
// If we got redirected to another dataType
// Search there (if not in progress or already tried)
if ( typeof( transport ) === "string" &&
initSearch( transport ) ) {
dataTypes.unshift( transport );
transport = 0;
}
}
}
return transport;
}
// Utility function that handles dataType when response is received
// (for those transports that can give text or xml responses)
function determineDataType( s , ct , text , xml ) {
var autoDataType = s.autoDataType,
type,
regexp,
dataTypes = s.dataTypes,
transportDataType = dataTypes[0],
response;
// Auto (xml, json, script or text determined given headers)
if ( transportDataType === "*" ) {
for ( type in autoDataType ) {
if ( ( regexp = autoDataType[ type ] ) && regexp.test( ct ) ) {
transportDataType = dataTypes[0] = type;
break;
}
}
}
// xml and parsed as such
if ( transportDataType === "xml" &&
xml &&
xml.documentElement /* #4958 */ ) {
response = xml;
// Text response was provided
} else {
response = text;
// If it's not really text, defer to dataConverters
if ( transportDataType !== "text" ) {
dataTypes.unshift( "text" );
}
}
return response;
}
})(jQuery);

4
test/data/atom+xml.php Normal file
View file

@ -0,0 +1,4 @@
<?php header("Content-type: atom+xml") ?>
<root>
<element />
</root>

15
test/data/css.php Normal file
View file

@ -0,0 +1,15 @@
<?php
error_reporting(0);
$id = isset ( $_REQUEST['id'] ) ? $_REQUEST['id'] : null;
$wait = isset( $_REQUEST['wait'] ) ? $_REQUEST['wait'] : null;
if ( $wait ) sleep( $wait );
header("Content-type: text/css");
if ( $id ) {
?>
div#<?= $id ?> { margin-left: 27px }
<?php
}
?>

5
test/data/headers.php Normal file
View file

@ -0,0 +1,5 @@
<?php
header( "Multiple-Line: Hello\n World" );
header( "Multiple-Multiple-Line: Hello\n Beautiful\n\t World" );
header( "Single-Line: Hello World" );

View file

@ -0,0 +1,7 @@
<?php
error_reporting(0);
$callback = $_REQUEST['callback'];
$json = $_REQUEST['json'];
$text = json_encode(file_get_contents(dirname(__FILE__)."/with_fries.xml"));
echo "$callback($text)";
?>

View file

@ -20,6 +20,10 @@
<script src="../src/manipulation.js"></script>
<script src="../src/css.js"></script>
<script src="../src/ajax.js"></script>
<script src="../src/xhr.js"></script>
<script src="../src/transports/jsonp.js"></script>
<script src="../src/transports/script.js"></script>
<script src="../src/transports/xhr.js"></script>
<script src="../src/effects.js"></script>
<script src="../src/offset.js"></script>
<script src="../src/dimensions.js"></script>

View file

@ -38,6 +38,216 @@ test("jQuery.ajax() - success callbacks", function() {
});
});
test("jQuery.ajax() - success callbacks - (url, options) syntax", function() {
expect( 8 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
setTimeout(function(){
jQuery('#foo').ajaxStart(function(){
ok( true, "ajaxStart" );
}).ajaxStop(function(){
ok( true, "ajaxStop" );
start();
}).ajaxSend(function(){
ok( true, "ajaxSend" );
}).ajaxComplete(function(){
ok( true, "ajaxComplete" );
}).ajaxError(function(){
ok( false, "ajaxError" );
}).ajaxSuccess(function(){
ok( true, "ajaxSuccess" );
});
jQuery.ajax( url("data/name.html") , {
beforeSend: function(){ ok(true, "beforeSend"); },
success: function(){ ok(true, "success"); },
error: function(){ ok(false, "error"); },
complete: function(){ ok(true, "complete"); }
});
}, 13);
});
test("jQuery.ajax() - success/error callbacks (remote)", function() {
var supports = jQuery.support.cors;
expect( supports ? 9 : 6 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
setTimeout(function(){
jQuery('#foo').ajaxStart(function(){
ok( true, "ajaxStart" );
}).ajaxStop(function(){
ok( true, "ajaxStop" );
start();
}).ajaxSend(function(){
ok( supports , "ajaxSend" );
}).ajaxComplete(function(){
ok( true, "ajaxComplete" );
}).ajaxError(function(){
ok( ! supports, "ajaxError" );
}).ajaxSuccess(function(){
ok( supports, "ajaxSuccess" );
});
jQuery.ajax({
// JULIAN TODO: Get an url especially for jQuery
url: "http://rockstarapps.com/test.php",
dataType: "text",
beforeSend: function(){ ok(supports, "beforeSend"); },
success: function( val ){ ok(supports, "success"); ok(supports && val.length, "data received"); },
error: function(_ , a , b ){ ok(!supports, "error"); },
complete: function(){ ok(true, "complete"); }
});
}, 13);
});
test("jQuery.ajax() - success callbacks (late binding)", function() {
expect( 8 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
setTimeout(function(){
jQuery('#foo').ajaxStart(function(){
ok( true, "ajaxStart" );
}).ajaxStop(function(){
ok( true, "ajaxStop" );
start();
}).ajaxSend(function(){
ok( true, "ajaxSend" );
}).ajaxComplete(function(){
ok( true, "ajaxComplete" );
}).ajaxError(function(){
ok( false, "ajaxError" );
}).ajaxSuccess(function(){
ok( true, "ajaxSuccess" );
});
jQuery.ajax({
url: url("data/name.html"),
beforeSend: function(){ ok(true, "beforeSend"); }
})
.complete(function(){ ok(true, "complete"); })
.success(function(){ ok(true, "success"); })
.error(function(){ ok(false, "error"); });
}, 13);
});
test("jQuery.ajax() - success callbacks (oncomplete binding)", function() {
expect( 8 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
setTimeout(function(){
jQuery('#foo').ajaxStart(function(){
ok( true, "ajaxStart" );
}).ajaxStop(function(){
ok( true, "ajaxStop" );
}).ajaxSend(function(){
ok( true, "ajaxSend" );
}).ajaxComplete(function(){
ok( true, "ajaxComplete" );
}).ajaxError(function(){
ok( false, "ajaxError" );
}).ajaxSuccess(function(){
ok( true, "ajaxSuccess" );
});
jQuery.ajax({
url: url("data/name.html"),
beforeSend: function(){ ok(true, "beforeSend"); },
complete: function(xhr) {
xhr
.complete(function(){ ok(true, "complete"); })
.success(function(){ ok(true, "success"); })
.error(function(){ ok(false, "error"); })
.complete(function(){ start(); });
}
})
}, 13);
});
test("jQuery.ajax() - success callbacks (very late binding)", function() {
expect( 8 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
setTimeout(function(){
jQuery('#foo').ajaxStart(function(){
ok( true, "ajaxStart" );
}).ajaxStop(function(){
ok( true, "ajaxStop" );
}).ajaxSend(function(){
ok( true, "ajaxSend" );
}).ajaxComplete(function(){
ok( true, "ajaxComplete" );
}).ajaxError(function(){
ok( false, "ajaxError" );
}).ajaxSuccess(function(){
ok( true, "ajaxSuccess" );
});
jQuery.ajax({
url: url("data/name.html"),
beforeSend: function(){ ok(true, "beforeSend"); },
complete: function(xhr) {
setTimeout (function() {
xhr
.complete(function(){ ok(true, "complete"); })
.success(function(){ ok(true, "success"); })
.error(function(){ ok(false, "error"); })
.complete(function(){ start(); });
},100);
}
})
}, 13);
});
test("jQuery.ajax() - success callbacks (order)", function() {
expect( 1 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
var testString = "";
setTimeout(function(){
jQuery.ajax({
url: url("data/name.html"),
success: function( _1 , _2 , xhr ) {
xhr.success(function() {
xhr.success(function() {
testString += "E";
});
testString += "D";
});
testString += "A";
},
complete: function() {
strictEqual(testString, "ABCDE", "Proper order");
start();
}
}).success(function() {
testString += "B";
}).success(function() {
testString += "C";
});
}, 13);
});
test("jQuery.ajax() - error callbacks", function() {
expect( 8 );
stop();
@ -68,9 +278,35 @@ test("jQuery.ajax() - error callbacks", function() {
});
});
test(".ajax() - headers" , function() {
// No multiple line headers in IE
expect( jQuery.browser.msie ? 2 : 4 );
stop();
jQuery.ajax({
url: url("data/headers.php"),
success: function( _1 , _2 , xhr ){
ok(true, "success");
equals( xhr.getResponseHeader( "Single-Line" ) , "Hello World" , "Single line header" );
// No multiple line headers in IE
if ( ! jQuery.browser.msie ) {
// Each browser has its own unique way to deal with spaces after line breaks
// in multiple line headers, so we use regular expressions
ok( /^Hello\s+World$/.test( xhr.getResponseHeader( "Multiple-Line" ) ) , "Multiple line" );
ok( /^Hello\s+Beautiful\s+World$/.test( xhr.getResponseHeader( "Multiple-Multiple-Line" ) ) , "Multiple multiple line" );
}
start();
},
error: function(){ ok(false, "error"); }
});
});
test(".ajax() - hash", function() {
expect(3);
jQuery.ajax({
url: "data/name.html#foo",
beforeSend: function( xhr, settings ) {
@ -78,15 +314,15 @@ test(".ajax() - hash", function() {
return false;
}
});
jQuery.ajax({
url: "data/name.html?abc#foo",
beforeSend: function( xhr, settings ) {
equals(settings.url, "data/name.html?abc", "Make sure that the URL is trimmed.");
equals(settings.url, "data/name.html?abc", "Make sure that the URL is trimmed.");
return false;
}
});
jQuery.ajax({
url: "data/name.html?abc#foo",
data: { "test": 123 },
@ -100,7 +336,7 @@ test(".ajax() - hash", function() {
test(".ajax() - 304", function() {
expect( 1 );
stop();
jQuery.ajax({
url: url("data/notmodified.php"),
success: function(){ ok(true, "304 ok"); },
@ -163,6 +399,136 @@ test("jQuery.ajax() - abort", function() {
equals( xhr.readyState, 0, "XHR readyState indicates successful abortion" );
});
test("jQuery.ajax() - readyState (success)", function() {
expect( 1 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
var control = "";
setTimeout(function(){
jQuery.ajax({
url: url("data/name.html"),
beforeSend: function( xhr ) {
xhr.onreadystatechange = function() {
control += xhr.readyState;
}
},
complete: function(){
setTimeout( function() {
equals( control , "1234" , "onreadystatechange was properly called" );
}, 13 );
start();
}
});
}, 13);
});
test("jQuery.ajax() - readyState (abort)", function() {
expect( 2 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
var control = "";
setTimeout(function(){
jQuery.ajaxSetup({ timeout: 500 });
jQuery.ajax({
url: url("data/name.php?wait=5"),
beforeSend: function( xhr ) {
xhr.onreadystatechange = function() {
control += xhr.readyState;
}
},
complete: function( xhr ){
setTimeout( function() {
equals( control , "14" , "onreadystatechange was properly called" );
equals( xhr.readyState, 0 , "readyState is 0" );
}, 13 );
start();
}
});
}, 13);
});
test("jQuery.xhr() - reuse", function() {
expect( 15 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
var number = 0;
setTimeout(function(){
jQuery('#foo').ajaxStart(function(){
ok( true, "ajaxStart" );
}).ajaxStop(function(){
ok( true, "ajaxStop" );
start();
}).ajaxSend(function(){
number++;
ok( true, "ajaxSend (" + number +")" );
}).ajaxComplete(function(){
ok( true, "ajaxComplete (" + number +")" );
}).ajaxError(function(){
ok( false, "ajaxError (" + number +")" );
}).ajaxSuccess(function(){
ok( true, "ajaxSuccess (" + number +")" );
});
jQuery.ajax({
url: url("data/name.html"),
beforeSend: function(){ ok(true, "beforeSend (1)"); },
success: function( _1 , _2 , xhr ){
ok(true, "success (1)");
xhr.complete(function() {
ok(true, "complete (1bis)");
});
xhr.open( "GET", url("data/name.html") );
xhr.success( function(){ ok(true, "beforeSend (2)"); } )
xhr.send( null, {
success: function(){ ok(true, "success (2)"); },
error: function(){ ok(false, "error (2)"); },
complete: function(){ ok(true, "complete (2)"); }
} );
},
error: function(){ ok(false, "error (1)"); },
complete: function(){ ok(true, "complete (1)"); }
});
}, 13);
});
test("jQuery.xhr() - early binding", function() {
expect( 2 );
jQuery.ajaxSetup({ timeout: 0 });
stop();
jQuery.xhr()
.success( function(){ ok(true, "success"); } )
.error( function(){ ok(false, "error"); } )
.complete( function(){ ok(true, "complete"); start(); } )
.open( "GET", url("data/name.html") )
.send();
});
test("jQuery.xhr() - get native implementation", function() {
var xhr = jQuery.xhr(true);
ok( xhr.readyState !== undefined , "implements XMLHttpRequest" );
ok( ! jQuery.isFunction( xhr.success ) , "is not jQuery's abstraction" );
});
test("Ajax events with context", function() {
expect(14);
@ -277,6 +643,34 @@ test("jQuery.ajax() - disabled globals", function() {
});
});
test("jQuery.xhr() - disabled globals through xhr.send(data , false)", function() {
expect( 2 );
stop();
jQuery('#foo').ajaxStart(function(){
ok( false, "ajaxStart" );
}).ajaxStop(function(){
ok( false, "ajaxStop" );
}).ajaxSend(function(){
ok( false, "ajaxSend" );
}).ajaxComplete(function(){
ok( false, "ajaxComplete" );
}).ajaxError(function(){
ok( false, "ajaxError" );
}).ajaxSuccess(function(){
ok( false, "ajaxSuccess" );
});
jQuery.xhr()
.success(function(){ ok(true, "success"); })
.error(function(){ ok(false, "error"); })
.complete(function(){
ok(true, "complete");
setTimeout(function(){ start(); }, 13);
})
.open("GET", url("data/name.html")).send(undefined, false);
});
test("jQuery.ajax - xml: non-namespace elements inside namespaced elements", function() {
expect(3);
stop();
@ -292,6 +686,21 @@ test("jQuery.ajax - xml: non-namespace elements inside namespaced elements", fun
});
});
test("jQuery.ajax - xml: non-namespace elements inside namespaced elements (over JSONP)", function() {
expect(3);
stop();
jQuery.ajax({
url: url("data/with_fries_over_jsonp.php"),
dataType: "jsonp xml",
success: function(resp) {
equals( jQuery("properties", resp).length, 1, 'properties in responseXML' );
equals( jQuery("jsconf", resp).length, 1, 'jsconf in responseXML' );
equals( jQuery("thing", resp).length, 2, 'things in responseXML' );
start();
}
});
});
test("jQuery.ajax - HEAD requests", function() {
expect(2);
@ -315,7 +724,7 @@ test("jQuery.ajax - HEAD requests", function() {
});
}
});
});
test("jQuery.ajax - beforeSend", function() {
@ -359,6 +768,27 @@ test("jQuery.ajax - beforeSend, cancel request (#2688)", function() {
ok( request === false, "canceled request must return false instead of XMLHttpRequest instance" );
});
test("jQuery.ajax - beforeSend, cancel request manually", function() {
expect(2);
var request = jQuery.ajax({
url: url("data/name.html"),
beforeSend: function(xhr) {
ok( true, "beforeSend got called, canceling" );
xhr.abort();
},
success: function() {
ok( false, "request didn't get canceled" );
},
complete: function() {
ok( false, "request didn't get canceled" );
},
error: function() {
ok( false, "request didn't get canceled" );
}
});
ok( request === false, "canceled request must return false instead of XMLHttpRequest instance" );
});
window.foobar = null;
window.testFoo = undefined;
@ -456,7 +886,7 @@ test("jQuery.param()", function() {
equals( jQuery.param({"foo": {"bar": []} }), "foo%5Bbar%5D=", "Empty array param" );
equals( jQuery.param({"foo": {"bar": [], foo: 1} }), "foo%5Bbar%5D=&foo%5Bfoo%5D=1", "Empty array param" );
equals( jQuery.param({"foo": {"bar": {}} }), "foo%5Bbar%5D=", "Empty object param" );
jQuery.ajaxSetup({ traditional: true });
var params = {foo:"bar", baz:42, quux:"All your base are belong to us"};
@ -841,12 +1271,13 @@ test("jQuery.ajax() - JSONP, Local", function() {
});
});
test("JSONP - Custom JSONP Callback", function() {
test("jQuery.ajax() - JSONP - Custom JSONP Callback", function() {
expect(1);
stop();
window.jsonpResults = function(data) {
ok( data.data, "JSON results returned (GET, custom callback function)" );
window.jsonpResults = undefined;
start();
};
@ -943,7 +1374,7 @@ test("jQuery.ajax() - script, Remote with POST", function() {
expect(3);
var base = window.location.href.replace(/[^\/]*$/, "");
stop();
jQuery.ajax({
@ -1033,6 +1464,30 @@ test("jQuery.ajax() - json by content-type", function() {
});
});
test("jQuery.ajax() - json by content-type disabled with options", function() {
expect(6);
stop();
jQuery.ajax({
url: url("data/json.php"),
data: { header: "json", json: "array" },
autoDataType: {
json: false
},
success: function( text ) {
equals( typeof text , "string" , "json wasn't auto-determined" );
var json = this.dataConverters["text => json"]( text );
ok( json.length >= 2, "Check length");
equals( json[0].name, 'John', 'Check JSON: first, name' );
equals( json[0].age, 21, 'Check JSON: first, age' );
equals( json[1].name, 'Peter', 'Check JSON: second, name' );
equals( json[1].age, 25, 'Check JSON: second, age' );
start();
}
});
});
test("jQuery.getJSON(String, Hash, Function) - JSON array", function() {
expect(5);
stop();
@ -1290,7 +1745,7 @@ test("jQuery.ajax - If-Modified-Since support", function() {
ok(data == null, "response body should be empty")
}
start();
},
},
error: function() {
// Do this because opera simply refuses to implement 304 handling :(
// A feature-driven way of detecting this would be appreciated
@ -1298,10 +1753,11 @@ test("jQuery.ajax - If-Modified-Since support", function() {
ok(jQuery.browser.opera, "error");
ok(jQuery.browser.opera, "error");
start();
}
}
});
},
error: function() {
equals(false, "error");
// Do this because opera simply refuses to implement 304 handling :(
// A feature-driven way of detecting this would be appreciated
// See: http://gist.github.com/599419
@ -1336,8 +1792,8 @@ test("jQuery.ajax - Etag support", function() {
ok(data == null, "response body should be empty")
}
start();
},
error: function() {
},
error: function() {
// Do this because opera simply refuses to implement 304 handling :(
// A feature-driven way of detecting this would be appreciated
// See: http://gist.github.com/599419
@ -1357,6 +1813,60 @@ test("jQuery.ajax - Etag support", function() {
});
});
test("jQuery ajax - headers", function() {
stop();
jQuery.ajax(url("data/css.php?wait=1&id=headers"), {
headers: {
testKey: "testValue"
},
beforeSend: function( xhr ) {
equals( xhr.getRequestHeader("testKey") , "testValue" , "Headers properly set" );
setTimeout( start , 13 );
return false;
}
});
});
test("jQuery ajax - failing cross-domain", function() {
expect( 2 );
stop();
var i = 2;
jQuery.ajax({
url: 'http://somewebsitethatdoesnotexist.com',
success: function(){ ok( false , "success" ); },
error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); },
complete: function() { if ( ! --i ) start(); }
});
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(); }
});
});
test("jQuery ajax - atom+xml", function() {
stop();
jQuery.ajax({
url: url( 'data/atom+xml.php' ),
success: function(){ ok( true , "success" ); },
error: function(){ ok( false , "error" ); },
complete: function() { start(); }
});
});
test("jQuery.ajax - active counter", function() {
ok( jQuery.active == 0, "ajax active counter should be zero: " + jQuery.active );
});
@ -1374,4 +1884,4 @@ test( "jQuery.ajax - Location object as url (#7531)", 1, function () {
}
//}
//}