From a5dbcaf67579b2266b64ffbdd2a328d180f7f64b Mon Sep 17 00:00:00 2001 From: John Resig Date: Mon, 3 Sep 2007 23:45:14 +0000 Subject: [PATCH] =?utf8?q?Added=20support=20for:=20-=20Cross=20Domain=20ge?= =?utf8?q?tScript=20=20=20$.getScript("http://foo.com/script.js");=20-=20J?= =?utf8?q?SONP=20=20=20$.ajax({=20url:=20"script.js",=20type:=20"jsonp"=20?= =?utf8?q?});=20=20=20$.getJSON("script.js=3Fcallback=3D=3F");=20-=20Cross?= =?utf8?q?=20Domain=20JSONP/getJSON=20=20=20$.getJSON("http://foo.com/scri?= =?utf8?q?pt.js=3Fcallback=3D=3F");=20-=20No-cache=20Ajax=20Requests=20=20?= =?utf8?q?=20$.ajax({=20url:=20"test.html",=20cache:=20false=20});?= --- build/test/data/jsonp.php | 10 + build/test/index.html | 4 +- src/ajax/ajax.js | 150 ++++++++++---- src/ajax/ajaxTest.js | 417 +++++++++++++++++++++++++++----------- 4 files changed, 420 insertions(+), 161 deletions(-) create mode 100644 build/test/data/jsonp.php diff --git a/build/test/data/jsonp.php b/build/test/data/jsonp.php new file mode 100644 index 000000000..75000252b --- /dev/null +++ b/build/test/data/jsonp.php @@ -0,0 +1,10 @@ + diff --git a/build/test/index.html b/build/test/index.html index 6956cb902..3b59b0e79 100644 --- a/build/test/index.html +++ b/build/test/index.html @@ -41,11 +41,11 @@
- + - + diff --git a/src/ajax/ajax.js b/src/ajax/ajax.js index ccf4f14a9..0b965e4a1 100644 --- a/src/ajax/ajax.js +++ b/src/ajax/ajax.js @@ -244,6 +244,8 @@ jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".sp }; }); +var jsc = (new Date).getTime(); + jQuery.extend({ /** @@ -599,30 +601,95 @@ jQuery.extend({ * @see ajaxSetup(Map) */ ajax: function( s ) { + var jsonp, jsre = /=(\?|%3F)/g, status, data; + // Extend the settings, but re-extend 's' so that it can be // checked again later (in the test suite, specifically) s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); - // if data available - if ( s.data ) { - // convert data if not already a string - if ( s.processData && typeof s.data != "string" ) - s.data = jQuery.param(s.data); + // convert data if not already a string + if ( s.data && s.processData && typeof s.data != "string" ) + s.data = jQuery.param(s.data); - // append data to url for get requests - if ( s.type.toLowerCase() == "get" ) { - // "?" + data or "&" + data (in case there are already params) - s.url += (s.url.indexOf("?") > -1 ? "&" : "?") + s.data; + // Break the data into one single string + var q = s.url.indexOf("?"); + if ( q > -1 ) { + s.data = (s.data ? s.data + "&" : "") + s.url.slice(q + 1); + s.url = s.url.slice(0, q); + } - // IE likes to send both get and post data, prevent this - s.data = null; - } + // Handle JSONP Parameter Callbacks + if ( s.dataType == "jsonp" ) { + if ( !s.data || !s.data.match(jsre) ) + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType == "json" && s.data && s.data.match(jsre) ) { + jsonp = "jsonp" + jsc++; + s.data = s.data.replace(jsre, "=" + jsonp); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + window[ jsonp ] = function(tmp){ + data = tmp; + success(); + // Garbage collect + window[ jsonp ] = undefined; + try{ delete window[ jsonp ]; } catch(e){} + }; + } + + if ( s.dataType == "script" && s.cache == null ) + s.cache = false; + + if ( s.cache === false && s.type.toLowerCase() == "get" ) + s.data = (s.data ? s.data + "&" : "") + "_=" + (new Date()).getTime(); + + // If data is available, append data to url for get requests + if ( s.data && s.type.toLowerCase() == "get" ) { + s.url += "?" + s.data; + + // IE likes to send both get and post data, prevent this + s.data = null; } // Watch for a new set of requests if ( s.global && ! jQuery.active++ ) jQuery.event.trigger( "ajaxStart" ); + // If we're requesting a remote document + // and trying to load JSON or Script + if ( !s.url.indexOf("http") && s.dataType == "script" ) { + var script = document.createElement("script"); + script.src = s.url; + + // Handle Script loading + if ( !jsonp && (s.success || s.complete) ) { + 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; + success(); + complete(); + document.body.removeChild( script ); + } + }; + } + + document.body.appendChild(script); + + // We handle everything using the script element injection + return; + } + var requestDone = false; // Create the request object; Microsoft failed to properly @@ -645,7 +712,7 @@ jQuery.extend({ xml.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // Allow custom headers/mimetypes - if( s.beforeSend ) + if ( s.beforeSend ) s.beforeSend(xml); if ( s.global ) @@ -663,7 +730,7 @@ jQuery.extend({ ival = null; } - var status = isTimeout == "timeout" && "timeout" || + status = isTimeout == "timeout" && "timeout" || !jQuery.httpSuccess( xml ) && "error" || s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" || "success"; @@ -672,7 +739,7 @@ jQuery.extend({ // Watch for, and catch, XML document parse errors try { // process the data (runs the xml through httpData regardless of callback) - var data = jQuery.httpData( xml, s.dataType ); + data = jQuery.httpData( xml, s.dataType ); } catch(e) { status = "parsererror"; } @@ -688,31 +755,18 @@ jQuery.extend({ if ( s.ifModified && modRes ) jQuery.lastModified[s.url] = modRes; - - // If a local callback was specified, fire it and pass it the data - if ( s.success ) - s.success( data, status ); - - // Fire the global callback - if ( s.global ) - jQuery.event.trigger( "ajaxSuccess", [xml, s] ); + + // JSONP handles its own success callback + if ( !jsonp ) + success(); } else jQuery.handleError(s, xml, status); - // The request was completed - if( s.global ) - jQuery.event.trigger( "ajaxComplete", [xml, s] ); - - // Handle the global AJAX counter - if ( s.global && ! --jQuery.active ) - jQuery.event.trigger( "ajaxStop" ); - - // Process result - if ( s.complete ) - s.complete(xml, status); + // Fire the complete handlers + complete(); // Stop memory leaks - if(s.async) + if ( s.async ) xml = null; } }; @@ -748,6 +802,30 @@ jQuery.extend({ // return XMLHttpRequest to allow aborting the request etc. return xml; + + function success(){ + // If a local callback was specified, fire it and pass it the data + if ( s.success ) + s.success( data, status ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxSuccess", [xml, s] ); + } + + function complete(){ + // Process result + if ( s.complete ) + s.complete(xml, status); + + // The request was completed + if ( s.global ) + jQuery.event.trigger( "ajaxComplete", [xml, s] ); + + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + } }, handleError: function( s, xml, status, e ) { @@ -793,7 +871,7 @@ jQuery.extend({ httpData: function( r, type ) { var ct = r.getResponseHeader("content-type"); var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0; - data = xml ? r.responseXML : r.responseText; + var data = xml ? r.responseXML : r.responseText; if ( xml && data.documentElement.tagName == "parsererror" ) throw "parsererror"; diff --git a/src/ajax/ajaxTest.js b/src/ajax/ajaxTest.js index 0fd4888ac..24afb5e5d 100644 --- a/src/ajax/ajaxTest.js +++ b/src/ajax/ajaxTest.js @@ -3,7 +3,164 @@ module("ajax"); // Safari 3 randomly crashes when running these tests, // but only in the full suite - you can run just the Ajax // tests and they'll pass -if ( !jQuery.browser.safari ) { +//if ( !jQuery.browser.safari ) { + +test("$.ajax() - success callbacks", function() { + expect( 8 ); + + $.ajaxSetup({ timeout: 0 }); + + stop(); + + setTimeout(function(){ + $('#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" ); + }); + + $.ajax({ + url: 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); +}); + +if ( !isLocal ) { + test("$.ajax() - error callbacks", function() { + expect( 8 ); + stop(); + + $('#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( true, "ajaxError" ); + }).ajaxSuccess(function(){ + ok( false, "ajaxSuccess" ); + }); + + $.ajaxSetup({ timeout: 500 }); + + $.ajax({ + url: url("data/name.php?wait=5"), + beforeSend: function(){ ok(true, "beforeSend"); }, + success: function(){ ok(false, "success"); }, + error: function(){ ok(true, "error"); }, + complete: function(){ ok(true, "complete"); } + }); + }); +} + +test("$.ajax() - disabled globals", function() { + expect( 3 ); + stop(); + + $('#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" ); + }); + + $.ajax({ + global: false, + url: url("data/name.html"), + beforeSend: function(){ ok(true, "beforeSend"); }, + success: function(){ ok(true, "success"); }, + error: function(){ ok(false, "error"); }, + complete: function(){ + ok(true, "complete"); + setTimeout(function(){ start(); }, 13); + } + }); +}); + +test("$.ajax - xml: non-namespace elements inside namespaced elements", function() { + expect(3); + stop(); + $.ajax({ + url: url("data/with_fries.xml"), + dataType: "xml", + success: function(resp) { + equals( $("properties", resp).length, 1, 'properties in responseXML' ); + equals( $("jsconf", resp).length, 1, 'jsconf in responseXML' ); + equals( $("thing", resp).length, 2, 'things in responseXML' ); + start(); + } + }); +}); + +test("$.ajax - beforeSend", function() { + expect(1); + stop(); + + var check = false; + + $.ajaxSetup({ timeout: 0 }); + + $.ajax({ + url: url("data/name.html"), + beforeSend: function(xml) { + check = true; + }, + success: function(data) { + ok( check, "check beforeSend was executed" ); + start(); + } + }); +}); + +var foobar; + +test("$.ajax - dataType html", function() { + expect(5); + stop(); + + foobar = null; + testFoo = undefined; + + var verifyEvaluation = function() { + ok( testFoo == "foo", 'Check if script was evaluated for datatype html' ); + ok( foobar == "bar", 'Check if script src was evaluated for datatype html' ); + start(); + }; + + $.ajax({ + dataType: "html", + url: url("data/test.html"), + success: function(data) { + $("#ap").html(data); + ok( data.match(/^html text/), 'Check content for datatype html' ); + setTimeout(verifyEvaluation, 600); + } + }); +}); test("serialize()", function() { expect(1); @@ -66,23 +223,23 @@ test("global ajaxSettings", function() { expect(3); var tmp = jQuery.extend({}, jQuery.ajaxSettings); - var orig = { url: "data/with_fries.xml", data: null }; + var orig = { url: "data/with_fries.xml", data: null }; var t; $.ajaxSetup({ data: {foo: 'bar', bar: 'BAR'} }); - t = jQuery.extend({}, orig); - $.ajax(t); + t = jQuery.extend({}, orig); + $.ajax(t); ok( t.url.indexOf('foo') > -1 && t.url.indexOf('bar') > -1, "Check extending null" ); - t = jQuery.extend({}, orig); + t = jQuery.extend({}, orig); t.data = {}; - $.ajax(t); + $.ajax(t); ok( t.url.indexOf('foo') > -1 && t.url.indexOf('bar') > -1, "Check extending {}" ); - t = jQuery.extend({}, orig); + t = jQuery.extend({}, orig); t.data = { zoo: 'a', ping: 'b' }; - $.ajax(t); + $.ajax(t); ok( t.url.indexOf('ping') > -1 && t.url.indexOf('zoo') > -1 && t.url.indexOf('foo') > -1 && t.url.indexOf('bar') > -1, "Check extending { zoo: 'a', ping: 'b' }" ); jQuery.ajaxSettings = tmp; @@ -179,139 +336,153 @@ test("$.getScript(String, Function) - no callback", function() { $.getScript(url("data/test.js"), start); }); -test("$.ajax - xml: non-namespace elements inside namespaced elements", function() { - expect(3); +if ( !isLocal ) { + +test("$.ajax() - JSONP, Local", function() { + expect(7); + + var count = 0; + function plus(){ if ( ++count == 7 ) start(); } + stop(); + $.ajax({ - url: url("data/with_fries.xml"), - dataType: "xml", - success: function(resp) { - equals( $("properties", resp).length, 1, 'properties in responseXML' ); - equals( $("jsconf", resp).length, 1, 'jsconf in responseXML' ); - equals( $("thing", resp).length, 2, 'things in responseXML' ); - start(); - } + url: "data/jsonp.php", + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (GET, no callback)" ); + plus(); + } }); -}); -test("test global handlers - success", function() { - expect( isLocal ? 4 : 8 ); - stop(); - - var counter = { complete: 0, success: 0, error: 0, send: 0 }, - success = function() { counter.success++ }, - error = function() { counter.error++ }, - complete = function() { counter.complete++ }, - send = function() { counter.send++ }; + $.ajax({ + url: "data/jsonp.php?callback=?", + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (GET, url callback)" ); + plus(); + } + }); - $('#foo').ajaxStart(complete).ajaxStop(complete).ajaxSend(send).ajaxComplete(complete).ajaxError(error).ajaxSuccess(success); - - // start with successful test - $.ajax({url: url("data/name.html"), beforeSend: send, success: success, error: error, complete: function() { - equals( counter.error, 0, 'Check succesful request, error callback' ); - equals( counter.success, 2, 'Check succesful request, success callback' ); - equals( counter.complete, 3, 'Check succesful request, complete callback' ); - equals( counter.send, 2, 'Check succesful request, send callback' ); - - if ( !isLocal ) { - counter.error = counter.success = counter.complete = counter.send = 0; - $.ajaxTimeout(500); - - $.ajax({url: url("data/name.php?wait=5"), beforeSend: send, success: success, error: error, complete: function() { - equals( counter.error, 2, 'Check failed request, error callback' ); - equals( counter.success, 0, 'Check failed request, success callback' ); - equals( counter.complete, 3, 'Check failed request, failed callback' ); - equals( counter.send, 2, 'Check failed request, send callback' ); - start(); - }}); - } else - start(); - }}); -}); + $.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + data: "callback=?", + success: function(data){ + ok( data.data, "JSON results returned (GET, data callback)" ); + plus(); + } + }); -test("test global handlers - failure", function() { - expect( isLocal ? 4 : 8 ); - stop(); - - var counter = { complete: 0, success: 0, error: 0, send: 0 }, - success = function() { counter.success++ }, - error = function() { counter.error++ }, - complete = function() { counter.complete++ }, - send = function() { counter.send++ }; - - $.ajaxTimeout(0); - - $('#foo').ajaxStart(complete).ajaxStop(complete).ajaxSend(send).ajaxComplete(complete).ajaxError(error).ajaxSuccess(success); - - $.ajax({url: url("data/name.php"), global: false, beforeSend: send, success: success, error: error, complete: function() { - ok( counter.error == 0, 'Check sucesful request without globals' ); - ok( counter.success == 1, 'Check sucesful request without globals' ); - ok( counter.complete == 0, 'Check sucesful request without globals' ); - ok( counter.send == 1, 'Check sucesful request without globals' ); - - if ( !isLocal ) { - counter.error = counter.success = counter.complete = counter.send = 0; - $.ajaxTimeout(500); - - $.ajax({url: url("data/name.php?wait=5"), global: false, beforeSend: send, success: success, error: error, complete: function() { - var x = counter; - ok( counter.error == 1, 'Check failed request without globals' ); - ok( counter.success == 0, 'Check failed request without globals' ); - ok( counter.complete == 0, 'Check failed request without globals' ); - ok( counter.send == 1, 'Check failed request without globals' ); - start(); - }}); - } else - start(); - }}); + $.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + data: { callback: "?" }, + success: function(data){ + ok( data.data, "JSON results returned (GET, data obj callback)" ); + plus(); + } + }); + + $.ajax({ + type: "POST", + url: "data/jsonp.php", + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (POST, no callback)" ); + plus(); + } + }); + + $.ajax({ + type: "POST", + url: "data/jsonp.php", + data: "callback=?", + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (POST, data callback)" ); + plus(); + } + }); + + $.ajax({ + type: "POST", + url: "data/jsonp.php", + data: { callback: "?" }, + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (POST, data obj callback)" ); + plus(); + } + }); }); -test("$.ajax - beforeSend", function() { - expect(1); +test("$.ajax() - JSONP, Remote", function() { + expect(4); + + var count = 0; + function plus(){ if ( ++count == 4 ) start(); } + + var base = window.location.href.replace(/\?.*$/, ""); + stop(); - - var check = false; - - $.ajaxSetup({ timeout: 0 }); - + $.ajax({ - url: url("data/name.html"), - beforeSend: function(xml) { - check = true; - }, - success: function(data) { - ok( check, "check beforeSend was executed" ); - start(); + url: base + "data/jsonp.php", + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (GET, no callback)" ); + plus(); + } + }); + + $.ajax({ + url: base + "data/jsonp.php?callback=?", + dataType: "jsonp", + success: function(data){ + ok( data.data, "JSON results returned (GET, url callback)" ); + plus(); + } + }); + + $.ajax({ + url: base + "data/jsonp.php", + dataType: "jsonp", + data: "callback=?", + success: function(data){ + ok( data.data, "JSON results returned (GET, data callback)" ); + plus(); + } + }); + + $.ajax({ + url: base + "data/jsonp.php", + dataType: "jsonp", + data: { callback: "?" }, + success: function(data){ + ok( data.data, "JSON results returned (GET, data obj callback)" ); + plus(); } }); }); -test("$.ajax - dataType html", function() { - expect(5); +test("$.ajax() - script, Remote", function() { + expect(2); + + var base = window.location.href.replace(/\?.*$/, ""); + stop(); - - foobar = null; - testFoo = undefined; - - var verifyEvaluation = function() { - ok( testFoo == "foo", 'Check if script was evaluated for datatype html' ); - ok( foobar == "bar", 'Check if script src was evaluated for datatype html' ); - start(); - }; - + $.ajax({ - dataType: "html", - url: url("data/test.html"), - success: function(data) { - $("#ap").html(data); - ok( data.match(/^html text/), 'Check content for datatype html' ); - setTimeout(verifyEvaluation, 600); - } + url: base + "data/test.js", + dataType: "script", + success: function(data){ + ok( foobar, "Script results returned (GET, no callback)" ); + start(); + } }); }); -if ( !isLocal ) { - test("$.getJSON(String, Hash, Function) - JSON array", function() { expect(4); stop(); @@ -454,4 +625,4 @@ test("custom timeout does not set error message when timeout occurs, see #970", } -} +//} -- 2.39.5