jQuery and Cufon Don't Mix!

Apr29
Missing Image
By SiteCrafting Staff

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!

Dev

Back To Feed