Intent to Implement and Ship: Pause HTML parser while loading stylesheets in <head>

262 views
Skip to first unread message

Rune Lillesveen

unread,
Aug 3, 2020, 12:48:56 PM8/3/20
to blink-dev
fut...@chromium.org, chri...@chromium.org https://github.com/chrishtr/rendering/blob/master/stylesheet-loading-behavior.md#blink-longer-term- https://github.com/chrishtr/rendering/blob/master/stylesheet-loading-proposal.md Specification: https://html.spec.whatwg.org/
We are not doing any changes to specified behavior. It is a continuation of shipping parser blocking stylesheets in body tracked by issue 481122 with the corresponding intent to ship.
Pause the HTML parser waiting for stylesheets to load instead of blocking rendering for FOUC (Flash of Unstyled Content) avoidance.

This is a continuation of shipping parser blocking stylesheets in body tracked by issue 481122 with the corresponding intent to ship. This behavior was not shipped for sheets in <head> at that time, at least due to HTML Imports being an issue for preload scanning. HTML Imports are now enabled by a reverse origin trial and will be removed in M87 according to the runtime feature documentation. There should be no change in behavior and no spec compliant tests should change result for this change, but the change can be observable in for timing (see Risks section).

In order to not have negative impact on performance, this behavior relies on the preload scanner being able to request resources necessary. There have been discussions around the need for specifying a preload scanner in order to specify this parser blocking behavior in a spec.

We intend to run a finch experiment to measure the impact on important performance metrics before we ship this change by default. The current status is that there is an implementation which has been behind a runtime flag (BlockHTMLParserOnStyleSheets) for a while. Various changes have landed after that and last time we did a try run on the bots with the flag enabled there was a few unit tests for scrolling and a non-spec-compliant wpt that started failing (it already failed in non-Blink browsers).

  • Predictability.
  • Simplifies the FOUC code - can remove render blocking stylesheets concept.
  • Avoids using incomplete styles on parsing finished:
The behavior here is not in a spec, but spec changes are outlined in the second link of the explainer section. To my knowledge this change is observable in timing changes where the order of events is not guaranteed by the spec. For instance, with this change, it is more likely that for the case below, the timeout handler will not find the #x element if the slow.css file takes more than 200ms to load: <script> setTimeout(() => document.querySelector("#x"), 200); </script> <link rel="stylesheet" href="slow.css"> <div id="x"></div> Firefox: No public signals Edge: No public signals Safari: No public signals Web developers: No signals
Yes Yes The behavior is undefined as far as the current specifications specify. The feature is tested in the sense that all current wpt tests should still pass given that they test specified behavior. https://crbug.com/891767 https://chromestatus.com/feature/5727739434762240
This intent message was generated by Chrome Platform Status.

Dominic Farolino

unread,
Aug 3, 2020, 12:55:57 PM8/3/20
to Rune Lillesveen, blink-dev
Could you be more specific, even if this behavior is already in the spec?
--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CACuPfeSgvQbnV3Pp8ouhSy3XK9DtPGNPsLrMR7XxfZ-p_anMrQ%40mail.gmail.com.

Yoav Weiss

unread,
Aug 4, 2020, 2:26:51 AM8/4/20
to Rune Lillesveen, blink-dev
I'm having a hard time seeing how this will not regress performance, given that HTML parsing would now be largely done serially (after CSS finished downloading & parsing), rather than in parallel as it is today.

Do you have numbers that estimate the current cost of HTML parsing and the impact of moving it to be serial? Do I understand correctly that tokenization will continue to be in parallel?

It should also be noted that the PreloadScanner doesn't cover all resources (e.g. iframes and videos), so delaying those could be another source of regressions.

--

Malte Ubl

unread,
Aug 4, 2020, 9:21:36 AM8/4/20
to Yoav Weiss, Rune Lillesveen, blink-dev
AMP has an optimization that times out stylesheets pointing to font providers (like Google Fonts or TypeKit) if they don't load within a given deadline https://github.com/ampproject/amphtml/blob/master/src/font-stylesheet-timeout.js#L71

That optimization relies on async scripts that are referenced after the link-stylesheet element being executed before the stylesheet loads. While this only impacts high percentile page loads, it does have significant impact for those.

Maybe a compromise would be to only ship this after the async attribute is implemented on the stylesheet element.

Rune Lillesveen

unread,
Aug 4, 2020, 11:00:30 AM8/4/20
to Malte Ubl, Yoav Weiss, blink-dev

Did you mean like this (or with an async attribute on the script tag moved below the links)?:

