@qooxdoo/framework
Version:
The JS Framework for Coders
1,187 lines (1,008 loc) • 34 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2011 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Tristan Koch (tristankoch)
************************************************************************ */
/**
* A wrapper of the XMLHttpRequest host object (or equivalent). The interface is
* similar to <a href="http://www.w3.org/TR/XMLHttpRequest/">XmlHttpRequest</a>.
*
* Hides browser inconsistencies and works around bugs found in popular
* implementations.
*
* <div class="desktop">
* Example:
*
* <pre class="javascript">
* var req = new qx.bom.request.Xhr();
* req.onload = function() {
* // Handle data received
* req.responseText;
* }
*
* req.open("GET", url);
* req.send();
* </pre>
*
* Example for binary data:
*
* <pre class="javascript">
* var req = new qx.bom.request.Xhr();
* req.onload = function() {
* // Handle data received
* var blob = req.response;
* img.src = URL.createObjectURL(blob);
* }
*
* req.open("GET", url);
* req.responseType = "blob";
* req.send();
* </pre>
* </div>
*
* @ignore(XDomainRequest)
* @ignore(qx.event, qx.event.GlobalError.*)
*
* @require(qx.bom.request.Xhr#open)
* @require(qx.bom.request.Xhr#send)
* @require(qx.bom.request.Xhr#on)
* @require(qx.bom.request.Xhr#onreadystatechange)
* @require(qx.bom.request.Xhr#onload)
* @require(qx.bom.request.Xhr#onloadend)
* @require(qx.bom.request.Xhr#onerror)
* @require(qx.bom.request.Xhr#onabort)
* @require(qx.bom.request.Xhr#ontimeout)
* @require(qx.bom.request.Xhr#setRequestHeader)
* @require(qx.bom.request.Xhr#getAllResponseHeaders)
* @require(qx.bom.request.Xhr#getRequest)
* @require(qx.bom.request.Xhr#overrideMimeType)
* @require(qx.bom.request.Xhr#dispose)
* @require(qx.bom.request.Xhr#isDisposed)
*
* @group (IO)
*/
qx.Bootstrap.define("qx.bom.request.Xhr",
{
extend: Object,
implement: [ qx.core.IDisposable ],
construct: function() {
var boundFunc = qx.Bootstrap.bind(this.__onNativeReadyStateChange, this);
// GlobalError shouldn't be included in qx.Website builds so use it
// if it's available but otherwise ignore it (see ignore stated above).
if (qx.event && qx.event.GlobalError && qx.event.GlobalError.observeMethod) {
this.__onNativeReadyStateChangeBound = qx.event.GlobalError.observeMethod(boundFunc);
} else {
this.__onNativeReadyStateChangeBound = boundFunc;
}
this.__onNativeAbortBound = qx.Bootstrap.bind(this.__onNativeAbort, this);
this.__onNativeProgressBound = qx.Bootstrap.bind(this.__onNativeProgress, this);
this.__onTimeoutBound = qx.Bootstrap.bind(this.__onTimeout, this);
this.__initNativeXhr();
this._emitter = new qx.event.Emitter();
// BUGFIX: IE
// IE keeps connections alive unless aborted on unload
if (window.attachEvent) {
this.__onUnloadBound = qx.Bootstrap.bind(this.__onUnload, this);
window.attachEvent("onunload", this.__onUnloadBound);
}
},
statics :
{
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4
},
events : {
/** Fired at ready state changes. */
"readystatechange" : "qx.bom.request.Xhr",
/** Fired on error. */
"error" : "qx.bom.request.Xhr",
/** Fired at loadend. */
"loadend" : "qx.bom.request.Xhr",
/** Fired on timeouts. */
"timeout" : "qx.bom.request.Xhr",
/** Fired when the request is aborted. */
"abort" : "qx.bom.request.Xhr",
/** Fired on successful retrieval. */
"load" : "qx.bom.request.Xhr",
/** Fired on progress. */
"progress" : "qx.bom.request.Xhr"
},
members :
{
/*
---------------------------------------------------------------------------
PUBLIC
---------------------------------------------------------------------------
*/
/**
* @type {Number} Ready state.
*
* States can be:
* UNSENT: 0,
* OPENED: 1,
* HEADERS_RECEIVED: 2,
* LOADING: 3,
* DONE: 4
*/
readyState: 0,
/**
* @type {String} The response of the request as text.
*/
responseText: "",
/**
* @type {Object} The response of the request as a Document object.
*/
response: null,
/**
* @type {Object} The response of the request as object.
*/
responseXML: null,
/**
* @type {Number} The HTTP status code.
*/
status: 0,
/**
* @type {String} The HTTP status text.
*/
statusText: "",
/**
* @type {String} The response Type to use in the request
*/
responseType: "",
/**
* @type {Number} Timeout limit in milliseconds.
*
* 0 (default) means no timeout. Not supported for synchronous requests.
*/
timeout: 0,
/**
* @type {Object} Wrapper to store data of the progress event which contains the keys
<code>lengthComputable</code>, <code>loaded</code> and <code>total</code>
*/
progress: null,
/**
* Initializes (prepares) request.
*
* @ignore(XDomainRequest)
*
* @param method {String?"GET"}
* The HTTP method to use.
* @param url {String}
* The URL to which to send the request.
* @param async {Boolean?true}
* Whether or not to perform the operation asynchronously.
* @param user {String?null}
* Optional user name to use for authentication purposes.
* @param password {String?null}
* Optional password to use for authentication purposes.
*/
open: function(method, url, async, user, password) {
this.__checkDisposed();
// Mimick native behavior
if (typeof url === "undefined") {
throw new Error("Not enough arguments");
} else if (typeof method === "undefined") {
method = "GET";
}
// Reset flags that may have been set on previous request
this.__abort = false;
this.__send = false;
this.__conditional = false;
// Store URL for later checks
this.__url = url;
if (typeof async == "undefined") {
async = true;
}
this.__async = async;
// Default values according to spec.
this.status = 0;
this.statusText = this.responseText = "";
this.responseXML = null;
this.response = null;
// BUGFIX
// IE < 9 and FF < 3.5 cannot reuse the native XHR to issue many requests
if (!this.__supportsManyRequests() && this.readyState > qx.bom.request.Xhr.UNSENT) {
// XmlHttpRequest Level 1 requires open() to abort any pending requests
// associated to the object. Since we're dealing with a new object here,
// we have to emulate this behavior. Moreover, allow old native XHR to be garbage collected
//
// Dispose and abort.
//
this.dispose();
// Replace the underlying native XHR with a new one that can
// be used to issue new requests.
this.__initNativeXhr();
}
// Restore handler in case it was removed before
this.__nativeXhr.onreadystatechange = this.__onNativeReadyStateChangeBound;
try {
if (qx.core.Environment.get("qx.debug.io")) {
qx.Bootstrap.debug(qx.bom.request.Xhr, "Open native request with method: " +
method + ", url: " + url + ", async: " + async);
}
this.__nativeXhr.open(method, url, async, user, password);
// BUGFIX: IE, Firefox < 3.5
// Some browsers do not support Cross-Origin Resource Sharing (CORS)
// for XMLHttpRequest. Instead, an exception is thrown even for async requests
// if URL is cross-origin (as per XHR level 1). Use the proprietary XDomainRequest
// if available (supports CORS) and handle error (if there is one) this
// way. Otherwise just assume network error.
//
// Basically, this allows to detect network errors.
} catch(OpenError) {
// Only work around exceptions caused by cross domain request attempts
if (!qx.util.Request.isCrossDomain(url)) {
// Is same origin
throw OpenError;
}
if (!this.__async) {
this.__openError = OpenError;
}
if (this.__async) {
// Try again with XDomainRequest
// (Success case not handled on purpose)
// - IE 9
if (window.XDomainRequest) {
this.readyState = 4;
this.__nativeXhr = new XDomainRequest();
this.__nativeXhr.onerror = qx.Bootstrap.bind(function() {
this._emit("readystatechange");
this._emit("error");
this._emit("loadend");
}, this);
if (qx.core.Environment.get("qx.debug.io")) {
qx.Bootstrap.debug(qx.bom.request.Xhr, "Retry open native request with method: " +
method + ", url: " + url + ", async: " + async);
}
this.__nativeXhr.open(method, url, async, user, password);
return;
}
// Access denied
// - IE 6: -2146828218
// - IE 7: -2147024891
// - Legacy Firefox
window.setTimeout(qx.Bootstrap.bind(function() {
if (this.__disposed) {
return;
}
this.readyState = 4;
this._emit("readystatechange");
this._emit("error");
this._emit("loadend");
}, this));
}
}
// BUGFIX: IE < 9
// IE < 9 tends to cache overly aggressive. This may result in stale
// representations. Force validating freshness of cached representation.
if (qx.core.Environment.get("engine.name") === "mshtml" &&
qx.core.Environment.get("browser.documentmode") < 9 &&
this.__nativeXhr.readyState > 0) {
this.__nativeXhr.setRequestHeader("If-Modified-Since", "-1");
}
// BUGFIX: Firefox
// Firefox < 4 fails to trigger onreadystatechange OPENED for sync requests
if (qx.core.Environment.get("engine.name") === "gecko" &&
parseInt(qx.core.Environment.get("engine.version"), 10) < 2 &&
!this.__async) {
// Native XHR is already set to readyState DONE. Fake readyState
// and call onreadystatechange manually.
this.readyState = qx.bom.request.Xhr.OPENED;
this._emit("readystatechange");
}
},
/**
* Sets an HTTP request header to be used by the request.
*
* Note: The request must be initialized before using this method.
*
* @param key {String}
* The name of the header whose value is to be set.
* @param value {String}
* The value to set as the body of the header.
* @return {qx.bom.request.Xhr} Self for chaining.
*/
setRequestHeader: function(key, value) {
this.__checkDisposed();
// Detect conditional requests
if (key == "If-Match" || key == "If-Modified-Since" ||
key == "If-None-Match" || key == "If-Range") {
this.__conditional = true;
}
this.__nativeXhr.setRequestHeader(key, value);
return this;
},
/**
* Sends request.
*
* @param data {String|Document?null}
* Optional data to send.
* @return {qx.bom.request.Xhr} Self for chaining.
*/
send: function(data) {
this.__checkDisposed();
// BUGFIX: IE & Firefox < 3.5
// For sync requests, some browsers throw error on open()
// while it should be on send()
//
if (!this.__async && this.__openError) {
throw this.__openError;
}
// BUGFIX: Opera
// On network error, Opera stalls at readyState HEADERS_RECEIVED
// This violates the spec. See here http://www.w3.org/TR/XMLHttpRequest2/#send
// (Section: If there is a network error)
//
// To fix, assume a default timeout of 10 seconds. Note: The "error"
// event will be fired correctly, because the error flag is inferred
// from the statusText property. Of course, compared to other
// browsers there is an additional call to ontimeout(), but this call
// should not harm.
//
if (qx.core.Environment.get("engine.name") === "opera" &&
this.timeout === 0) {
this.timeout = 10000;
}
// Timeout
if (this.timeout > 0) {
this.__timerId = window.setTimeout(this.__onTimeoutBound, this.timeout);
}
// BUGFIX: Firefox 2
// "NS_ERROR_XPC_NOT_ENOUGH_ARGS" when calling send() without arguments
data = typeof data == "undefined" ? null : data;
// Whitelisting the allowed data types regarding the spec
// -> http://www.w3.org/TR/XMLHttpRequest2/#the-send-method
// All other data input will be transformed to a string to e.g. prevent
// an SendError in Firefox (at least <= 31) and to harmonize it with the
// behaviour of all other browsers (Chrome, IE and Safari)
var dataType = qx.Bootstrap.getClass(data);
data = (data !== null && this.__dataTypeWhiteList.indexOf(dataType) === -1) ? data.toString() : data;
// Some browsers may throw an error when sending of async request fails.
// This violates the spec which states only sync requests should.
try {
if (qx.core.Environment.get("qx.debug.io")) {
qx.Bootstrap.debug(qx.bom.request.Xhr, "Send native request");
}
if (this.__async) {
this.__nativeXhr.responseType = this.responseType;
}
this.__nativeXhr.send(data);
} catch(SendError) {
if (!this.__async) {
throw SendError;
}
// BUGFIX
// Some browsers throws error when file not found via file:// protocol.
// Synthesize readyState changes.
if (this._getProtocol() === "file:") {
this.readyState = 2;
this.__readyStateChange();
var that = this;
window.setTimeout(function() {
if (that.__disposed) {
return;
}
that.readyState = 3;
that.__readyStateChange();
that.readyState = 4;
that.__readyStateChange();
});
}
}
// BUGFIX: Firefox
// Firefox fails to trigger onreadystatechange DONE for sync requests
if (qx.core.Environment.get("engine.name") === "gecko" && !this.__async) {
// Properties all set, only missing native readystatechange event
this.__onNativeReadyStateChange();
}
// Set send flag
this.__send = true;
return this;
},
/**
* Abort request - i.e. cancels any network activity.
*
* Note:
* On Windows 7 every browser strangely skips the loading phase
* when this method is called (because readyState never gets 3).
*
* So keep this in mind if you rely on the phases which are
* passed through. They will be "opened", "sent", "abort"
* instead of normally "opened", "sent", "loading", "abort".
*
* @return {qx.bom.request.Xhr} Self for chaining.
*/
abort: function() {
this.__checkDisposed();
this.__abort = true;
this.__nativeXhr.abort();
if (this.__nativeXhr && this.readyState !== qx.bom.request.Xhr.DONE) {
this.readyState = this.__nativeXhr.readyState;
}
return this;
},
/**
* Helper to emit events and call the callback methods.
* @param event {String} The name of the event.
*/
_emit: function(event) {
if (this["on" + event]) {
this["on" + event]();
}
this._emitter.emit(event, this);
},
/**
* Event handler for XHR event that fires at every state change.
*
* Replace with custom method to get informed about the communication progress.
*/
onreadystatechange: function() {},
/**
* Event handler for XHR event "load" that is fired on successful retrieval.
*
* Note: This handler is called even when the HTTP status indicates an error.
*
* Replace with custom method to listen to the "load" event.
*/
onload: function() {},
/**
* Event handler for XHR event "loadend" that is fired on retrieval.
*
* Note: This handler is called even when a network error (or similar)
* occurred.
*
* Replace with custom method to listen to the "loadend" event.
*/
onloadend: function() {},
/**
* Event handler for XHR event "error" that is fired on a network error.
*
* Replace with custom method to listen to the "error" event.
*/
onerror: function() {},
/**
* Event handler for XHR event "abort" that is fired when request
* is aborted.
*
* Replace with custom method to listen to the "abort" event.
*/
onabort: function() {},
/**
* Event handler for XHR event "timeout" that is fired when timeout
* interval has passed.
*
* Replace with custom method to listen to the "timeout" event.
*/
ontimeout: function() {},
/**
* Event handler for XHR event "progress".
*
* Replace with custom method to listen to the "progress" event.
*/
onprogress: function() {},
/**
* Add an event listener for the given event name.
*
* @param name {String} The name of the event to listen to.
* @param listener {Function} The function to execute when the event is fired
* @param ctx {var?} The context of the listener.
* @return {qx.bom.request.Xhr} Self for chaining.
*/
on: function(name, listener, ctx) {
this._emitter.on(name, listener, ctx);
return this;
},
/**
* Get a single response header from response.
*
* @param header {String}
* Key of the header to get the value from.
* @return {String}
* Response header.
*/
getResponseHeader: function(header) {
this.__checkDisposed();
if (qx.core.Environment.get("browser.documentmode") === 9 &&
this.__nativeXhr.aborted)
{
return "";
}
return this.__nativeXhr.getResponseHeader(header);
},
/**
* Get all response headers from response.
*
* @return {String} All response headers.
*/
getAllResponseHeaders: function() {
this.__checkDisposed();
if (qx.core.Environment.get("browser.documentmode") === 9 &&
this.__nativeXhr.aborted)
{
return "";
}
return this.__nativeXhr.getAllResponseHeaders();
},
/**
* Overrides the MIME type returned by the server
* and must be called before @send()@.
*
* Note:
*
* * IE doesn't support this method so in this case an Error is thrown.
* * after calling this method @getResponseHeader("Content-Type")@
* may return the original (Firefox 23, IE 10, Safari 6) or
* the overridden content type (Chrome 28+, Opera 15+).
*
*
* @param mimeType {String} The mimeType for overriding.
* @return {qx.bom.request.Xhr} Self for chaining.
*/
overrideMimeType: function(mimeType) {
this.__checkDisposed();
if (this.__nativeXhr.overrideMimeType) {
this.__nativeXhr.overrideMimeType(mimeType);
} else {
throw new Error("Native XHR object doesn't support overrideMimeType.");
}
return this;
},
/**
* Get wrapped native XMLHttpRequest (or equivalent).
*
* Can be XMLHttpRequest or ActiveX.
*
* @return {Object} XMLHttpRequest or equivalent.
*/
getRequest: function() {
return this.__nativeXhr;
},
/*
---------------------------------------------------------------------------
HELPER
---------------------------------------------------------------------------
*/
/**
* Dispose object and wrapped native XHR.
* @return {Boolean} <code>true</code> if the object was successfully disposed
*/
dispose: function() {
if (this.__disposed) {
return false;
}
window.clearTimeout(this.__timerId);
// Remove unload listener in IE. Aborting on unload is no longer required
// for this instance.
if (window.detachEvent) {
window.detachEvent("onunload", this.__onUnloadBound);
}
// May fail in IE
try {
this.__nativeXhr.onreadystatechange;
} catch(PropertiesNotAccessable) {
return false;
}
// Clear out listeners
var noop = function() {};
this.__nativeXhr.onreadystatechange = noop;
this.__nativeXhr.onload = noop;
this.__nativeXhr.onerror = noop;
this.__nativeXhr.onprogress = noop;
// Abort any network activity
this.abort();
// Remove reference to native XHR
this.__nativeXhr = null;
this.__disposed = true;
return true;
},
/**
* Check if the request has already beed disposed.
* @return {Boolean} <code>true</code>, if the request has been disposed.
*/
isDisposed : function() {
return !!this.__disposed;
},
/*
---------------------------------------------------------------------------
PROTECTED
---------------------------------------------------------------------------
*/
/**
* Create XMLHttpRequest (or equivalent).
*
* @return {Object} XMLHttpRequest or equivalent.
*/
_createNativeXhr: function() {
var xhr = qx.core.Environment.get("io.xhr");
if (xhr === "xhr") {
return new XMLHttpRequest();
}
if (xhr == "activex") {
return new window.ActiveXObject("Microsoft.XMLHTTP");
}
qx.Bootstrap.error(this, "No XHR support available.");
},
/**
* Get protocol of requested URL.
*
* @return {String} The used protocol.
*/
_getProtocol: function() {
var url = this.__url;
var protocolRe = /^(\w+:)\/\//;
// Could be http:// from file://
if (url !== null && url.match) {
var match = url.match(protocolRe);
if (match && match[1]) {
return match[1];
}
}
return window.location.protocol;
},
/*
---------------------------------------------------------------------------
PRIVATE
---------------------------------------------------------------------------
*/
/**
* @type {Object} XMLHttpRequest or equivalent.
*/
__nativeXhr: null,
/**
* @type {Boolean} Whether request is async.
*/
__async: null,
/**
* @type {Function} Bound __onNativeReadyStateChange handler.
*/
__onNativeReadyStateChangeBound: null,
/**
* @type {Function} Bound __onNativeAbort handler.
*/
__onNativeAbortBound: null,
/**
* @type {Function} Bound __onNativeProgress handler.
*/
__onNativeProgressBound: null,
/**
* @type {Function} Bound __onUnload handler.
*/
__onUnloadBound: null,
/**
* @type {Function} Bound __onTimeout handler.
*/
__onTimeoutBound: null,
/**
* @type {Boolean} Send flag
*/
__send: null,
/**
* @type {String} Requested URL
*/
__url: null,
/**
* @type {Boolean} Abort flag
*/
__abort: null,
/**
* @type {Boolean} Timeout flag
*/
__timeout: null,
/**
* @type {Boolean} Whether object has been disposed.
*/
__disposed: null,
/**
* @type {Number} ID of timeout timer.
*/
__timerId: null,
/**
* @type {Error} Error thrown on open, if any.
*/
__openError: null,
/**
* @type {Boolean} Conditional get flag
*/
__conditional: null,
/**
* @type {Array} Whitelist with all allowed data types for the request payload
*/
__dataTypeWhiteList: null,
/**
* Init native XHR.
*/
__initNativeXhr: function() {
// Create native XHR or equivalent and hold reference
this.__nativeXhr = this._createNativeXhr();
// Track native ready state changes
this.__nativeXhr.onreadystatechange = this.__onNativeReadyStateChangeBound;
// Track native abort, when supported
if (qx.Bootstrap.getClass(this.__nativeXhr.onabort) !== "Undefined") {
this.__nativeXhr.onabort = this.__onNativeAbortBound;
}
// Track native progress, when supported
if (qx.Bootstrap.getClass(this.__nativeXhr.onprogress) !== "Undefined") {
this.__nativeXhr.onprogress = this.__onNativeProgressBound;
this.progress = {
lengthComputable: false,
loaded: 0,
total: 0
};
}
// Reset flags
this.__disposed = this.__send = this.__abort = false;
// Initialize data white list
this.__dataTypeWhiteList = [ "ArrayBuffer", "Blob", "File", "HTMLDocument", "String", "FormData" ];
},
/**
* Track native abort.
*
* In case the end user cancels the request by other
* means than calling abort().
*/
__onNativeAbort: function() {
// When the abort that triggered this method was not a result from
// calling abort()
if (!this.__abort) {
this.abort();
}
},
/**
* Track native progress event.
@param e {Event} The native progress event.
*/
__onNativeProgress: function(e) {
this.progress.lengthComputable = e.lengthComputable;
this.progress.loaded = e.loaded;
this.progress.total = e.total;
this._emit("progress");
},
/**
* Handle native onreadystatechange.
*
* Calls user-defined function onreadystatechange on each
* state change and syncs the XHR status properties.
*/
__onNativeReadyStateChange: function() {
var nxhr = this.__nativeXhr,
propertiesReadable = true;
if (qx.core.Environment.get("qx.debug.io")) {
qx.Bootstrap.debug(qx.bom.request.Xhr, "Received native readyState: " + nxhr.readyState);
}
// BUGFIX: IE, Firefox
// onreadystatechange() is called twice for readyState OPENED.
//
// Call onreadystatechange only when readyState has changed.
if (this.readyState == nxhr.readyState) {
return;
}
// Sync current readyState
this.readyState = nxhr.readyState;
// BUGFIX: IE
// Superfluous onreadystatechange DONE when aborting OPENED
// without send flag
if (this.readyState === qx.bom.request.Xhr.DONE &&
this.__abort && !this.__send) {
return;
}
// BUGFIX: IE
// IE fires onreadystatechange HEADERS_RECEIVED and LOADING when sync
//
// According to spec, only onreadystatechange OPENED and DONE should
// be fired.
if (!this.__async && (nxhr.readyState == 2 || nxhr.readyState == 3)) {
return;
}
// Default values according to spec.
this.status = 0;
this.statusText = this.responseText = "";
this.responseXML = null;
this.response = null;
if (this.readyState >= qx.bom.request.Xhr.HEADERS_RECEIVED) {
// In some browsers, XHR properties are not readable
// while request is in progress.
try {
this.status = nxhr.status;
this.statusText = nxhr.statusText;
this.response = nxhr.response;
if ((this.responseType === "") || (this.responseType === "text")) {
this.responseText = nxhr.responseText;
}
if ((this.responseType === "") || (this.responseType === "document")) {
this.responseXML = nxhr.responseXML;
}
} catch(XhrPropertiesNotReadable) {
propertiesReadable = false;
}
if (propertiesReadable) {
this.__normalizeStatus();
this.__normalizeResponseXML();
}
}
this.__readyStateChange();
// BUGFIX: IE
// Memory leak in XMLHttpRequest (on-page)
if (this.readyState == qx.bom.request.Xhr.DONE) {
// Allow garbage collecting of native XHR
if (nxhr) {
nxhr.onreadystatechange = function() {};
}
}
},
/**
* Handle readystatechange. Called internally when readyState is changed.
*/
__readyStateChange: function() {
// Cancel timeout before invoking handlers because they may throw
if (this.readyState === qx.bom.request.Xhr.DONE) {
// Request determined DONE. Cancel timeout.
window.clearTimeout(this.__timerId);
}
// Always fire "readystatechange"
this._emit("readystatechange");
if (this.readyState === qx.bom.request.Xhr.DONE) {
this.__readyStateChangeDone();
}
},
/**
* Handle readystatechange. Called internally by
* {@link #__readyStateChange} when readyState is DONE.
*/
__readyStateChangeDone: function() {
// Fire "timeout" if timeout flag is set
if (this.__timeout) {
this._emit("timeout");
// BUGFIX: Opera
// Since Opera does not fire "error" on network error, fire additional
// "error" on timeout (may well be related to network error)
if (qx.core.Environment.get("engine.name") === "opera") {
this._emit("error");
}
this.__timeout = false;
// Fire either "abort", "load" or "error"
} else {
if (this.__abort) {
this._emit("abort");
} else{
if (this.__isNetworkError()) {
this._emit("error");
} else {
this._emit("load");
}
}
}
// Always fire "onloadend" when DONE
this._emit("loadend");
},
/**
* Check for network error.
*
* @return {Boolean} Whether a network error occurred.
*/
__isNetworkError: function() {
var error;
// Infer the XHR internal error flag from statusText when not aborted.
// See http://www.w3.org/TR/XMLHttpRequest2/#error-flag and
// http://www.w3.org/TR/XMLHttpRequest2/#the-statustext-attribute
//
// With file://, statusText is always falsy. Assume network error when
// response is empty.
if (this._getProtocol() === "file:") {
error = !this.responseText;
} else {
error = this.status === 0;
}
return error;
},
/**
* Handle faked timeout.
*/
__onTimeout: function() {
// Basically, mimick http://www.w3.org/TR/XMLHttpRequest2/#timeout-error
var nxhr = this.__nativeXhr;
this.readyState = qx.bom.request.Xhr.DONE;
// Set timeout flag
this.__timeout = true;
// No longer consider request. Abort.
nxhr.aborted = true;
nxhr.abort();
this.responseText = "";
this.responseXML = null;
// Signal readystatechange
this.__readyStateChange();
},
/**
* Normalize status property across browsers.
*/
__normalizeStatus: function() {
var isDone = this.readyState === qx.bom.request.Xhr.DONE;
// BUGFIX: Most browsers
// Most browsers tell status 0 when it should be 200 for local files
if (this._getProtocol() === "file:" && this.status === 0 && isDone) {
if (!this.__isNetworkError()) {
this.status = 200;
}
}
// BUGFIX: IE
// IE sometimes tells 1223 when it should be 204
if (this.status === 1223) {
this.status = 204;
}
// BUGFIX: Opera
// Opera tells 0 for conditional requests when it should be 304
//
// Detect response to conditional request that signals fresh cache.
if (qx.core.Environment.get("engine.name") === "opera") {
if (
isDone && // Done
this.__conditional && // Conditional request
!this.__abort && // Not aborted
this.status === 0 // But status 0!
) {
this.status = 304;
}
}
},
/**
* Normalize responseXML property across browsers.
*/
__normalizeResponseXML: function() {
// BUGFIX: IE
// IE does not recognize +xml extension, resulting in empty responseXML.
//
// Check if Content-Type is +xml, verify missing responseXML then parse
// responseText as XML.
if (qx.core.Environment.get("engine.name") == "mshtml" &&
(this.getResponseHeader("Content-Type") || "").match(/[^\/]+\/[^\+]+\+xml/) &&
this.responseXML && !this.responseXML.documentElement) {
var dom = new window.ActiveXObject("Microsoft.XMLDOM");
dom.async = false;
dom.validateOnParse = false;
dom.loadXML(this.responseText);
this.responseXML = dom;
}
},
/**
* Handler for native unload event.
*/
__onUnload: function() {
try {
// Abort and dispose
if (this) {
this.dispose();
}
} catch(e) {}
},
/**
* Helper method to determine whether browser supports reusing the
* same native XHR to send more requests.
* @return {Boolean} <code>true</code> if request object reuse is supported
*/
__supportsManyRequests: function() {
var name = qx.core.Environment.get("engine.name");
var version = qx.core.Environment.get("browser.version");
return !(name == "mshtml" && version < 9 ||
name == "gecko" && version < 3.5);
},
/**
* Throw when already disposed.
*/
__checkDisposed: function() {
if (this.__disposed) {
throw new Error("Already disposed");
}
}
},
defer: function() {
qx.core.Environment.add("qx.debug.io", false);
}
});