Path: blob/trunk/third_party/closure/goog/net/xhrio.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Wrapper class for handling XmlHttpRequests.16*17* One off requests can be sent through goog.net.XhrIo.send() or an18* instance can be created to send multiple requests. Each request uses its19* own XmlHttpRequest object and handles clearing of the event callback to20* ensure no leaks.21*22* XhrIo is event based, it dispatches events on success, failure, finishing,23* ready-state change, or progress (download and upload).24*25* The ready-state or timeout event fires first, followed by26* a generic completed event. Then the abort, error, or success event27* is fired as appropriate. Progress events are fired as they are28* received. Lastly, the ready event will fire to indicate that the29* object may be used to make another request.30*31* The error event may also be called before completed and32* ready-state-change if the XmlHttpRequest.open() or .send() methods throw.33*34* This class does not support multiple requests, queuing, or prioritization.35*36* When progress events are supported by the browser, and progress is37* enabled via .setProgressEventsEnabled(true), the38* goog.net.EventType.PROGRESS event will be the re-dispatched browser39* progress event. Additionally, a DOWNLOAD_PROGRESS or UPLOAD_PROGRESS event40* will be fired for download and upload progress respectively.41*42*/434445goog.provide('goog.net.XhrIo');46goog.provide('goog.net.XhrIo.ResponseType');4748goog.require('goog.Timer');49goog.require('goog.array');50goog.require('goog.asserts');51goog.require('goog.debug.entryPointRegistry');52goog.require('goog.events.EventTarget');53goog.require('goog.json');54goog.require('goog.log');55goog.require('goog.net.ErrorCode');56goog.require('goog.net.EventType');57goog.require('goog.net.HttpStatus');58goog.require('goog.net.XmlHttp');59goog.require('goog.string');60goog.require('goog.structs');61goog.require('goog.structs.Map');62goog.require('goog.uri.utils');63goog.require('goog.userAgent');6465goog.forwardDeclare('goog.Uri');66676869/**70* Basic class for handling XMLHttpRequests.71* @param {goog.net.XmlHttpFactory=} opt_xmlHttpFactory Factory to use when72* creating XMLHttpRequest objects.73* @constructor74* @extends {goog.events.EventTarget}75*/76goog.net.XhrIo = function(opt_xmlHttpFactory) {77goog.net.XhrIo.base(this, 'constructor');7879/**80* Map of default headers to add to every request, use:81* XhrIo.headers.set(name, value)82* @type {!goog.structs.Map}83*/84this.headers = new goog.structs.Map();8586/**87* Optional XmlHttpFactory88* @private {goog.net.XmlHttpFactory}89*/90this.xmlHttpFactory_ = opt_xmlHttpFactory || null;9192/**93* Whether XMLHttpRequest is active. A request is active from the time send()94* is called until onReadyStateChange() is complete, or error() or abort()95* is called.96* @private {boolean}97*/98this.active_ = false;99100/**101* The XMLHttpRequest object that is being used for the transfer.102* @private {?goog.net.XhrLike.OrNative}103*/104this.xhr_ = null;105106/**107* The options to use with the current XMLHttpRequest object.108* @private {Object}109*/110this.xhrOptions_ = null;111112/**113* Last URL that was requested.114* @private {string|goog.Uri}115*/116this.lastUri_ = '';117118/**119* Method for the last request.120* @private {string}121*/122this.lastMethod_ = '';123124/**125* Last error code.126* @private {!goog.net.ErrorCode}127*/128this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;129130/**131* Last error message.132* @private {Error|string}133*/134this.lastError_ = '';135136/**137* Used to ensure that we don't dispatch an multiple ERROR events. This can138* happen in IE when it does a synchronous load and one error is handled in139* the ready statte change and one is handled due to send() throwing an140* exception.141* @private {boolean}142*/143this.errorDispatched_ = false;144145/**146* Used to make sure we don't fire the complete event from inside a send call.147* @private {boolean}148*/149this.inSend_ = false;150151/**152* Used in determining if a call to {@link #onReadyStateChange_} is from153* within a call to this.xhr_.open.154* @private {boolean}155*/156this.inOpen_ = false;157158/**159* Used in determining if a call to {@link #onReadyStateChange_} is from160* within a call to this.xhr_.abort.161* @private {boolean}162*/163this.inAbort_ = false;164165/**166* Number of milliseconds after which an incomplete request will be aborted167* and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no timeout168* is set.169* @private {number}170*/171this.timeoutInterval_ = 0;172173/**174* Timer to track request timeout.175* @private {?number}176*/177this.timeoutId_ = null;178179/**180* The requested type for the response. The empty string means use the default181* XHR behavior.182* @private {goog.net.XhrIo.ResponseType}183*/184this.responseType_ = goog.net.XhrIo.ResponseType.DEFAULT;185186/**187* Whether a "credentialed" request is to be sent (one that is aware of188* cookies and authentication). This is applicable only for cross-domain189* requests and more recent browsers that support this part of the HTTP Access190* Control standard.191*192* @see http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute193*194* @private {boolean}195*/196this.withCredentials_ = false;197198/**199* Whether progress events are enabled for this request. This is200* disabled by default because setting a progress event handler201* causes pre-flight OPTIONS requests to be sent for CORS requests,202* even in cases where a pre-flight request would not otherwise be203* sent.204*205* @see http://xhr.spec.whatwg.org/#security-considerations206*207* Note that this can cause problems for Firefox 22 and below, as an208* older "LSProgressEvent" will be dispatched by the browser. That209* progress event is no longer supported, and can lead to failures,210* including throwing exceptions.211*212* @see http://bugzilla.mozilla.org/show_bug.cgi?id=845631213* @see b/23469793214*215* @private {boolean}216*/217this.progressEventsEnabled_ = false;218219/**220* True if we can use XMLHttpRequest's timeout directly.221* @private {boolean}222*/223this.useXhr2Timeout_ = false;224};225goog.inherits(goog.net.XhrIo, goog.events.EventTarget);226227228/**229* Response types that may be requested for XMLHttpRequests.230* @enum {string}231* @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute232*/233goog.net.XhrIo.ResponseType = {234DEFAULT: '',235TEXT: 'text',236DOCUMENT: 'document',237// Not supported as of Chrome 10.0.612.1 dev238BLOB: 'blob',239ARRAY_BUFFER: 'arraybuffer'240};241242243/**244* A reference to the XhrIo logger245* @private {?goog.log.Logger}246* @const247*/248goog.net.XhrIo.prototype.logger_ = goog.log.getLogger('goog.net.XhrIo');249250251/**252* The Content-Type HTTP header name253* @type {string}254*/255goog.net.XhrIo.CONTENT_TYPE_HEADER = 'Content-Type';256257258/**259* The Content-Transfer-Encoding HTTP header name260* @type {string}261*/262goog.net.XhrIo.CONTENT_TRANSFER_ENCODING = 'Content-Transfer-Encoding';263264265/**266* The pattern matching the 'http' and 'https' URI schemes267* @type {!RegExp}268*/269goog.net.XhrIo.HTTP_SCHEME_PATTERN = /^https?$/i;270271272/**273* The methods that typically come along with form data. We set different274* headers depending on whether the HTTP action is one of these.275*/276goog.net.XhrIo.METHODS_WITH_FORM_DATA = ['POST', 'PUT'];277278279/**280* The Content-Type HTTP header value for a url-encoded form281* @type {string}282*/283goog.net.XhrIo.FORM_CONTENT_TYPE =284'application/x-www-form-urlencoded;charset=utf-8';285286287/**288* The XMLHttpRequest Level two timeout delay ms property name.289*290* @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute291*292* @private {string}293* @const294*/295goog.net.XhrIo.XHR2_TIMEOUT_ = 'timeout';296297298/**299* The XMLHttpRequest Level two ontimeout handler property name.300*301* @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute302*303* @private {string}304* @const305*/306goog.net.XhrIo.XHR2_ON_TIMEOUT_ = 'ontimeout';307308309/**310* All non-disposed instances of goog.net.XhrIo created311* by {@link goog.net.XhrIo.send} are in this Array.312* @see goog.net.XhrIo.cleanup313* @private {!Array<!goog.net.XhrIo>}314*/315goog.net.XhrIo.sendInstances_ = [];316317318/**319* Static send that creates a short lived instance of XhrIo to send the320* request.321* @see goog.net.XhrIo.cleanup322* @param {string|goog.Uri} url Uri to make request to.323* @param {?function(this:goog.net.XhrIo, ?)=} opt_callback Callback function324* for when request is complete.325* @param {string=} opt_method Send method, default: GET.326* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}327* opt_content Body data.328* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the329* request.330* @param {number=} opt_timeoutInterval Number of milliseconds after which an331* incomplete request will be aborted; 0 means no timeout is set.332* @param {boolean=} opt_withCredentials Whether to send credentials with the333* request. Default to false. See {@link goog.net.XhrIo#setWithCredentials}.334* @return {!goog.net.XhrIo} The sent XhrIo.335*/336goog.net.XhrIo.send = function(337url, opt_callback, opt_method, opt_content, opt_headers,338opt_timeoutInterval, opt_withCredentials) {339var x = new goog.net.XhrIo();340goog.net.XhrIo.sendInstances_.push(x);341if (opt_callback) {342x.listen(goog.net.EventType.COMPLETE, opt_callback);343}344x.listenOnce(goog.net.EventType.READY, x.cleanupSend_);345if (opt_timeoutInterval) {346x.setTimeoutInterval(opt_timeoutInterval);347}348if (opt_withCredentials) {349x.setWithCredentials(opt_withCredentials);350}351x.send(url, opt_method, opt_content, opt_headers);352return x;353};354355356/**357* Disposes all non-disposed instances of goog.net.XhrIo created by358* {@link goog.net.XhrIo.send}.359* {@link goog.net.XhrIo.send} cleans up the goog.net.XhrIo instance360* it creates when the request completes or fails. However, if361* the request never completes, then the goog.net.XhrIo is not disposed.362* This can occur if the window is unloaded before the request completes.363* We could have {@link goog.net.XhrIo.send} return the goog.net.XhrIo364* it creates and make the client of {@link goog.net.XhrIo.send} be365* responsible for disposing it in this case. However, this makes things366* significantly more complicated for the client, and the whole point367* of {@link goog.net.XhrIo.send} is that it's simple and easy to use.368* Clients of {@link goog.net.XhrIo.send} should call369* {@link goog.net.XhrIo.cleanup} when doing final370* cleanup on window unload.371*/372goog.net.XhrIo.cleanup = function() {373var instances = goog.net.XhrIo.sendInstances_;374while (instances.length) {375instances.pop().dispose();376}377};378379380/**381* Installs exception protection for all entry point introduced by382* goog.net.XhrIo instances which are not protected by383* {@link goog.debug.ErrorHandler#protectWindowSetTimeout},384* {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or385* {@link goog.events.protectBrowserEventEntryPoint}.386*387* @param {goog.debug.ErrorHandler} errorHandler Error handler with which to388* protect the entry point(s).389*/390goog.net.XhrIo.protectEntryPoints = function(errorHandler) {391goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =392errorHandler.protectEntryPoint(393goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);394};395396397/**398* Disposes of the specified goog.net.XhrIo created by399* {@link goog.net.XhrIo.send} and removes it from400* {@link goog.net.XhrIo.pendingStaticSendInstances_}.401* @private402*/403goog.net.XhrIo.prototype.cleanupSend_ = function() {404this.dispose();405goog.array.remove(goog.net.XhrIo.sendInstances_, this);406};407408409/**410* Returns the number of milliseconds after which an incomplete request will be411* aborted, or 0 if no timeout is set.412* @return {number} Timeout interval in milliseconds.413*/414goog.net.XhrIo.prototype.getTimeoutInterval = function() {415return this.timeoutInterval_;416};417418419/**420* Sets the number of milliseconds after which an incomplete request will be421* aborted and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no422* timeout is set.423* @param {number} ms Timeout interval in milliseconds; 0 means none.424*/425goog.net.XhrIo.prototype.setTimeoutInterval = function(ms) {426this.timeoutInterval_ = Math.max(0, ms);427};428429430/**431* Sets the desired type for the response. At time of writing, this is only432* supported in very recent versions of WebKit (10.0.612.1 dev and later).433*434* If this is used, the response may only be accessed via {@link #getResponse}.435*436* @param {goog.net.XhrIo.ResponseType} type The desired type for the response.437*/438goog.net.XhrIo.prototype.setResponseType = function(type) {439this.responseType_ = type;440};441442443/**444* Gets the desired type for the response.445* @return {goog.net.XhrIo.ResponseType} The desired type for the response.446*/447goog.net.XhrIo.prototype.getResponseType = function() {448return this.responseType_;449};450451452/**453* Sets whether a "credentialed" request that is aware of cookie and454* authentication information should be made. This option is only supported by455* browsers that support HTTP Access Control. As of this writing, this option456* is not supported in IE.457*458* @param {boolean} withCredentials Whether this should be a "credentialed"459* request.460*/461goog.net.XhrIo.prototype.setWithCredentials = function(withCredentials) {462this.withCredentials_ = withCredentials;463};464465466/**467* Gets whether a "credentialed" request is to be sent.468* @return {boolean} The desired type for the response.469*/470goog.net.XhrIo.prototype.getWithCredentials = function() {471return this.withCredentials_;472};473474475/**476* Sets whether progress events are enabled for this request. Note477* that progress events require pre-flight OPTIONS request handling478* for CORS requests, and may cause trouble with older browsers. See479* progressEventsEnabled_ for details.480* @param {boolean} enabled Whether progress events should be enabled.481*/482goog.net.XhrIo.prototype.setProgressEventsEnabled = function(enabled) {483this.progressEventsEnabled_ = enabled;484};485486487/**488* Gets whether progress events are enabled.489* @return {boolean} Whether progress events are enabled for this request.490*/491goog.net.XhrIo.prototype.getProgressEventsEnabled = function() {492return this.progressEventsEnabled_;493};494495496/**497* Instance send that actually uses XMLHttpRequest to make a server call.498* @param {string|goog.Uri} url Uri to make request to.499* @param {string=} opt_method Send method, default: GET.500* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}501* opt_content Body data.502* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the503* request.504* @suppress {deprecated} Use deprecated goog.structs.forEach to allow different505* types of parameters for opt_headers.506*/507goog.net.XhrIo.prototype.send = function(508url, opt_method, opt_content, opt_headers) {509if (this.xhr_) {510throw Error(511'[goog.net.XhrIo] Object is active with another request=' +512this.lastUri_ + '; newUri=' + url);513}514515var method = opt_method ? opt_method.toUpperCase() : 'GET';516517this.lastUri_ = url;518this.lastError_ = '';519this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;520this.lastMethod_ = method;521this.errorDispatched_ = false;522this.active_ = true;523524// Use the factory to create the XHR object and options525this.xhr_ = this.createXhr();526this.xhrOptions_ = this.xmlHttpFactory_ ? this.xmlHttpFactory_.getOptions() :527goog.net.XmlHttp.getOptions();528529// Set up the onreadystatechange callback530this.xhr_.onreadystatechange = goog.bind(this.onReadyStateChange_, this);531532// Set up upload/download progress events, if progress events are supported.533if (this.getProgressEventsEnabled() && 'onprogress' in this.xhr_) {534this.xhr_.onprogress =535goog.bind(function(e) { this.onProgressHandler_(e, true); }, this);536if (this.xhr_.upload) {537this.xhr_.upload.onprogress = goog.bind(this.onProgressHandler_, this);538}539}540541/**542* Try to open the XMLHttpRequest (always async), if an error occurs here it543* is generally permission denied544*/545try {546goog.log.fine(this.logger_, this.formatMsg_('Opening Xhr'));547this.inOpen_ = true;548this.xhr_.open(method, String(url), true); // Always async!549this.inOpen_ = false;550} catch (err) {551goog.log.fine(552this.logger_, this.formatMsg_('Error opening Xhr: ' + err.message));553this.error_(goog.net.ErrorCode.EXCEPTION, err);554return;555}556557// We can't use null since this won't allow requests with form data to have a558// content length specified which will cause some proxies to return a 411559// error.560var content = opt_content || '';561562var headers = this.headers.clone();563564// Add headers specific to this request565if (opt_headers) {566goog.structs.forEach(567opt_headers, function(value, key) { headers.set(key, value); });568}569570// Find whether a content type header is set, ignoring case.571// HTTP header names are case-insensitive. See:572// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2573var contentTypeKey =574goog.array.find(headers.getKeys(), goog.net.XhrIo.isContentTypeHeader_);575576var contentIsFormData =577(goog.global['FormData'] && (content instanceof goog.global['FormData']));578if (goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) &&579!contentTypeKey && !contentIsFormData) {580// For requests typically with form data, default to the url-encoded form581// content type unless this is a FormData request. For FormData,582// the browser will automatically add a multipart/form-data content type583// with an appropriate multipart boundary.584headers.set(585goog.net.XhrIo.CONTENT_TYPE_HEADER, goog.net.XhrIo.FORM_CONTENT_TYPE);586}587588// Add the headers to the Xhr object589headers.forEach(function(value, key) {590this.xhr_.setRequestHeader(key, value);591}, this);592593if (this.responseType_) {594this.xhr_.responseType = this.responseType_;595}596// Set xhr_.withCredentials only when the value is different, or else in597// synchronous XMLHtppRequest.open Firefox will throw an exception.598// https://bugzilla.mozilla.org/show_bug.cgi?id=736340599if ('withCredentials' in this.xhr_ &&600this.xhr_.withCredentials !== this.withCredentials_) {601this.xhr_.withCredentials = this.withCredentials_;602}603604/**605* Try to send the request, or other wise report an error (404 not found).606*/607try {608this.cleanUpTimeoutTimer_(); // Paranoid, should never be running.609if (this.timeoutInterval_ > 0) {610this.useXhr2Timeout_ = goog.net.XhrIo.shouldUseXhr2Timeout_(this.xhr_);611goog.log.fine(612this.logger_, this.formatMsg_(613'Will abort after ' + this.timeoutInterval_ +614'ms if incomplete, xhr2 ' + this.useXhr2Timeout_));615if (this.useXhr2Timeout_) {616this.xhr_[goog.net.XhrIo.XHR2_TIMEOUT_] = this.timeoutInterval_;617this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] =618goog.bind(this.timeout_, this);619} else {620this.timeoutId_ =621goog.Timer.callOnce(this.timeout_, this.timeoutInterval_, this);622}623}624goog.log.fine(this.logger_, this.formatMsg_('Sending request'));625this.inSend_ = true;626this.xhr_.send(content);627this.inSend_ = false;628629} catch (err) {630goog.log.fine(this.logger_, this.formatMsg_('Send error: ' + err.message));631this.error_(goog.net.ErrorCode.EXCEPTION, err);632}633};634635636/**637* Determines if the argument is an XMLHttpRequest that supports the level 2638* timeout value and event.639*640* Currently, FF 21.0 OS X has the fields but won't actually call the timeout641* handler. Perhaps the confusion in the bug referenced below hasn't642* entirely been resolved.643*644* @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute645* @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816646*647* @param {!goog.net.XhrLike.OrNative} xhr The request.648* @return {boolean} True if the request supports level 2 timeout.649* @private650*/651goog.net.XhrIo.shouldUseXhr2Timeout_ = function(xhr) {652return goog.userAgent.IE && goog.userAgent.isVersionOrHigher(9) &&653goog.isNumber(xhr[goog.net.XhrIo.XHR2_TIMEOUT_]) &&654goog.isDef(xhr[goog.net.XhrIo.XHR2_ON_TIMEOUT_]);655};656657658/**659* @param {string} header An HTTP header key.660* @return {boolean} Whether the key is a content type header (ignoring661* case.662* @private663*/664goog.net.XhrIo.isContentTypeHeader_ = function(header) {665return goog.string.caseInsensitiveEquals(666goog.net.XhrIo.CONTENT_TYPE_HEADER, header);667};668669670/**671* Creates a new XHR object.672* @return {!goog.net.XhrLike.OrNative} The newly created XHR object.673* @protected674*/675goog.net.XhrIo.prototype.createXhr = function() {676return this.xmlHttpFactory_ ? this.xmlHttpFactory_.createInstance() :677goog.net.XmlHttp();678};679680681/**682* The request didn't complete after {@link goog.net.XhrIo#timeoutInterval_}683* milliseconds; raises a {@link goog.net.EventType.TIMEOUT} event and aborts684* the request.685* @private686*/687goog.net.XhrIo.prototype.timeout_ = function() {688if (typeof goog == 'undefined') {689// If goog is undefined then the callback has occurred as the application690// is unloading and will error. Thus we let it silently fail.691} else if (this.xhr_) {692this.lastError_ =693'Timed out after ' + this.timeoutInterval_ + 'ms, aborting';694this.lastErrorCode_ = goog.net.ErrorCode.TIMEOUT;695goog.log.fine(this.logger_, this.formatMsg_(this.lastError_));696this.dispatchEvent(goog.net.EventType.TIMEOUT);697this.abort(goog.net.ErrorCode.TIMEOUT);698}699};700701702/**703* Something errorred, so inactivate, fire error callback and clean up704* @param {goog.net.ErrorCode} errorCode The error code.705* @param {Error} err The error object.706* @private707*/708goog.net.XhrIo.prototype.error_ = function(errorCode, err) {709this.active_ = false;710if (this.xhr_) {711this.inAbort_ = true;712this.xhr_.abort(); // Ensures XHR isn't hung (FF)713this.inAbort_ = false;714}715this.lastError_ = err;716this.lastErrorCode_ = errorCode;717this.dispatchErrors_();718this.cleanUpXhr_();719};720721722/**723* Dispatches COMPLETE and ERROR in case of an error. This ensures that we do724* not dispatch multiple error events.725* @private726*/727goog.net.XhrIo.prototype.dispatchErrors_ = function() {728if (!this.errorDispatched_) {729this.errorDispatched_ = true;730this.dispatchEvent(goog.net.EventType.COMPLETE);731this.dispatchEvent(goog.net.EventType.ERROR);732}733};734735736/**737* Abort the current XMLHttpRequest738* @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use -739* defaults to ABORT.740*/741goog.net.XhrIo.prototype.abort = function(opt_failureCode) {742if (this.xhr_ && this.active_) {743goog.log.fine(this.logger_, this.formatMsg_('Aborting'));744this.active_ = false;745this.inAbort_ = true;746this.xhr_.abort();747this.inAbort_ = false;748this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT;749this.dispatchEvent(goog.net.EventType.COMPLETE);750this.dispatchEvent(goog.net.EventType.ABORT);751this.cleanUpXhr_();752}753};754755756/**757* Nullifies all callbacks to reduce risks of leaks.758* @override759* @protected760*/761goog.net.XhrIo.prototype.disposeInternal = function() {762if (this.xhr_) {763// We explicitly do not call xhr_.abort() unless active_ is still true.764// This is to avoid unnecessarily aborting a successful request when765// dispose() is called in a callback triggered by a complete response, but766// in which browser cleanup has not yet finished.767// (See http://b/issue?id=1684217.)768if (this.active_) {769this.active_ = false;770this.inAbort_ = true;771this.xhr_.abort();772this.inAbort_ = false;773}774this.cleanUpXhr_(true);775}776777goog.net.XhrIo.base(this, 'disposeInternal');778};779780781/**782* Internal handler for the XHR object's readystatechange event. This method783* checks the status and the readystate and fires the correct callbacks.784* If the request has ended, the handlers are cleaned up and the XHR object is785* nullified.786* @private787*/788goog.net.XhrIo.prototype.onReadyStateChange_ = function() {789if (this.isDisposed()) {790// This method is the target of an untracked goog.Timer.callOnce().791return;792}793if (!this.inOpen_ && !this.inSend_ && !this.inAbort_) {794// Were not being called from within a call to this.xhr_.send795// this.xhr_.abort, or this.xhr_.open, so this is an entry point796this.onReadyStateChangeEntryPoint_();797} else {798this.onReadyStateChangeHelper_();799}800};801802803/**804* Used to protect the onreadystatechange handler entry point. Necessary805* as {#onReadyStateChange_} maybe called from within send or abort, this806* method is only called when {#onReadyStateChange_} is called as an807* entry point.808* {@see #protectEntryPoints}809* @private810*/811goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = function() {812this.onReadyStateChangeHelper_();813};814815816/**817* Helper for {@link #onReadyStateChange_}. This is used so that818* entry point calls to {@link #onReadyStateChange_} can be routed through819* {@link #onReadyStateChangeEntryPoint_}.820* @private821*/822goog.net.XhrIo.prototype.onReadyStateChangeHelper_ = function() {823if (!this.active_) {824// can get called inside abort call825return;826}827828if (typeof goog == 'undefined') {829// NOTE(user): If goog is undefined then the callback has occurred as the830// application is unloading and will error. Thus we let it silently fail.831832} else if (833this.xhrOptions_[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] &&834this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE &&835this.getStatus() == 2) {836// NOTE(user): In IE if send() errors on a *local* request the readystate837// is still changed to COMPLETE. We need to ignore it and allow the838// try/catch around send() to pick up the error.839goog.log.fine(840this.logger_,841this.formatMsg_('Local request error detected and ignored'));842843} else {844// In IE when the response has been cached we sometimes get the callback845// from inside the send call and this usually breaks code that assumes that846// XhrIo is asynchronous. If that is the case we delay the callback847// using a timer.848if (this.inSend_ &&849this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE) {850goog.Timer.callOnce(this.onReadyStateChange_, 0, this);851return;852}853854this.dispatchEvent(goog.net.EventType.READY_STATE_CHANGE);855856// readyState indicates the transfer has finished857if (this.isComplete()) {858goog.log.fine(this.logger_, this.formatMsg_('Request complete'));859860this.active_ = false;861862try {863// Call the specific callbacks for success or failure. Only call the864// success if the status is 200 (HTTP_OK) or 304 (HTTP_CACHED)865if (this.isSuccess()) {866this.dispatchEvent(goog.net.EventType.COMPLETE);867this.dispatchEvent(goog.net.EventType.SUCCESS);868} else {869this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR;870this.lastError_ =871this.getStatusText() + ' [' + this.getStatus() + ']';872this.dispatchErrors_();873}874} finally {875this.cleanUpXhr_();876}877}878}879};880881882/**883* Internal handler for the XHR object's onprogress event. Fires both a generic884* PROGRESS event and either a DOWNLOAD_PROGRESS or UPLOAD_PROGRESS event to885* allow specific binding for each XHR progress event.886* @param {!ProgressEvent} e XHR progress event.887* @param {boolean=} opt_isDownload Whether the current progress event is from a888* download. Used to determine whether DOWNLOAD_PROGRESS or UPLOAD_PROGRESS889* event should be dispatched.890* @private891*/892goog.net.XhrIo.prototype.onProgressHandler_ = function(e, opt_isDownload) {893goog.asserts.assert(894e.type === goog.net.EventType.PROGRESS,895'goog.net.EventType.PROGRESS is of the same type as raw XHR progress.');896this.dispatchEvent(897goog.net.XhrIo.buildProgressEvent_(e, goog.net.EventType.PROGRESS));898this.dispatchEvent(899goog.net.XhrIo.buildProgressEvent_(900e, opt_isDownload ? goog.net.EventType.DOWNLOAD_PROGRESS :901goog.net.EventType.UPLOAD_PROGRESS));902};903904905/**906* Creates a representation of the native ProgressEvent. IE doesn't support907* constructing ProgressEvent via "new", and the alternatives (e.g.,908* ProgressEvent.initProgressEvent) are non-standard or deprecated.909* @param {!ProgressEvent} e XHR progress event.910* @param {!goog.net.EventType} eventType The type of the event.911* @return {!ProgressEvent} The progress event.912* @private913*/914goog.net.XhrIo.buildProgressEvent_ = function(e, eventType) {915return /** @type {!ProgressEvent} */ ({916type: eventType,917lengthComputable: e.lengthComputable,918loaded: e.loaded,919total: e.total920});921};922923924/**925* Remove the listener to protect against leaks, and nullify the XMLHttpRequest926* object.927* @param {boolean=} opt_fromDispose If this is from the dispose (don't want to928* fire any events).929* @private930*/931goog.net.XhrIo.prototype.cleanUpXhr_ = function(opt_fromDispose) {932if (this.xhr_) {933// Cancel any pending timeout event handler.934this.cleanUpTimeoutTimer_();935936// Save reference so we can mark it as closed after the READY event. The937// READY event may trigger another request, thus we must nullify this.xhr_938var xhr = this.xhr_;939var clearedOnReadyStateChange =940this.xhrOptions_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] ?941goog.nullFunction :942null;943this.xhr_ = null;944this.xhrOptions_ = null;945946if (!opt_fromDispose) {947this.dispatchEvent(goog.net.EventType.READY);948}949950try {951// NOTE(user): Not nullifying in FireFox can still leak if the callbacks952// are defined in the same scope as the instance of XhrIo. But, IE doesn't953// allow you to set the onreadystatechange to NULL so nullFunction is954// used.955xhr.onreadystatechange = clearedOnReadyStateChange;956} catch (e) {957// This seems to occur with a Gears HTTP request. Delayed the setting of958// this onreadystatechange until after READY is sent out and catching the959// error to see if we can track down the problem.960goog.log.error(961this.logger_,962'Problem encountered resetting onreadystatechange: ' + e.message);963}964}965};966967968/**969* Make sure the timeout timer isn't running.970* @private971*/972goog.net.XhrIo.prototype.cleanUpTimeoutTimer_ = function() {973if (this.xhr_ && this.useXhr2Timeout_) {974this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = null;975}976if (goog.isNumber(this.timeoutId_)) {977goog.Timer.clear(this.timeoutId_);978this.timeoutId_ = null;979}980};981982983/**984* @return {boolean} Whether there is an active request.985*/986goog.net.XhrIo.prototype.isActive = function() {987return !!this.xhr_;988};989990991/**992* @return {boolean} Whether the request has completed.993*/994goog.net.XhrIo.prototype.isComplete = function() {995return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE;996};997998999/**1000* @return {boolean} Whether the request completed with a success.1001*/1002goog.net.XhrIo.prototype.isSuccess = function() {1003var status = this.getStatus();1004// A zero status code is considered successful for local files.1005return goog.net.HttpStatus.isSuccess(status) ||1006status === 0 && !this.isLastUriEffectiveSchemeHttp_();1007};100810091010/**1011* @return {boolean} whether the effective scheme of the last URI that was1012* fetched was 'http' or 'https'.1013* @private1014*/1015goog.net.XhrIo.prototype.isLastUriEffectiveSchemeHttp_ = function() {1016var scheme = goog.uri.utils.getEffectiveScheme(String(this.lastUri_));1017return goog.net.XhrIo.HTTP_SCHEME_PATTERN.test(scheme);1018};101910201021/**1022* Get the readystate from the Xhr object1023* Will only return correct result when called from the context of a callback1024* @return {goog.net.XmlHttp.ReadyState} goog.net.XmlHttp.ReadyState.*.1025*/1026goog.net.XhrIo.prototype.getReadyState = function() {1027return this.xhr_ ?1028/** @type {goog.net.XmlHttp.ReadyState} */ (this.xhr_.readyState) :1029goog.net.XmlHttp.ReadyState1030.UNINITIALIZED;1031};103210331034/**1035* Get the status from the Xhr object1036* Will only return correct result when called from the context of a callback1037* @return {number} Http status.1038*/1039goog.net.XhrIo.prototype.getStatus = function() {1040/**1041* IE doesn't like you checking status until the readystate is greater than 21042* (i.e. it is receiving or complete). The try/catch is used for when the1043* page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_.1044*/1045try {1046return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ?1047this.xhr_.status :1048-1;1049} catch (e) {1050return -1;1051}1052};105310541055/**1056* Get the status text from the Xhr object1057* Will only return correct result when called from the context of a callback1058* @return {string} Status text.1059*/1060goog.net.XhrIo.prototype.getStatusText = function() {1061/**1062* IE doesn't like you checking status until the readystate is greater than 21063* (i.e. it is receiving or complete). The try/catch is used for when the1064* page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_.1065*/1066try {1067return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ?1068this.xhr_.statusText :1069'';1070} catch (e) {1071goog.log.fine(this.logger_, 'Can not get status: ' + e.message);1072return '';1073}1074};107510761077/**1078* Get the last Uri that was requested1079* @return {string} Last Uri.1080*/1081goog.net.XhrIo.prototype.getLastUri = function() {1082return String(this.lastUri_);1083};108410851086/**1087* Get the response text from the Xhr object1088* Will only return correct result when called from the context of a callback.1089* @return {string} Result from the server, or '' if no result available.1090*/1091goog.net.XhrIo.prototype.getResponseText = function() {1092try {1093return this.xhr_ ? this.xhr_.responseText : '';1094} catch (e) {1095// http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute1096// states that responseText should return '' (and responseXML null)1097// when the state is not LOADING or DONE. Instead, IE can1098// throw unexpected exceptions, for example when a request is aborted1099// or no data is available yet.1100goog.log.fine(this.logger_, 'Can not get responseText: ' + e.message);1101return '';1102}1103};110411051106/**1107* Get the response body from the Xhr object. This property is only available1108* in IE since version 7 according to MSDN:1109* http://msdn.microsoft.com/en-us/library/ie/ms534368(v=vs.85).aspx1110* Will only return correct result when called from the context of a callback.1111*1112* One option is to construct a VBArray from the returned object and convert1113* it to a JavaScript array using the toArray method:1114* {@code (new window['VBArray'](xhrIo.getResponseBody())).toArray()}1115* This will result in an array of numbers in the range of [0..255]1116*1117* Another option is to use the VBScript CStr method to convert it into a1118* string as outlined in http://stackoverflow.com/questions/19199721119*1120* @return {Object} Binary result from the server or null if not available.1121*/1122goog.net.XhrIo.prototype.getResponseBody = function() {11231124try {1125if (this.xhr_ && 'responseBody' in this.xhr_) {1126return this.xhr_['responseBody'];1127}1128} catch (e) {1129// IE can throw unexpected exceptions, for example when a request is aborted1130// or no data is yet available.1131goog.log.fine(this.logger_, 'Can not get responseBody: ' + e.message);1132}1133return null;1134};113511361137/**1138* Get the response XML from the Xhr object1139* Will only return correct result when called from the context of a callback.1140* @return {Document} The DOM Document representing the XML file, or null1141* if no result available.1142*/1143goog.net.XhrIo.prototype.getResponseXml = function() {11441145try {1146return this.xhr_ ? this.xhr_.responseXML : null;1147} catch (e) {1148goog.log.fine(this.logger_, 'Can not get responseXML: ' + e.message);1149return null;1150}1151};115211531154/**1155* Get the response and evaluates it as JSON from the Xhr object1156* Will only return correct result when called from the context of a callback1157* @param {string=} opt_xssiPrefix Optional XSSI prefix string to use for1158* stripping of the response before parsing. This needs to be set only if1159* your backend server prepends the same prefix string to the JSON response.1160* @throws Error if the response text is invalid JSON.1161* @return {Object|undefined} JavaScript object.1162*/1163goog.net.XhrIo.prototype.getResponseJson = function(opt_xssiPrefix) {1164if (!this.xhr_) {1165return undefined;1166}11671168var responseText = this.xhr_.responseText;1169if (opt_xssiPrefix && responseText.indexOf(opt_xssiPrefix) == 0) {1170responseText = responseText.substring(opt_xssiPrefix.length);1171}11721173return goog.json.parse(responseText);1174};117511761177/**1178* Get the response as the type specificed by {@link #setResponseType}. At time1179* of writing, this is only directly supported in very recent versions of WebKit1180* (10.0.612.1 dev and later). If the field is not supported directly, we will1181* try to emulate it.1182*1183* Emulating the response means following the rules laid out at1184* http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute1185*1186* On browsers with no support for this (Chrome < 10, Firefox < 4, etc), only1187* response types of DEFAULT or TEXT may be used, and the response returned will1188* be the text response.1189*1190* On browsers with Mozilla's draft support for array buffers (Firefox 4, 5),1191* only response types of DEFAULT, TEXT, and ARRAY_BUFFER may be used, and the1192* response returned will be either the text response or the Mozilla1193* implementation of the array buffer response.1194*1195* On browsers will full support, any valid response type supported by the1196* browser may be used, and the response provided by the browser will be1197* returned.1198*1199* @return {*} The response.1200*/1201goog.net.XhrIo.prototype.getResponse = function() {12021203try {1204if (!this.xhr_) {1205return null;1206}1207if ('response' in this.xhr_) {1208return this.xhr_.response;1209}1210switch (this.responseType_) {1211case goog.net.XhrIo.ResponseType.DEFAULT:1212case goog.net.XhrIo.ResponseType.TEXT:1213return this.xhr_.responseText;1214// DOCUMENT and BLOB don't need to be handled here because they are1215// introduced in the same spec that adds the .response field, and would1216// have been caught above.1217// ARRAY_BUFFER needs an implementation for Firefox 4, where it was1218// implemented using a draft spec rather than the final spec.1219case goog.net.XhrIo.ResponseType.ARRAY_BUFFER:1220if ('mozResponseArrayBuffer' in this.xhr_) {1221return this.xhr_.mozResponseArrayBuffer;1222}1223}1224// Fell through to a response type that is not supported on this browser.1225goog.log.error(1226this.logger_, 'Response type ' + this.responseType_ + ' is not ' +1227'supported on this browser');1228return null;1229} catch (e) {1230goog.log.fine(this.logger_, 'Can not get response: ' + e.message);1231return null;1232}1233};123412351236/**1237* Get the value of the response-header with the given name from the Xhr object1238* Will only return correct result when called from the context of a callback1239* and the request has completed1240* @param {string} key The name of the response-header to retrieve.1241* @return {string|undefined} The value of the response-header named key.1242*/1243goog.net.XhrIo.prototype.getResponseHeader = function(key) {1244if (!this.xhr_ || !this.isComplete()) {1245return undefined;1246}12471248var value = this.xhr_.getResponseHeader(key);1249return goog.isNull(value) ? undefined : value;1250};125112521253/**1254* Gets the text of all the headers in the response.1255* Will only return correct result when called from the context of a callback1256* and the request has completed.1257* @return {string} The value of the response headers or empty string.1258*/1259goog.net.XhrIo.prototype.getAllResponseHeaders = function() {1260return this.xhr_ && this.isComplete() ? this.xhr_.getAllResponseHeaders() :1261'';1262};126312641265/**1266* Returns all response headers as a key-value map.1267* Multiple values for the same header key can be combined into one,1268* separated by a comma and a space.1269* Note that the native getResponseHeader method for retrieving a single header1270* does a case insensitive match on the header name. This method does not1271* include any case normalization logic, it will just return a key-value1272* representation of the headers.1273* See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method1274* @return {!Object<string, string>} An object with the header keys as keys1275* and header values as values.1276*/1277goog.net.XhrIo.prototype.getResponseHeaders = function() {1278var headersObject = {};1279var headersArray = this.getAllResponseHeaders().split('\r\n');1280for (var i = 0; i < headersArray.length; i++) {1281if (goog.string.isEmptyOrWhitespace(headersArray[i])) {1282continue;1283}1284var keyValue = goog.string.splitLimit(headersArray[i], ': ', 2);1285if (headersObject[keyValue[0]]) {1286headersObject[keyValue[0]] += ', ' + keyValue[1];1287} else {1288headersObject[keyValue[0]] = keyValue[1];1289}1290}1291return headersObject;1292};129312941295/**1296* Get the value of the response-header with the given name from the Xhr object.1297* As opposed to {@link #getResponseHeader}, this method does not require that1298* the request has completed.1299* @param {string} key The name of the response-header to retrieve.1300* @return {?string} The value of the response-header, or null if it is1301* unavailable.1302*/1303goog.net.XhrIo.prototype.getStreamingResponseHeader = function(key) {1304return this.xhr_ ? this.xhr_.getResponseHeader(key) : null;1305};130613071308/**1309* Gets the text of all the headers in the response. As opposed to1310* {@link #getAllResponseHeaders}, this method does not require that the request1311* has completed.1312* @return {string} The value of the response headers or empty string.1313*/1314goog.net.XhrIo.prototype.getAllStreamingResponseHeaders = function() {1315return this.xhr_ ? this.xhr_.getAllResponseHeaders() : '';1316};131713181319/**1320* Get the last error message1321* @return {goog.net.ErrorCode} Last error code.1322*/1323goog.net.XhrIo.prototype.getLastErrorCode = function() {1324return this.lastErrorCode_;1325};132613271328/**1329* Get the last error message1330* @return {string} Last error message.1331*/1332goog.net.XhrIo.prototype.getLastError = function() {1333return goog.isString(this.lastError_) ? this.lastError_ :1334String(this.lastError_);1335};133613371338/**1339* Adds the last method, status and URI to the message. This is used to add1340* this information to the logging calls.1341* @param {string} msg The message text that we want to add the extra text to.1342* @return {string} The message with the extra text appended.1343* @private1344*/1345goog.net.XhrIo.prototype.formatMsg_ = function(msg) {1346return msg + ' [' + this.lastMethod_ + ' ' + this.lastUri_ + ' ' +1347this.getStatus() + ']';1348};134913501351// Register the xhr handler as an entry point, so that1352// it can be monitored for exception handling, etc.1353goog.debug.entryPointRegistry.register(1354/**1355* @param {function(!Function): !Function} transformer The transforming1356* function.1357*/1358function(transformer) {1359goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ =1360transformer(goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_);1361});136213631364