In this article I will explore the DOM, look at some common kinds of errors that are found in the DOM and how different debugging tools can be used to find such DOM errors and make sure that the DOM is interpreted consistently across browsers. Along the way, I’ll introduce Opera Dragonfly, the new kid on the web development debugging block, and show how it performs.
What is the DOM?
The “parent” of the DOM is the
document object, and using its methods and properties you can reach every part of the HTML file — for example by getting the first
document.getElementsByTagName('div') or the first element in the document with
The DOM is usually visualised as a tree-like structure, as seen in Figure 1. The document itself should have only one child — typically the
html element — and that element usually has the two children
body. Each of those can have several children, so comparing this structure to the branches of a tree is a good way to understand it.
Figure 1: The DOM structure of a simple HTML document, as viewed in the DOM viewer of Opera Dragonfly as a tree structure.
When constructing the DOM, the quality of the markup is very important. If the markup is not valid — for example if elements are not closed correctly — it’s much harder to create a tree structure from it. If, say, a document contains the markup
<html><head><body></body></html> — what is the browser going to do? Make
body a child of
head? Since the
head contents are not visible in user agents, this might make the entire document disappear!
Because invalid markup is so common, browsers have to add lots of special rules for creating DOM structures from invalid markup. These rules differ somewhat between browsers, which means that using invalid markup dramatically increases the risk that your site will appear differently in different browsers — or even break completely in some of them.
Common markup / DOM errors
Below I will look at some commonly encountered DOM errors, and how browsers interpret the markup.
Bad closing and nesting of elements
If you look at the first example file — df-dom-demo-unclosed-b.htm — you will see that the markup is invalid — there are multiple problems with it, in terms of unclosed elements and incorrect nesting. If you look at the generated DOM in various web page debugger tools (see Figure 2), you will see that browsers interpret this HTML very differently.
Figure 2: From top to bottom, this shows our incorrectly closed/nested example DOM as represented in Opera Dragonfly, Mozilla Firebug, and the Internet Explorer developer toolbar.
The DOM inspectors reveal that these browsers handle markup errors in very different ways: Opera makes the subsequent elements children of the
b that lacks a closing tag. Firefox adds extra
b elements between the
p tags that were not present in the markup. In IE’s DOM we see that the text “This text should be a link” in fact appears to be outside the
a tag that creates the link.
Because there is no standard way to handle invalid markup, none of these browsers are doing anything particularly wrong here. However, if you start adding extra styling like
display:none to any of these elements or try cloning and manipulating them through the DOM, you’ get unexpected results across browsers because of the way the invalid markup is parsed into different DOM structures.
body inside the
In the second sample file — df-dom-demo-head-contents.htm — you can see another example of invalid markup. There is a
div element inside the
head section. Check out this demo in various DOM explorers and you will see that all browsers move the
div element out of the
head and into the
body. Figure 3 shows the different ways in which this DOM is interpreted by different browsers:
Figure 3: From top to bottom, this shows our content in the head example’s DOM as represented in Opera Dragonfly, Mozilla Firebug, and the Internet Explorer developer toolbar.
In Opera and IE, this means that any
head content after the
div also is moved outside of the
head section. Also note the way Firefox has moved the
style element from the body into the head.
style elements are not allowed inside the
body of course, so this is a logical thing to do, but it might confuse your script if it goes looking for the
style element in a certain place in the tree.
Using DOM inspectors for debugging
The third example file — df-dom-demo-app-meta.htm — is created by a rather unlucky web developer who has made several assumptions about how the DOM works. It turns out that he is wrong on most counts, and the app causes issues in most browsers! To start with, it doesn’t include a proper doctype in the first line. It then goes on to include a custom
test element and a
meta element with a custom
http-equiv attribute value inside the
head. Lastly, it includes a script inside the
body that tries to read attribute values from both the
meta and the
In Opera it generates two exceptions. Using the Opera Dragonfly DOM explorer (see Figure 4) you can see that the reason is that the
metaelements are not inside the
head. When Opera sees the
testelement, it thinks the custom element name indicates the page content start, so it closes the
headsection and traps both elements outside it.
Figure 4: Opera Dragonfly shows that Opera has trapped the two rogue elements outside the
head. Hence, when the script looks for them inside the
headsection it can’t find them and throws exceptions.
In Firefox (see Figure 5), you will get the odd output message “name of bar attribute is _moz-userdefined” and sure enough, with Firebug’s DOM explorer you will see that Firefox added a
_moz-userdefinedattribute to the
Figure 5: Firebug shows that Firefox has returned an error message and added an attribute to mark the
testelement as undefined in HTML. Since the script reads the first attribute from the element, it sees the
_moz-userdefinedattribute instead of what we expected.
In WebKit you get one exception (see Figure 6). Like Opera, it moves the
testelement out of
head. Unlike Opera, it appends the element to
body, and it doesn’t move the
Figure 6: WebKit shows that Safari has moved the
testelement into the
In IE7 the output is “Name of bar attribute is language. Value of meta attribute is null” — there are no exceptions reported, and neither result is what was expected. The IE Developer Toolbar’s DOM view is not quite powerful enough to explain these results — there are two IE bugs at play here: one is that IE exposes internal generic attributes in the
Element.attributescollection, second is that weirdly enough the
meta http-equivattribute simply disappears. See Figure 7 for the output in the IE web developer toolbar.
Figure 7: The IE web developer toolbar does not quite show us the DOM the scripts are working on. It appears to show that IE has preserved the
meta http-equivattribute, yet when we try reading it with the DOM method getAttribute() we get nothing.
Our unlucky developer has a few things to fix to get this example working properly cross-browser, but in this example it’s not hard to do so. He’ll need to validate the markup, get rid of the custom elements (and avoid these in the future, particularly inside the
head as browsers are very picky about what they allow to appear there), and not rely on attribute order when reading attributes. You can see from the examples shown above that valid code is perhaps the most important factor for cross-browser compatibility on the DOM level, and that DOM inspectors help you understand how your scripts can access the document and how the markup is interpreted.