blob: 9752e217e17b0bea06ee47ab6eecac9dd528bba1 [file] [log] [blame]
/**
* XMLHttpRequest.js Copyright (C) 2011 Sergey Ilinsky (http://www.ilinsky.com)
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This work is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* merchantability or fitness for a particular purpose. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
(function () {
// Save reference to earlier defined object implementation (if any)
var oXMLHttpRequest = window.XMLHttpRequest;
// Define on browser type
var bGecko = !!window.controllers;
var bIE = window.document.all && !window.opera;
var bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
// Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
function fXMLHttpRequest() {
this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
this._listeners = [];
}
// Constructor
function cXMLHttpRequest() {
return new fXMLHttpRequest;
}
cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
// BUGFIX: Firefox with Firebug installed would break pages if not executed
if (bGecko && oXMLHttpRequest.wrapped) {
cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
}
// Constants
cXMLHttpRequest.UNSENT = 0;
cXMLHttpRequest.OPENED = 1;
cXMLHttpRequest.HEADERS_RECEIVED = 2;
cXMLHttpRequest.LOADING = 3;
cXMLHttpRequest.DONE = 4;
// Public Properties
cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
cXMLHttpRequest.prototype.responseText = '';
cXMLHttpRequest.prototype.responseXML = null;
cXMLHttpRequest.prototype.status = 0;
cXMLHttpRequest.prototype.statusText = '';
// Priority proposal
cXMLHttpRequest.prototype.priority = "NORMAL";
// Instance-level Events Handlers
cXMLHttpRequest.prototype.onreadystatechange = null;
// Class-level Events Handlers
cXMLHttpRequest.onreadystatechange = null;
cXMLHttpRequest.onopen = null;
cXMLHttpRequest.onsend = null;
cXMLHttpRequest.onabort = null;
// Public Methods
cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
// Delete headers, required when object is reused
delete this._headers;
// When bAsync parameter value is omitted, use true as default
if (arguments.length < 3) {
bAsync = true;
}
// Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
this._async = bAsync;
// Set the onreadystatechange handler
var oRequest = this;
var nState = this.readyState;
var fOnUnload = null;
// BUGFIX: IE - memory leak on page unload (inter-page leak)
if (bIE && bAsync) {
fOnUnload = function() {
if (nState != cXMLHttpRequest.DONE) {
fCleanTransport(oRequest);
// Safe to abort here since onreadystatechange handler removed
oRequest.abort();
}
};
window.attachEvent("onunload", fOnUnload);
}
// Add method sniffer
if (cXMLHttpRequest.onopen) {
cXMLHttpRequest.onopen.apply(this, arguments);
}
if (arguments.length > 4) {
this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
} else if (arguments.length > 3) {
this._object.open(sMethod, sUrl, bAsync, sUser);
} else {
this._object.open(sMethod, sUrl, bAsync);
}
this.readyState = cXMLHttpRequest.OPENED;
fReadyStateChange(this);
this._object.onreadystatechange = function() {
if (bGecko && !bAsync) {
return;
}
// Synchronize state
oRequest.readyState = oRequest._object.readyState;
fSynchronizeValues(oRequest);
// BUGFIX: Firefox fires unnecessary DONE when aborting
if (oRequest._aborted) {
// Reset readyState to UNSENT
oRequest.readyState = cXMLHttpRequest.UNSENT;
// Return now
return;
}
if (oRequest.readyState == cXMLHttpRequest.DONE) {
// Free up queue
delete oRequest._data;
// Uncomment these lines for bAsync
/**
* if (bAsync) {
* fQueue_remove(oRequest);
* }
*/
fCleanTransport(oRequest);
// Uncomment this block if you need a fix for IE cache
/**
* // BUGFIX: IE - cache issue
* if (!oRequest._object.getResponseHeader("Date")) {
* // Save object to cache
* oRequest._cached = oRequest._object;
*
* // Instantiate a new transport object
* cXMLHttpRequest.call(oRequest);
*
* // Re-send request
* if (sUser) {
* if (sPassword) {
* oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
* } else {
* oRequest._object.open(sMethod, sUrl, bAsync);
* }
*
* oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
* // Copy headers set
* if (oRequest._headers) {
* for (var sHeader in oRequest._headers) {
* // Some frameworks prototype objects with functions
* if (typeof oRequest._headers[sHeader] == "string") {
* oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
* }
* }
* }
* oRequest._object.onreadystatechange = function() {
* // Synchronize state
* oRequest.readyState = oRequest._object.readyState;
*
* if (oRequest._aborted) {
* //
* oRequest.readyState = cXMLHttpRequest.UNSENT;
*
* // Return
* return;
* }
*
* if (oRequest.readyState == cXMLHttpRequest.DONE) {
* // Clean Object
* fCleanTransport(oRequest);
*
* // get cached request
* if (oRequest.status == 304) {
* oRequest._object = oRequest._cached;
* }
*
* //
* delete oRequest._cached;
*
* //
* fSynchronizeValues(oRequest);
*
* //
* fReadyStateChange(oRequest);
*
* // BUGFIX: IE - memory leak in interrupted
* if (bIE && bAsync) {
* window.detachEvent("onunload", fOnUnload);
* }
*
* }
* };
* oRequest._object.send(null);
*
* // Return now - wait until re-sent request is finished
* return;
* };
*/
// BUGFIX: IE - memory leak in interrupted
if (bIE && bAsync) {
window.detachEvent("onunload", fOnUnload);
}
// BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
if (nState != oRequest.readyState) {
fReadyStateChange(oRequest);
}
nState = oRequest.readyState;
}
};
};
cXMLHttpRequest.prototype.send = function(vData) {
// Add method sniffer
if (cXMLHttpRequest.onsend) {
cXMLHttpRequest.onsend.apply(this, arguments);
}
if (!arguments.length) {
vData = null;
}
// BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
// BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
// BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
if (vData && vData.nodeType) {
vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
if (!this._headers["Content-Type"]) {
this._object.setRequestHeader("Content-Type", "application/xml");
}
}
this._data = vData;
/**
* // Add to queue
* if (this._async) {
* fQueue_add(this);
* } else { */
fXMLHttpRequest_send(this);
/**
* }
*/
};
cXMLHttpRequest.prototype.abort = function() {
// Add method sniffer
if (cXMLHttpRequest.onabort) {
cXMLHttpRequest.onabort.apply(this, arguments);
}
// BUGFIX: Gecko - unnecessary DONE when aborting
if (this.readyState > cXMLHttpRequest.UNSENT) {
this._aborted = true;
}
this._object.abort();
// BUGFIX: IE - memory leak
fCleanTransport(this);
this.readyState = cXMLHttpRequest.UNSENT;
delete this._data;
/* if (this._async) {
* fQueue_remove(this);
* }
*/
};
cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
return this._object.getAllResponseHeaders();
};
cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
return this._object.getResponseHeader(sName);
};
cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
// BUGFIX: IE - cache issue
if (!this._headers) {
this._headers = {};
}
this._headers[sName] = sValue;
return this._object.setRequestHeader(sName, sValue);
};
// EventTarget interface implementation
cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) {
if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) {
return;
}
}
// Add listener
this._listeners.push([sName, fHandler, bUseCapture]);
};
cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) {
if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) {
break;
}
}
// Remove listener
if (oListener) {
this._listeners.splice(nIndex, 1);
}
};
cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
var oEventPseudo = {
'type': oEvent.type,
'target': this,
'currentTarget': this,
'eventPhase': 2,
'bubbles': oEvent.bubbles,
'cancelable': oEvent.cancelable,
'timeStamp': oEvent.timeStamp,
'stopPropagation': function() {}, // There is no flow
'preventDefault': function() {}, // There is no default action
'initEvent': function() {} // Original event object should be initialized
};
// Execute onreadystatechange
if (oEventPseudo.type == "readystatechange" && this.onreadystatechange) {
(this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
}
// Execute listeners
for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) {
if (oListener[0] == oEventPseudo.type && !oListener[2]) {
(oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
}
}
};
//
cXMLHttpRequest.prototype.toString = function() {
return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
};
cXMLHttpRequest.toString = function() {
return '[' + "XMLHttpRequest" + ']';
};
/**
* // Queue manager
* var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
* aQueueRunning = [];
* function fQueue_add(oRequest) {
* oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
* //
* setTimeout(fQueue_process);
* };
*
* function fQueue_remove(oRequest) {
* for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
* if (bFound) {
* aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
* } else {
* if (aQueueRunning[nIndex] == oRequest) {
* bFound = true;
* }
* }
*
* if (bFound) {
* aQueueRunning.length--;
* }
*
*
* //
* setTimeout(fQueue_process);
* };
*
* function fQueue_process() {
* if (aQueueRunning.length < 6) {
* for (var sPriority in oQueuePending) {
* if (oQueuePending[sPriority].length) {
* var oRequest = oQueuePending[sPriority][0];
* oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
* //
* aQueueRunning.push(oRequest);
* // Send request
* fXMLHttpRequest_send(oRequest);
* break;
* }
* }
* }
* };
*/
// Helper function
function fXMLHttpRequest_send(oRequest) {
oRequest._object.send(oRequest._data);
// BUGFIX: Gecko - missing readystatechange calls in synchronous requests
if (bGecko && !oRequest._async) {
oRequest.readyState = cXMLHttpRequest.OPENED;
// Synchronize state
fSynchronizeValues(oRequest);
// Simulate missing states
while (oRequest.readyState < cXMLHttpRequest.DONE) {
oRequest.readyState++;
fReadyStateChange(oRequest);
// Check if we are aborted
if (oRequest._aborted) {
return;
}
}
}
}
function fReadyStateChange(oRequest) {
// Sniffing code
if (cXMLHttpRequest.onreadystatechange){
cXMLHttpRequest.onreadystatechange.apply(oRequest);
}
// Fake event
oRequest.dispatchEvent({
'type': "readystatechange",
'bubbles': false,
'cancelable': false,
'timeStamp': new Date + 0
});
}
function fGetDocument(oRequest) {
var oDocument = oRequest.responseXML;
var sResponse = oRequest.responseText;
// Try parsing responseText
if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
oDocument.async = false;
oDocument.validateOnParse = false;
oDocument.loadXML(sResponse);
}
// Check if there is no error in document
if (oDocument){
if ((bIE && oDocument.parseError !== 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror")) {
return null;
}
}
return oDocument;
}
function fSynchronizeValues(oRequest) {
try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
try { oRequest.status = oRequest._object.status; } catch (e) {}
try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
}
function fCleanTransport(oRequest) {
// BUGFIX: IE - memory leak (on-page leak)
oRequest._object.onreadystatechange = new window.Function;
}
// Internet Explorer 5.0 (missing apply)
if (!window.Function.prototype.apply) {
window.Function.prototype.apply = function(oRequest, oArguments) {
if (!oArguments) {
oArguments = [];
}
oRequest.__func = this;
oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
delete oRequest.__func;
};
}
// Register new object with window
window.XMLHttpRequest = cXMLHttpRequest;
})();