Deadly Expandos

If I had to rate my least favorite browser bugs I’d have to put this one near the top. A holdover from the old DOM0 days it’s a practice where elements with a given name or ID are added as an expando property to another DOM node.

Here are my two favorite examples of this bug in action:

The first is a simple form that does a search on a site. Additionally a link is provided that, when clicked, fills in a search value and submits the form.

<form action="" method="POST" id="form">
  Search: <input type="text" name="search" id="search"/>
  <input type="submit" id="submit"/>
</form>
<a href="#" id="quick">Quick Search 'JavaScript'</a>
<script>
document.getElementById("quick").onclick = function(){
  document.getElementById("search").value = "JavaScript";
  document.getElementById("form").submit();
};
</script>

Before running the example you can spot the problem with a quick run to the address bar:

javascript:alert(document.getElementById("form").submit)
"[object HTMLInputElement]"

The .submit() method (which is available on all Form elements) is overwritten by the input element of the same name. This ends up being a very common problem – with frameworks using id=”submit” as a default in their code.

Worst of all this fails in all browsers (preventing you from accessing the overwritten method).

The second example is even more devious. In this case we’re going to loop over all the DOM elements in the page and alert out their contents.

<div id="length">12 stories</div>
<div id="makeup">radiation</div>
<script>
var all = document.getElementsByTagName("*");
for ( var i = 0; i < all.length; i++ ) {
  alert( all&#91;i&#93;.innerHTML );
}
</script>

This will work in most browsers – but not Internet Explorer. To understand why we return to the address bar.

javascript:alert(document.getElementsByTagName("*").makeup)
"[object]"
javascript:alert(document.getElementsByTagName("*").length)
"[object]"

Oops. All browsers turn elements with specific IDs into expandos of the returned NodeSet. But Internet Explorer goes a step farther and decides to overwrite the built-in .length property as well, breaking current forms of iterating over the DOM elements.

At least within jQuery you’ll see a number of cases where, instead of doing the normal array traversal, we do the following in order to work around the issue:

for ( var i = 0; elems[i]; i++ ) {
  // Do stuff with elems[i]
}

It’s a little more obtuse but at least it’s guaranteed to work against cases of broken NodeSet iteration.

Garrett Smith has a highly technical write-up on the variety of issues that stem from this form of expansion. In short: No browser is immune from these problems. It’s unfortunate that this whole system couldn’t just be done away with (to avoid these types of issues in the first place) but legacy pages will likely necessitate their inclusion for many, many, years to come.

Posted: November 10th, 2008


Subscribe for email updates

15 Comments (Show Comments)



Comments are closed.
Comments are automatically turned off two weeks after the original post. If you have a question concerning the content of this post, please feel free to contact me.


Secrets of the JavaScript Ninja

Secrets of the JS Ninja

Secret techniques of top JavaScript programmers. Published by Manning.

John Resig Twitter Updates

@jeresig / Mastodon

Infrequent, short, updates and links.