Learnings from researching fault tolerent CSS delivery

Orde Saunders' avatarUpdated: Published: by Orde Saunders

Whilst researching fault tolerant CSS delivery I went down a number of blind alleys and, whilst they didn't end up as part of the final post, I'm sharing some of them here out of interest.

Caveat lector: Note that I haven't investigated these as thoroughly as I normally would before I post here.

Adding a stylesheet by injecting <link rel="stylesheet"> before the DOMContentLoaded event will block DOMContentLoaded until the stylesheet request completes. This is a problem if your JavaScript is waiting for DOMContentLoaded before it executes: you don't have all the styles and you don't have your JavaScript running.

Injecting a <link>using the DOMContentLoaded event is too late, there's a noticeable flash of partially styled content. It will also delay the page load event.

Injecting a <link> element does not seem to completely block script execution if done after DOMContentLoaded but I've not in any way systematically tested this - it's just something I think I noticed whilst trying to sort out other problems.

<script href="…" async> and DOMContentLoaded

If you set the async flag on an external script then there's a real probability* that the script will execute after the DOMContentLoaded event has fired. If you are binding your JavaScript initialisation code to this event then it will never run. You'll need to check the state of the DOM first:

if (document.readyState !== 'loading') {
  // DOMContentLoaded has already fired.
  init();
} else {
  document.addEventListener('DOMContentLoaded', init);
}

* I.e. It's going to happen. Maybe not every time, but once is enough in this case.

Detaching from events

If you add a time out to try and detach your code from running as part of an event then I found I needed to set the time out to at least 100ms. (I say "at least" but 100ms worked in Firefox and Chrome and I didn't systematically test it until I found the lowest possible value.)

// This will run as part of the event and delay it's completion.
document.addEventListener('DOMContentLoaded', function() {
  setTimeout(function () {
    // Code that takes a long time to complete.
  }, 1);
});

// This will run after the event has completed.
document.addEventListener('DOMContentLoaded', function() {
  setTimeout(function () {
    // Code that takes a long time to complete.
  }, 100);
});

Internet Explorer and XMLHttpRequest

Internet Explorer introduced support for XMLHttpRequest in version 7 but didn't add the onload handler until version 9. This means that in versions 7 and 8 you have to use onreadystatechange and check the state to see if it is complete. To aovoid this whilst not breaking code and still providing a fallback we need to feature detect support for the onload hander:

var use_fallback = true;
if (typeof XMLHttpRequest !== 'undefined') {
  var request = new XMLHttpRequest;
  if ('onload' in request) {
    use_fallback = false;
    // We can now use XMLHttpRequest with the onload handler
  }
}
if (use_fallback) {
  // Rely on the fallback.
}

Internet Explorer 7 style elements

If you want to add CSS content to a <style> element in IE7 you have to use the styleSheet non-standard interface:

var s = document.createElement('style');
if (s.styleSheet) {
  s.styleSheet.cssText = css;
} else {
  s.appendChild(document.createTextNode(css));
}

Source

Fortunately, this combines nicely with the XMLHttpRequest support meaning we can drop this if we cut the mustard on XMLHttpRequests. [I love it when a plan comes together.]