Re: [css-font-load-events] Using Futures

On Thu, Apr 4, 2013 at 5:29 PM, Bjoern Hoehrmann <derhoermi@gmx.net> wrote:
> * Tab Atkins Jr. wrote:
>>[...]
>
> (Some gut reactions:)
>
>>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.
>
> The dependency would be on the font that is "ultimately" used, not on
> any loaded font, otherwise web pages would break when users disable the
> use of illegible fonts in their browser...

Right, that's why that case is solved with document.fonts.ready(),
which doesn't care which font is ultimately used, just whether the
fonts that are currently being used are all loaded or not.  If the
user has overriden the fonts that the page specifies, it'll still work
correctly.

>>[http://gitbug.com/slightlyoff/DOMFuture]
>
> http://github.com/slightlyoff/DOMFuture

I... have no idea how I mistyped that.

>>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;
>>}
>
> Having event listener attributes on the object strikes me as a bad idea.
> It is likely that several separate components would want to listen for
> them, but having the attributes encourages code that would override the
> listeners of one component in another, leading to bugs that are annoying
> to debug (only happens on high latency networks, only with empty cache).

I'm sticking with the general consensus that on* attributes are useful
for simple things, so they should be provided, and robust code should
use addEventListener instead.  Since the FontList is an event target,
you get that for free, right?

>>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.
>
> That sounds more like `document.styleSheets.fontFaceRules` to me.

No reason to hide this API behind such an unwieldy name.

> It's
> rather unclear to me that this filtered and read-only view is a good
> idea.

I gave my reasoning, but I could be convinced otherwise.  I'd like to
design it so that Workers and the main document have the same view, as
much as possible, unless that becomes unwieldy.

>>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().
>
> It's not clear to me whether "same future" refers to object identity and
> if it does, why this would not return a new object on each invocation.

I made an arbitrary choice to return the same object.  Returning a
different object each time that resolved at the same time would also
be completely fine.

> I
> am not sure from your proposal what the object would be "resolved" with,
> and when.

The value it is resolved with is irrelevant; we can either make it
something simple like undefined, or try and shoehorn in something
useful, like returning an array referencing all the currently-loaded
Fonts.

I specified when it resolves: when the browser has loaded all
stylesheets and finished loading all the fonts it initially chooses to
load.  This can probably be stated better, but it's when the browser
transitions from "loading some fonts" to "not loading any fonts".

>>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.
>
> It seems you mean that you propose `checkFont` and `loadFont` to have
> the same prototype (argument list), but otherwise they are not identical
> at all.

Yes, sorry that I used unclear wording.

>>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.
>
> This sounds like you are proposing that load failures cannot be observed
> through the Event APIs. If so, I am not sure what motivates that.

Hm, not sure how you got that.  Given that the Font.ready() function
is replacing a trio of events, one of which is "error", that's clearly
not true.  Futures just capture the notion idiomatically, is all.

> Overall, I think it would be good to have a common protocol for similar
> APIs, and if "Futures" are such a protocol I think it makes sense to use
> them for `loadFont`, but beyond that I am not really a fan of your pro-
> posal, or the `FontLoader` so far.
>
> One worry is that when you actually want to take measurements only when,
> as far as the browser knows, final layout no longer depends on unknowns,
> you have to wait for more than just fonts, there may also be images or
> web components or other things that are not yet "ready", so binding to
> font loads would be incorrect.

Yes, the next time we invent CSSOM measurement APIs, they should be
async and based on Futures, so that all of these things are taken into
account automatically.  Are you suggesting that this means we
shouldn't partially solve the problem at this level?

> Another worry is the liveness of the FontList you propose; a "Future"
> that encapsulates multiple "Future" objects is a common idiom, but you
> usually would know what you are waiting for, and the typical libraries
> do not appear to support "live" semantics; as an example, it bothers me
> that `X.all(Array.from(document.fonts).map(x => x.ready()))` is quite
> different from `document.fonts.ready()`, where `X.all` creates a Future
> out of a list of Futures.

They're different because, well, they're different!  My proposed
FontList.ready() is answering "when is the browser done loading fonts
currently needed on the page", while your all()-based one is asking
"when is the browser done loading *all* fonts".

The latter doesn't actually seem useful to anyone, so I'm comfortable
not providing that.  Is it the fact that the function name is the same
that's bothering you?  If so, can you suggest something better that's
still reasonably short and clear?

> Yet another problem I see is that currently components tend to be poorly
> isolated. You might have a third party script on your site that attempts
> to use a font that fails to load, but you do not want your own code to
> break because of that, so using `document.fonts.ready()` is a bad idea
> for robust code. That is a problem that might go away if and when compo-
> nents become better isolated, but right now it is a concern.

I addressed this up above - I don't understand what problem you're
referring to.  Could you sketch it out in more detail?  Note that the
future returned by FontList.ready() never gets rejected, as it's not
based on the state of individual fonts, but rather on the browser's
loading behavior.

~TJ

Received on Monday, 8 April 2013 14:25:26 UTC