This article attempts to summarize some experience in working with the
XMLHttpRequest object by showing how to:
Most people are aware of how to make the Internet Explorer 6.0 browser compatible to the others with respect to its support for XMLHttpRequest object.
In order to create a real cross-browser implementation of the XMLHttpRequest object and to be able later to fix native XMLHttpRequest bugs along with implementation of sniffing we should re-implement XMLHttpRequest object from scratch by defining a new object that would incapsulate the native one. Let's define constructor, public properties and public methods. In the listing below there is only one property and one method implementation shown, still this should be enough to catch the idea.
This way we have a prototyped implementation of the XMLHttpRequest object. Let's go further and see how we could enable sniffing.
The implementation should be capable of transparent sniffing of all operations done on all instances of the XMLHttpRequest object. We define custom static members on XMLHttpRequest global object (constructor) with names of public methods prefixed with "on" (onopen, onsend, onabort) plus onreadystatechange. Whenever a method will be invoked, we will first call a corresponding static member on the XMLHttpRequest object (if it is set) on behalf of the instance with arguments passed into the call. Take a look at the following piece of code.
Now we have a cross-browser implementation of XMLHttpRequest object that also allows sniffing of its instances transparently.
The FireBug extension for Firefox does some evil with XMLHttpRequest object by also wrapping it. Having another object wrapped for the second time actually breaks Firebug that in its own turn breaks the page display. The exception you would get has the following content: "win.XMLHttpRequest.wrapped has no properties" Fortunately there is a neat trick - copy the original pointer to the wrapped object.
Since we are using browser native XMLHttpRequest as the underlaying transport, our implementation is still affected by its bugs. In the following paragraphs we will see how all of these annoying bugs can be fixed.
Despite to the fact that most of native XMLHttpRequest object implementations do not differ much in their API, they still differ a lot in their behavior. Some implementations suffer from sever issues such as memory leak in Internet Explorer or missing readystatechange calls for synchronous requests in Gecko. There are other minor issues as well. In this paragraph I'd like to highlight issues considered.
Bug: The instance of XMLHttpRequest doesn't get garbage collected in case you have a reference to the instance or to an other COM object (for example: DOM Node etc.) in its onreadystatechange handler, thus producing runtime memory leaks.
This bug can easily be solved by cleaning the onreadystatechange property when the object readyState has changed to DONE.
Bug: The instance of XMLHttpRequest doesn't get garbage collected on page unload in case you have a reference to the instance or to an other COM object (for example: DOM Node etc.) in its onreadystatechange handler, thus producing inter-page memory leaks.
Solving this bug can be done by registering an onunload handler in which we would clean the onreadystatechange property of the request in progress.
Bug: Internet Explorer does not issue a validation request to the server if resource is available from its cache, no matter what expiration date this file was set to.
To work around the issue we will make an additional request to the server with proper "If-Modified-Since" header set in case the data was found in browser cache (can be verified by checking the presence of "Date" field in the response headers). If the server responds with status 304 (Not modified), we can safely use data loaded from cache.
Bug: When the request is synchronous, no readystatechange is fired.
Thanks to the fact that we have full control over XMLHttpRequest object now, we can easily simulate missing calls to the onreadystatechange handlers. Worth noting, the Firebug also affects behavior of the native XMLHttpRequest object. When Firebug is installed and is enabled it solves the issue partially by sending DONE readystatechange (while still missing the other state changes).
Bug: When the request is aborted, Gecko always fires readystatechange DONE.
To workaround this issue we can simply ignore the readystatechange call (in our implementation) made by Gecko if the abort method was called.
Bug: If the content retrieved by XMLHttpRequest was invalid with respect to XML well-formedness, Gecko returns a DOM document with an error report wrapped instead of null.
To solve that issue, we can simply check if the documentElement of the responseXML body is "parsererror" element, and if it is, reset the property to the value of null.
Bug: Internet Explorer and Gecko web-browsers both issue an unnecessary OPEN readystatechange right after the send method is invoked.
Solution is simple - cache the readyState changes and only dispatch unique ones.
Bug: Internet Explorer does not recognize retrieved XML document as such in case the later was served with application/soap+xml content-type.
Solving the issue is simple - let's parse the responseText into an XML document.
Bug: Safari chokes on sending Document if it was modified dynamically, for example by setting some namespaced attribute.
The issue is fixed by manual serialization (with XMLSerializer) of Node passed into the send method along with setting appropriate Content-Type (application/xml).
Bug: Internet Explorer overrides any custom Content-Type header with "text/xml" value when sending XML nodes.
The only way to approach solving this issue is following previous bugfix - sending serialized Node with proper Content-Type set manually.
The examples given below demonstrate normal programming techniques used by developers when working with the XMLHttpRequest object. You can see that there is no debugging code put into the samples, however whenever the test is executed (by pressing 'click to run!' button) you can see XMLHttpRequest objects activities reported to the "XMLHttpRequest calling log" below. This is enabled by having class handlers set to XMLHttpRequest object.
This test demonstrates an asynchronous GET request call.
show/hide source code
This test demonstrates a synchronous POST request call.
show/hide source code
This test demonstrates a synchronous GET request call.
show/hide source code
This test demonstrates an asynchronous GET request call with abort. See that readyState DONE is not fired.
show/hide source code
Please invoke the examples above and see here the log of operations.
|Browser||Availability||Standard API||Context||Leaks||Send execution sequence||Abort execution sequence
(abort is called after send)
|XMLHttpRequest.js||available||Standard-compliant (*)||instance||no||O, R1, S, R2, R3, R4||O, R1, S, R2, R3, R4||O, R1, S, (R2, R3)?, A, (R4)?|
|Firefox 2.0 / 3.0||available||Missing Constants||handler||no||O, R1, S, R1, R2, R3, R4||O, S||O, R1, S, R1, (R2, R3)?, A, R4|
|Firefox X.0 (Firebug)||available||Missing Constants||handler||no||O, R1, S, R1, R2, R3, R4||O, S, R4||O, R1, S, R1, (R2, R3)?, A, R4|
|Internet Explorer 6.0-||missing (**)||Missing Constants||window||yes||O, R1, S, R1, R2, R3, R4||O, R1, S, R1, R2, R3, R4||O, R1, S, R1, (R2, R3)?, A, (R4)?|
|Internet Explorer 7.0||available||Missing Constants||window||yes||O, R1, S, R1, R2, R3, R4||O, R1, S, R1, R2, R3, R4||O, R1, S, R1, (R2, R3)?, A, (R4)?|
|Opera 9.2||available||Missing Constants||instance||no||O, R1, S, R2, R3, R4||O, R1, S, R2, R3, R4||O, R1, S, (R2, R3)?, A, (R4)?|
|Safari 3.0||available||Missing Constants||instance||no||O, R1, S, R2, R3, R4||O, R1, S, R2, R3, R4||O, R1, S, (R2, R3)?, A, (R4)?|
The source code containing the implementation described above, can be obtained from XMLHttpRequest project.