How to prevent race conditions when loading CSS with the YUI Get Utility

The YUI Get Utility makes it easy to dynamically load CSS and JavaScript on demand, but there’s one tiny gotcha: Firefox, Safari, and other Gecko/WebKit-based browsers don’t provide a reliable way to tell when a CSS file has finished loading. As a result, when you load CSS in one of these browsers using the Get.css() method, the onSuccess callback is fired instantly.

In many cases this isn’t a problem, but if you need to ensure that you don’t perform an action (such as appending HTML to the page) until after the CSS has loaded, this can cause a race condition. There’s no way for the Get Utility to automatically work around this problem, but with a little bit of extra effort on our part, we can work around it ourselves.

The trick is to include a rule at the end of the dynamically-loaded CSS file that will send a message to our script by setting a style property on a special element. Once we see that property change, we know the CSS has finished loading.

First, the contents of our CSS file:

/* ... imagine lots of other CSS here ... */

#css-done { display: none; }

And now the JavaScript that will load it:

(function () {
  var Y = YAHOO;

  Y.util.Get.css('example.css', {
    onSuccess: function () {
      var el, poll;

      function finished() {
        alert('CSS has finished loading.');
      }

      if (Y.env.ua.gecko || Y.env.ua.webkit) {
        el    = document.body.appendChild(document.createElement('div'));
        el.id = 'css-done';

        poll = function () {
          if (Y.util.Dom.getStyle(el, 'display') === 'none') {
            // Once the element's display property changes, we know the CSS has
            // finished loading.
            el.parentNode.removeChild(el);
            finished();
          } else {
            // The element's display property hasn't changed yet, so call this
            // function again in 50ms.
            setTimeout(poll, 50);
          }
        };

        poll();
      } else {
        finished();
      }
    }
  });
})();