define([ '../errors/RequestError', './watch', './handlers', './util', '../has'/*=====, '../request', '../_base/declare' =====*/ ], function(RequestError, watch, handlers, util, has/*=====, request, declare =====*/){ has.add('native-xhr', function(){ // if true, the environment has a native XHR implementation return typeof XMLHttpRequest !== 'undefined'; }); has.add('dojo-force-activex-xhr', function(){ return has('activex') && window.location.protocol === 'file:'; }); has.add('native-xhr2', function(){ if(!has('native-xhr') || has('dojo-force-activex-xhr')){ return; } var x = new XMLHttpRequest(); return typeof x['addEventListener'] !== 'undefined' && (typeof opera === 'undefined' || typeof x['upload'] !== 'undefined'); }); has.add('native-formdata', function(){ // if true, the environment has a native FormData implementation return typeof FormData !== 'undefined'; }); has.add('native-blob', function(){ // if true, the environment has a native Blob implementation return typeof Blob !== 'undefined'; }); has.add('native-arraybuffer', function(){ // if true, the environment has a native ArrayBuffer implementation return typeof ArrayBuffer !== 'undefined'; }); has.add('native-response-type', function(){ return has('native-xhr') && typeof new XMLHttpRequest().responseType !== 'undefined'; }); has.add('native-xhr2-blob', function(){ if(!has('native-response-type')){ return; } var x = new XMLHttpRequest(); // The URL used here does not have to be reachable as the XHR's `send` method is never called. // It does need to be parsable/resolvable in all cases, so it should be an absolute URL. // XMLHttpRequest within a Worker created from a Blob does not support relative URL paths. x.open('GET', 'https://dojotoolkit.org/', true); x.responseType = 'blob'; // will not be set if unsupported var responseType = x.responseType; x.abort(); return responseType === 'blob'; }); // Google Chrome doesn't support "json" response type // up to version 30, so it's intentionally not included here var nativeResponseTypes = { 'blob': has('native-xhr2-blob') ? 'blob' : 'arraybuffer', 'document': 'document', 'arraybuffer': 'arraybuffer' }; function handleResponse(response, error){ var _xhr = response.xhr; response.status = response.xhr.status; try { // Firefox throws an error when trying to access // xhr.responseText if response isn't text response.text = _xhr.responseText; } catch (e) {} if(response.options.handleAs === 'xml'){ response.data = _xhr.responseXML; } var handleError; if(error){ this.reject(error); }else{ try{ handlers(response); }catch(e){ handleError = e; } if(util.checkStatus(_xhr.status)){ if(!handleError){ this.resolve(response); }else{ this.reject(handleError); } }else{ if(!handleError){ error = new RequestError('Unable to load ' + response.url + ' status: ' + _xhr.status, response); this.reject(error); }else{ error = new RequestError('Unable to load ' + response.url + ' status: ' + _xhr.status + ' and an error in handleAs: transformation of response', response); this.reject(error); } } } } var isValid, isReady, addListeners, cancel; if(has('native-xhr2')){ // Any platform with XHR2 will only use the watch mechanism for timeout. isValid = function(response){ // summary: // Check to see if the request should be taken out of the watch queue return !this.isFulfilled(); }; cancel = function(dfd, response){ // summary: // Canceler for deferred response.xhr.abort(); }; addListeners = function(_xhr, dfd, response, uploadProgress){ // summary: // Adds event listeners to the XMLHttpRequest object function onLoad(evt){ dfd.handleResponse(response); } function onError(evt){ var _xhr = evt.target; var error = new RequestError('Unable to load ' + response.url + ' status: ' + _xhr.status, response); dfd.handleResponse(response, error); } function onProgress(transferType, evt){ response.transferType = transferType; if(evt.lengthComputable){ response.loaded = evt.loaded; response.total = evt.total; dfd.progress(response); } else if(response.xhr.readyState === 3){ response.loaded = ('loaded' in evt) ? evt.loaded : evt.position; dfd.progress(response); } } function onDownloadProgress(evt) { return onProgress('download', evt); } function onUploadProgress(evt) { return onProgress('upload', evt); } _xhr.addEventListener('load', onLoad, false); _xhr.addEventListener('error', onError, false); _xhr.addEventListener('progress', onDownloadProgress, false); if (uploadProgress && _xhr.upload) { _xhr.upload.addEventListener('progress', onUploadProgress, false); } return function(){ _xhr.removeEventListener('load', onLoad, false); _xhr.removeEventListener('error', onError, false); _xhr.removeEventListener('progress', onDownloadProgress, false); _xhr.upload.removeEventListener('progress', onUploadProgress, false); _xhr = null; }; }; }else{ isValid = function(response){ return response.xhr.readyState; //boolean }; isReady = function(response){ return 4 === response.xhr.readyState; //boolean }; cancel = function(dfd, response){ // summary: // canceller function for util.deferred call. var xhr = response.xhr; var _at = typeof xhr.abort; if(_at === 'function' || _at === 'object' || _at === 'unknown'){ xhr.abort(); } }; } function getHeader(headerName){ return this.xhr.getResponseHeader(headerName); } var undefined, defaultOptions = { data: null, query: null, sync: false, method: 'GET' }; function xhr(url, options, returnDeferred){ var isFormData = has('native-formdata') && options && options.data && options.data instanceof FormData; var response = util.parseArgs( url, util.deepCreate(defaultOptions, options), isFormData ); url = response.url; options = response.options; var hasNoData = !options.data && options.method !== 'POST' && options.method !== 'PUT'; if(has('ie') <= 10){ // older IE breaks point 9 in http://www.w3.org/TR/XMLHttpRequest/#the-open()-method and sends fragment, so strip it url = url.split('#')[0]; } var remover, last = function(){ remover && remover(); }; //Make the Deferred object for this xhr request. var dfd = util.deferred( response, cancel, isValid, isReady, handleResponse, last ); var _xhr = response.xhr = xhr._create(); if(!_xhr){ // If XHR factory somehow returns nothings, // cancel the deferred. dfd.cancel(new RequestError('XHR was not created')); return returnDeferred ? dfd : dfd.promise; } response.getHeader = getHeader; if(addListeners){ remover = addListeners(_xhr, dfd, response, options.uploadProgress); } // IE11 treats data: undefined different than other browsers var data = typeof(options.data) === 'undefined' ? null : options.data, async = !options.sync, method = options.method; try{ // IE6 won't let you call apply() on the native function. _xhr.open(method, url, async, options.user || undefined, options.password || undefined); if(options.withCredentials){ _xhr.withCredentials = options.withCredentials; } if(has('native-response-type') && options.handleAs in nativeResponseTypes) { _xhr.responseType = nativeResponseTypes[options.handleAs]; } var headers = options.headers, contentType = (isFormData || hasNoData) ? false : 'application/x-www-form-urlencoded'; if(headers){ for(var hdr in headers){ if(hdr.toLowerCase() === 'content-type'){ contentType = headers[hdr]; }else if(headers[hdr]){ //Only add header if it has a value. This allows for instance, skipping //insertion of X-Requested-With by specifying empty value. _xhr.setRequestHeader(hdr, headers[hdr]); } } } if(contentType && contentType !== false){ _xhr.setRequestHeader('Content-Type', contentType); } if(!headers || !('X-Requested-With' in headers)){ _xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); } if(util.notify){ util.notify.emit('send', response, dfd.promise.cancel); } _xhr.send(data); }catch(e){ dfd.reject(e); } watch(dfd); _xhr = null; return returnDeferred ? dfd : dfd.promise; } /*===== xhr = function(url, options){ // summary: // Sends a request using XMLHttpRequest with the given URL and options. // url: String // URL to request // options: dojo/request/xhr.__Options? // Options for the request. // returns: dojo/request.__Promise }; xhr.__BaseOptions = declare(request.__BaseOptions, { // sync: Boolean? // Whether to make a synchronous request or not. Default // is `false` (asynchronous). // data: String|Object|FormData? // Data to transfer. This is ignored for GET and DELETE // requests. // headers: Object? // Headers to use for the request. // user: String? // Username to use during the request. // password: String? // Password to use during the request. // withCredentials: Boolean? // For cross-site requests, whether to send credentials // or not. // uploadProgress: Boolean? // Upload progress events cause preflighted requests. This // option enables upload progress event support but also // causes all requests to be preflighted. }); xhr.__MethodOptions = declare(null, { // method: String? // The HTTP method to use to make the request. Must be // uppercase. Default is `"GET"`. }); xhr.__Options = declare([xhr.__BaseOptions, xhr.__MethodOptions]); xhr.get = function(url, options){ // summary: // Send an HTTP GET request using XMLHttpRequest with the given URL and options. // url: String // URL to request // options: dojo/request/xhr.__BaseOptions? // Options for the request. // returns: dojo/request.__Promise }; xhr.post = function(url, options){ // summary: // Send an HTTP POST request using XMLHttpRequest with the given URL and options. // url: String // URL to request // options: dojo/request/xhr.__BaseOptions? // Options for the request. // returns: dojo/request.__Promise }; xhr.put = function(url, options){ // summary: // Send an HTTP PUT request using XMLHttpRequest with the given URL and options. // url: String // URL to request // options: dojo/request/xhr.__BaseOptions? // Options for the request. // returns: dojo/request.__Promise }; xhr.del = function(url, options){ // summary: // Send an HTTP DELETE request using XMLHttpRequest with the given URL and options. // url: String // URL to request // options: dojo/request/xhr.__BaseOptions? // Options for the request. // returns: dojo/request.__Promise }; =====*/ xhr._create = function(){ // summary: // does the work of portably generating a new XMLHTTPRequest object. throw new Error('XMLHTTP not available'); }; if(has('native-xhr') && !has('dojo-force-activex-xhr')){ xhr._create = function(){ return new XMLHttpRequest(); }; }else if(has('activex')){ try{ new ActiveXObject('Msxml2.XMLHTTP'); xhr._create = function(){ return new ActiveXObject('Msxml2.XMLHTTP'); }; }catch(e){ try{ new ActiveXObject('Microsoft.XMLHTTP'); xhr._create = function(){ return new ActiveXObject('Microsoft.XMLHTTP'); }; }catch(e){} } } util.addCommonMethods(xhr); return xhr; });