How I made IE6 More Standard Compliant, And Faster

Background

It’s 2006, I’m at Apple, I’ve been developing advanced user experience with JavaScript in Netscape 4.7 and IE 4.5 for HomePage using the DynAPI framework. An example is the iDisk Finder-like navigation bellow, that shipped in 2001. I used a hidden iframe to load some JSON-equivalent JS from the server, and render it client side, which with 56K modems, made a BIG difference vs reloading a page every time.

 

Choosing a folder on iDisk, to create a Photo Album in .Mac HomePage

Choosing a folder on iDisk, to create a Photo Album in .Mac HomePage

I realize the next logical step is to focus on single-page applications, which would essentially have the same architecture as Desktop native Apps: All the logic and rendering is client side, the data is coming from a backend service. Having developed Applications on NeXTSTEP from 1994-1999, I wanted the essence of what made me so productive developing on that OS (the very same Tim Berners Lee used to invent the Web): AppKit, WebObjects Components and EOF. But I very much wanted a Web framework, based on standards, that would embrace the declarative nature of the Web and the amazing potential of the separation of concern between CSS and markup. While also easing the workflow between the JS dev and the HTML/CSS dev. Such JavaScript framework didn’t exist and I decided to start one.

Taking the High Road

So I’m starting to code my first JavaScript framework, (that I poorly named Gianduia, I know… let’s move on) and I want to only use standard DOM API, which of course is a problem for IE6. The ideal solution would be to somehow make IE6 standard-compliant. Which means any object from IE6 DOM would implement standard DOM API. For example, DOM objects in IE implemented attachEvent() and not addEventListener(). This would certainly require quite a bit of JavaScript, but at least it would only be loaded in IE, leaving the rest of the framework clean.

Two Code Path To Address

In a page, DOM elements either exist from the page source at load, or they are created by some JavaScript. In both cases, I need to make sure DOM elements implement the set of new methods I was going to add. You would think that all I needed to do was to add such methods to the prototype of DOM elements, and so did I, but that just wasn’t possible in IE6. Which means it had to be added to every single element…

IE6 Proprietary API To The Rescue

DOM Elements Created From Page Source

It turns out, IE6 has a way to execute JavaScript code for DOM Elements, through CSS: IE Behaviors . With this, the set of methods providing DOM API can be added to every DOM object built from the page source, and it conceptually looks something like this:


/*
Universal selector to target all elements,
"this" is the current DOM Element in an expression

We're passing it as an argument to a function makeElementStandard() that needs to:
1. Add the DOM methods to the element passed as an argument
2. Remove the behavior once done so it doesn't happen again when/if CSS would be re-evaluated
*/
* {behavior: expression((function(element){makeElementStandard(element);})(this));}";

 

DOM Elements Created Programmatically

The second code path to cover is the one when a DOM Element is created programmatically, through document.createElement(), or anElement.cloneNode(). This is more straightforward:

  1. Override document.createElement(elementName)
    1. Call the native document.createElement()
    2. Add DOM methods to new element
    3. return new element
  2. Override Element.cloneNode()
    1. Call the native Element.cloneNode()
    2. Add DOM methods to new element
    3. return new cloned element

My primary worry was about performance, looping over all properties to add them one by one, every time. Remember, JavaScript wasn’t as fast as it is today.

A Faster DOM Compliant IE6

Leveraging the Expando Property

In IE6, an expando property is simply an arbitrary property with value, added to a browser object. It turns out, cloneNode() in IE also clones any expando that was added to the object being cloned! So no need to loop in JavaScript anymore and doing this in native code is obviously faster.

But I can also leverage that for implementing document.createElement(elementName): I lazily create a “template” instance of each tag, on demand through a document.createElement() call, add the DOM compliant methods to it, cache it, and clone away every time a new element with that tag name is needed.

A Faster IE6

At that point I’m really excited to get all that working well, but still worried about the impact of all that code on performance. So I setup some benchmarks to compare a large number of DOM elements creation operations, with and without my code additions.  Looking at the results, I make a disconcerting, yet very pleasant discovery: The code with my additions is faster than bare IE6. Believe me, I checked again, and again, but it’s real, and I’m able to isolate it. The reason why my implementation is faster than bare IE6 is because Element.prototype.cloneNode() is faster than document.createElement(). So not only it makes IE6 a lot more standard compliant, keep the code of the framework clean and lean, but on top of that it makes any App built with that framework more performant in IE6!

Take Away: Aim High

There are many anecdotes like that in the front-end development trenches, it feels like there were more in the past, because browsers are more solid now than they’ve ever been. But the key take away is that my design/architecture instincts felt right: isolating the burden to the backward looking IE6 exception (though an exception with quite a big market share…), rather than tainting and sandbagging my nascent framework in a standard-based future, was the best thing to do.

But, it meant a non-trivial effort: I had to do some research, override browser based objects, which in the context of that framework made sense, use proprietary, though stable, API to do so. I had to test to make sure that my implementation was conform to the native one. I had a risk of a performance penalty in a browser that already had a reputation for not being a speed daemon. And of course, such a potential performance gap could only be assessed in the end…

And yet, taking the high road delivered in spades. What I learned through these years building Apps and a first framework at Apple was instrumental in making Montage what it is today. So the next time you’re facing a similar dilemma, in the context of building technology, don’t look the other way right away. Think about it, ask around, read, experiment a bit, implement a partial solution in the direction you want to go, iterate. Be curious, it’s your learning, it’s your growth. Who knows, you may be facing the same rewarding results now or in a future project!

Leave a Reply

Your email address will not be published. Required fields are marked *