- From: Tab Atkins Jr. <jackalmage@gmail.com>
- Date: Wed, 3 Apr 2013 22:35:57 -0700
- To: www-style list <www-style@w3.org>
- Cc: Anne van Kesteren <annevk@annevk.nl>
The Font Load Events spec is currently event-based, with a few ad-hoc callback-based APIs as well. It has an issue questioning whether it's worthwhile to switch to DOM Futures. I believe it is, and have a suggested new API for doing so. To start, I'll outline the three use-cases I think the spec is trying to address. 1. Sites that are going to do some type of DOM measurement that will depend on the loaded font (such as the size/position of an inline, or the height of a block), would like to be able to check if all fonts are loaded, and be notified when any currently-loading fonts are done. 2. Drawing text in <canvas> uses the CSS font mechanism, but doesn't trigger autoloading of webfonts like text in DOM does. Apps that do this would like some way to force font loads and be notified when they're finished, so they can actually draw the text. 3. Sites which offer UI for the fonts that are available, like Google Docs, would like to be able to tell when a given webfont is loading or loaded, so they can indicate the status appropriately in the UI. Right now, #1 is addressed through notifyWhenFontsReady(), which takes a callback called when fonts are ready (in the next event-loop spin if fonts are already done loading). #2 is addressed by loadFont(), which takes a 'font' declaration and optionally some text that it'll be applied to, and kicks off font loads if there are any. #3 is addressed by the loading/loadingdone events while fire based on the global state (whether any fonts are loading or not), and the loadstart/load/error events which fire per-@font-face. We can simplify this significantly by adding futures, and rearchitecting somewhat. Futures are a new concept defined in the DOM spec, based on significant and long-running experiences in JS APIs, where they have also been known as "Promises" or "Deferreds". Futures are designed to be the standardized way to listen to a *single* event. It solves this use-case more simply than DOM Events (less baggage from Event objects, for example), and address the important problem listening for an event that may have already happened (no race conditions!). Please check out Alex Russell's explanation and examples at <http://gitbug.com/slightlyoff/DOMFuture> for more information. Here's my proposed new API (explanations/justifications will follow): partial interface document { readonly attribute FontList fonts; } interface FontList : EventTarget { /* whatever idl magic you need to make this an array-like filled with Font objects */ Future ready() boolean checkFont(DOMString font, optional DOMString text = " "); Future loadFont(DOMString font, optional DOMString text = " "); attribute EventHandler onloading; attribute EventHandler onloadingdone; } interface Font : EventTarget { Future ready() /* readonly versions of all the CSSFontFaceRule attributes, minus ones that point into the stylesheet */ } 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 seems like a useful convenience API all by itself, but it's also 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. The FontList.ready() function returns a Future. This is initially unresolved, and resolves when the browser has loaded all stylesheets and has finished loading all the fonts it chooses to initially load. It must return the same future across multiple calls. If, after resolving the future, the browser beings loading more fonts, it must create a new future to return from .ready(), and again resolve it when the browser stops loading fonts. This addresses use-case #1, replacing notifyWhenFontsReady(). The loadFont() function has had its API reverted to be identical to checkFont, and it just returns a future which is resolved as soon as all the necessary requested fonts have loaded (which may be immediately, if all necessary fonts are already loaded). This addresses use-case #2. checkFont() is unchanged. To address use-case #3, the API has two pieces. If you care about the loading status of individual fonts, look for the relevant Font object and call its ready() function to obtain a Future which is resolved when the font is done loading, or cancelled when the font load has an error. This replaces the loadstart/load/error events. The loading/loadingDone events are unchanged - they're useful for providing UI indicating whether fonts are loading or not, rather than a one-off "tell me when fonts are ready", which is what the .ready() future is for. I think this significantly simplifies several parts of the API, and avoids potential race conditions with individual-font loads. It also unifies the APIs across several parts - right now you pass a callback as an argument to one function, as part of an arg object to another, and register event listeners for a third. This is the reason de etre for Futures, so it appears to be doing its job. ^_^ Finally, this addresses the use-case of handling fonts in Workers well (necessary since we're adding <canvas> to Workers), while the existing API has severe race-condition problems in that case. Thoughts? ~TJ
Received on Thursday, 4 April 2013 05:39:24 UTC