Why DOMContentLoaded handler can block first paint?
Asked Answered
A

2

10

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>
Arundell answered 25/7, 2020 at 23:7 Comment(2)
Because DOMContentLoaded is after all html has been printed and ready to be interacted with but before it has been rendered to screen.Underlay
I am just curious about what exactly you want to achieve?Liegnitz
A
0

I figured it out. There is a bug in Blink rendering engine that dispatches DOMContentLoaded event synchronously

// #blink/renderer/core/dom/document.cc
// #blink::Document::FinishedParsing

// FIXME: DOMContentLoaded is dispatched synchronously, but this should be
// dispatched in a queued task, see https://crbug.com/425790
if (document_timing_.DomContentLoadedEventStart().is_null())
  document_timing_.MarkDomContentLoadedEventStart();
DispatchEvent(*Event::CreateBubble(event_type_names::kDOMContentLoaded));
if (document_timing_.DomContentLoadedEventEnd().is_null())
  document_timing_.MarkDomContentLoadedEventEnd();
SetParsingState(kFinishedParsing);

** Why it sometimes was dispatched asynchronously I don't know. Now, on my side, it's always synchronous (probably there was one more bug at the time question was asked)

Arundell answered 23/1, 2022 at 12:28 Comment(0)
A
2

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();
}
Alvinia answered 28/7, 2020 at 12:37 Comment(0)
A
0

I figured it out. There is a bug in Blink rendering engine that dispatches DOMContentLoaded event synchronously

// #blink/renderer/core/dom/document.cc
// #blink::Document::FinishedParsing

// FIXME: DOMContentLoaded is dispatched synchronously, but this should be
// dispatched in a queued task, see https://crbug.com/425790
if (document_timing_.DomContentLoadedEventStart().is_null())
  document_timing_.MarkDomContentLoadedEventStart();
DispatchEvent(*Event::CreateBubble(event_type_names::kDOMContentLoaded));
if (document_timing_.DomContentLoadedEventEnd().is_null())
  document_timing_.MarkDomContentLoadedEventEnd();
SetParsingState(kFinishedParsing);

** Why it sometimes was dispatched asynchronously I don't know. Now, on my side, it's always synchronous (probably there was one more bug at the time question was asked)

Arundell answered 23/1, 2022 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.