When is it Safe to Modify the DOM?

This is a pretty important question when in comes to DOM programming.  Start manipulating the DOM to early and bad things happen.  Do it too late and the users may see that annoying flicker.   So what's the best technique for figuring this out?  Well, I was curious about this myself so I took a look at how some of the popular Ajax libraries (jQuery, MooTools, YUI, Prototype and ASP.NET AJAX) are doing this.  Here is what I found out ...

 

Update: 08/11/2008: I came across an article that sheds more light on ASP.NET AJAX's init technique.  There is a lot of good content in the comments as well (look for the ones left by Dave Reed).  Here are a few snippets of what Dave has to say ...

His comments with respect to the location of where the ScriptManager injects the call to Application.initialize ...

ScriptManager does not put the call to initialize in a “random” location. It is in fact, always the very last thing in the form, just before the closing form tag. There shouldn’t be anything after that (for better or for worse, form is a major part of an asp.net page), and if there is, it could only be because the dev put content after the closing form tag, and all they must do is move it inside

 

And what he had to say regarding why they chose this approach instead of what some of the other Ajax libraries are using ...

To address your follow up post that questions why we used this technique rather than a “proven” technique developed by the community — when we release the framework, we always have to consider the shifting browser space. New browsers may come along. Updates for all the existing browsers come out all the time, sometimes causing bugs, like this one:

http://weblogs.asp.net/infinitiesloop/archive/2008/04/30/flickering-ui-from-the-asp-net-ajax-toolkit-tabcontainer-while-in-an-updatepanel.aspx

So when we come up against a browser quirk, there are a few rules we try to stick to if it makes sense. The primary one is SIMPLICITY. Because a simple solution is less likely to break than a complex one. Another one is CONSISTENCY. If one technique works in all browsers, it is better to use it in all browsers, otherwise you are more likely to wind up with subtle bugs in one browser but not in another. Another example in the framework of that rule in action is the requirement for the Sys.Application.notifyScriptLoaded call at the end of script references (required, at least, for them to load successfully during a partial update in an update panel). We really didn’t want to require it, but it was the best way to make it work in Safari at the time. So what if you accidentally left out the call in your script? We detect this, even if you are in IE or FF or Opera, and throw an exception! It can work in those browsers without, but its better to warn you — hey, this is wrong, it won’t work in Safari.

 

You don't need to wait for window.onload ...

Its definitely safe to muck with the DOM in the window's onload event, but there is really no need to wait that long.  The onload event doesn't fire until the entire page has loaded (DOM plus images) and often that ends up being too late.  The user ends up seeing the page in potentially 3 different states: DOM without images, DOM with images, and finally the DOM with images and what ever changes your script has applied.  There is no reason why your script can't run just after the DOM has loaded, but before the images are downloaded.

 

DOMContentLoaded

FireFox, Opera 9+ and recent Safari builds all support a DOMContentLoaded event that fires at precisely this moment (after loading the DOM, but before the images are downloaded).  Opera even fires it even before all of the stylesheets have loaded which actually turns out to be a bit too early.  But the bottom line is that you wire an event handler, add your code and be on your way ...

   1: if (document.addEventListener) {
   2:   document.addEventListener("DOMContentLoaded", function(){
   3:     // run some code here ...
   4:   }, false);
   5: }

 

Wait - What about IE?

Internet Explorer is a little different.  There is no DOMContentLoaded event so we have to rely on IE specific hacks tricks.  There seem to be generally two different approaches to simulate the DOMContentLoaded event ...

The first is to exploit an IE's non-standard defer attribute located on the script tag.  When IE comes across this it will hold off on executing the script until after the DOM is loaded.  So you can simulate the DOMContentLoaded event by including the script you need executed (include it in onload.js in the example below).

   1: <script defer src="onload.js"></script>

 

