Unjam CSS

loading spinner

Abstract: Unjam CSS is a technique for removing render-blocking CSS from pages over which we have only partial control, such as CMS or portals.

The problem: a portal generates HTML pages based on a template. Custom HTML can be inserted at specific place holders, but we can’t modify the rest of the page HTML. The unmodifiable part loads a CSS from an external location which takes a long time to respond, displaying a blank page until the CSS either loads or times out.

Requirement: Remove the blocking CSS link and get the page to render faster by obeying a simple rule: all we are allowed to do is add our own HTML (or Javascript) at the predefined place holder. Explicitly forbidden: changes to the infrastructure, DNS, modifications to the web server hosting the external CSS, modifications to the portal template or portal installation, browser modifications.

Portal template with place holder for custom
HTML and reference to external CSS

The problem is known as “render-blocking CSS” [1] where browsers won’t show anything until referenced CSS resources have finished loading. While there are various workarounds [2], they all go into the direction of “don’t do it/inline stuff/load asynchronously” and unanimously assume that you have control over the loading document. In our case we don’t sufficiently control the HTML page generation since we can’t remove the reference to the external, blocking style sheet.

Far from a Gedankenexperiment, this is a reality in corporate intranets consisting of multiple network domains. The portal runs in one domain and references a CSS file from a different domain which won’t load fast (enough) for some users, ie. because DNS resolution there is slow, resulting in a blank page and the browser spinning the “loading” wheel.

Sequence diagram of browser loading portal page and then being stuck in requesting CSS from external web server
Sequence diagram of browser requesting portal page and external CSS

Solution: unjam CSS

The solution concept is simple, the implementation not so: inject Javascript into the template which locates the external style sheet in the page DOM, removes it and reattaches it later by one of the many asynchronous CSS loading techniques [2]. If it were only that easy! One step at a time:

1. Locate and remove the blocking CSS
That one is obvious:

function processLinks(){
var links = document.getElementsByTagName("link");
for (var i=0;i	<links.length;i++){
var link = links[i];
//insert arbitrary condition to determine whether link needs to be modified
var parent = link.parentNode;
parent.removeChild(link);
}
}

2. Find the right moment

When processLinks is called in the HTML document before the node it won’t find the link in the DOM because the browser hasn’t constructed that node yet. The window.onload method won’t do either because it executes only after the blocking CSS has loaded, which is too late. The two places that work is the document.onreadystatechange method and a deferred timer, eg.:

window.setTimeout(processLinks,10);

3. Page layout may break without the CSS
Arguably the page will look broken without the missing CSS, which is still better than not rendering at all or only rendering after a large delay. Some solutions: either inline it (we can do that in the template place holder we control) or load it asynchronously, eg.:

4. Does it work?
At this point the hack will work only in Chrome (tested 56.0.2924.87). Internet Explorer (tested 11.0) and Firefox (tested 51.0.1) will still block rendering even when the link has been removed from the DOM. There’s one more tweak to make for those browsers:

window.setTimeout(function(){
parent.appendChild(link);
},100);

5. Stop the entire document from loading
Firefox and Chrome implement a window.stop function, Internet Explorer implements document.execCommand(‘Stop’) towards the same end. When called, pending HTTP requests will be cancelled, including those stalled CSS requests.

6. Putting it all together
There is a bit of a timing issue which requires a bit more of window.setTimeout than I’d like for an elegant, cross-browser solution, but here it is:

function processLinks(){
var links = document.getElementsByTagName("link");
for (var i=0;i<links.length;i++){
var link = links[i];
var parent = link.parentNode;
parent.removeChild(link);
window.setTimeout(function(){
parent.appendChild(link);
},100);
}
window.setTimeout(function(){
try {
window.stop();
} catch (exception) {
document.execCommand('Stop');
}
},10);
}

window.setTimeout(processLinks,10);

Reflecting on unjam CSS

Scope: unjam is a technique for mitigating the impact of render-blocking resources by working around three issues:

  1.  the lack of control in portal/CMS software over generated HTML
  2. render-blocking induced by external, blocking CSS
  3. a browser quirk which makes browsers choke on already detached resources

What unjam is not: a library or framework, a dynamic CSS loader [6]. Unjam gets a page unstuck by removing the blocking CSS which normally resides outside our control; it’s up to us to figure out if/when/how we get it back in.

Applicability: While I suspect that the technique can be extended to other render-blocking resources as well (eg. Javascript files), I admit to not having put the hypothesis to test. A crucial prerequisite for unjam to work is that it must be injected before the blocking resource, otherwise the code isn’t executed in time to cancel the blocking request. A fair warning: I didn’t test unjam on mobile browsers but I’d be happy to hear from you if you do!

Robustness: Determining when to stop loading the page document can be tricky since it will cancel any outstanding requests for images, fonts and other resources loaded by script loaders. While it should be possible to re-load some of those resources by traversing the DOM and re-initiating their download, this approach doesn’t only seem wasteful but fails with dynamically loaded resources for which there is no entry in the DOM (e.g. pending XmlHttpRequests). To complicate things further, just “waiting” long enough before cancelling the document loading process won’t help either because any outstanding resources won’t be requested by the browser until the blocking resource has been loaded, leaving the page in a semi-complete state. Using unjam will require complete knowledge of the resources included in a page and a programmatic reload phase [5]. There is plenty [3] of evidence [4] that dynamically loading CSS is not a robust cross-browser technique – but at least unjam will get the page unstuck and it gives the designer an option to include the missing CSS in a non-blocking manner.

Timing: the unjam hack is, unfortunately, dominated by deferred script execution which complicates its code. While testing on Chrome, Firefox and Internet Explorer I found this approach to be the least common denominator that would work across these browsers and versions.

Completeness: This post serves little more purpose than to demonstrate the concept; a fully working solution would have to cover not only topics mentioned in robustness, but also take into account proper order of re-attaching, re-loading resources and handling error conditions.

Resources

[1] Render blocking CSS
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-blocking-css

[2] Loading scripts without blocking
https://www.stevesouders.com/blog/2009/04/27/loading-scripts-without-blocking/

[3] Lazy loading CSS in Drupal Fails
https://www.drupal.org/node/1071818

[4] Loading CSS without blocking render
http://keithclark.co.uk/articles/loading-css-without-blocking-render/

[5] Refresh image with a new one at the same URL
http://stackoverflow.com/questions/1077041/refresh-image-with-a-new-one-at-the-same-url

[6] loadCSS
https://github.com/filamentgroup/loadCSS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.