Font Load Tracking with Futures

Last updated:

This is an attempt to rewrite the CSS Font Load Events spec to instead use Futures, as I believe it makes for much simpler and easier-to-use interface.


partial interface document {
  readonly attribute FontList fonts;

enum LoadStatus { "loading", "ready" };

interface FontList {
  /* indexed getters for Font objects corresponding to 
     all the @font-face rules in the document */
  Future ready()
  boolean checkFont(DOMString font, optional DOMString text = " ");
  Future loadFont(DOMString font, optional DOMString text = " ");
  attribute LoadStatus loadStatus;

interface Font {
  Future ready()
  /* readonly versions of all the CSSFontFaceRule attributes,
     minus ones that point into the stylesheet */

Replacing document.fontLoader

First of all, I'm replacing document.fontLoader with document.fonts, an array-like of Font objects. Each Font object is a readonly version of a CSSFontFaceRule object, without any links into the CSSOM. This is necessary if we want to expose rules from cross-origin stylesheets because we're not allowed to expose cssText on them. It should also be something that can be safely exposed to Workers, as there's no DOM/CSSOM link to make it unsafe. The FontList contains Font objects for all the @font-face rules in all the stylesheets for the document, in document order.

Replacing notifyWhenFontsReady()

Next, let's replace notifyWhenFontsReady(). This is a textbook example of something that's already a Future, but doesn't know it yet.

To replace it, I added FontList#ready(). It must return a new future on every call. The state of the returned future varies over time:

  • Futures requested while the document is loading are in the "pending" state. They are accepted when the document has finished loading, there are no pending font loads, and no pending layout operations that may depend on the state of font loads.
  • After the document is finished loading, if there are no currently pending font loads or layout operations that may depend on the state of font loads, futures requested are already accepted.
  • Otherwise, futures requested are in the "pending" state, and they are accepted when there are no pending font loads or layout operations that may depend on the state of font loads.

(Is there a reasonable accept value to return here?)

Fixing loadFont()

The current loadFont() is another textbook "future but doesn't know it" API, with its success and error callbacks. To fix it, I just simplified its API to be the same as checkFont(), and had it return a Future.

If the requested fonts are already loaded, the returned future is already accepted. Otherwise, the function kicks off the necessary loads and returns a pending future, which is accepted when the fonts load, or rejected if any of the fonts fail. (Exactly equivalent to simply retrieving all the relevant Font objects, and doing Future.all(>x.ready())).)

checkFont() is unchanged. I'm unsure what it's necessary for, but assuming that it's worth keeping, it works fine.

Replacing the loading/loading-done events

These are replaced with a simple attribute loadStatus. While the document is loading, or while fonts are loading, the status is "loading". Otherwise, the status is "ready".

This isn't quite an adequate replacement, but I'm planning to augment it with a Streams proposal that makes it trivial to get a stream of changes to an attribute, so you can then just watch the loadStatus attribute.

Replacing the loadstart/load/error events

This is another simple Future conversion. Instead of events for every font fired at a central FontLoader, we now have a convenient way to get at individual @font-faces via the FontList. So, this is replaced by a new Font#ready() function on individual Font objects.

The future returned is pending while the document is loading, before the font is needed, or while the font is loading. It's accepted after the font finishes loading, or rejected if the font encounters an error while loading.

(Should this have some value when you accept? Perhaps just the Font object itself, so you can reuse the same callback on lots of Fonts and be able to distinguish which one loaded?)

(What's the convention for platform rejections? Probably reject with the Error object?)

Once we iron out the design of ProgressFuture, this should change to a progress future, which reports its progress as "unloaded" or "loading" before completing.

That's It

I think the new API is much smaller and easier to use, and more functional than before. Once we get Streams for observing property changes, it'll be a strict superset of the old functionality, but without any race conditions or the unneeded bagged of DOM events.

(a limited set of Markdown is supported)

Is there a reasonable accept value to return here?

As I read it I asked myself the same question. Maybe the font name?


(a limited set of Markdown is supported)

Drive-by feedback: A shared worker can have multiple documents objects associated with it. Furthermore, I think we want a way to create a font from a worker and not have to tunnel that through a Document first to setup some CSS rules. We should have something like Font.fromURL() which returns a future which is resolved with a Font that can then be used by canvas and such.


(a limited set of Markdown is supported)

This is cool, but I'm actually more interested in these JavaScript "Futures" you speak of.

I'm finding it difficult to Google these, but it sounds like you're talking about something similar to Deferreds or Promises. Are these finally getting baked into the language?


(a limited set of Markdown is supported)

Re #3:

Found the proposal for DOM Futures in your other post:

I'm a bit confused though, why is this a DOM proposal instead of an ECMAScript proposal? :S


(a limited set of Markdown is supported)

Re #1: But what font? There could be multiple, or none. Unlike some of the others, I don't think returning a list of the fonts that loaded would actually be very useful.


(a limited set of Markdown is supported)