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

2

8

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:

  • Parse "One line of html code"
  • Seeing some block of JavaScript
  • Finish rendering the HTML, preceding the "JavaScript block", so the browser will show at this point: "One line of HTML code"
  • Now pause rendering and execute the JavaScript code.
  • After executing the JavaScript code, start rendering again.

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.

Hydroquinone answered 25/10, 2017 at 11:18 Comment(7)
Oddly enough, if you put a breakpoint in the JS you'll see that One line of html code is shown before the 5 seconds.Mcclenon
@Mcclenon That's not that "oddly", because the definition of debugger is: "When the debugger is invoked, execution is paused at the debugger statement." They are talking about javascript execution. Rendering can not take place while executing javascript, but i can take place when there is no execution. So when pausing the execution, the html preceded the javascript, can be rendered again.Hydroquinone
The browser does parse the html, and it is available in the script. There is a difference between parsing and rendering. One could also say that there is a big change that the script can change the content, which would need a re-rendering of the page. So there is a speed gain when waiting with the rendering as the browser does now. Obviously your example is far from real live where one would go asynchronous where possible. I don't have proof or links to specs, so I'll leave this as a comment.Spaceship
@Spaceship Asynchronous would give the same result if the browser would take the script from cache. And if the html preceded Javascript, is for example not "One line of html code", but "Thousands lines of html code" then it's possible that a part is already rendered before "javascript execution" and another part not. A browser will not always block rendering of the first part, so not because of speed gain like you're suggesting. Otherwise the browser would neve show any html before the "javascript execution" and that's not true.Hydroquinone
@Spaceship It's no problem for a browser to re-render a page because of some javascript lines. This will have no influence on speed. The reason why a browser is not rendering and "executing javascript" at the same time (at least Chrome and Firefox), is that javascript can change the style of elements. You can not show and change something on exact the same moment. That's the reason for that and not the speed gain, because there is not.Hydroquinone
My guess is that parsing and rendering are independent operations in browsers, but some amount of parsing has to happen before anything is rendered.Ammonium
@Ammonium Rendering depends on "parsing", because first "parsing" must be done before you can start "rendering". So they are not independent. And if some amount of parsing has to happen before you could render something, then a browser would not render anything if you have html less tha that amount. So that's actually impossible,Hydroquinone
C
2

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

Caparison answered 6/4, 2018 at 10:43 Comment(3)
Thanks, but it's not working like that. Also external scripts can block rendering. Especially when the content it's coming from cache and there is no download time.Hydroquinone
If you want the rendering to happen before the javascript executes, try lazy loading of javascript, which gets invoked only after the DOM onload event triggers. for example, you can add async to the externalised script. <script async src="script.js"></script>Caparison
Also with async it's not like that. Async is misunderstood by a lot of people, so it doesn't suprise me that you're thinking like that. Javascript always blocks rendering, If the preceding html is not done yet with rendering, it will be blocked during javascript execution. Async has no influence on that.Hydroquinone
A
0

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.

Ammonium answered 1/11, 2017 at 23:55 Comment(11)
Part 1: I'm reading your article now and i did also tests like you in the past, but your test is actually saying nothing extra. It would be enough to know that "One line of html code" is in the DOM, but not yet on the screen. My example is showing the same. The rest is unnecessary for this discussion. So it's actually no answer to the question ;).Hydroquinone
Part 2: Also one correction about what you were saying: "but it appears that parsing doesn't necessarily need to be totally complete for JavaScript code to run" . HTML before the javascript execution (sync) must always be first in the DOM, before starting the execution (so html parsing must be completed). Rendering of the preceding html does not necessarily need to be totally complete for JavaScript code to run. But you must not confuse "rendering" and "html parsing".Hydroquinone
@MaartenBruins I am not sure how your original example shows that "One line of html code" is in the DOM but not the screen, that's what my example does. Nothing in your original example is accessing the DOM via JavaScript. I don't believe I've confused rendering and parsing, I was trying to say individual snippets of a page can be parsed and subsequently rendered independently per-snippet.Ammonium
You're right that i did not access the DOM in this example of me, because the question was not about that part. I did not prove that with my test, but from theory (and earlier tests) i know that in an synchronous environment the html must be in the DOM, before starting the execution of the code. My test was more about if "rendering" could be not done yet when starting the execution.Hydroquinone
@segfaultBut i don't know if you're wrong, but to get answer to that, you're saying: "but it appears that parsing doesn't necessarily need to be totally complete for JavaScript code to run" (example is about synchronous environment) What do you mean exactly with "parsing". Which parser? The html parser?Hydroquinone
@MaartenBruins Yes, HTML parsing doesn't need to be complete for JavaScript code to run. HTML can be partially parsed, and a <script type="text/javascript"> tag can be in turn passed to a JavaScript parser and start executing before the HTML that appears underneath it is parsed.Ammonium
Oh i understood you wrong. You were talking about the HTML underneath the Javascript. Yeah of course that will not be in the DOM yet, before executing the Javascript. Because the execution of Javascript is blocking the "HTML parser" to go further (in synchronous environment). But i don't see the connection for that to my question?Hydroquinone
In my example: "One line of html code" is coming before the JavaScript block, so i don't understand why you start to talk about html after the JavaScript block, because in my example that's not the case. Yeah "</body></html>", but in my example it was about "One line of html code". And anyway "</body></html> has no influence on rendering, because it will not be showns on the screen.Hydroquinone
And you are saying: "I am not sure why the "One line of html code" string isn't rendered on the page before the synchronous delay happens" The reason behind that and why it's like that and not vice versa ... that's exactly what my question is, because i'm also not sure ;). So actually we have the same question ;).Hydroquinone
@MaartenBruins I am guessing that the reason it appears in the DOM but not on the screen before the synchronous delay is purely due to the specific implementation of the browser, and that there isn't a formal reason that would appear in a specification.Ammonium
But if they don't have a good reason for that implementation then they must try to change it. If there would be no formal reason, then you could also first finish rendering of the preceding html. It would speed up the rendering process, while the "total page load time" will not change. Only different order of doing things with 1 advantage: speed gain in term of rendering. That's my point.Hydroquinone

© 2022 - 2024 — McMap. All rights reserved.