xhr.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. define("dojo/_base/xhr", [
  2. "./kernel", "./sniff", "require", "../io-query", "../dom", "../dom-form", "./Deferred", "./json", "./lang", "./array", "../on"
  3. ], function(dojo, has, require, ioq, dom, domForm, deferred, json, lang, array, on){
  4. // module:
  5. // dojo/_base.xhr
  6. // summary:
  7. // This modules defines the dojo.xhr* API.
  8. has.add("native-xhr", function() {
  9. // if true, the environment has a native XHR implementation
  10. return typeof XMLHttpRequest !== 'undefined';
  11. });
  12. if(1 && require.getXhr){
  13. dojo._xhrObj = require.getXhr;
  14. }else if (has("native-xhr")){
  15. dojo._xhrObj = function(){
  16. // summary:
  17. // does the work of portably generating a new XMLHTTPRequest object.
  18. try{
  19. return new XMLHttpRequest();
  20. }catch(e){
  21. throw new Error("XMLHTTP not available: "+e);
  22. }
  23. };
  24. }else{
  25. // PROGIDs are in order of decreasing likelihood; this will change in time.
  26. for(var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], progid, i = 0; i < 3;){
  27. try{
  28. progid = XMLHTTP_PROGIDS[i++];
  29. if (new ActiveXObject(progid)) {
  30. // this progid works; therefore, use it from now on
  31. break;
  32. }
  33. }catch(e){
  34. // squelch; we're just trying to find a good ActiveX PROGID
  35. // if they all fail, then progid ends up as the last attempt and that will signal the error
  36. // the first time the client actually tries to exec an xhr
  37. }
  38. }
  39. dojo._xhrObj= function() {
  40. return new ActiveXObject(progid);
  41. };
  42. }
  43. var cfg = dojo.config;
  44. // mix in io-query and dom-form
  45. dojo.objectToQuery = ioq.objectToQuery;
  46. dojo.queryToObject = ioq.queryToObject;
  47. dojo.fieldToObject = domForm.fieldToObject;
  48. dojo.formToObject = domForm.toObject;
  49. dojo.formToQuery = domForm.toQuery;
  50. dojo.formToJson = domForm.toJson;
  51. // need to block async callbacks from snatching this thread as the result
  52. // of an async callback might call another sync XHR, this hangs khtml forever
  53. // must checked by watchInFlight()
  54. dojo._blockAsync = false;
  55. // MOW: remove dojo._contentHandlers alias in 2.0
  56. var handlers = dojo._contentHandlers = dojo.contentHandlers = {
  57. // summary:
  58. // A map of availble XHR transport handle types. Name matches the
  59. // `handleAs` attribute passed to XHR calls.
  60. //
  61. // description:
  62. // A map of availble XHR transport handle types. Name matches the
  63. // `handleAs` attribute passed to XHR calls. Each contentHandler is
  64. // called, passing the xhr object for manipulation. The return value
  65. // from the contentHandler will be passed to the `load` or `handle`
  66. // functions defined in the original xhr call.
  67. //
  68. // example:
  69. // Creating a custom content-handler:
  70. // | dojo.contentHandlers.makeCaps = function(xhr){
  71. // | return xhr.responseText.toUpperCase();
  72. // | }
  73. // | // and later:
  74. // | dojo.xhrGet({
  75. // | url:"foo.txt",
  76. // | handleAs:"makeCaps",
  77. // | load: function(data){ /* data is a toUpper version of foo.txt */ }
  78. // | });
  79. "text": function(xhr){
  80. // summary: A contentHandler which simply returns the plaintext response data
  81. return xhr.responseText;
  82. },
  83. "json": function(xhr){
  84. // summary: A contentHandler which returns a JavaScript object created from the response data
  85. return json.fromJson(xhr.responseText || null);
  86. },
  87. "json-comment-filtered": function(xhr){
  88. // summary: A contentHandler which expects comment-filtered JSON.
  89. // description:
  90. // A contentHandler which expects comment-filtered JSON.
  91. // the json-comment-filtered option was implemented to prevent
  92. // "JavaScript Hijacking", but it is less secure than standard JSON. Use
  93. // standard JSON instead. JSON prefixing can be used to subvert hijacking.
  94. //
  95. // Will throw a notice suggesting to use application/json mimetype, as
  96. // json-commenting can introduce security issues. To decrease the chances of hijacking,
  97. // use the standard `json` contentHandler, and prefix your "JSON" with: {}&&
  98. //
  99. // use djConfig.useCommentedJson = true to turn off the notice
  100. if(!dojo.config.useCommentedJson){
  101. console.warn("Consider using the standard mimetype:application/json."
  102. + " json-commenting can introduce security issues. To"
  103. + " decrease the chances of hijacking, use the standard the 'json' handler and"
  104. + " prefix your json with: {}&&\n"
  105. + "Use djConfig.useCommentedJson=true to turn off this message.");
  106. }
  107. var value = xhr.responseText;
  108. var cStartIdx = value.indexOf("\/*");
  109. var cEndIdx = value.lastIndexOf("*\/");
  110. if(cStartIdx == -1 || cEndIdx == -1){
  111. throw new Error("JSON was not comment filtered");
  112. }
  113. return json.fromJson(value.substring(cStartIdx+2, cEndIdx));
  114. },
  115. "javascript": function(xhr){
  116. // summary: A contentHandler which evaluates the response data, expecting it to be valid JavaScript
  117. // FIXME: try Moz and IE specific eval variants?
  118. return dojo.eval(xhr.responseText);
  119. },
  120. "xml": function(xhr){
  121. // summary: A contentHandler returning an XML Document parsed from the response data
  122. var result = xhr.responseXML;
  123. if(has("ie")){
  124. if((!result || !result.documentElement)){
  125. //WARNING: this branch used by the xml handling in dojo.io.iframe,
  126. //so be sure to test dojo.io.iframe if making changes below.
  127. var ms = function(n){ return "MSXML" + n + ".DOMDocument"; };
  128. var dp = ["Microsoft.XMLDOM", ms(6), ms(4), ms(3), ms(2)];
  129. array.some(dp, function(p){
  130. try{
  131. var dom = new ActiveXObject(p);
  132. dom.async = false;
  133. dom.loadXML(xhr.responseText);
  134. result = dom;
  135. }catch(e){ return false; }
  136. return true;
  137. });
  138. }
  139. }
  140. return result; // DOMDocument
  141. },
  142. "json-comment-optional": function(xhr){
  143. // summary: A contentHandler which checks the presence of comment-filtered JSON and
  144. // alternates between the `json` and `json-comment-filtered` contentHandlers.
  145. if(xhr.responseText && /^[^{\[]*\/\*/.test(xhr.responseText)){
  146. return handlers["json-comment-filtered"](xhr);
  147. }else{
  148. return handlers["json"](xhr);
  149. }
  150. }
  151. };
  152. /*=====
  153. dojo.__IoArgs = function(){
  154. // url: String
  155. // URL to server endpoint.
  156. // content: Object?
  157. // Contains properties with string values. These
  158. // properties will be serialized as name1=value2 and
  159. // passed in the request.
  160. // timeout: Integer?
  161. // Milliseconds to wait for the response. If this time
  162. // passes, the then error callbacks are called.
  163. // form: DOMNode?
  164. // DOM node for a form. Used to extract the form values
  165. // and send to the server.
  166. // preventCache: Boolean?
  167. // Default is false. If true, then a
  168. // "dojo.preventCache" parameter is sent in the request
  169. // with a value that changes with each request
  170. // (timestamp). Useful only with GET-type requests.
  171. // handleAs: String?
  172. // Acceptable values depend on the type of IO
  173. // transport (see specific IO calls for more information).
  174. // rawBody: String?
  175. // Sets the raw body for an HTTP request. If this is used, then the content
  176. // property is ignored. This is mostly useful for HTTP methods that have
  177. // a body to their requests, like PUT or POST. This property can be used instead
  178. // of postData and putData for dojo.rawXhrPost and dojo.rawXhrPut respectively.
  179. // ioPublish: Boolean?
  180. // Set this explicitly to false to prevent publishing of topics related to
  181. // IO operations. Otherwise, if djConfig.ioPublish is set to true, topics
  182. // will be published via dojo.publish for different phases of an IO operation.
  183. // See dojo.__IoPublish for a list of topics that are published.
  184. // load: Function?
  185. // This function will be
  186. // called on a successful HTTP response code.
  187. // error: Function?
  188. // This function will
  189. // be called when the request fails due to a network or server error, the url
  190. // is invalid, etc. It will also be called if the load or handle callback throws an
  191. // exception, unless djConfig.debugAtAllCosts is true. This allows deployed applications
  192. // to continue to run even when a logic error happens in the callback, while making
  193. // it easier to troubleshoot while in debug mode.
  194. // handle: Function?
  195. // This function will
  196. // be called at the end of every request, whether or not an error occurs.
  197. this.url = url;
  198. this.content = content;
  199. this.timeout = timeout;
  200. this.form = form;
  201. this.preventCache = preventCache;
  202. this.handleAs = handleAs;
  203. this.ioPublish = ioPublish;
  204. this.load = function(response, ioArgs){
  205. // ioArgs: dojo.__IoCallbackArgs
  206. // Provides additional information about the request.
  207. // response: Object
  208. // The response in the format as defined with handleAs.
  209. }
  210. this.error = function(response, ioArgs){
  211. // ioArgs: dojo.__IoCallbackArgs
  212. // Provides additional information about the request.
  213. // response: Object
  214. // The response in the format as defined with handleAs.
  215. }
  216. this.handle = function(loadOrError, response, ioArgs){
  217. // loadOrError: String
  218. // Provides a string that tells you whether this function
  219. // was called because of success (load) or failure (error).
  220. // response: Object
  221. // The response in the format as defined with handleAs.
  222. // ioArgs: dojo.__IoCallbackArgs
  223. // Provides additional information about the request.
  224. }
  225. }
  226. =====*/
  227. /*=====
  228. dojo.__IoCallbackArgs = function(args, xhr, url, query, handleAs, id, canDelete, json){
  229. // args: Object
  230. // the original object argument to the IO call.
  231. // xhr: XMLHttpRequest
  232. // For XMLHttpRequest calls only, the
  233. // XMLHttpRequest object that was used for the
  234. // request.
  235. // url: String
  236. // The final URL used for the call. Many times it
  237. // will be different than the original args.url
  238. // value.
  239. // query: String
  240. // For non-GET requests, the
  241. // name1=value1&name2=value2 parameters sent up in
  242. // the request.
  243. // handleAs: String
  244. // The final indicator on how the response will be
  245. // handled.
  246. // id: String
  247. // For dojo.io.script calls only, the internal
  248. // script ID used for the request.
  249. // canDelete: Boolean
  250. // For dojo.io.script calls only, indicates
  251. // whether the script tag that represents the
  252. // request can be deleted after callbacks have
  253. // been called. Used internally to know when
  254. // cleanup can happen on JSONP-type requests.
  255. // json: Object
  256. // For dojo.io.script calls only: holds the JSON
  257. // response for JSONP-type requests. Used
  258. // internally to hold on to the JSON responses.
  259. // You should not need to access it directly --
  260. // the same object should be passed to the success
  261. // callbacks directly.
  262. this.args = args;
  263. this.xhr = xhr;
  264. this.url = url;
  265. this.query = query;
  266. this.handleAs = handleAs;
  267. this.id = id;
  268. this.canDelete = canDelete;
  269. this.json = json;
  270. }
  271. =====*/
  272. /*=====
  273. dojo.__IoPublish = function(){
  274. // summary:
  275. // This is a list of IO topics that can be published
  276. // if djConfig.ioPublish is set to true. IO topics can be
  277. // published for any Input/Output, network operation. So,
  278. // dojo.xhr, dojo.io.script and dojo.io.iframe can all
  279. // trigger these topics to be published.
  280. // start: String
  281. // "/dojo/io/start" is sent when there are no outstanding IO
  282. // requests, and a new IO request is started. No arguments
  283. // are passed with this topic.
  284. // send: String
  285. // "/dojo/io/send" is sent whenever a new IO request is started.
  286. // It passes the dojo.Deferred for the request with the topic.
  287. // load: String
  288. // "/dojo/io/load" is sent whenever an IO request has loaded
  289. // successfully. It passes the response and the dojo.Deferred
  290. // for the request with the topic.
  291. // error: String
  292. // "/dojo/io/error" is sent whenever an IO request has errored.
  293. // It passes the error and the dojo.Deferred
  294. // for the request with the topic.
  295. // done: String
  296. // "/dojo/io/done" is sent whenever an IO request has completed,
  297. // either by loading or by erroring. It passes the error and
  298. // the dojo.Deferred for the request with the topic.
  299. // stop: String
  300. // "/dojo/io/stop" is sent when all outstanding IO requests have
  301. // finished. No arguments are passed with this topic.
  302. this.start = "/dojo/io/start";
  303. this.send = "/dojo/io/send";
  304. this.load = "/dojo/io/load";
  305. this.error = "/dojo/io/error";
  306. this.done = "/dojo/io/done";
  307. this.stop = "/dojo/io/stop";
  308. }
  309. =====*/
  310. dojo._ioSetArgs = function(/*dojo.__IoArgs*/args,
  311. /*Function*/canceller,
  312. /*Function*/okHandler,
  313. /*Function*/errHandler){
  314. // summary:
  315. // sets up the Deferred and ioArgs property on the Deferred so it
  316. // can be used in an io call.
  317. // args:
  318. // The args object passed into the public io call. Recognized properties on
  319. // the args object are:
  320. // canceller:
  321. // The canceller function used for the Deferred object. The function
  322. // will receive one argument, the Deferred object that is related to the
  323. // canceller.
  324. // okHandler:
  325. // The first OK callback to be registered with Deferred. It has the opportunity
  326. // to transform the OK response. It will receive one argument -- the Deferred
  327. // object returned from this function.
  328. // errHandler:
  329. // The first error callback to be registered with Deferred. It has the opportunity
  330. // to do cleanup on an error. It will receive two arguments: error (the
  331. // Error object) and dfd, the Deferred object returned from this function.
  332. var ioArgs = {args: args, url: args.url};
  333. //Get values from form if requestd.
  334. var formObject = null;
  335. if(args.form){
  336. var form = dom.byId(args.form);
  337. //IE requires going through getAttributeNode instead of just getAttribute in some form cases,
  338. //so use it for all. See #2844
  339. var actnNode = form.getAttributeNode("action");
  340. ioArgs.url = ioArgs.url || (actnNode ? actnNode.value : null);
  341. formObject = domForm.toObject(form);
  342. }
  343. // set up the query params
  344. var miArgs = [{}];
  345. if(formObject){
  346. // potentially over-ride url-provided params w/ form values
  347. miArgs.push(formObject);
  348. }
  349. if(args.content){
  350. // stuff in content over-rides what's set by form
  351. miArgs.push(args.content);
  352. }
  353. if(args.preventCache){
  354. miArgs.push({"dojo.preventCache": new Date().valueOf()});
  355. }
  356. ioArgs.query = ioq.objectToQuery(lang.mixin.apply(null, miArgs));
  357. // .. and the real work of getting the deferred in order, etc.
  358. ioArgs.handleAs = args.handleAs || "text";
  359. var d = new deferred(canceller);
  360. d.addCallbacks(okHandler, function(error){
  361. return errHandler(error, d);
  362. });
  363. //Support specifying load, error and handle callback functions from the args.
  364. //For those callbacks, the "this" object will be the args object.
  365. //The callbacks will get the deferred result value as the
  366. //first argument and the ioArgs object as the second argument.
  367. var ld = args.load;
  368. if(ld && lang.isFunction(ld)){
  369. d.addCallback(function(value){
  370. return ld.call(args, value, ioArgs);
  371. });
  372. }
  373. var err = args.error;
  374. if(err && lang.isFunction(err)){
  375. d.addErrback(function(value){
  376. return err.call(args, value, ioArgs);
  377. });
  378. }
  379. var handle = args.handle;
  380. if(handle && lang.isFunction(handle)){
  381. d.addBoth(function(value){
  382. return handle.call(args, value, ioArgs);
  383. });
  384. }
  385. //Plug in topic publishing, if dojo.publish is loaded.
  386. if(cfg.ioPublish && dojo.publish && ioArgs.args.ioPublish !== false){
  387. d.addCallbacks(
  388. function(res){
  389. dojo.publish("/dojo/io/load", [d, res]);
  390. return res;
  391. },
  392. function(res){
  393. dojo.publish("/dojo/io/error", [d, res]);
  394. return res;
  395. }
  396. );
  397. d.addBoth(function(res){
  398. dojo.publish("/dojo/io/done", [d, res]);
  399. return res;
  400. });
  401. }
  402. d.ioArgs = ioArgs;
  403. // FIXME: need to wire up the xhr object's abort method to something
  404. // analagous in the Deferred
  405. return d;
  406. };
  407. var _deferredCancel = function(/*Deferred*/dfd){
  408. // summary: canceller function for dojo._ioSetArgs call.
  409. dfd.canceled = true;
  410. var xhr = dfd.ioArgs.xhr;
  411. var _at = typeof xhr.abort;
  412. if(_at == "function" || _at == "object" || _at == "unknown"){
  413. xhr.abort();
  414. }
  415. var err = dfd.ioArgs.error;
  416. if(!err){
  417. err = new Error("xhr cancelled");
  418. err.dojoType="cancel";
  419. }
  420. return err;
  421. };
  422. var _deferredOk = function(/*Deferred*/dfd){
  423. // summary: okHandler function for dojo._ioSetArgs call.
  424. var ret = handlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr);
  425. return ret === undefined ? null : ret;
  426. };
  427. var _deferError = function(/*Error*/error, /*Deferred*/dfd){
  428. // summary: errHandler function for dojo._ioSetArgs call.
  429. if(!dfd.ioArgs.args.failOk){
  430. console.error(error);
  431. }
  432. return error;
  433. };
  434. // avoid setting a timer per request. It degrades performance on IE
  435. // something fierece if we don't use unified loops.
  436. var _inFlightIntvl = null;
  437. var _inFlight = [];
  438. //Use a separate count for knowing if we are starting/stopping io calls.
  439. //Cannot use _inFlight.length since it can change at a different time than
  440. //when we want to do this kind of test. We only want to decrement the count
  441. //after a callback/errback has finished, since the callback/errback should be
  442. //considered as part of finishing a request.
  443. var _pubCount = 0;
  444. var _checkPubCount = function(dfd){
  445. if(_pubCount <= 0){
  446. _pubCount = 0;
  447. if(cfg.ioPublish && dojo.publish && (!dfd || dfd && dfd.ioArgs.args.ioPublish !== false)){
  448. dojo.publish("/dojo/io/stop");
  449. }
  450. }
  451. };
  452. var _watchInFlight = function(){
  453. //summary:
  454. // internal method that checks each inflight XMLHttpRequest to see
  455. // if it has completed or if the timeout situation applies.
  456. var now = (new Date()).getTime();
  457. // make sure sync calls stay thread safe, if this callback is called
  458. // during a sync call and this results in another sync call before the
  459. // first sync call ends the browser hangs
  460. if(!dojo._blockAsync){
  461. // we need manual loop because we often modify _inFlight (and therefore 'i') while iterating
  462. // note: the second clause is an assigment on purpose, lint may complain
  463. for(var i = 0, tif; i < _inFlight.length && (tif = _inFlight[i]); i++){
  464. var dfd = tif.dfd;
  465. var func = function(){
  466. if(!dfd || dfd.canceled || !tif.validCheck(dfd)){
  467. _inFlight.splice(i--, 1);
  468. _pubCount -= 1;
  469. }else if(tif.ioCheck(dfd)){
  470. _inFlight.splice(i--, 1);
  471. tif.resHandle(dfd);
  472. _pubCount -= 1;
  473. }else if(dfd.startTime){
  474. //did we timeout?
  475. if(dfd.startTime + (dfd.ioArgs.args.timeout || 0) < now){
  476. _inFlight.splice(i--, 1);
  477. var err = new Error("timeout exceeded");
  478. err.dojoType = "timeout";
  479. dfd.errback(err);
  480. //Cancel the request so the io module can do appropriate cleanup.
  481. dfd.cancel();
  482. _pubCount -= 1;
  483. }
  484. }
  485. };
  486. if(dojo.config.debugAtAllCosts){
  487. func.call(this);
  488. }else{
  489. try{
  490. func.call(this);
  491. }catch(e){
  492. dfd.errback(e);
  493. }
  494. }
  495. }
  496. }
  497. _checkPubCount(dfd);
  498. if(!_inFlight.length){
  499. clearInterval(_inFlightIntvl);
  500. _inFlightIntvl = null;
  501. }
  502. };
  503. dojo._ioCancelAll = function(){
  504. //summary: Cancels all pending IO requests, regardless of IO type
  505. //(xhr, script, iframe).
  506. try{
  507. array.forEach(_inFlight, function(i){
  508. try{
  509. i.dfd.cancel();
  510. }catch(e){/*squelch*/}
  511. });
  512. }catch(e){/*squelch*/}
  513. };
  514. //Automatically call cancel all io calls on unload
  515. //in IE for trac issue #2357.
  516. if(has("ie")){
  517. on(window, "unload", dojo._ioCancelAll);
  518. }
  519. dojo._ioNotifyStart = function(/*Deferred*/dfd){
  520. // summary:
  521. // If dojo.publish is available, publish topics
  522. // about the start of a request queue and/or the
  523. // the beginning of request.
  524. // description:
  525. // Used by IO transports. An IO transport should
  526. // call this method before making the network connection.
  527. if(cfg.ioPublish && dojo.publish && dfd.ioArgs.args.ioPublish !== false){
  528. if(!_pubCount){
  529. dojo.publish("/dojo/io/start");
  530. }
  531. _pubCount += 1;
  532. dojo.publish("/dojo/io/send", [dfd]);
  533. }
  534. };
  535. dojo._ioWatch = function(dfd, validCheck, ioCheck, resHandle){
  536. // summary:
  537. // Watches the io request represented by dfd to see if it completes.
  538. // dfd: Deferred
  539. // The Deferred object to watch.
  540. // validCheck: Function
  541. // Function used to check if the IO request is still valid. Gets the dfd
  542. // object as its only argument.
  543. // ioCheck: Function
  544. // Function used to check if basic IO call worked. Gets the dfd
  545. // object as its only argument.
  546. // resHandle: Function
  547. // Function used to process response. Gets the dfd
  548. // object as its only argument.
  549. var args = dfd.ioArgs.args;
  550. if(args.timeout){
  551. dfd.startTime = (new Date()).getTime();
  552. }
  553. _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle});
  554. if(!_inFlightIntvl){
  555. _inFlightIntvl = setInterval(_watchInFlight, 50);
  556. }
  557. // handle sync requests
  558. //A weakness: async calls in flight
  559. //could have their handlers called as part of the
  560. //_watchInFlight call, before the sync's callbacks
  561. // are called.
  562. if(args.sync){
  563. _watchInFlight();
  564. }
  565. };
  566. var _defaultContentType = "application/x-www-form-urlencoded";
  567. var _validCheck = function(/*Deferred*/dfd){
  568. return dfd.ioArgs.xhr.readyState; //boolean
  569. };
  570. var _ioCheck = function(/*Deferred*/dfd){
  571. return 4 == dfd.ioArgs.xhr.readyState; //boolean
  572. };
  573. var _resHandle = function(/*Deferred*/dfd){
  574. var xhr = dfd.ioArgs.xhr;
  575. if(dojo._isDocumentOk(xhr)){
  576. dfd.callback(dfd);
  577. }else{
  578. var err = new Error("Unable to load " + dfd.ioArgs.url + " status:" + xhr.status);
  579. err.status = xhr.status;
  580. err.responseText = xhr.responseText;
  581. err.xhr = xhr;
  582. dfd.errback(err);
  583. }
  584. };
  585. dojo._ioAddQueryToUrl = function(/*dojo.__IoCallbackArgs*/ioArgs){
  586. //summary: Adds query params discovered by the io deferred construction to the URL.
  587. //Only use this for operations which are fundamentally GET-type operations.
  588. if(ioArgs.query.length){
  589. ioArgs.url += (ioArgs.url.indexOf("?") == -1 ? "?" : "&") + ioArgs.query;
  590. ioArgs.query = null;
  591. }
  592. };
  593. /*=====
  594. dojo.declare("dojo.__XhrArgs", dojo.__IoArgs, {
  595. constructor: function(){
  596. // summary:
  597. // In addition to the properties listed for the dojo._IoArgs type,
  598. // the following properties are allowed for dojo.xhr* methods.
  599. // handleAs: String?
  600. // Acceptable values are: text (default), json, json-comment-optional,
  601. // json-comment-filtered, javascript, xml. See `dojo.contentHandlers`
  602. // sync: Boolean?
  603. // false is default. Indicates whether the request should
  604. // be a synchronous (blocking) request.
  605. // headers: Object?
  606. // Additional HTTP headers to send in the request.
  607. // failOk: Boolean?
  608. // false is default. Indicates whether a request should be
  609. // allowed to fail (and therefore no console error message in
  610. // the event of a failure)
  611. // contentType: String|Boolean
  612. // "application/x-www-form-urlencoded" is default. Set to false to
  613. // prevent a Content-Type header from being sent, or to a string
  614. // to send a different Content-Type.
  615. this.handleAs = handleAs;
  616. this.sync = sync;
  617. this.headers = headers;
  618. this.failOk = failOk;
  619. }
  620. });
  621. =====*/
  622. dojo.xhr = function(/*String*/ method, /*dojo.__XhrArgs*/ args, /*Boolean?*/ hasBody){
  623. // summary:
  624. // Sends an HTTP request with the given method.
  625. // description:
  626. // Sends an HTTP request with the given method.
  627. // See also dojo.xhrGet(), xhrPost(), xhrPut() and dojo.xhrDelete() for shortcuts
  628. // for those HTTP methods. There are also methods for "raw" PUT and POST methods
  629. // via dojo.rawXhrPut() and dojo.rawXhrPost() respectively.
  630. // method:
  631. // HTTP method to be used, such as GET, POST, PUT, DELETE. Should be uppercase.
  632. // hasBody:
  633. // If the request has an HTTP body, then pass true for hasBody.
  634. //Make the Deferred object for this xhr request.
  635. var dfd = dojo._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError);
  636. var ioArgs = dfd.ioArgs;
  637. //Pass the args to _xhrObj, to allow alternate XHR calls based specific calls, like
  638. //the one used for iframe proxies.
  639. var xhr = ioArgs.xhr = dojo._xhrObj(ioArgs.args);
  640. //If XHR factory fails, cancel the deferred.
  641. if(!xhr){
  642. dfd.cancel();
  643. return dfd;
  644. }
  645. //Allow for specifying the HTTP body completely.
  646. if("postData" in args){
  647. ioArgs.query = args.postData;
  648. }else if("putData" in args){
  649. ioArgs.query = args.putData;
  650. }else if("rawBody" in args){
  651. ioArgs.query = args.rawBody;
  652. }else if((arguments.length > 2 && !hasBody) || "POST|PUT".indexOf(method.toUpperCase()) == -1){
  653. //Check for hasBody being passed. If no hasBody,
  654. //then only append query string if not a POST or PUT request.
  655. dojo._ioAddQueryToUrl(ioArgs);
  656. }
  657. // IE 6 is a steaming pile. It won't let you call apply() on the native function (xhr.open).
  658. // workaround for IE6's apply() "issues"
  659. xhr.open(method, ioArgs.url, args.sync !== true, args.user || undefined, args.password || undefined);
  660. if(args.headers){
  661. for(var hdr in args.headers){
  662. if(hdr.toLowerCase() === "content-type"){
  663. if(!args.contentType){
  664. args.contentType = args.headers[hdr];
  665. }
  666. }else if(args.headers[hdr]){
  667. //Only add header if it has a value. This allows for instnace, skipping
  668. //insertion of X-Requested-With by specifying empty value.
  669. xhr.setRequestHeader(hdr, args.headers[hdr]);
  670. }
  671. }
  672. }
  673. // FIXME: is this appropriate for all content types?
  674. if(args.contentType !== false){
  675. xhr.setRequestHeader("Content-Type", args.contentType || _defaultContentType);
  676. }
  677. if(!args.headers || !("X-Requested-With" in args.headers)){
  678. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  679. }
  680. // FIXME: set other headers here!
  681. dojo._ioNotifyStart(dfd);
  682. if(dojo.config.debugAtAllCosts){
  683. xhr.send(ioArgs.query);
  684. }else{
  685. try{
  686. xhr.send(ioArgs.query);
  687. }catch(e){
  688. ioArgs.error = e;
  689. dfd.cancel();
  690. }
  691. }
  692. dojo._ioWatch(dfd, _validCheck, _ioCheck, _resHandle);
  693. xhr = null;
  694. return dfd; // dojo.Deferred
  695. };
  696. dojo.xhrGet = function(/*dojo.__XhrArgs*/ args){
  697. // summary:
  698. // Sends an HTTP GET request to the server.
  699. return dojo.xhr("GET", args); // dojo.Deferred
  700. };
  701. dojo.rawXhrPost = dojo.xhrPost = function(/*dojo.__XhrArgs*/ args){
  702. // summary:
  703. // Sends an HTTP POST request to the server. In addtion to the properties
  704. // listed for the dojo.__XhrArgs type, the following property is allowed:
  705. // postData:
  706. // String. Send raw data in the body of the POST request.
  707. return dojo.xhr("POST", args, true); // dojo.Deferred
  708. };
  709. dojo.rawXhrPut = dojo.xhrPut = function(/*dojo.__XhrArgs*/ args){
  710. // summary:
  711. // Sends an HTTP PUT request to the server. In addtion to the properties
  712. // listed for the dojo.__XhrArgs type, the following property is allowed:
  713. // putData:
  714. // String. Send raw data in the body of the PUT request.
  715. return dojo.xhr("PUT", args, true); // dojo.Deferred
  716. };
  717. dojo.xhrDelete = function(/*dojo.__XhrArgs*/ args){
  718. // summary:
  719. // Sends an HTTP DELETE request to the server.
  720. return dojo.xhr("DELETE", args); //dojo.Deferred
  721. };
  722. /*
  723. dojo.wrapForm = function(formNode){
  724. //summary:
  725. // A replacement for FormBind, but not implemented yet.
  726. // FIXME: need to think harder about what extensions to this we might
  727. // want. What should we allow folks to do w/ this? What events to
  728. // set/send?
  729. throw new Error("dojo.wrapForm not yet implemented");
  730. }
  731. */
  732. dojo._isDocumentOk = function(http){
  733. var stat = http.status || 0;
  734. stat =
  735. (stat >= 200 && stat < 300) || // allow any 2XX response code
  736. stat == 304 || // or, get it out of the cache
  737. stat == 1223 || // or, Internet Explorer mangled the status code
  738. !stat; // or, we're Titanium/browser chrome/chrome extension requesting a local file
  739. return stat; // Boolean
  740. };
  741. dojo._getText = function(url){
  742. var result;
  743. dojo.xhrGet({url:url, sync:true, load:function(text){
  744. result = text;
  745. }});
  746. return result;
  747. };
  748. // Add aliases for static functions to dojo.xhr since dojo.xhr is what's returned from this module
  749. lang.mixin(dojo.xhr, {
  750. _xhrObj: dojo._xhrObj,
  751. fieldToObject: domForm.fieldToObject,
  752. formToObject: domForm.toObject,
  753. objectToQuery: ioq.objectToQuery,
  754. formToQuery: domForm.toQuery,
  755. formToJson: domForm.toJson,
  756. queryToObject: ioq.queryToObject,
  757. contentHandlers: handlers,
  758. _ioSetArgs: dojo._ioSetArgs,
  759. _ioCancelAll: dojo._ioCancelAll,
  760. _ioNotifyStart: dojo._ioNotifyStart,
  761. _ioWatch: dojo._ioWatch,
  762. _ioAddQueryToUrl: dojo._ioAddQueryToUrl,
  763. _isDocumentOk: dojo._isDocumentOk,
  764. _getText: dojo._getText,
  765. get: dojo.xhrGet,
  766. post: dojo.xhrPost,
  767. put: dojo.xhrPut,
  768. del: dojo.xhrDelete // because "delete" is a reserved word
  769. });
  770. return dojo.xhr;
  771. });