Pitfalls and problems when using innerHTML

Dec 14 2011

Lately I've been fixing some issues in an application that updates the HTML content in response to user actions via AJAX calls to get the new HTML content and then inserting it into the HTML via innerHTML.  It's nothing that anyone would consider rocket-science, but we've been getting all kinds of wierd problems when testing it in IE9.  Problems that we don't see at all in Firefox, Safari, Chrome or even IE8.

In working through the various issues and problems with the innerHTML calls and IE9, I've managed to figure out what's going on and why it's happening.  Since the answer is somewhat obscure, I thought it would be good to share it so that maybe it helps someone else.

The underlying symptoms that I've been seeing has been html text inserted via the innerHTML somehow ends up being being altered and the resulting document HTML no longer matches what was originally inserted.  This has manifested itself in various ways, but mostly it shows itself in submit buttons that are not becoming active when user enters a textbox or a pages that need to be filled out yet the use can still navigate past without error.

In looking into these issues, it seems that either the form element is being lost in the document HTML after the innerHTML insert, or the only input element that shows up as a member of the form is the hidden input element with the form number.

So, as an example, here's partial chunk of code that we'd insert into the page:
<div class="bodyTxt clearBoth">
  <p class="sessiontxt">
    <form name="P35M1P19F1" id="P35M1P19F1">
      <input name="formID" type="hidden" value="P35M1P19F1" />
      <ol>
        <li>Ideas and comments:<br />
          <textarea name="formField9005002" id="formField9005002" cols = "100" onclick="enableSubmit(this);"></textarea>
        </li>

The when the user clicks on the text area, the enableSubmit function would try to navigate upwards through it's parentNodes to find the form element that contained it, and it never could find it.

You'd expect the tree of parentNodes to be textarea -> li -> ol -> form -> p -> div, but what you'd end up with is textarea -> li -> ol -> div,  so the enableSubmit would fail since the it couldn't find the parentNode of the textarea that was the form and so the submit button would not be enabled.

In looking at the document html after inserting the text via innerHTML, our earlier chunk of code would somehow end up looking like this:
<div class="bodyTxt clearBoth">
  <p class="sessiontxt">
    <form name="P35M1P19F1" id="P35M1P19F1">
      <input name="formID" type="hidden" value="P35M1P19F1" />
    </form>
    <ol>
      <li>Ideas and comments:<br />
        <textarea name="formField9005002" id="formField9005002" cols = "100" onclick="enableSubmit(this);"></textarea>
      </li>

Notice the ending </form> tag after the first <input> tag.  That wasn't part of our original text, yet it now appeared in the document HTML.  And since the ending </form> tag was before our textarea, it would explain why the textarea's parentNode no longer included the <form> tag.

The other interesting thing to notice is that you can see from our second piece of code that the opening <p> tag was still there, yet it doesn't appear in the textarea's parentNode list.

What I did to fix it - without totally understanding why - was to remove the <p> tag.  When I did that, the tree of parentNodes nested as expected (textarea -> li -> ol -> form), and the document HTML ended up looked like this:
<div class="bodyTxt clearBoth">
  <form name="P35M1P19F1" id="P35M1P19F1">
    <input name="formID" type="hidden" value="P35M1P19F1" />
    <ol>
      <li>Ideas and comments:<br />
        <textarea name="formField9005002" id="formField9005002" cols = "100" onclick="enableSubmit(this);"></textarea>
      </li>

It worked, but I wanted to know why so I kept digging and discovered the answer for why this was happening.  I stumbled on this nugget from Microsoft (http://blogs.msdn.com/b/ie/archive/2010/09/13/interoperable-html-parsing-in-ie9.aspx) - " The <p> element can’t legally contain a <div>". 

That was news to me, so I kept digging a bit further.  It turns out that in the HTML standard (HTML 4.01 and XHMTL), the <p> tag cannot contain "block-level" elements (http://www.w3.org/TR/html401/struct/text.html#h-9.3.1) .  Those block-level elements include DIV, OL, and others, which is exactly what's going on in our HTML. 

In those cases where a <p> tag contains a block-level element, the browsers parser will close the <p> tag and any other elements contained in the <p> tag right before the block level element in order to preserve proper markup structure.  Since the <form> tag appears after the <p> tag but before the <ol> block-level element, it closes the <form> tag before the <ol> tag.  Additionally, although you can't see it in the document HTML, it also implicitly closes the <p> tag without actually adding the </p> tag to the markup (since the <p> tag - for historical reason - doesn't require a </p> tag to close).

In our case, the IE9 parser is rewriting our HTML in order to close the <p> tag in order to follow the HTML rule, and the by-product of that process is that it's also closing the <form> tag leaving the form elements without a form parent.  This process makes our HTML look truly like this (I've included the implicit </p> tag for visualization):
<div class="bodyTxt clearBoth">
  <p class="sessiontxt">
    <form name="P35M1P19F1" id="P35M1P19F1">
      <input name="formID" type="hidden" value="P35M1P19F1" />
    </form>
  </p>
  <ol>
    <li>Ideas and comments:<br />
      <textarea name="formField9005002" id="formField9005002" cols = "100" onclick="enableSubmit(this);"></textarea>
    </li>

This self-correcting parser process explains why it doesn't work in IE9, but it doesn't explain why it all still works in IE8, FF, Safari and Chrome.  The answer for that is simple - IE9 doesn't consider the <form> tag a block-level element, but IE8, FF, Safari and Chrome do consider the <form> element as block-level element (https://developer.mozilla.org/en/HTML/Block-level_elements).  Unlike IE9, they will self-close the <p> tag so that the <form> tag won't be contained within a <p> tag.  So when IE8, FF, Safari and Chrome see our original HTML, they rewrite it like this:
<div class="bodyTxt clearBoth">
  <p class="sessiontxt">
  </p>
  <form name="P35M1P19F1" id="P35M1P19F1">
    <input name="formID" type="hidden" value="P35M1P19F1" />
    <ol>
      <li>Ideas and comments:<br />
        <textarea name="formField9005002" id="formField9005002" cols = "100" onclick="enableSubmit(this);"></textarea>
      </li>

This small difference in browser-parser rewriting explains why the same HTML snippet of code that works fine in all browsers will fail in IE9.

There are three lessons to take away from this:

  1. Make sure that the all tags are closed properly and in the proper nesting order.
  2. Make sure that <p> tag is only used when it's actually needed.
  3. Make sure that the <p> tag only contains inline-level elements.  Any block-level elements in a <p> tag will have unintended consequences.

 

thanks - very helpful

this was super helpful - I had a similar problem caused by one little unclosed <p> code messing with some innerHTML injected content

IE drives me crazy...

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.