Solution to Missing DOMContentLoaded Event when Enabling both HTML Auto Minify and Rocket Loader in Cloudflare
Tags: cloud, javascript, webdev, programming
Published: 2021-10-19 16:09:49

<div class="entry-content">
		
<p>Recently, I moved <a href="https://hollowman.ml">my domain</a> DNS record under <a href="https://cloudflare.com/">Cloudflare</a>. Cloudflare can smartly optimize web pages. But when I enabled HTML Auto Minify and Rocket Loader simultaneously, I found that the <code>DOMContentLoaded</code> event was missing when accessing the web pages. In this post, I’ll share my experience and solutions to this problem.</p>



<h2>Introduction to Auto Minify and Rocket Loader</h2>



<p><a href="https://support.cloudflare.com/hc/en-us/articles/200168196-How-do-I-minify-HTML-CSS-and-JavaScript-to-optimize-my-site-">Auto Minify</a> 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.</p>



<p><a href="https://support.cloudflare.com/hc/en-us/articles/200168056-What-does-Rocket-Loader-do-">Rocket Loader</a> reduces rendering time by asynchronously loading JavaScript, including embedded JavaScript in web pages and third-party scripts. Please refer to <a href="https://blog.cloudflare.com/we-have-lift-off-rocket-loader-ga-is-mobile/">their blog</a> for further details.</p>



<h2>Finding that the DOMContentLoaded event is missing</h2>



<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState">As we all know</a>, <code>document.readyState</code> is defined as three states in Chrome:</p>



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



<p>The <code>DOMContentLoaded</code> window event is triggered when the state changes from <code>loading</code> to <code>interactive</code>. The <code>load</code> window event is triggered when the state changes from <code>interactive</code> to <code>complete</code>.</p>



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



<p>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 <code>document.readyState</code> is <code>interactive</code> or <code>complete</code>:</p>


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


<p>So it’s puzzling.</p>



<p>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 <code>document.readyState</code> every time the state changes:</p>


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


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



<p>It seems to be a bug to Rocket Loader or may be just intentional.</p>



<p>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, <code>document.readyState</code> should already be <code>interactive</code>.</p>



<p>However, if HTML Auto Minify is also turned on simultaneously, <code>document.readyState</code> is incorrectly set to <code>loading</code> concluded from the result fore-mentioned. (I guess maybe a piece of code in Rocket Loader incorrectly assigned <code>document.readyState</code> to <code>loading</code> 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 <code>DOMContentLoaded</code> event still being registered.</p>



<h2>Solution</h2>



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

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


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



<p>Of course, you can also resolve this problem by directly disabling the global Auto Minify for HTML, or <a href="https://support.cloudflare.com/hc/en-us/articles/218411427-Understanding-and-Configuring-Cloudflare-Page-Rules-Page-Rules-Tutorial-">Configure Page Rules</a> for HTML pages that use the <code>DOMContentLoaded</code> event so that the Auto Minify can be disabled on those pages.</p>