Same Markup: Explaining "@_jscript_version" and Styling New HTML5 Elements


Last month I posted general guidelines for writing cross-browser code. Specifically I emphasized that feature detection (rather than browser detection) is better for working around differences between browsers. This is because feature detection automatically adapts to what a given browser supports and thus deals gracefully with new releases. Browser detection requires researching the level of support in every version of every browser and must be updated each time a new browser is released. Today I want to dig into a real-world example of this from some recent discussions. The conversation began around a piece of code like the following:

 

// DON'T USE THIS
/*@cc_on
	@if( @_jscript_version < 9 )
		// Enable styling of new HTML5 elements
		...
	@end
@*/

The goal of this code is to detect and work around a missing feature. In this case the feature is styling new HTML5 elements, or more specifically, using CSS to customize the display of new HTML5 elements. As written, this code fails to run in IE9 Compatibility View and ends up leaving new HTML5 elements without styles. In IE9 Standards Mode this is not a problem as IE9 enables styling for all elements by default.

The first problem that leads to this effect is the use of JScript’s conditional compilation which is similar to conditional comments. Both of these are forms of browser detection and should generally be avoided, especially when targeting the latest version of a browser. The second and more serious problem in this code is an attempt to use the @_jscript_version statement to detect the document mode of the page. The @_jscript_version statement is actually an indicator of which version of JScript is in use by the browser as a whole. In ALL document modes of IE9, this statement currently equates to “9”. In ALL document modes of IE8 it equates to “5.8” and in IE7 it is “5.7”. Thus it is not the correct piece of information to use to determine document mode.

Fortunately, developers can determine document mode directly and robustly. A simple DOM API was introduced in IE8 that provides exactly this information: document.documentMode. Modifying the original code to make use of this API results in the following:

// Better, but still don't use
// Avoid in favor of feature detection
if(document.documentMode < 9) {
	// Enable styling of new HTML5 elements
	...
} 

This is better, but the best way to achieve the original goal is detecting directly if styling of HTML5 elements is available, and avoiding browser sniffing altogether.

Here’s how to do it:

// DO THIS: Feature detection for styling unknown elements
var elm = document.createElement("div");
elm.innerHTML = "<foo>test</foo>";
if(elm.childNodes.length !== 1) {
     // Enable styling of new HTML5 elements
     var elms = [
          "abbr","article","aside","audio","canvas","command",
          "datalist","details","figcaption","figure","footer",
          "header","hgroup","mark","meter","nav","output",
          "progress","section","summary","time","video"
     ];
     for(var i = 0; i < elms.length; i++) {
          document.createElement(elms[i]);
     }
}

Here’s how it works.

New HTML5 elements can’t be styled by default in versions of IE prior to IE9 for two reasons. The first is because new HTML5 elements are treated as unknown elements. The second is that earlier versions of IE collapse all unknown elements at parse time. Being collapsed simply means that all children of an element actually become children of that element’s parent. Furthermore, collapsed elements end up with separate entries in the DOM for their start and end tags. The following sample code and resulting DOM representations illustrate this point:

var elm = document.createElement("div");
elm.innerHTML = "<foo>test</foo>";

Resulting DOM in IE8

- <DIV>
	- test
	- </FOO>

Resulting DOM in IE9

- <div>
	- <foo>
		- test

As you can see, the statement elm.innerHTML = "<foo>test</foo>" actually results in two children instead of one in browsers that collapse unknown elements. This behavior can be easily identified using feature detection (e.g. testing if elm.childNodes.length !== 1).

The final piece is understanding that enabling the styling of an unknown element is as simple as calling document.createElement("unknownElementName") from script before any element of that type is encountered by the HTML parser. Thus enabling the styling of new HTML5 elements involves calling document.createElement for each new element defined by the HTML5 spec.

This approach is an excellent example of the benefits of using feature detection instead of browser sniffing. Using the above code eliminates worrying about which version of which browser supports styling new HTML5 elements. Since the code tests for the behavior itself, it will automatically apply the appropriate workaround if and only if the workaround is actually needed.

Tony Ross
Program Manager

Edit 2:28pm: edit in the third code sample.

