Search code examples
javascriptdomdomcontentloaded

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>

Solution

  • 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)