IE7, XmlHttp, and Nirvana
We make extensive use of the principles of AJAX on our products, as they deliver an excellent user experience. However, with IE7 we started encounterung an error that was anything but excellent – IE would lock up if the user too quickly navigated from one entity to another from within our Split View control. Because our Split View control uses AJAX calls to load the panels behind the scenes, if the asynchronous requests got too backed up IE would lock-up and the CPU usage would spike until the application was forcibly closed by the user (usually via Task Manager).
At the root of most AJAX calls it the reliance on the XmlHttp object, which traditionally is instantiated through JavaScript (the "J" in AJAX) by means of the ActiveX Object named Microsoft.XmlHttp. However, IE7 now includes a "native" XMLHttpRequest (XHR) object that supersedes the ActiveX version. Whether or not the IE7 version is really just a wrapper to the ActiveX version is a matter of debate, but for the purposes of this solution it is a moot point. Of course there is no guarantee your users are using IE7, so we must now code for both scenarios – first trying the IE7 approach and then falling back to the ActiveX object:
var xmlhttp = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
I found a number of different designs on the web for determining the correct version for the browser, but since we can safely assume (in our case, at least) the user is using IE6 or IE7 the above check is more than sufficient.
OK, so does switching to the new IE7 "native" XHR solve our issue? Actually, no it doesn't. But it is the first step in the process. Because the issue is a result of a backlog of AJAX request, and all but the last are actually no longer valid, the solution is to change the way the AJAX calls are queued. For the Split View the only AJAX call being used (at the panel level) is the one to load the panels themselves. By changing the scope the XHR object from local to global we are able to reuse the object for each subsequent request. This small change actually is what makes all the different. Why?
Each time the Open() method is called on the XHR object, it will implicitly Abort() any previous requests. The browser automatically takes care of cleaning up after itself and IE behaves no matter how quickly the user navigates from record to record. After doing a bit more research I stumbled upon this blog post by Paven Keely where he adds it is important to execute the XHR statements in a certain order. Where it is common practice to first set the callback method (via the onreadystatechange event) and then open() and send() the request such as:
xmlhttp.onreadystatechange = function() { // Custom Function }
xmlhttp.open('GET', url, true);
xmlhttp.send(null);
In order to successfully reuse the XHR object you must first open the request and set the callback method as the second statement as follows:
xmlhttp.open('GET', url, true);
xmlhttp.onreadystatechange = function() { // Custom Function }
xmlhttp.send(null);
Putting these three changes together, using the "native" XHR object, changing the scope of the XHR object, and reordering the statements in our code, our issue has been resolved. After reviewing the solution the problem becomes obvious. Best practices for successful coding require we clean up after ourselves and dispose of objects after they are no longer needed. In the case of the XHR, the Abort() method was always an option. However, JavaScript is an often underappreciated technology and consequently JavaScript code does not necessarily follow those best practices. This exercise has given me a greater appreciation for the power and complexity of JavaScript and a greater understanding of how to tame the beast.