Today, all modern browsers support the onload event on link tags. So I would guard hacks, such as creating an img element and setting the onerror:

if !('onload' in document.createElement('link')) {
  imgTag = document.createElement(img);
  imgTag.onerror = function() {};
  imgTag.src = ...;
} 

This should provide a workaround for FF-8 and earlier and old Safari & Chrome versions.

minor update:

As Michael pointed out, there are some browser exceptions for which we always want to apply the hack. In Coffeescript:

isSafari5: ->
  !!navigator.userAgent.match(' Safari/') &&
      !navigator.userAgent.match(' Chrom') &&
      !!navigator.userAgent.match(' Version/5.')

# Webkit: 535.23 and above supports onload on link tags.
isWebkitNoOnloadSupport: ->
  [supportedMajor, supportedMinor] = [535, 23]
  if (match = navigator.userAgent.match(/\ AppleWebKit\/(\d+)\.(\d+)/))
    match.shift()
    [major, minor] = [+match[0], +match[1]]
    major < supportedMajor || major == supportedMajor && minor < supportedMinor

This is kind of a hack, but if you can edit the CSS, you could add a special style (with no visible effect) that you can listen for using the technique in this post: http://www.west-wind.com/weblog/posts/478985.aspx

You would need an element in the page that has a class or an id that the CSS will affect. When your code detects that its style has changed, the CSS has been loaded.

A hack, as I said :)


The way I did it on Chrome (not tested on other browsers) is to load the CSS using an Image object and catching its onerror event. The thing is that browser does not know is this resource an image or not, so it will try fetching it anyway. However, since it is not an actual image it will trigger onerror handlers.

var css = new Image();
css.onerror = function() {
    // method body
}
// Set the url of the CSS. In link case, link.href
// This will make the browser try to fetch the resource.
css.src = url_of_the_css;

Note that if the resource has already been fetched, this fetch request will hit the cache.


E.g. Android browser doesn't support "onload" / "onreadystatechange" events for element: http://pieisgood.org/test/script-link-events/
But it returns:

"onload" in link === true

So, my solution is to detect Android browser from userAgent and then wait for some special css rule in your stylesheet (e.g., reset for "body" margins).
If it's not Android browser and it supports "onload" event- we will use it:

var userAgent = navigator.userAgent,
    iChromeBrowser = /CriOS|Chrome/.test(userAgent),
    isAndroidBrowser = /Mozilla\/5.0/.test(userAgent) && /Android/.test(userAgent) && /AppleWebKit/.test(userAgent) && !iChromeBrowser; 

addCssLink('PATH/NAME.css', function(){
    console.log('css is loaded');
});

function addCssLink(href, onload) {
    var css = document.createElement("link");
    css.setAttribute("rel", "stylesheet");
    css.setAttribute("type", "text/css");
    css.setAttribute("href", href);
    document.head.appendChild(css);
    if (onload) {
        if (isAndroidBrowser || !("onload" in css)) {
            waitForCss({
                success: onload
            });
        } else {
            css.onload = onload;
        }
    }
}

// We will check for css reset for "body" element- if success-> than css is loaded
function waitForCss(params) {
    var maxWaitTime = 1000,
        stepTime = 50,
        alreadyWaitedTime = 0;

    function nextStep() {
        var startTime = +new Date(),
            endTime;

        setTimeout(function () {
            endTime = +new Date();
            alreadyWaitedTime += (endTime - startTime);
            if (alreadyWaitedTime >= maxWaitTime) {
                params.fail && params.fail();
            } else {
                // check for style- if no- revoke timer
                if (window.getComputedStyle(document.body).marginTop === '0px') {
                    params.success();
                } else {
                    nextStep();
                }
            }
        }, stepTime);
    }

    nextStep();
}

Demo: http://codepen.io/malyw/pen/AuCtH


Since you didn't like my hack :) I looked around for some other way and found one by brothercake.

Basically, what is suggested is to get the CSS using AJAX to make the browser cache it and then treat the link load as instantaneous, since the CSS is cached. This will probably not work every single time (since some browsers may have cache turned off, for example), but almost always.