The second workaround (which seems to be a little more popular) includes setting up a function that attempts to scroll an element.  If an error is raised, the DOM isn't quite ready for us yet and we try it again later.  If no exception is raised we know it is safe to run our script.  Here is an example of what this looks like (taken from ajaxian) ...

   1: (function (){
   2:         //check IE's proprietary DOM members
   3:         if (!document.uniqueID && document.expando) return; 
   4:  
   5:         //you can create any tagName, even customTag like <document :ready />
   6:         var tempNode = document.createElement('document:ready');  
   7:  
   8:         try {
   9:                 //see if it throws errors until after ondocumentready
  10:                 tempNode.doScroll('left');
  11:  
  12:                 //call your function which catch window.onDocumentReady
  13:                 alert('window.onDocumentReady()');
  14:  
  15:                 //relaese some memory, if possible
  16:  
  17:                 tempNode = null;
  18:  
  19:         } catch (err) {
  20:                 setTimeout(arguments.callee, 0);
  21:         }
  22: })();
  23:  

 

jQuery 1.2.6, MooTools 1.2 and YUI 2.5.2 all use variations of the second technique to simulate the DOMContentLoaded in Internet Explorer.  Prototype 1.6.0.2 includes a variation of the first technique.

 

Will IE8 Support DOMContentLoaded?

I am not sure.  I found this gem in the transcripts for the Windows Internet Explorer 8 Expert Zone Chat (March 20, 2008)

Marc Silbey [MSFT] (Expert):
Q: Will IE8 support HTMLElement? addEventListener? DOMContentLoaded?
A: We are working on improving our standards support to be more interoperable with other browsers. That said, we can't really promise support for features or standards in future releases. You can look for the features you want on the IE8 Beta Feedback site and vote on them. Check out the IE blog post here for more information:
http://blogs.msdn.com/ie/archive/2008/03/05/ie8-beta-feedback.aspx

 

What Does ASP.NET AJAX Do?

I found it interesting that ASP.NET AJAX doesn't attempt any browser specific optimizations (like attaching to the DOMContentLoaded event).  Instead, ASP.NET AJAX simply queues its initialization function to be executed after the DOM is loaded by using window.setTimeout.  Here is the bit of code that does this ...

   1: function Sys$_Application$initialize() {
   2:     if(!this._initialized && !this._initializing) {
   3:         this._initializing = true;
   4:         // Raise the init events on a timeout so it is queued. This delays the component creation until after the body is
   5:         // is ready for use. Without this, if a component adds a dom element to body it will be modifying the body before
   6:         // window.onload, which causes an "operation aborted" error in IE. We use this trick for all browsers for consistency.
   7:         window.setTimeout(Function.createDelegate(this, this._doInitialize), 0);
   8:     }
   9: }

 

I tried to find some documentation explaining why this works, but I couldn't find any.  Apparently queuing a function like this ensures it will execute after the DOM has loaded.  This is real simple, but unless I am mistaken, not taking advantage of the other browsers DOMContentLoaded event causes ASP.NET AJAX's initialization function to execute just a bit later than it could in FireFox, Opera and Safari.  Not the end of the world, but it could maybe use some optimization as well.

Another thing I was curious about was if this approach would work if the call to Sys.Application.initialize(); wasn't located so far down in the page (I believe the ScriptManager injects this guy way down by the body's closing tag).  Anyone know anything more about this?

 

Some Real Simple Tests

To play around with these I created a real simple page where I used each of the Ajax libraries initialization functions to run a bit of code that hides a big red banner.  Then I loaded the pages in the different browsers to see how well they performed.  I know, nothing too complicated, but just enough to get a feel for it.

In each of these pages I have wired the Ajax libraries init function to hide an element that contains the text 'Did this flicker?'.  Ideally you will never see this text in any of the demos.  Under the header I print out roughly the number of milliseconds it took for the init function to be invoked and finally at the bottom of the page I have links to the other demo pages.

Depending on the what browser/Ajax library combination you are using will depend on how well the page performs.

Here are the links to these test pages:  ASP.NET AJAX | jQuery | MooTools | Yahoo!

image 

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/143

Comments


Matt -

As always ANOTHER fantastic post. I think developers like yourself and others like Rick Strahl are definitely leading the way for .net developers that love the ms stack but love innovative tools that do it better in respective places. For all that ASP.NET AJAX does tools like jQuery do it better and much more efficient.

thanks for the post and keep up the good work!

regards,

Kevin

Posted by: David Knight on August 6, 2008 02:57 AM

