DEV Community

Hollow Man
Hollow Man

Posted on

Solution to Missing DOMContentLoaded Event when Enabling both HTML Auto Minify and Rocket Loader in Cloudflare

Recently, I moved my domain DNS record under Cloudflare. Cloudflare can smartly optimize web pages. But when I enabled HTML Auto Minify and Rocket Loader simultaneously, I found that the DOMContentLoaded event was missing when accessing the web pages. In this post, I’ll share my experience and solutions to this problem.

Introduction to Auto Minify and Rocket Loader

Auto Minify can delete unnecessary characters (such as spaces, comments, etc.) in the website source code to reduce the source file size, including CSS, Javascript, and HTML. As a result, it reduces the amount of data that needs to be transmitted to the visitor and shortens the page loading time.

Rocket Loader reduces rendering time by asynchronously loading JavaScript, including embedded JavaScript in web pages and third-party scripts. Please refer to their blog for further details.

Finding that the DOMContentLoaded event is missing

As we all know, document.readyState is defined as three states in Chrome:

  • When the value is loading, it means that the browser is rendering the web page.
  • When it becomes interactive, DOM elements on the web page can be accessed. However, resources such as images, style sheets, and frames are still being loaded.
  • When finally it becomes complete, it means that all resources of the webpage have been loaded.

The DOMContentLoaded window event is triggered when the state changes from loading to interactive. The load window event is triggered when the state changes from interactive to complete.

However, after enabling HTML Auto Minify and Rocket Loader simultaneously, I found that the functions meant to be executed when the DOMContentLoaded window event was triggered were not executed actually.

First of all, I judged that it’s impossible to be caused by bugs from web pages, as the website is working normally when I test them locally, and I also used the following code to ensure that the functions are directly executed when document.readyState is interactive or complete:

if (document.readyState === "interactive" ||
    document.readyState === "complete") {
  foo();
} else {
  window.addEventListener("DOMContentLoaded", foo);
}

So it’s puzzling.

Then I embed the following code into the web page so that starting from the time when JavaScript is executed, the console will show the value of document.readyState every time the state changes:

console.log(document.readyState);
document.onreadystatechange = function () {
  console.log(document.readyState)
}

Then learned from the result that after enabling both HTML Auto Minify and Rocket Loader, document.readyState only has two states, loading and complete. The state interactive is missing, and when the state changes from loading to complete, only the load window event will be triggered. The DOMContentLoaded event has never been triggered.

It seems to be a bug to Rocket Loader or may be just intentional.

According to the principles of Rocket Loader introduced by Cloudflare, it will postpone the loading of all JavaScript until rendering is finished. When rendering completes and the JavaScript code is being executed, document.readyState should already be interactive.

However, if HTML Auto Minify is also turned on simultaneously, document.readyState is incorrectly set to loading concluded from the result fore-mentioned. (I guess maybe a piece of code in Rocket Loader incorrectly assigned document.readyState to loading when it starts to execute Javascript code. Because Rocket Loader is not open-sourced, the reasons for doing this are unknown. Of course, the fact may be completely different from my guess.) As a result, the situation makes judgment for whether to directly execute functions completely invalid, resulting in the DOMContentLoaded event still being registered.

Solution

Now that the mechanism is known to us, the solution is also straightforward. Add the following code before all DOMContentLoaded event listeners. You don’t need to change any of the original codes and then, congratulations, you fixed this.

var inCloudFlare = true;
window.addEventListener("DOMContentLoaded", function () {
  inCloudFlare = false;
});
if (document.readyState === "loading") {
  window.addEventListener("load", function () {
    if (inCloudFlare) window.dispatchEvent(new Event("DOMContentLoaded"));
  });
}

This code judges that if the DOMContentLoaded event has still not occurred after the load event occurs, the code will manually triggers the DOMContentLoaded event, thereby making the DOMContentLoaded event equivalent to the load event. The only disadvantage of this solution is that the DOMContentLoaded event is only triggered when document.readyState is complete, but this is currently a necessary cost for fixing.

Of course, you can also resolve this problem by directly disabling the global Auto Minify for HTML, or Configure Page Rules for HTML pages that use the DOMContentLoaded event so that the Auto Minify can be disabled on those pages.

Discussion (0)