Fixing 'getElementsByTagName' in Firefox

I know I mentioned I'd be bringing over part 3 of the loosely couple eventing in Silverlight 2.0 entry next, but I've been working on a project with very tight timelines - and frankly haven't had chance to finish it.  However, all is not lost!  I recently ran in to a problem with Firefox, specifically issues with getElementsByTagName and I wanted to post a little fix, that while perhaps not ideal - might get some people out of a bind.

For some background, I'm currently working on a very large ASP.NET AJAX application and as you can imagine, there is a lot of Javascript flying around - and we're using DOM element creation pretty extensively, specifically for late-binding event handlers to items within the page.  This is where I ran into a peculiar problem that manifests itself in a couple of different scenarios, where basically utilising a DOM query like var links = parentElem.getElementsByTagName( 'a' ) would throw an exception and basically crater the JS parser in Firefox 2.0.0.13 on a multitude of platforms (Vista, XP, Mac OS etc).  Fortunately becase we had silo'd the classes in the application it didn't tear the whole app down, but it did present a problem.

A couple of hours Googling later and I discovered that while people were aware of the issue, specifically manifesting itself when doing XML parsing using the DOM and were fixing it using childNode subqueries.  That didn't really help in this instance though so that left me with the options of rewriting a pretty extensive piece of UI code to work round it or, changing the way event binding was being handled.  I wanted to work out a performant solution that minimised developer churn as much as possible.  And here's the result.

getElementsByTagName : function( parentElem, tagName )
{
  var elements;
  try { elements = parentElem.getElementsByTagName( tagName ); }
  catch( e ) { elements = [];
  this._getElementsByTagNameInternal( parentElem, tagName, elements ); }
  return elements;
},
_getElementsByTagNameInternal : function( parentElem, tagName, collection )
{
  var activeElem;
  for( var x = 0; x < parentElem.childNodes.length; x++ )
  {
   activeElem = parentElem.childNodes[x];
   if( activeElem.tagName === tagName ) { collection.push( activeElem ); }
   if( activeElem.childNodes.length > 0 && activeElem.nodeType == 1 )
   { this._getElementsByTagNameInternal( activeElem, tagName, collection ); }
  }
}

The first method getElementsByTagName is a simple facade to wrap up the try, catch block and return the completed collection, the second method is recursive and just walks the underlying DOM of the presented parent element, retrieving all the elements that match the current tagName.  It's not sophisticated in its matching, but you could easily extend this to look for other properties, text etc.

The biggest downside to this approach is that you need to step out to some external method to perform this operation, but if you wrap it up in a util static class that you can refer to with a shortcut, then it's only marginally more expensive on the typing front.  The good side is that if your browser does not behave strangely running this type of DOM query, then the standard one works just fine - but if you're unlucky it will still work just fine and your consuming code will be none the wiser.  It also has the side benefit of standardising the collection into an Array for enumeration, something which occasionally causes issues with browsers sometimes returning HTMLCollection and sometimes returning an Array. 

Click for larger version

After profiling the above on a 60 element collection going after the child links held within a table, you can see that the alternative method is only very marginally slower than the native method.  I must add, I haven't profiled this across huge collections so I have no idea how well it will scale, but for reasonable size collections this will be a good alternative to get you round a problem.  I've attached an HTML document to this post that has the above test harness in it with the code.

I hope you find it useful, if you have any comments or additional optimisations that you find, please send them over!

getElements.html (4.16 kb)

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Add comment


 

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

November 21. 2008 07:55