defer is not a non standard attribute, it is however up to the user agent to decide whether it will actually defer script execution or not

(see http://www.w3.org/TR/html4/interact/scripts.html#adef-defer)

Very informative Matt and really good analysis.
I noticed the flicker when using ASP.NET AJAX and Mootools on IE 7.0. And flicker when using ASP.NET AJAX only FF 3.0.1 but not with mootools this time.

First one to read on this subject actually.
Cheers

Where will this inter-browser nightmare end?
Would it not be very nice to have W3C as an organization with *real* power, at last? As a single source for all standards web related. Yes, we all know this is the idea ... same as we all know how far is this idea from implementation.
Just imagine what would happen to the hardware universe without IEEE.
Everything else is waste of time ... There has to single DOM. Agreed by all, and followed by all. M$ included.

Dusan

@David -
Thanks for clarifing that point.

@Kevin -
Thanks Kevin. Rick is in a league of his own, but I appreciate the complement.

@Muhammad -
I was surprised to see that MooTools and Yahoo don't just queue the polling function instead of setting a timeout (jQuery does it this way). Seems like you get init firing faster in IE if they did it that way ...

Posted by: Igor Loginov on August 6, 2008 08:59 AM

An intersting post, thank you. And really important point for evaluation.

I noticed flickering in IE7 for ASP.NET AJAX and mootools. In FF 3.01 all four did not flicker. Init in IE7 took ~3 times longer than in FF. Init leaders in IE7 are jQuery and ASP.NET AJAX.

Don't want to call any of them a looser in this competition (I believe, they can be fixed easily), but jQuery is the winner. Of course, IMHO :)

Posted by: Ernest Laurentin on August 7, 2008 10:54 AM

Matt,
Great post as always. I found that one particularly very informative. jQuery approach seems the most optimal.
Keep them coming!
Ernest

Matt,

Good summary of the different techniques. I'm not sure if you're right in that because ASP.NET AJAX does not use the DOMContentLoaded and uses the timeout method it executes a bit slower. It kind of depends on how and when the DOMContentLoaded event is raised by the browser. You'd think the performance of both would be almost identical.

As for the technical reason why the window.setTimeout(method, 0) works...

JS has a single thread of execution. It works by executing all instructions in a call stack and then switching to another call stack and executing all of its instructions and so forth.

By registering a method execution using a timeout of zero, you are essentially saying, immediately create a call stack for this method and as soon as you're done executing the current call stack switch to it and execute the method.

So in the case of page rendering, processing the markup's content is the first call stack. It reaches the window.setTimeout(method, 0) instruction, executes it which registers another call stack to execute when the timeout expires, which happens right away. But, because JS is single-threaded and can only process a single call stack at a time, the second call stack (the one that contains the method registered by the timeout) doesn't start processing until the call stack that is processing the markup's content completes, thus making it safe to modify the DOM once the second call stack starts executing.

Keep up the good posts.

Nice explanation. Thank you. This will be very helpful for me

Matt,
why don't you try the original IEContentLoaded solution ?

You can find it here:

http://javascript.nwbox.com/IEContentLoaded

The modifications that has been done to my code are known to fail in one way or another. Additionally this is a Microsoft described behavior, so better trust the vendor itself. The solution is one year old...

Diego Perini

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Diego Perini wrote: Matt, why don't you try the original IEContentLoaded solution ? You can find it here: <a href="htt...
  • Bayram wrote: Nice explanation. Thank you. This will be very helpful for me...
  • Joel Rumerman wrote: Matt, Good summary of the different techniques. I'm not sure if you're right in that because ASP.NE...
  • Ernest Laurentin wrote: Matt, Great post as always. I found that one particularly very informative. jQuery approach seems th...
  • Igor Loginov wrote: An intersting post, thank you. And really important point for evaluation. I noticed flickering in I...
  • Matt Berseth wrote: @David - Thanks for clarifing that point. @Kevin - Thanks Kevin. Rick is in a league...
  • Dusan wrote: Where will this inter-browser nightmare end? Would it not be very nice to have W3C as an organizatio...
  • Muhammad Mosa wrote: Very informative Matt and really good analysis. I noticed the flicker when using ASP.NET AJAX and Mo...