Why DOMContentLoaded handler can block first paint?

There is a similar question that wasn't resolved.

I've encountered a situation when handler that listens to DOMContentLoaded can block first paint. Sometimes it blocks, sometimes it doesn't

I tried many times cmd + R to see it. Is there any explanation to this behaviuor?

Also I recordered a video to show this: https://www.youtube.com/watch?v=EDZQ1nLCK2w&feature=youtu.be

  1. When you see a blank page after reload then it means DOMContentLoaded blocked first paint
  2. When you see the text "Some text" and then a blank page after reload it means DOMContentLoaded didn't block first paint
window.addEventListener('DOMContentLoaded', () => {
    let i = 0;
    while (i++ < 1000000000) {
        continue;
    }
    document.getElementById('el').remove();
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p id="el">Some text</p>
</body>
</html>

It's a race condition. Sometimes the document is already loaded/interactive before you add the listener.

You can see the difference by inspecting the document's readyState: document.readyState. Your code isn't running because sometimes the state is already interactive or complete, meaning that the DOMContentLoaded event has already fired before you've actually added the listener.

The way to handle it would be something like this:

function init() {
    let i = 0;
    while (i++ < 1000000000) {
        continue;
    }
    document.getElementById('el').remove();
}

if (document.readyState === 'loading') {
    // Document not yet loaded, so wait for it.
    window.addEventListener('DOMContentLoaded', init);
} else {
    // Document is ready (interactive or complete), so call init immediately.
    init();
}