UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,187 lines (1,008 loc) 34 kB
/* ************************************************************************ 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); } });