Edit 6/11 – correction in IE8 resulting DOM description.

Comments (21)

  1. Mitch 74 says:

    So, it used to be that conditional comment were the stuff to use to support older IE versions, but they are not recommended anymore? If you meant, for new browser versions, all right, but then you should have been a bit more explicit.

    Now, you rely upon the fact that IE won't create a node for an unknown tag when using innerHTML; other browsers may instead create an unknown inline block (akin to a span) and fill it (because they are smart enough to recognize when a closing tag matches an opening one), but not support any styling on an unnamed tag – after all, non-HTML 5 browsers were made at a time when innerHTML was badly documented by its creator.

    For example, when was it documented that using innerHTML on a table node would create an unknown fatal error?

    You would thus start styling elements, but only those that are actually known by the browser would be styled.

    So, for your solution to work, I would first have to detect if I'm running IE, then do your detection, then do another detection for all other browsers…

    A correct solution would be to create the required element, determine what kind of node it became, if the right one, try to style it, then read back to see if its layout was actually affected.

    Seriously… Would you MS guys PLEASE recommend actually correct programming methods on both MSDN and here, and look somewhere else than your belly button? The definition of 'other browsers' isn't limited to IE 5, 6, 7, 8 and 9, it is also all Webkit-based ones, KHTML based ones, Gecko based ones, Presto based ones, Amaya, Prince… Dang!

  2. Nasko says:

    Wouldn't having a @_HTML5_feature be better? Or @_HTML_version < 5

  3. RF says:

    Mitch: Behavior detection can't handle every imaginable unexpected behavior.  But unlike user-agent sniffing, it stands a chance of working on browsers that you didn't specifically test on because they weren't yet released or you just didn't have the time.  Specifically, it'll work when the browser is close enough to one of the "behavior profiles" your code was meant to handle (i.e., either it styles HTML5 by default or it treats unknown elements the way legacy IE does).

    In the case of a recipe like the one Tony posted, it's been found to work on enough browsers to be quite practical, and it's pretty simple.  "Actually correct programming methods" involve a little compromise when you just don't know what environment will be running your code.

  4. Adrian Bateman [MSFT] says:

    @Mitch 74: In our experience following the transition for IE6 to IE7 to IE8 (and now looking forward to IE9), most of the compatibility issues we see are related to incorrect feature detection code. This is usually related to user agent checking or browser version checking. When new features come along in later versions of a browser they may break the page functionality because the user agent check is now making invalid assumptions.

    We're certainly not suggesting you have to go back and rewrite all your detection code and, as RF says, feature detection can't work every time. It is a best practice though and recommended where possible. Any time you do a check using something like conditional comments there is a risk of an invalid assumption. Many of those risks are because developers often code if(IE) { } or if(IE>7) { } checks and these are fragile as new versions are released.

    While I've talked about compatibility between different versions of IE, the whole reason why this is an interesting topic is because of the other dimension of interoperability to other non-IE browsers like the ones you mention. In general the feature detection approach will work well as new standards features are added by all browser vendors at different points in time.

  5. Tony Ross [MSFT] says:

    @Mitch 74:

    > other browsers may instead create an unknown inline block (akin to a span)

    > and fill it (because they are smart enough to recognize when a closing tag

    > matches an opening one), but not support any styling on an unnamed tag

    Do you have examples of this happening in practice? If so, then I agree that a more complete solution would need to take this into account. However I doubt applying the same workaround of calling document.createElement() would be sufficient and a new workaround would need to be found for such scenarios.

    > A correct solution would be to create the required element, determine what

    > kind of node it became, if the right one, try to style it, then read back

    > to see if its layout was actually affected.

    In this post I apply parser-oriented detection via innerHTML because, although the issue manifests itself as a lack of styling, the root cause is actually that the parser collapses the element.

  6. Why don't you include the HTML5shiv script in the post? This is a widely used script for enabling HTML5 elements which recently has been updated with Jonathan Neal's script (http://www.iecss.com/print-protector), which also supports printing pages with HTML5 elements. The version you've listed on this page still breaks when printing any page using HTML5 tags.

  7. Mitch 74 says:

    @Tony, @Adrian: the goal of feature detection is to detect if a feature is supported. Now, it is reasonable to assume that some features do come together, and detecting each and every one of them is indeed impractical.

    However, in THAT current case, you're using innerHTML (which is implemented in varying ways across browsers) and the way unknown tags are created with it to do your object detection.

    Heck, your method would BREAK an XHTML 1.0 document (loaded with application/xhtml+xml MIMEtype)  because it attempts to create an entity unknown in the XHTML DTD! Yup, that one actually bit me in the *** on Chrome, Safari, Opera and your own IE 9 Developer Preview (Firefox didn't break, but then it might come to pass the day Mozilla decides to actually parse and enforce the DTDs on XHTML documents).

    So, what now? Well, don't use innerHTML. Use document.createElement('test'), and check 'test's nodeType. You can then set as many properties to it as you want (and check if they existed beforehand along the way), check their return values, then graft it onto the document's tree if it completes without errors. But at least this way you won't destroy the page if it fails hard somewhere. This may not be perfect, but it works on HTML and XHTML documents alike, it works on browsers that support HTML 4, it works on browsers that implement only a subset of HTML 5, and it will work on HTML 5 browsers.

    And, for all the beginner developers out there, it would be code that they would have to understand instead of cut'n'pasting and wondering why, for the life of 'em, this code works on IE browsers and not on LigntHTML5Browser v.1.5b used on that special cell phone. "But I found it on MSDN, and it works in IE!"

  8. Richard says:

    Why does _jscript_version evaluate to 9 rather than 5.9?  When you say it currently does so, does that mean this decision is yet to be finalised?

  9. David Owens says:

    @Mitch 74

    If you're creating new elements in an XHTML document, then of course you should add them to the DTD too.

    The article is about styling HTML5 elements. If you are using HTML5 elements then you should be using an HTML5 doctype, and not XHTML1.0. You can still use XHTML syntax when using an HTML5 doctype.

  10. John-David Dalton says:

    @Tony It's really cool to see the IE team advocate feature detection/testing.

    A minor correction:

    You state the resulting DOM in IE8 is <foo/>, test, </foo/> (3 nodes) but it's really test, </foo> (2 nodes).

    There are also some gotchas when using the createElement hack:

    1) It isn't supported for older browsers like Firefox 2

    2) Elements like COMMAND that have an empty content model aren't supported.

    3) Printing the HTML5 elements requires workarounds.

    4) Adding HTML5 support to elements created by createElement(), cloneNode() and document fragments requires additional workarounds.

    5) The hacked HTML element's tagName/nodeName properties are not UPPERCASED by default. Instead they are case-sensitive to how they appear in the HTML source.

    6) The hacked HTML elements are inline by default and may require adding display:block or display:inline-block styles

    Jonathan Neal has done a great job of getting HTML5 elements to work in IE.

    He provides a proof of concept solution for dynamically created elements.

    http://www.iecss.com/shimprove

    And his HTML5 printing solution is now included in Remy's html5shiv.

    http://www.iecss.com/print-protector

  11. Tony Ross [MSFT] says:

    @John-David Dalton

    Thanks for the correction. I'll get the post updated shortly.

    On the note of this is not being supported in Firefox 2. It's true that this particular bit of detection and workaround doesn't fix Firefox 2's own styling issues, but as far as I'm aware the code does not cause any ill effect in Firefox either.

    The existing libraries definitely do a more complete job at covering the gotchas you pointed out and I won't attempt to duplicate that effort. My current concern is that nearly all of them have updated to use "if(@_jscript_version < 9)" since the first platform preview of IE9 was made available. As I point out in my post, this doesn't work they way they expect in IE9's compatibility view.

  12. @Tony Ross [MSFT]

    > Last month I posted general guidelines for writing cross-browser code.

    And you did not make any correction to your posted code in that post.

    >Fortunately, developers can determine document mode directly and robustly.

    documentMode is IE-only and is not DOM compliant; so its usefulness is limited to IE8+.

    > elm.innerHTML = "<foo>test</foo>";

    1- Why resort to innertHTML? Why not resort to createElement() like you did just one line before? And then use appendChild or insertBefore for DOM-inserting. Please explain this.

    2- Writing HTML elements in a script require to escape forward slashes because "end tags are recognized within SCRIPT elements, but other kinds of markup–such as start tags and comments–are not."

    htmlhelp.com/…/problems.html

    > if(elm.childNodes.length !== 1)

    This is where I do not understand you. If elm has not exactly 1 childNode, then shouldn't you just stop the script (return) and tell your user (somewhere in the webpage: in a browser support link or code conformance notice, whatever) that (s)he may need to use a better browser or upgrade or switch or tell him/her that such webpage requires a browser that properly/correctly supports DOM, HTML5, etc.. whatever? Why propose to continue with a browser that may have a faulty, unreliable implementation of createElement or childNodes or faulty scripting DOMtree capabilities or faulty innerHTML method or etc..?

    Have you tried your DOM-creating and DOM-inserting (with innerHTML) HTML5 elements script with IE6? IE7? and non-IE browsers?

    > var elms = [

             "abbr","article","aside","audio","canvas","command",

    abbr is not a new HTML5 element.

    http://www.w3.org/…/html5-diff

    > Resulting DOM in IE9 (…) the statement elm.innerHTML = "<foo>test</foo>"  actually results in three children

    It should create 1 child node which itself has one text node. Anything else involves an error, absence of support or an implementation bug.

    > This behavior can be easily identified using feature detection (e.g. testing if elm.childNodes.length !== 1).

    You may be over-stating too far here, I'd say, with claiming to use feature detection.

    if(elm.childNodes && elm.childNodes.length !== 1)

    is proper, adequate feature support detection: you first check if the element implements a childNodes support. elm.childNodes.length !== 1 is more about checking for/comparing the return value. Not really/not exactly feature/method support detection.

    Some people go very methodically with method/feature support detection and this makes their script functions much more safe to use and reuse, more reliable, robust and not creating unneedlessly error messages in javascript/error console. E.g.:

    if(elm && elm.childNodes && elm.childNodes.length && elm.childNodes.length !== 1)

    {…};

    E.g.:

    if(document.documentMode && documentMode < 9)

    {…};

    > for(var i = 0; i < elms.length; i++) {

             document.createElement(elms[i]);

        }

    If the comparison returns the wrong number of nodes, then your script commands to create those HTML5 elements anyway. Your example is really IE8-oriented and can not be transposed or used or reused for non-IE browsers. So, its usefulness is very limited. And those elements are never actually DOM-inserted in the document: so it may cause a crash or just fail anyway.

    Like others said (Hello Mitch!), real method/feature support detection coding practices have worth but have also limitations.

    E.g.:

    http://www.gtalbot.org/…/create-button-define-type-before-after.html

    <input type="button">foo</input> works in IE8

    but

     var objNewInputButton = document.createElement("input");

     document.getElementById("before").appendChild(objNewInputButton);

     objNewInputButton.type = "button";

    fails in IE8.

    I see no method/feature support detection which would detect during script execution and prevent this.

    Browser feature/method support detection should never replace browser manufacturers working responsibly to fix bugs and users regularly upgrading their browser.

    @John-David Dalton

    > HTML elements are inline by default

    This is per spec too: the initial value for display is inline.

    @David Owens

    > The article is about styling HTML5 elements.

    This is not how I read such article. There is no styling of HTML5 elements anywhere in the article. None whatsoever. The article was intended to be about feature support detection and the more you carefully read it, the more it seems to be about how to implement HTML5 elements in IE8 (regardless) versus in IE9.

    regards, Gérard

  13. RobertWrayUK says:

    @Gérard:

    "shouldn't you just stop the script (return) and tell your user (somewhere in the webpage: in a browser support link or code conformance notice, whatever) that (s)he may need to use a better browser or upgrade or switch or tell him/her that such webpage requires a browser that properly/correctly supports DOM, HTML5, etc.. whatever? Why propose to continue with a browser that may have a faulty, unreliable implementation of createElement or childNodes or faulty scripting DOMtree capabilities or faulty innerHTML method or etc..?"

    Because that's not the users problem, they don't care. They just want to click on the "Internet" button and view the website (usually one that has singing, dancing kittens). Telling "Auntie Ethel" to "upgrade to a standards compliant browser" is *never* going to work. Yes, as a developer it sucks, but, it's really not the end users problem to solve. One way or another, it's ours. Developers.

  14. wechrome says:

    I do agree feature detection is the right approach than browser detection, but the given example is simply WRONG WRONG WRONG. I wonder why MS come up with such a stupid and fundamentally absurd example? Feature detection is to detect the feature to be used, or at least some relevant functions. You said your goal is detecting directly if styling of HTML5 elements is available, but what you are doing is detecting whether the browser is collapsing unknown elements, which are two completely unrelated things. So basically you are depending on the fact that those browsers that collapse unknown elements do not have styling of HTML5 elements, which means it's still a browser detection, not a feature detection.

    also conditional comments are still arguably the most reliable way to single out IE6 and IE7.

  15. David Mark says:

    @wechrome:

    "I do agree feature detection is the right approach than browser detection, but the given example is simply WRONG WRONG WRONG."

    You are right, right, right!  🙂

    They appear to have mistaken object inferences for feature detection (same as jQuery).

    "also conditional comments are still arguably the most reliable way to single out IE6 and IE7."

    Yes, but they should be used sparingly, typically to include proprietary IE styles like zoom, filter, etc.

  16. David Mark says:

    @Tony

    "However I doubt applying the same workaround of calling document.createElement() would be sufficient and a new workaround would need to be found for such scenarios."

    And what leads you to doubt that?  It's obviously a better strategy for environments that feature browsers other than IE.

    Feature tests are supposed to be as simple and direct as possible.  It doesn't get much more simple and direct than appending one of the offending elements, styling it and then determining if the styles took effect.

    On the other hand, testing with innerHTML introduces many additional variables (not to mention that you are testing the wrong behavior).

    What you've done is simply a bad object inference, which is another form of browser sniffing popularized around the turn of the century.

    http://www.jibbering.com/…/detect-browser

    How can MS be so far behind on the Web?  Granted, Apple and Yahoo! are almost as clueless.  Can't any of these behemoths find good help in this area?

  17. wechrome says:

    exactly, feature detection should be about a direct detection of the required feature, not some illogical attempt at inferencing the availability of one feature from another completely different feature.

    It reminds me of one former colleague who tried to use the availability of the attachEvent method to determine whether to use some IE-only feature, which completely breaks Opera since Opera supports both attachEvent and addEventListener methods. And now the IE team is more or less trying to push the same kind of stupid browser sniffing method to developers world-wide?

    I really thought the IE team can do better than that, you are developing the world's most used browser for god's sake!!!

  18. Greg says:

    Or better yet, use this:

    // DO THIS: Feature detection for styling unknown elements

    var elm = document.createElement('div');

    elm.innerHTML = '<foo>test</foo>';

    if(elm.childNodes.length !== 1){

    // Enable styling of new HTML5 elements

    var elStr = 'abbr,article,aside,audio,canvas,command,datalist,details,figcaption,figure,footer,header,hgroup,mark,meter,nav,output,progress,section,summary,time,video';

    var elArr = elStr.split(',');

    for(var i=0,L=elArr.length;i<L;i++){

    document.createElement(elArr[i]);

    }

    }

  19. Philip Kahn says:

    While philosophically not using browser detection is the right way to do things, the fact of the matter is that the only browser that needs detection is IE 95% of the time. I don't feel bad at all UA-sniffing to insert IE-specific code, and wrapping those in <[if IE lt 9]–> blocks, etc. If I only need to work around one browser, I'm not going to waste processing or delivery time for the rest of the browser market that plays nice.

  20. wechrome says:

    browser sniffing is bad largely because of questionable reliability and lack of support for future versions, but IE's conditional comment is officially supported and reliable, so I personally don't see any problem using conditional comments to single out earlier versions of IE. Even for later versions of IE, it's still quite okay as long as you remember to send an X-UA-COMPATIBLE header.