Is JavaScript execution deferred until CSSOM is built or not?
Asked Answered
A

1

5

The answer to this question has been clear to me ever since I read/learned about CSSOM, until today. I can't seem to be able to find the initial article, but it explained quite clear, with examples, that JavaScript execution is deferred until CSSOM is built from all <style> and <link> tags in <head> (except those not applying, based on @media queries).
Or at least that's what I made of it at the time and I had no reason to doubt it until today.

This seems to be backed up by the bold-ed statement in this sub-chapter of Web Fundamentals / Performance, from Google:

... the browser delays script execution and DOM construction until it has finished downloading and constructing the CSSOM.

However, this statement was seriously challenged by a friendly chat on the subject with another SO user under this answer I provided, in which he proposed the following to prove the opposite:

<head>
  <script>document.write("<!--");</script>
  <style> body { background-color: red; } </style>
  -->
</head>

Ok, so let's make sure. Let's replace the <style> with

<link rel="stylesheet" type="text/css" href="test.php" />

... and make test.php hang for a few seconds:

<?php
sleep(10);
header('Content-Type: text/css');
?>

/* adding styles here would be futile */

If I am right (and js execution is deferred until CSSOM is built), the page hangs blank for 10 seconds, before building CSSOM and before executing the <script> that would comment the <link /> out and would allow the page to render.

If he is right, the js is ran as it's met and the <link /> request never leaves, because it's a comment by now.

Surprise:

  • the page renders right away. He's right!
  • but the <link /> request leaves and the browser tab shows a loading icon for 10 seconds. I'm right, too! Or am I? I'm confused, that's what I am...

Could anyone shed some light into this? What is going on?
Does it have to do with document.write?
Does it have to do with loading a .php file instead of a .css?


If it makes any difference, I tested in Chrome, on Ubuntu.

I kindly ask linking a credible (re)source or providing an eloquent example/test to back-up any answer you might consider providing.

Activate answered 5/6, 2017 at 18:28 Comment(6)
I don't feel qualified to answer this. I did find this article developers.google.com/web/fundamentals/performance/… and in that section it states this: "Even if the script is inlined directly into the page, the browser can't execute it until the CSSOM is constructed. In short, inlined JavaScript is also parser blocking."Asis
@Asis I know. You'll find this statement in several places in that documentation (which is otherwise pretty solid and a very good read), but it conflicts with what's happening in the browser, methinks. In their browser, even. Something doesn't add up.Activate
Out of curiosity, if you place a dependency on the CSSOM in your script prior to document.write, does it change the outcome? For example, console.log(document.documentElement.style.width);Unwilling
@TravisJ console.log(document.documentElement.style.width); outputs nothing (an empty line). However, console.log('nothing') outputs what you'd expect: nothing. And it happens right away.Activate
Yeah, I know it outputs nothing (there are no computed styles yet), however I was just curious if accessing the style would cause a dependency on the CSSOM or not.Unwilling
I was also curious! Ilya, the author, actually mentioned this exception in his Udacity course video: "Inline scripts will always block on CSS Object Model. Well, with one exception. If you put your JavaScript above your CSS, then it will execute without blocking on CSS." I wonder if it's spec'ed, but it makes some sense to me. That is, when a browser parses the document and encounters a script, it doesn't know if there will be CSS after the script. It can choose to just execute the JS. youtu.be/AKkzB7A2C58?t=94Sternpost
R
3

This quote is true, but it means that if you place your style sheet first, and a script after it, the script will not execute until the style sheet is downloaded and parsed. See this example:

test.php:

<?php
sleep(5);
header('Content-Type: text/css');
echo 'body {background-color: red;}';

index.html:

<link rel="stylesheet" href="test.php">
<script>console.log('done');</script>

The console.log call won't be executed until background color changes to red.

This leads to the conclusion that building CSSOM isn't done once for all style sheets, but it's a gradual process – when browser encounters a style sheet, it downloads it, parses, and moves next. Also probably browser first makes a list of all CSS resources and adds them to the download queue, even before executing any scripts. That would explain why the request is made even though the link tag is commented by a script.

Resource answered 5/6, 2017 at 20:25 Comment(6)
This basically leads us to the conclusion that <head> parsing is, in fact, single-threaded. And that building of CSSOM is gradual, done in stages, as each <style> or <link /> tag is met and parsed. It's not a process of reading all CSS resources from <head> deferring all <script>s in <head> until final "head" CSSOM is done, after which all loaded <script>s would execute, prior to the start of document rendering, as I previously thought. But it still doesn't explain why in my example the request for test.php is made. Probably a list of head resources is made before js runs.Activate
@AndreiGheorghiu In your example the request being made looks like a browser bug, because if you inspect the DOM tree in console, you'll see that the link tag is commented.Coremaker
I'd say the browser reads the list of all CSS resources first and loads it in a separate process, because it's a list that has to be loaded (in 99.99% of cases - the above being the exception). Doing it in a separate thread makes the process faster, as it's not prone to blocking from main thread. I'd say it's a feature, not a bug. Thank you for taking the time to answer.Activate
@AndreiGheorghiu But at least it should abort the request when the tag is commented.Coremaker
I totally agree.Activate
@MichałPerłakowski I did some testing and it would indeed "appear" to be a browser bug. Both Firefox and Chrome put calls in to load the resources that will become commented out before the inline script that starts the html comment executes. They both then load the files even though they never parse or execute them. You can see this happen in the Network tabs of their Developer tools. Oddly enough IE 11 doesn't do this (IMHO the only correct behavior). It completely ignores the tags "that will become commented out".Asis

© 2022 - 2024 — McMap. All rights reserved.