Perfection Kills

by kangax

Exploring Javascript by example

← back 631 words

DOMLint — resolving name conflicts

DOMLint test suite

One stupid aspect of DOM is the way it allows to access certain elements as properties of their containing “parent” element. Form controls, for example, can be accessed by their “name” through property access of <form> element containing them:


<form>
  <input type="text" name="foo">
</form>
...
document.forms[0].foo; // "[object HTMLInputElement]"

At first, this might seem like a great convenience – surely, it’s easier to write formElement.foo rather than a more verbose formElement.elements.foo. The joy of convenience, unfortunately, starts to fade out once you realize how badly DOM handles conflicts between these “magic” properties and actual properties of a parent element defined as part of its standard DOM interface. Form elements, for example, implement HTMLFormElement interface and that interface consists of 8 properties – elements, length, name, acceptCharset, action, enctype, method, target and 2 methods – submit and reset. The form doesn’t just have these properties. DOM says that HTMLFormElement should inherit from HTMLElement, which in its turn inherits from Element which inherits from Node and so on…

What this means is that a very simple form element happens to have a bunch of very useful methods inherited through one of its numerous interfaces.

What happens if we try to “magically” access an element whose name matches one of those “interface” properties? Do we get an element or a property/method? The horrible truth is that we usually get an element. Magical properties turn out to be too magical and shamelessly shadow all of those useful methods we might need to use:


<form>
  <input type="text" name="submit" value="Submit!">
  <input type="text" name="style">
</form>
...
<script type="text/javascript">
  document.forms[0].submit; // "[object HTMLInputElement]"
  document.forms[0].style; // "[object HTMLInputElement]"
</script>

Just like that, having 2 simple elements with somewhat unfortunate names, we end up not being able to use a very crucial `submit` method, and a not less useful `style` property.

Even creepier examples demonstrate that names on form elements themselves can cause the same mess:


<form name="getElementById">
  ...
</form>
<form name="body">
  ...
</form>
...
<script type="text/javascript">
  document.getElementById; // "[object HTMLFormElement]"
  document.body; // "[object HTMLFormElement]"
</script>

and -


<form name="window" onclick="console.log(window, setInterval)">
  <input name="setInterval">
</form>
// when clicked, logs [object HTMLFormElement], [object HTMLInputElement]

The latter example actually shows another annoyance – scopes of intrinsic event handlers being augmented with element object (as well as a document that an element is contained within).

When working with legacy documents, such unsafe names can lead to quite nightmarish debugging sessions. This is why I created a simple test suite to catch the offenders. It’s called DOMLint and is powered by a wonderful YQL. It is also hosted on github.

DOMLint is a work in progress. It doesn’t yet catch as many of the unsafe names as it could. Finding all of the names is impossible, since many browsers implement proprietary members on global object, document or elements. It nevertheless covers many of the most common ones. Currently, the project page performs 6 tests including the one for Prototype.js conflicts. Each test is thoroughly explained and is accompanied by an example.

I’d like to thank Garrett Smith, whose Unsafe names article made me aware of these issues and has eventually inspired to create a DOMLint. Garrett goes into much more details on the subject and covers even more failing cases. I highly recommend reading it.

I haven’t had time to find out whether YQL allows to send documents as text, so for now the tests are performed against a URL. If you have any suggestions or comments, I will be more than happy to hear them out.

Did you like this? Donations are welcome

comments powered by Disqus