aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjaubourg <aubourg.julian@gmail.com>2010-12-09 13:34:28 -0500
committerjeresig <jeresig@gmail.com>2010-12-09 13:34:28 -0500
commitab3ba4a81252c4357a7aab5f24d765d41d47986e (patch)
tree75c16f8e7cd8a41041ee19855e799d15c074fd67
parent29616e60c88bf300f4b2ee4ad1a89a8ac6481027 (diff)
downloadjquery-ab3ba4a81252c4357a7aab5f24d765d41d47986e.tar.gz
jquery-ab3ba4a81252c4357a7aab5f24d765d41d47986e.zip
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.
-rw-r--r--Makefile6
-rw-r--r--src/ajax.js571
-rw-r--r--src/transports/jsonp.js89
-rw-r--r--src/transports/script.js83
-rw-r--r--src/transports/xhr.js191
-rw-r--r--src/xhr.js941
-rw-r--r--test/data/atom+xml.php4
-rw-r--r--test/data/css.php15
-rw-r--r--test/data/headers.php5
-rw-r--r--test/data/with_fries_over_jsonp.php7
-rw-r--r--test/index.html4
-rw-r--r--test/unit/ajax.js538
12 files changed, 1991 insertions, 463 deletions
diff --git a/Makefile b/Makefile
index 0dae73230..fdc655d60 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/src/ajax.js b/src/ajax.js
index 90dc350fc..ceeef5eb4 100644
--- a/src/ajax.js
+++ b/src/ajax.js
@@ -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: "*/*"
- }
- },
-
- 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) {}
- }
-
- 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") + "=?";
+ 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");
}
- } 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 ) {}
+ },
+
+ // Check if xml has been properly parsed
+ "xml": function(data) {
+ var documentElement = data ? data.documentElement : data;
+ if ( ! documentElement || ! documentElement.nodeName ) {
+ jQuery.error("typeerror");
}
-
- 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]);
+ if ( documentElement.nodeName == "parsererror" ) {
+ jQuery.error("parsererror");
}
}
-
- // 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();
+ },
+
+ // 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);
}
-
- cleanup();
+ return xml;
}
- };
-
- // 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();
+ // Main method
+ ajax: function( url , s ) {
+
+ if ( arguments.length === 1 ) {
+ s = url;
+ url = s ? s.url : undefined;
}
-
- // 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 ) {}
};
}
+var testXHR = jQuery.ajaxSettings.xhr();
+
// Does this browser support XHR requests?
-jQuery.support.ajax = !!jQuery.ajaxSettings.xhr();
+jQuery.support.ajax = !!testXHR;
+
+// Does this browser support crossDomain XHR requests
+jQuery.support.cors = testXHR && "withCredentials" in testXHR;
-})( jQuery );
+})(jQuery); \ No newline at end of file
diff --git a/src/transports/jsonp.js b/src/transports/jsonp.js
new file mode 100644
index 000000000..2bdce2390
--- /dev/null
+++ b/src/transports/jsonp.js
@@ -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);
diff --git a/src/transports/script.js b/src/transports/script.js
new file mode 100644
index 000000000..5470decce
--- /dev/null
+++ b/src/transports/script.js
@@ -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);
diff --git a/src/transports/xhr.js b/src/transports/xhr.js
new file mode 100644
index 000000000..129058ee6
--- /dev/null
+++ b/src/transports/xhr.js
@@ -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);
diff --git a/src/xhr.js b/src/xhr.js
new file mode 100644
index 000000000..fae1110c4
--- /dev/null
+++ b/src/xhr.js
@@ -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);
diff --git a/test/data/atom+xml.php b/test/data/atom+xml.php
new file mode 100644
index 000000000..944591abf
--- /dev/null
+++ b/test/data/atom+xml.php
@@ -0,0 +1,4 @@
+<?php header("Content-type: atom+xml") ?>
+<root>
+ <element />
+</root> \ No newline at end of file
diff --git a/test/data/css.php b/test/data/css.php
new file mode 100644
index 000000000..9d079e73f
--- /dev/null
+++ b/test/data/css.php
@@ -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
+}
+?> \ No newline at end of file
diff --git a/test/data/headers.php b/test/data/headers.php
new file mode 100644
index 000000000..598cadcde
--- /dev/null
+++ b/test/data/headers.php
@@ -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" ); \ No newline at end of file
diff --git a/test/data/with_fries_over_jsonp.php b/test/data/with_fries_over_jsonp.php
new file mode 100644
index 000000000..456aeb3bd
--- /dev/null
+++ b/test/data/with_fries_over_jsonp.php
@@ -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)";
+?>
diff --git a/test/index.html b/test/index.html
index e66872725..bd11ebbd7 100644
--- a/test/index.html
+++ b/test/index.html
@@ -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>
diff --git a/test/unit/ajax.js b/test/unit/ajax.js
index a0f3d49f2..87430c93a 100644
--- a/test/unit/ajax.js
+++ b/test/unit/ajax.js
@@ -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 () {
}
-//}
+//} \ No newline at end of file