[csswg-drafts] [css-fonts] Add a `font-display` keyword to eliminate `@font-face` FOIT & layout shifts (#7271)

xiaochengh has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-fonts] Add a `font-display` keyword to eliminate `@font-face` FOIT & layout shifts ==
Currently, we don't have any easy & sound way to eliminate FOIT & layout shifts caused by web fonts:
- `font-display: optional` elimintes layout shifts and FOIT, but at the cost that the web font may not be used, which limits its usage
- Other `font-display` values do not reduce layout shifts at all
- `size-adjust` descriptor only reduces the layout shift but can't guarantee 0 CLS, and there might still be a FOIT. It's also complicated to use since we need to find out the correct value.

I'm proposing adding a new keyword to the `font-display` descriptor, tentative named `font-display: critical`, which:
- Turns the `@font-face` a [critical subresource](https://html.spec.whatwg.org/multipage/infrastructure.html#critical-subresources) of the style sheet, and hence makes it block the `load` event of the style sheet
- Makes UA start loading such a `@font-face` eagerly; other font faces are still loaded lazily when used
- It has the same [Font Display Timeline](https://www.w3.org/TR/css-fonts-4/#font-display-timeline) as `font-display: block`

In this way, as long as the `@font-face` is defined in a [render-blocking](https://html.spec.whatwg.org/multipage/dom.html#render-blocking-mechanism) style sheet (which everyone knows about but hadn't been specified until [recently](https://github.com/whatwg/html/pull/7474)), then it will block the first render of the document, and hence eliminate FOIT / layout shifts.

This feature is supposed to be used on web fonts that are truly critical, so that developers want to eliminate FOIT / CLS at a great cost of delaying rendering. It will be footgun-ish and shouldn't be used arbitrarily.

# Use cases

Basic usage:
```html
<head>
  <style>
  @font-face {
    font-family: my-font;
    font-display: critical;
    src: url(/my-font.ttf);
  }
  body { font-family: my-font; }
  </style>
</head>
<body>
  Text in my-font with absolutely no FOIT / CLS
</body>
````

There's also a particular use case I'd like to support: making a 3rd web font render-blocking without knowing the font url.

Developer page:
```html
<head>
  <link rel=stylesheet href="https://3p-fonts.com/cool-font.css?critical=yes">
  <style>body { font-family: cool-font; }</style>
</head>
<body>
  Text in cool-font with absolutely no FOIT / CLS
</body>
```

https://3p-fonts.com/cool-font.css?critical=yes:
```css
@font-face {
  font-family: cool-font;
  font-display: critical;
  /* url is maintained by 3p-fonts.com. May change at any time; may even be generated. */
  /* Developers shouldn't preload this url or inline this style sheet. */
  src: url(/some-random-hash-123654ABCFED-or-whatever.ttf);
}
```

# Possible discussions

- Q: How about preloading the font?
- A: Preloading just makes it less likely to cause FOIT / layout shifts, but there's no guarantee. And it doesn't work for the 3rd party web font use case.
- Q: How about adding `blocking=render` to the font preload `<link>` to block rendering until the preload finishes?
- A: Besides not working for 3rd-party web fonts, there are many issues with making preload explicitly interact with rendering, so we decided to remove `blocking=render` from preload. See more discussions at
  - whatwg/html#7896
  -  https://github.com/w3c/csswg-drafts/issues/5924#issuecomment-1120212780
- Q: This further blocks rendering on a subsequent load, which is bad for loading performance
- A: We can't fully eliminate subsequent loading behavior, since we must use external style sheet for 3p web fonts. However, there are ways to alleviate this by starting the font loading as early as possible (see below):
  - UA can use a preload scanner that not only preloads the external style sheet
  - UA can also use a preload scanner to scan the external style sheet response to preload the font
  - The external style sheet response can also have a `Link` header that preloads the font
- Q: Why making it a `font-display` keyword?
- A: This provides an even stronger alternative than `font-display: block`. So it doesn't make much sense to be a standalone descriptor and then interact with the other `font-display` values. That's also the reason why its font display timeline is the same as `font-display: block`, though it's mainly for spec completeness -- if we ever need to use such a font while it's still pending, it's likely a misuse.
- Q: What if it blocks rendering indefinitely?
- A: Render-blocking stylesheets and scripts can already block rendering indefinitely (or until the UA gives up), so it's not more footgun-ish than existing render-blocking things. And it allows developers to achieve a tradeoff that's hard to achieve before.

# Possible blockers

- "CSS critical subresource" seems very under-specified at the moment (#1088). But I guess we can just say "critical subresources should at least include `font-display: critical` font faces" without having to fully define what other critical subresources are.

@tabatkins @chrishtr 

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/7271 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 11 May 2022 23:26:45 UTC