<!doctype html>
<script>
  let timer = setInterval(() => {
    let sheet_count = document.styleSheets.length;
    console.log(document.styleSheets.length);
    if (sheet_count == 2)
      clearInterval(timer);
  }, 1000);
</script>
<link rel="stylesheet" href="https://wpt.live/css/support/a-green.css?pipe=trickle(d10)">
<link rel="stylesheet" href="https://wpt.live/css/support/a-green.css?pipe=trickle(d5)">
<p>Renders after 10 seconds.</p>


The sheet that takes 5s to load appears in document.styleSheets at the same time (after 10s) as the first sheet with the BlockHTMLParseOnStyleSheets runtime flag enabled. After 5s in current Chrome.

Tested the snippet above in Firefox and Safari, and Firefox instantiates the stylesheet objects immediately, before loading finishes, and Safari TP adds both sheets after 10s.

Also tested with the same snippet, only with moving the script element below the links, adding an async attribute, and making the script an external resource. Same results in the various browsers as the first snippet, but with BlockHTMLParserOnStyleSheets enabled, Chrome does not run the async script until both stylesheets finishes loading.

patm...@gmail.com

unread,
Aug 4, 2020, 1:09:20 PM8/4/20
to blink-dev
Just a quick sanity check, this is only for media-match stylesheets, right? Or will the parser also be blocked on print stylesheets?  If I remember correctly, the existing in-body parser blocking is only for media-matching sheets. Otherwise it will break the common pattern people use today for async stylesheets (tag starts with a print media and has an onload handler that changes it to screen).

Daniel Bratell

unread,
Aug 6, 2020, 2:37:58 PM8/6/20
to Rune Lillesveen, Malte Ubl, Yoav Weiss, blink-dev

You mention a couple of things that need to go right for this to be shipped and since I agree, I think it would be best to wait with a shipping decision until we know more about those things.

Specifically whether the improved preload scanner will do a good enough job and whether there will be something else in the policy change that affects loading time negatively. Also (lesser concern) whether there will be compatibility problems from the timing changes.

/Daniel

Rune Lillesveen

unread,
Aug 10, 2020, 9:47:47 AM8/10/20
to Yoav Weiss, blink-dev
On Tue, Aug 4, 2020 at 8:26 AM Yoav Weiss <yo...@yoav.ws> wrote:
I'm having a hard time seeing how this will not regress performance, given that HTML parsing would now be largely done serially (after CSS finished downloading & parsing), rather than in parallel as it is today.

Yes, I agree that it should to some extent.

Do you have numbers that estimate the current cost of HTML parsing and the impact of moving it to be serial?

No. Need to run some benchmarks before running an experiment in the wild.

Do I understand correctly that tokenization will continue to be in parallel?

Yes. I'm not familiar with the html parser implementation details, but at least the <p> element is tokenized immediately for this case:

  <!doctype html>
  <head><link rel="stylesheet" href="https://wpt.live/css/support/a-green.css?pipe=trickle(d10)"></head>
  <p>Renders after 10 seconds.</p>

It should also be noted that the PreloadScanner doesn't cover all resources (e.g. iframes and videos), so delaying those could be another source of regressions.

I see. Thanks.

Rune Lillesveen

unread,
Aug 10, 2020, 9:49:37 AM8/10/20
to patm...@gmail.com, blink-dev
On Tue, Aug 4, 2020 at 7:09 PM <patm...@gmail.com> wrote:
Just a quick sanity check, this is only for media-match stylesheets, right? Or will the parser also be blocked on print stylesheets?  If I remember correctly, the existing in-body parser blocking is only for media-matching sheets. Otherwise it will break the common pattern people use today for async stylesheets (tag starts with a print media and has an onload handler that changes it to screen).

The parser blocking would be for the same stylesheets which are currently render blocking, so <link rel=stylesheet media=print> will not block.

--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.

Malte Ubl

unread,
Aug 10, 2020, 9:52:54 AM8/10/20
to Rune Lillesveen, Yoav Weiss, blink-dev
I mean something like that but with the script element after the link element. This is being used on a large document corpus that cannot be changed easily in terms of order.

Rune Lillesveen

unread,
Aug 10, 2020, 10:08:49 AM8/10/20
to Malte Ubl, Yoav Weiss, blink-dev
I see. I tried with an async script after the stylesheets which does not have execution blocked by the stylesheet with the current behavior, but will with the parser blocking:

<!doctype html>
<script async src="slow.js"></script>

<p>Renders after 10 seconds.</p>

with slow.js:

let timer = setInterval(() => {
  let sheet_count = document.styleSheets.length;
  console.log(document.styleSheets.length);
  if (sheet_count == 2)
    clearInterval(timer);
}, 1000);

Reply all
Reply to author
Forward
0 new messages