zachleat’s Ugly Mug

Zach Leatherman

Faking Onload for Link Elements

29 July 2010Read this in about 3 minutes.

Updated 2011/09/27: Rejoice! This issue has now been fixed in Firefox.


Or, I Am Dynamically Loaded CSS (and So Can You!)

Dynamic resource loading is one of the keys to have a performance happy web application. There are generally three different criteria we must address when making a request: cross domain security policies, asynchronous/synchronous (will it block the host page while loading), and whether or not events are triggered when the request completes.

If the resource and host page are on the same domain, obviously XMLHttpRequest works the best. We can control whether or not the resource is loaded asynchronously or synchronously, and we know exactly when it gets done.

If the resource and host page are on different domains (increasingly more common with CDN’s), our options narrow. Loading the JavaScript is a solved problem, just use the onload event on the ` tag and you’re good to go (onreadystatechange` for IE). But CSS is more complicated.

ResourceMethodOption for (a)synchronousEvent
JavaScript/CSS Same DomainXMLHttpRequestBothonreadystatechange
JavaScript Different Domain<script>Synchronous (Asynchronous where async property is supported)onload
onreadystatechange for IE
CSS Different Domain<link>AsynchronousWhat this blog post is about.

Existing Solutions

In all of the library source code I evaluated, Internet Explorer didn’t cause any issues. It fires both the onload and onreadystatechange events for `` nodes. Obviously this is ideal behavior, and IE got it right. But what about Firefox and Safari/Chrome?

YUI 2.8.1 and 3.1.1

Original Source

// FireFox does not support the onload event for link nodes, so there is
// no way to make the css requests synchronous. This means that the css 
// rules in multiple files could be applied out of order in this browser
// if a later request returns before an earlier one.  Safari too.
if ((ua.webkit || ua.gecko) && q.type === "css") {
    _next(id, url);
}

I wouldn’t be surprised if the commit log there was from Bon Jovi; that code is living on a prayer.

LazyLoad

Original Source

// Gecko and WebKit don't support the onload event on link nodes. In
// WebKit, we can poll for changes to document.styleSheets to figure out
// when stylesheets have loaded, but in Gecko we just have to finish
// after a brief delay and hope for the best.
if (ua.webkit) {
    // resolve relative URLs (or polling won't work)
    p.urls[i] = node.href;
    poll();
} else {
    setTimeout(_finish, 50 * len);
}

Better, closer, warmer. This includes a nice method for working with webkit browsers. The poll method compares document.styleSheets, since Webkit has the nice option of only appending to the styleSheets object when the styleSheet has successfully loaded.

So we have working solutions for IE and Safari/Chrome. The only unsolved piece of the puzzle here is Firefox.

This post from the same author as LazyLoad also describes another solution which involves modifying the source CSS and polling against it. But that’s not really ideal. Can we do better?

Solution

Here’s what I came up with (using jQuery for brevity, note that this solution only fixes Firefox, and does not incorporate the above already solved solutions):

var url = 'css.php',
    id = 'dynamicCss' + (new Date).getTime();

$('<style/>').attr({
    id: id,
    type: 'text/css'
}).html('@import url(' + url + ')').appendTo(document.getElementsByTagName('head')[0]);

function poll() {
    try {
        var sheets = document.styleSheets;
        for(var j=, k=sheets.length; j<k; j++) {
            if(sheets[j].ownerNode.id == id) {
                sheets[j].cssRules;
            }
        }
        // If you made it here, success!
        alert('success!');
    } catch(e) {
        window.setTimeout(poll, 50);
    }
}

window.setTimeout(poll, 50);

See this Demo in Action (Firefox Only)

Update: After much joy and celebration, I have discovered that an approach similar to the above was written by Oleg Slobodskoi in his xLazyLoader plugin for jQuery. It shouldn’t be surprising that two independent developers might reach the same solution, and is just more proof that software patents are stupid. :)

Update #2 Added note about HTML5 async property on script tags.

Say hello on and GitHub.

Let my Feed sit idle in your RSS Reader.

Zach Leatherman is a Professional Front End Engineer. He loves building for the web and has been writing here since 2007.

He enjoys spending time with his beautiful wife Traci and their two Great Danes, Roxie and Ella. They also have a cat, a rabbit, goldfish, and usually one or more tarantulas. Read more »