Rewrite of the Ajax module by Julian Aubourg. Some (dated) details can be found here...
[jquery.git] / src / xhr.js
1 (function( jQuery , undefined ) {
2
3 var rquery = /\?/,
4         rhash = /#.*$/,
5         rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
6         rnoContent = /^(?:GET|HEAD)$/,
7         rts = /([?&])_=[^&]*/,
8         rurl = /^(\w+:)?\/\/([^\/?#]+)/,
9         
10         slice = Array.prototype.slice,
11         
12         isFunction = jQuery.isFunction;
13         
14 // Creates a jQuery xhr object
15 jQuery.xhr = function( _native ) {
16         
17         if ( _native ) {
18                 return jQuery.ajaxSettings.xhr();
19         }
20         
21         function reset(force) {
22                 
23                 // We only need to reset if we went through the init phase
24                 // (with the exception of object creation)
25                 if ( force || internal ) {
26                 
27                         // Reset callbacks lists
28                         callbacksLists = {
29                                 success: createCBList(),
30                                 error: createCBList(),
31                                 complete: createCBList()
32                         };
33                         
34                         // Reset private variables
35                         requestHeaders = {};
36                         responseHeadersString = responseHeaders = internal = done = timeoutTimer = s = undefined;
37                         
38                         // Reset state
39                         xhr.readyState = 0;
40                         sendFlag = 0;
41                         
42                         // Remove responseX fields
43                         for ( var name in xhr ) {
44                                 if ( /^response/.test(name) ) {
45                                         delete xhr[name];
46                                 }
47                         }
48                 }
49         }
50         
51         function init() {
52                 
53                 var // Options extraction
54                 
55                         // Remove hash character (#7531: first for string promotion)
56                         url = s.url = ( "" + s.url ).replace( rhash , "" ),
57                         
58                         // Uppercase the type
59                         type = s.type = s.type.toUpperCase(),
60                         
61                         // Determine if request has content
62                         hasContent = s.hasContent = ! rnoContent.test( type ),
63                         
64                         // Extract dataTypes list
65                         dataType = s.dataType,
66                         dataTypes = s.dataTypes = dataType ? jQuery.trim(dataType).toLowerCase().split(/\s+/) : ["*"],
67                         
68                         // Determine if a cross-domain request is in order
69                         parts = rurl.exec( url.toLowerCase() ),
70                         loc = location,
71                         crossDomain = s.crossDomain = !!( parts && ( parts[1] && parts[1] != loc.protocol || parts[2] != loc.host ) ),
72                         
73                         // Get other options locally
74                         data = s.data,
75                         originalContentType = s.contentType,
76                         prefilters = s.prefilters,
77                         accepts = s.accepts,
78                         headers = s.headers,
79                         
80                         // Other Variables
81                         transportDataType,
82                         i;
83
84                 // Convert data if not already a string
85                 if ( data && s.processData && typeof data != "string" ) {
86                         data = s.data = jQuery.param( data , s.traditional );
87                 }
88                 
89                 // Apply option prefilters
90                 for (i in prefilters) {
91                         prefilters[i](s);
92                 }
93                 
94                 // Get internal
95                 internal = selectTransport( s );
96                 
97                 // Re-actualize url & data
98                 url = s.url;
99                 data = s.data;
100                 
101                 // If internal was found
102                 if ( internal ) {
103                         
104                         // Get transportDataType
105                         transportDataType = dataTypes[0];
106                         
107                         // More options handling for requests with no content
108                         if ( ! hasContent ) {
109                                 
110                                 // If data is available, append data to url
111                                 if ( data ) {
112                                         url += (rquery.test(url) ? "&" : "?") + data;
113                                 }
114                                                                 
115                                 // Add anti-cache in url if needed
116                                 if ( s.cache === false ) {
117                                         
118                                         var ts = jQuery.now(),
119                                                 // try replacing _= if it is there
120                                                 ret = url.replace(rts, "$1_=" + ts );
121                                                 
122                                         // if nothing was replaced, add timestamp to the end
123                                         url = ret + ((ret == url) ? (rquery.test(url) ? "&" : "?") + "_=" + ts : "");
124                                 }
125                                 
126                                 s.url = url;
127                         }
128                         
129                         // Set the correct header, if data is being sent
130                         if ( ( data && hasContent ) || originalContentType ) {
131                                 requestHeaders["content-type"] = s.contentType;
132                         }
133                 
134                         // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
135                         if ( s.ifModified ) {
136                                 if ( jQuery_lastModified[url] ) { 
137                                         requestHeaders["if-modified-since"] = jQuery_lastModified[url];
138                                 }
139                                 if ( jQuery_etag[url] ) {
140                                         requestHeaders["if-none-match"] = jQuery_etag[url];
141                                 }
142                         }
143                 
144                         // Set the Accepts header for the server, depending on the dataType
145                         requestHeaders.accept = transportDataType && accepts[ transportDataType ] ?
146                                 accepts[ transportDataType ] + ( transportDataType !== "*" ? ", */*; q=0.01" : "" ) :
147                                 accepts[ "*" ];
148                                 
149                         // Check for headers option
150                         if ( headers ) {
151                                 xhr.setRequestHeaders( headers );
152                         }                       
153                 }
154                         
155                 callbackContext = s.context || s;
156                 globalEventContext = s.context ? jQuery(s.context) : jQuery.event;
157                 
158                 for ( i in callbacksLists ) {
159                         callbacksLists[i].bind(s[i]);
160                 }
161                 
162                 // Watch for a new set of requests
163                 if ( s.global && jQuery.active++ === 0 ) {
164                         jQuery.event.trigger( "ajaxStart" );
165                 }
166                 
167                 done = whenDone;
168         }
169         
170         function whenDone(status, statusText, response, headers) {
171                 
172                 // Called once
173                 done = undefined;
174                 
175                 // Reset sendFlag
176                 sendFlag = 0;
177                 
178                 // Cache response headers
179                 responseHeadersString = headers || "";
180
181                 // Clear timeout if it exists
182                 if ( timeoutTimer ) {
183                         clearTimeout(timeoutTimer);
184                 }
185                 
186                 var // Reference url
187                         url = s.url,
188                         // and ifModified status
189                         ifModified = s.ifModified,
190                         
191                         // Is it a success?
192                         isSuccess = 0,
193                         // Stored success
194                         success,
195                         // Stored error
196                         error = statusText;
197
198                 // If not timeout, force a jQuery-compliant status text
199                 if ( statusText != "timeout" ) {
200                         statusText = ( status >= 200 && status < 300 ) ? 
201                                 "success" :
202                                 ( status === 304 ? "notmodified" : "error" );
203                 }
204                 
205                 // If successful, handle type chaining
206                 if ( statusText === "success" || statusText === "notmodified" ) {
207                         
208                         // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
209                         if ( ifModified ) {
210                                 var lastModified = xhr.getResponseHeader("Last-Modified"),
211                                         etag = xhr.getResponseHeader("Etag");
212                                         
213                                 if (lastModified) {
214                                         jQuery_lastModified[url] = lastModified;
215                                 }
216                                 if (etag) {
217                                         jQuery_etag[url] = etag;
218                                 }
219                         }
220                         
221                         if ( ifModified && statusText === "notmodified" ) {
222                                 
223                                 success = null;
224                                 isSuccess = 1;
225                                 
226                         } else {
227                                 // Chain data conversions and determine the final value
228                                 // (if an exception is thrown in the process, it'll be notified as an error)
229                                 try {
230                                         
231                                         function checkData(data) {
232                                                 if ( data !== undefined ) {
233                                                         var testFunction = s.dataCheckers[srcDataType];
234                                                         if ( isFunction( testFunction ) ) {
235                                                                 testFunction(data);
236                                                         }
237                                                 }
238                                         }
239                                         
240                                         function convertData (data) {
241                                                 var conversionFunction = dataConverters[srcDataType+" => "+destDataType] ||
242                                                                 dataConverters["* => "+destDataType],
243                                                         noFunction = ! isFunction( conversionFunction );
244                                                 if ( noFunction ) {
245                                                         if ( srcDataType != "text" && destDataType != "text" ) {
246                                                                 // We try to put text inbetween
247                                                                 var first = dataConverters[srcDataType+" => text"] ||
248                                                                                 dataConverters["* => text"],
249                                                                         second = dataConverters["text => "+destDataType] ||
250                                                                                 dataConverters["* => "+destDataType],
251                                                                         areFunctions = isFunction( first ) && isFunction( second );
252                                                                 if ( areFunctions ) {
253                                                                         conversionFunction = function (data) {
254                                                                                 return second( first ( data ) );
255                                                                         };
256                                                                 }
257                                                                 noFunction = ! areFunctions;
258                                                         }
259                                                         if ( noFunction ) {
260                                                                 jQuery.error( "no data converter between " + srcDataType + " and " + destDataType );
261                                                         }
262                                                         
263                                                 }
264                                                 return conversionFunction(data);
265                                         }
266                                         
267                                         var dataTypes = s.dataTypes,
268                                                 i,
269                                                 length,
270                                                 data = response,
271                                                 dataConverters = s.dataConverters,
272                                                 srcDataType,
273                                                 destDataType,
274                                                 responseTypes = s.xhrResponseFields;
275                                                 
276                                         for ( i = 0, length = dataTypes.length ; i < length ; i++ ) {
277         
278                                                 destDataType = dataTypes[i];
279                                                 
280                                                 if ( !srcDataType ) { // First time
281                                                         
282                                                         // Copy type
283                                                         srcDataType = destDataType;
284                                                         // Check
285                                                         checkData(data);
286                                                         // Apply dataFilter
287                                                         if ( isFunction( s.dataFilter ) ) {
288                                                                 data = s.dataFilter(data, s.dataType);
289                                                                 // Recheck data
290                                                                 checkData(data);
291                                                         }
292                                                         
293                                                 } else { // Subsequent times
294                                                         
295                                                         // handle auto
296                                                         // JULIAN: for reasons unknown to me === doesn't work here
297                                                         if (destDataType == "*") {
298         
299                                                                 destDataType = srcDataType;
300                                                                 
301                                                         } else if ( srcDataType != destDataType ) {
302                                                                 
303                                                                 // Convert
304                                                                 data = convertData(data);
305                                                                 // Copy type & check
306                                                                 srcDataType = destDataType;
307                                                                 checkData(data);
308                                                                 
309                                                         }
310                                                         
311                                                 }
312         
313                                                 // Copy response into the xhr if it hasn't been already
314                                                 var responseDataType,
315                                                         responseType = responseTypes[srcDataType];
316                                                 
317                                                 if ( responseType ) {
318                                                         
319                                                         responseDataType = srcDataType;
320                                                         
321                                                 } else {
322                                                         
323                                                         responseType = responseTypes[ responseDataType = "text" ];
324                                                         
325                                                 }
326                                                         
327                                                 if ( responseType !== 1 ) {
328                                                         xhr[ "response" + responseType ] = data;
329                                                         responseTypes[ responseType ] = 1;
330                                                 }
331                                                 
332                                         }
333         
334                                         // We have a real success
335                                         success = data;
336                                         isSuccess = 1;
337                                         
338                                 } catch(e) {
339                                         
340                                         statusText = "parsererror";
341                                         error = "" + e;
342                                         
343                                 }
344                         }
345                         
346                 } else { // if not success, mark it as an error
347                         
348                                 error = error || statusText;
349                                 
350                 }
351                         
352                 // Set data for the fake xhr object
353                 xhr.status = status;
354                 xhr.statusText = statusText;
355                 
356                 // Keep local copies of vars in case callbacks re-use the xhr
357                 var _s = s,
358                         _callbacksLists = callbacksLists,
359                         _callbackContext = callbackContext,
360                         _globalEventContext = globalEventContext;
361                         
362                 // Set state if the xhr hasn't been re-used
363                 function _setState( value ) {
364                         if ( xhr.readyState && s === _s ) {
365                                 setState( value );
366                         }
367                 }
368                                 
369                 // Really completed?
370                 if ( status && s.async ) {
371                         setState( 2 );
372                         _setState( 3 );
373                 }
374                 
375                 // We're done
376                 _setState( 4 );
377                 
378                 // Success
379                 _callbacksLists.success.fire( isSuccess , _callbackContext , success, statusText, xhr);
380                 if ( isSuccess && _s.global ) {
381                         _globalEventContext.trigger( "ajaxSuccess", [xhr, _s, success] );
382                 }
383                 // Error
384                 _callbacksLists.error.fire( ! isSuccess , _callbackContext , xhr, statusText, error);
385                 if ( !isSuccess && _s.global ) {
386                         _globalEventContext.trigger( "ajaxError", [xhr, _s, error] );   
387                 }
388                 // Complete
389                 _callbacksLists.complete.fire( 1 , _callbackContext, xhr, statusText);
390                 if ( _s.global ) {
391                         _globalEventContext.trigger( "ajaxComplete", [xhr, _s] );
392                         // Handle the global AJAX counter
393                         if ( ! --jQuery.active ) {
394                                 jQuery.event.trigger( "ajaxStop" );
395                         }
396                 }
397         }
398         
399         // Ready state control
400         function checkState( expected , test ) {
401                 if ( expected !== true && ( expected === false || test === false || xhr.readyState !== expected ) ) {
402                         jQuery.error("INVALID_STATE_ERR");
403                 }
404         }
405         
406         // Ready state change
407         function setState( value ) {
408                 xhr.readyState = value;
409                 if ( isFunction( xhr.onreadystatechange ) ) {
410                         xhr.onreadystatechange();
411                 }
412         }
413         
414         var // jQuery lists
415                 jQuery_lastModified = jQuery.lastModified,
416                 jQuery_etag = jQuery.etag,
417                 // Options object
418                 s,
419                 // Callback stuff
420                 callbackContext,
421                 globalEventContext,
422                 callbacksLists,
423                 // Headers (they are sent all at once)
424                 requestHeaders,
425                 // Response headers
426                 responseHeadersString,
427                 responseHeaders,
428                 // Done callback
429                 done,
430                 // transport
431                 internal,
432                 // timeout handle
433                 timeoutTimer,
434                 // The send flag
435                 sendFlag,
436                 // Fake xhr
437                 xhr = {
438                         // state
439                         readyState: 0,
440                         
441                         // Callback
442                         onreadystatechange: null,
443                         
444                         // Open
445                         open: function(type, url, async, username, password) {
446                                 
447                                 xhr.abort();
448                                 reset();
449                                 
450                                 s = {
451                                         type: type,
452                                         url: url,
453                                         async: async,
454                                         username: username,
455                                         password: password
456                                 };
457                                 
458                                 setState(1);
459                                 
460                                 return xhr;
461                         },
462                         
463                         // Send
464                         send: function(data, moreOptions) {
465                                 
466                                 checkState(1 , !sendFlag);
467                                 
468                                 s.data = data;
469                                 
470                                 s = jQuery.extend( true,
471                                         {},
472                                         jQuery.ajaxSettings,
473                                         s,
474                                         moreOptions || ( moreOptions === false ? { global: false } : {} ) );
475                                         
476                                 if ( moreOptions ) {
477                                         // We force the original context
478                                         // (plain objects used as context get extended)
479                                         s.context = moreOptions.context;
480                                 }
481                                 
482                                 init();
483                                 
484                                 // If not internal, abort
485                                 if ( ! internal ) {
486                                         done( 0 , "transport not found" );
487                                         return false;
488                                 }
489                                 
490                                 // Allow custom headers/mimetypes and early abort
491                                 if ( s.beforeSend ) {
492                                         
493                                         var _s = s;
494                                         
495                                         if ( s.beforeSend.call(callbackContext, xhr, s) === false || ! xhr.readyState || _s !== s ) {
496                                                 
497                                                 // Abort if not done
498                                                 if ( xhr.readyState && _s === s ) {
499                                                         xhr.abort();
500                                                 }
501         
502                                                 // Handle the global AJAX counter
503                                                 if ( _s.global && ! --jQuery.active ) {
504                                                         jQuery.event.trigger( "ajaxStop" );
505                                                 }
506                                                 
507                                                 return false;
508                                         }
509                                 }
510                                 
511                                 sendFlag = 1;
512                                 
513                                 // Send global event
514                                 if ( s.global ) {
515                                         globalEventContext.trigger("ajaxSend", [xhr, s]);
516                                 }
517                                 
518                                 // Timeout
519                                 if ( s.async && s.timeout > 0 ) {
520                                         timeoutTimer = setTimeout(function(){
521                                                 xhr.abort("timeout");
522                                         }, s.timeout);
523                                 }
524                                 
525                                 if ( s.async ) {
526                                         setState(1);
527                                 }
528                                 
529                                 try {
530                                         
531                                         internal.send(requestHeaders, done);
532                                         return xhr;
533                                                                                         
534                                 } catch (e) {
535                                         
536                                         if ( done ) {
537                                                 
538                                                 done(0, "error", "" + e);
539                                                 
540                                         } else {
541                                                 
542                                                 jQuery.error(e);
543                                                 
544                                         }
545                                 }
546                                 
547                                 return false;
548                         },
549                         
550                         // Caches the header
551                         setRequestHeader: function(name,value) {
552                                 checkState(1, !sendFlag);
553                                 requestHeaders[ name.toLowerCase() ] = value;
554                                 return xhr;
555                         },
556                         
557                         // Ditto with an s
558                         setRequestHeaders: function(map) {
559                                 checkState(1, !sendFlag);
560                                 for ( var name in map ) {
561                                         requestHeaders[ name.toLowerCase() ] = map[name];
562                                 }
563                                 return xhr;
564                         },
565                         
566                         // Utility method to get headers set
567                         getRequestHeader: function(name) {
568                                 checkState(1, !sendFlag);
569                                 return requestHeaders[ name.toLowerCase() ];
570                         },
571                         
572                         // Raw string
573                         getAllResponseHeaders: function() {
574                                 return xhr.readyState <= 1 ? "" : responseHeadersString;
575                         },
576                         
577                         // Builds headers hashtable if needed
578                         getResponseHeader: function( key ) {
579                                 
580                                 if ( xhr.readyState <= 1 ) {
581                                         
582                                         return null;
583                                         
584                                 }
585                                 
586                                 if ( responseHeaders === undefined ) {
587                                         
588                                         responseHeaders = {};
589                                         
590                                         if ( typeof responseHeadersString === "string" ) {
591                                                 
592                                                 var match;
593                                                 
594                                                 while( ( match = rheaders.exec( responseHeadersString ) ) ) {
595                                                         responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
596                                                 }
597                                         }
598                                 }
599                                 return responseHeaders[ key.toLowerCase() ];
600                         },
601                         
602                         // Cancel the request
603                         abort: function(statusText) {
604                                 if (internal) {
605                                         internal.abort( statusText || "abort" );
606                                 }
607                                 xhr.readyState = 0;
608                         }
609                 };
610                 
611         // Init data (so that we can bind callbacks early
612         reset(1);
613
614         // Install callbacks related methods
615         jQuery.each(["bind","unbind"], function(_, name) {
616                 xhr[name] = function(type) {
617                         
618                         var functors = slice.call(arguments,1),
619                                 list;
620                                 
621                         jQuery.each(type.split(/\s+/g), function() {
622                                 list = callbacksLists[this];
623                                 if ( list ) {
624                                         list[name].apply(list, functors );
625                                 }
626                         });
627                         
628                         return this;
629                 };
630         });
631
632         jQuery.each(callbacksLists, function(name) {
633                 var list;
634                 xhr[name] = function() {
635                         list = callbacksLists[name];
636                         if ( list ) {
637                                 list.bind.apply(list, arguments );
638                         }
639                         return this;
640                 };
641         });
642         
643         // Return the xhr emulation
644         return xhr;
645 };
646
647 // Create a callback list
648 function createCBList() {
649         
650         var functors = [],
651                 autoFire = 0,
652                 fireArgs,
653                 list = {
654                 
655                         fire: function( flag , context ) {
656                                 
657                                 // Save info for later bindings
658                                 fireArgs = arguments;
659                                 
660                                 // Remove autoFire to keep bindings in order
661                                 autoFire = 0;
662                                         
663                                 var args = slice.call( fireArgs , 2 );
664                                         
665                                 // Execute callbacks
666                                 while ( flag && functors.length ) {
667                                         flag = functors.shift().apply( context , args ) !== false;
668                                 }
669                                         
670                                 // Clean if asked to stop
671                                 if ( ! flag ) {
672                                         clean();
673                                 }
674                                                 
675                                 // Set autoFire
676                                 autoFire = 1;                                   
677                         },
678                         
679                         bind: function() {
680                                 
681                                 var args = arguments,
682                                         i = 0,
683                                         length = args.length,
684                                         func;
685                                 
686                                 for ( ; i < length ; i++ ) {
687                                         
688                                         func = args[ i ];
689                                         
690                                         if ( jQuery.isArray(func) ) {
691                                                 
692                                                 list.bind.apply( list , func );
693                                                 
694                                         } else if ( isFunction(func) ) {
695                                                 
696                                                 // Add if not already in
697                                                 if ( ! pos( func ) ) {
698                                                         functors.push( func );
699                                                 }
700                                         }
701                                 }
702                                 
703                                 if ( autoFire ) {
704                                         list.fire.apply( list , fireArgs );
705                                 }
706                         },
707                         
708                         unbind: function() {
709                                 
710                                 var i = 0,
711                                         args = arguments,
712                                         length = args.length,
713                                         func,                                   
714                                         position;
715                                         
716                                 if ( length ) {
717                                                 
718                                         for( ; i < length ; i++ ) {
719                                                 func = args[i];
720                                                 if ( jQuery.isArray(func) ) {
721                                                         list.unbind.apply(list,func);
722                                                 } else if ( isFunction(func) ) {
723                                                         position = pos(func);
724                                                         if ( position ) {
725                                                                 functors.splice(position-1,1);
726                                                         }
727                                                 }
728                                         }
729                                 
730                                 } else {
731                                         
732                                         functors = [];
733                                 
734                                 }
735
736                         }
737                         
738                 };
739
740         // Get the index of the functor in the list (1-based)
741         function pos( func ) {
742                 for (var i = 0, length = functors.length; i < length && functors[i] !== func; i++) {
743                 }
744                 return i < length ? ( i + 1 ) : 0;
745         }
746                 
747         // Clean the object
748         function clean() {
749                 // Empty callbacks list
750                 functors = [];
751                 // Inhibit methods
752                 for (var i in list) {
753                         list[i] = jQuery.noop;
754                 }
755         }
756                                 
757         return list;
758 }
759
760 jQuery.extend(jQuery.xhr, {
761         
762         // Add new prefilter
763         prefilter: function (functor) {
764                 if ( isFunction(functor) ) {
765                         jQuery.ajaxSettings.prefilters.push( functor );
766                 }
767                 return this;
768         },
769         
770         // Bind a transport to one or more dataTypes
771         bindTransport: function () {
772                 
773                 var args = arguments,
774                         i,
775                         start = 0,
776                         length = args.length,
777                         dataTypes = [ "*" ],
778                         functors = [],
779                         functor,
780                         first,
781                         append,
782                         list,
783                         transports = jQuery.ajaxSettings.transports;
784                         
785                 if ( length ) {
786                                 
787                         if ( ! isFunction( args[ 0 ] ) ) {
788                                 
789                                 dataTypes = args[ 0 ].toLowerCase().split(/\s+/);
790                                 start = 1;
791                                 
792                         }
793                         
794                         if ( dataTypes.length && start < length ) {
795                                 
796                                 for ( i = start; i < length; i++ ) {
797                                         functor = args[i];
798                                         if ( isFunction(functor) ) {
799                                                 functors.push( functor );
800                                         }
801                                 }
802                                                 
803                                 if ( functors.length ) {
804                                                         
805                                         jQuery.each ( dataTypes, function( _ , dataType ) {
806                                                 
807                                                 first = /^\+/.test( dataType );
808                                                 
809                                                 if (first) {
810                                                         dataType = dataType.substr(1);
811                                                 }
812                                                 
813                                                 if ( dataType !== "" ) {
814                                                 
815                                                         append = Array.prototype[ first ? "unshift" : "push" ];
816                                                         
817                                                         list = transports[ dataType ];
818                                         
819                                                         jQuery.each ( functors, function( _ , functor ) {
820                                                                         
821                                                                 if ( ! list ) {
822                                                                         
823                                                                         list = transports[ dataType ] = [ functor ];
824                                                                         
825                                                                 } else {
826                                                                         
827                                                                         append.call( list , functor );
828                                                                 }
829                                                         } );
830                                                 }
831                                                                         
832                                         } );
833                                 }
834                         }
835                 }
836                 
837                 return this;
838         }
839
840         
841 });
842
843 // Select a transport given options
844 function selectTransport( s ) {
845
846         var dataTypes = s.dataTypes,
847                 transportDataType,
848                 transportsList,
849                 transport,
850                 i,
851                 length,
852                 checked = {},
853                 flag;
854                 
855         function initSearch( dataType ) {
856
857                 flag = transportDataType !== dataType && ! checked[ dataType ];
858                 
859                 if ( flag ) {
860                         
861                         checked[ dataType ] = 1;
862                         transportDataType = dataType;
863                         transportsList = s.transports[ dataType ];
864                         i = -1;
865                         length = transportsList ? transportsList.length : 0 ;
866                 }
867
868                 return flag;
869         }
870         
871         initSearch( dataTypes[ 0 ] );
872
873         for ( i = 0 ; ! transport && i <= length ; i++ ) {
874                 
875                 if ( i === length ) {
876                         
877                         initSearch( "*" );
878                         
879                 } else {
880
881                         transport = transportsList[ i ]( s , determineDataType );
882
883                         // If we got redirected to another dataType
884                         // Search there (if not in progress or already tried)
885                         if ( typeof( transport ) === "string" &&
886                                 initSearch( transport ) ) {
887
888                                 dataTypes.unshift( transport );
889                                 transport = 0;
890                         }
891                 }
892         }
893
894         return transport;
895 }
896         
897 // Utility function that handles dataType when response is received
898 // (for those transports that can give text or xml responses)
899 function determineDataType( s , ct , text , xml ) {
900         
901         var autoDataType = s.autoDataType,
902                 type,
903                 regexp,
904                 dataTypes = s.dataTypes,
905                 transportDataType = dataTypes[0],
906                 response;
907         
908         // Auto (xml, json, script or text determined given headers)
909         if ( transportDataType === "*" ) {
910
911                 for ( type in autoDataType ) {
912                         if ( ( regexp = autoDataType[ type ] ) && regexp.test( ct ) ) {
913                                 transportDataType = dataTypes[0] = type;
914                                 break;
915                         }
916                 }                       
917         } 
918         
919         // xml and parsed as such
920         if ( transportDataType === "xml" &&
921                 xml &&
922                 xml.documentElement /* #4958 */ ) {
923                 
924                 response = xml;
925         
926         // Text response was provided
927         } else {
928                 
929                 response = text;
930                 
931                 // If it's not really text, defer to dataConverters
932                 if ( transportDataType !== "text" ) {
933                         dataTypes.unshift( "text" );
934                 }
935                 
936         }
937         
938         return response;
939 }       
940
941 })(jQuery);