Apr. 29, 2010 at 10:30amjQuery and Cufon Don't Mix!

IE7+8 Bugs with VML break Sizzle

When using Sizzle (the selector engine in jQuery 1.3+, Prototype 1.7+, dojo, and others) and Cufon together, you need to make sure that your selectors are excluding Cufon elements. Internet Explorer 7 and Internet Explorer 8 both have a bug that cause Sizzle to break for some selectors with VML elements. I'll describe the problem, and some simple fixes, after the break.

We recently ran into a strange JavaScript error in IE 7 and 8 where the browser would inexplicably fail, and give no indication as to why. After some in-depth analysis of Sizzle and jQuery, it turns out that our font replacement script caused Sizzle to fizzle out.

The Problem

Custom fonts have always been a pain. There's plenty of ways to do it, which I won't go over here. One solution is a script called Cufon. With the use of javascript, simple things like headers and banners can be easily converted into the font of your choice. To do this, the javascript replaces the text with those vector graphics (SVG or VML) that show up on the page as new HTML entities (actually they aren't quite HTML, more on that later).

Sizzle is a very powerful framework that looks up elements using CSS selectors. We've talk a lot about it here on this blog, though we usually do so in the context of jQuery. It's selectors give Sizzle considerable power over how to manipulate elements. As an example, lets quickly dive into the ":text" selector. Below is some fake code to show kind of what it does.
function getTextElements(parent)  {
    var elems = (parent || document).getElementsByTagName('*');
    var elem;
    var text = [];
    for(var i = 0, len = elems.length; i< len; i += 1) {
        elem = elems[i];
        if('text' === elem.type) {
            text.push(elems[i]);
        }
    }
    return text;
}
The real Sizzle is more complicated, so I've simplified it here. But the core of the problem is within this code. The offending line is "'text' === elem.type". If you want to view this in Sizzle, it's located on line 524, here.

The IE7+8 bug occurs when asking a VML object for a property that has not been defined. Because Cufon uses VML in IE7+8, any time a Cufon generated element leaks into a Sizzle form selector, it will break. What's worse, this error is very difficult to detect. The browser puts out a generic error code 0 with a equally generic message of "Failed". Who would think a common selector would break a popular browser like IE7 or IE8?

What is Affected?

Which selectors are affected? Any selector that uses undefined VML properties. That is pretty much all of the input selectors. But here's a list: (with jQuery 1.4.2 error codes)

  • :enabled (Line: 82, Char: 168, Error: Failed, Code: 0)
  • :text (Line: 83, Char: 18, Error: Failed, Code: 0)
  • :radio (Line: 83, Char: 59, Error: Failed, Code: 0)
  • :checkbox (Line: 83, Char: 104, Error: Failed, Code: 0)
  • :file (Line: 83, Char: 148, Error: Failed, Code: 0)
  • :password (Line: 83, Char: 192, Error: Failed, Code: 0)
  • :submit (Line: 83, Char: 238, Error: Failed, Code: 0)
  • :image (Line: 83, Char: 281, Error: Failed, Code: 0)
  • :reset (Line: 83, Char: 323, Error: Failed, Code: 0)
  • :button (Line: 83, Char: 366, Error: Failed, Code: 0)

The Fix

So what do we do about this? Oddly enough, the fix is a performance trick. In the example above, just be specific about what element tags you are looking for. Since ":text" only applies to inputs, you can use "input:text" instead. This will yield the same results, but will do so with much quicker lookups. This is because the browser can use the native getElementsByTagName to get the specific tags. My example code is changed below.
function getTextElements(parent) {
    var elems = (parent || document).getElementsByTagName('input');
    var elem;
    var text = [];
    for(var i = 0, len = elems.length; i< len; i += 1) {
        elem = elems[i];
        if('text' === elem.type) {
            text.push(elems[i]);
        }
    }
    return text;
}
The code is almost the exact same, except now elems doesn't include things like divs, spans, paragraphs, or those pesky VMLs. Since those can't be text inputs anyways this is a considerable speed up. Generally speaking, it is a good idea to use an ID, then tag name, before any other selectors as a way to narrow down the elements before the heavy lifting by the selector engine.

I reported this to the Sizzle group two months ago, but unfortunately they haven't fixed it yet. Since these are both widely used libraries, I'm doing a write up on this to help other developers avoid this disastrous and obscure issue. If you find more selector errors like this with Cufon (or VML in general), let me know and I'll add it to the list. I suspect, but have not tested, that other VML based libraries are susceptible to the same bug (like the various canvas libraries).

Update: (2010-07-14)

After digging into this further, it turns out there are a bunch more selectors that will bomb in IE 7 and 8. I've reported it here. Here's just a few:

  • .class (Line: 80, Char: 316, Error: Failed, Code: 0)
  • [name=name] (Line: 88, Char: 381, Error: Failed, Code: 0)
  • * (Line: 91, Char: 437, Error: Failed, Code: 0)
  • [style] (Line: 86, Char: 381, Error: Failed, Code: 0)
It turns out just about every attribute selector bombs (a class is just a fancy attribute). This is really bad new for anyone who uses Cufon. My recommendation is to only use Cufon on non IE browsers. Luckily, IE has long supported custom fonts in other ways.

If this post was helpful to you please help pass the word by liking it, thanks all!

Can you provide a test case so that others can test their selector engines please?

Left by Dean Edwards | Apr. 30, 2010 at 3:03am

@Dean, I spend some time this morning and created this demo file.
http://www.sitecrafting.com/files/library/4432f98952a4397b.html

Left by Paul at SC | Apr. 30, 2010 at 10:12am

Paul reported this as a bug:
http://github.com/jeresig/sizzle/issues/#issue/13

I created a simple testcase:
https://gist.github.com/8c5702fb5ef57c4bc811

Left by John Firebaugh | May. 26, 2010 at 1:44pm

I am so thankful you found this. I've been pounding my head against a wall for DAYS to sort this out.

I'm still a little clear on the fix, however. To take the situation I'm in:

I've got a div with a headline in it that is being replaced by Cufon. If I use the following code:

$j("div.product-top").click(function()
{
//click function goes here
}

...I get the error you describe above. Disabling Cufon removes the error, so I assume I'm getting the error you describe. So how do I avoid this error? Ideally, I'd like to keep the entire DIV clickable (since it's an expanding div), but then that means Sizzle will select the H2 tag and therefore the VML too, won't it?

Left by TheCosmonaut | Jul. 9, 2010 at 12:23pm

@TheCosmonaut, It sounds like there is a problem with the elem.className code too. I dug into it a bit and found that, yes, it too breaks. I'm going to look into this more because I found that elem.className, elem.tagName, and elem.name all cause errors. This appears to be a bigger issue then I previously thought. I'll post an update here once I do more testing.

Left by Paul at SC | Jul. 14, 2010 at 12:04pm

I just had the exact same problem, so I went and google'd it and found this: http://www.sitecrafting.com/blog/jquery-cufon-dont-mix/ . I am very disappointed that I can't dev Cufon side by side with jQuery in IE from now on. O well. Hopefully there will be a fix soon. All of the best :)

Left by Marc Uberstein | Aug. 30, 2010 at 3:22pm

Leave a Comment

Remember me

Name:

Email:

URL:

Comment: * No HTML, http:// will auto-link
* required
Comment Guidelines