Maarten Bruins
Maarten Bruins

Reputation: 189

Why is a browser not always finishing rendering of the preceding HTML, before executing JavaScript?

The question is about the following code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Test</title>
</head>
<body>
    One line of HTML code
    <script>
        // Synchronous delay of 5 seconds
        var timeWhile = new Date().getTime();
        while( new Date().getTime() - timeWhile < 5000 );
    </script>
</body>

I tested it in Firefox and Chrome and they are showing (rendering): "One line of HTML code" after 5 seconds and not within 5 seconds. Why is a browser doing that?

I understand why a browser has to stop rendering when executing JavaScript, because you can change the style of elements with JavaScript (as an example). It would give problems if the browser has to show and change content exactly at the same moment. That's why a browser is blocking rendering while executing JavaScript.

In the example above when starting with the executing of JavaScript, "One line of HTML code" is already parsed by the "HTML parser". It has to, because JavaScript can contain for example document.write, so then the appended string has to come after the preceding HTML. Apparently there is some time between "parsing HTML" and showing / rendering that same HTML, because otherwise the browser in this example would already show something within 5 seconds, but that's not the case.

When you replace "One line of HTML code" by a lot of HTML code then the browser will already show some content within the 5 seconds, so in principle it's possible to show already some content.

If I would be a browser then I would do:

In an example like this, the browser could show some content 5 seconds earlier. That's a big speed gain in terms of rendering.

Maybe it's something that browsers can improve, but maybe there is another reason. Maybe someone knows more about it and can explain me that.

Upvotes: 8

Views: 1891

Answers (2)

Eby Jacob
Eby Jacob

Reputation: 1458

Try to externalise the inline javascript which you have in above example.

In the inline script, the time is taken up running the script, which might change the DOM. Trying to render the DOM while it's mutating is a recipe for a mess. So rendering only happens at points when the JS is stalled, and therefore the DOM is stable.

While waiting for an external script to download, the running of scripts is stalled, so the DOM can be rendered safely. The downloaded JS won't be run until the rendering is complete.

Hope this Helps!

Regards, Eby

Upvotes: 2

segfault
segfault

Reputation: 572

Parsing and rendering are two distinct operations that can be independently run by the browser, but both can operate on small snippets of HTML/CSS/etc code, and don't need all resources to be completely loaded to start doing their respective work. Of course, anything that gets rendered must first be parsed, but it appears that parsing doesn't necessarily need to be totally complete for JavaScript code to run, and to start showing the user content as quickly as possible, it would also make sense for browsers to start rendering the page before parsing completes.

Consider this modification of your example code (I tested this in Google Chrome Version 62.0.3202.75 on macOS):

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Test</title>
    </head>
    <body>
        One line of html code
        <script>
            // Synchronous delay of 5 seconds
            var timeWhile = new Date().getTime();
            for (var current = new Date().getTime(); current - timeWhile < 5000; current = new Date().getTime()) {
                if (current - timeWhile === 2500) {
                    alert(document.body.childNodes[0].nodeValue);
                    alert(document.body.childNodes[2].nodeValue);
                }
            };
        </script>
        Another line of HTML code
    </body>

I added alert()s to your code instead of console.log()s because flushing/writing to the JavaScript console also seems to be blocked by the synchronous delay.

The first alert() shows the "One line of html code" string before any text appears on the page, proving that that part of the page has been parsed before it has been rendered.

However, the second alert() doesn't happen. Because the "Another line of HTML code" line hasn't been parsed yet, it is not defined as a child node of document.body, so attempting to access it throws an error that prevents the alert from displaying, and instead shows up in the JavaScript console: Uncaught TypeError: Cannot read property 'nodeValue' of undefined.

If you manually re-run alert(document.body.childNodes[2].nodeValue); in the console after the page has loaded, you see an alert with "Another line of HTML code" as expected.

I am not sure why the "One line of html code" string isn't rendered on the page before the synchronous delay happens, but I assume that behavior is specific to the implementation of the browser.

Upvotes: 0

Related Questions