Bug 202548 - FontFaceSet's ready promise is not always resolved
Summary: FontFaceSet's ready promise is not always resolved
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Text (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Myles C. Maxfield
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2019-10-03 16:29 PDT by Myles C. Maxfield
Modified: 2019-10-10 12:53 PDT (History)
14 users (show)

See Also:


Attachments
Test (1.68 KB, patch)
2019-10-03 16:29 PDT, Myles C. Maxfield
no flags Details | Formatted Diff | Diff
Patch (5.86 KB, patch)
2019-10-03 17:00 PDT, Myles C. Maxfield
youennf: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Myles C. Maxfield 2019-10-03 16:29:09 PDT
When we do layout on an element, FontRanges::glyphDataForCharacter() will cause the first available font to start loading, but will continue looking at subsequent fonts to see if there's a good one we can render while the load is happening. When looking for a fallback font, it calls FontRanges::Range::font() with a ExternalResourceDownloadPolicy set to Forbid. This is fine, except that a side effect of calling this function is that the CSSFontFace marks itself as Loading, which means document.fonts.ready is deferred. Then, the load finishes, and the subsequent CSSFontFace is never actually used, meaning it never exits the Loading state, which means document.fonts.ready never fires.
Comment 1 Myles C. Maxfield 2019-10-03 16:29:39 PDT
Created attachment 380167 [details]
Test
Comment 2 Myles C. Maxfield 2019-10-03 16:30:32 PDT
This was discovered during Chris's investigation of https://bugs.webkit.org/show_bug.cgi?id=202476.
Comment 3 Myles C. Maxfield 2019-10-03 16:33:14 PDT
The fix for this is likely in CSSFontFace::pump() where it setStatus(Status::Loading) right before source->load().
Comment 4 Myles C. Maxfield 2019-10-03 17:00:13 PDT
Created attachment 380169 [details]
Patch
Comment 5 Chris Dumez 2019-10-04 08:36:04 PDT
Comment on attachment 380169 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=380169&action=review

> Source/WebCore/css/CSSFontFace.cpp:740
> +                if (policy == ExternalResourceDownloadPolicy::Allow && m_status == Status::Pending)

I have one concern.

Imagine the case where this is a local font and this local font ends up being successful and Policy is ExternalResourceDownloadPolicy::Forbid. Previously, we would do:
1. Status update: Pending -> Loading
2. Call load()
3. Then below, source->status() returns Success
4. Status update: Loading -> Success (See switch below).

After your change:
1. Call load() without status update, so we are Pending
2. Then below, source->status() returns Success
3. Status update: Pending -> Loading

Now we're stuck in Loading state even though our source successfully loaded, right?
Comment 6 Chris Dumez 2019-10-04 08:39:07 PDT
Comment on attachment 380169 [details]
Patch

cq- to make sure this does not inadvertently committed before answering my comment. If my comment is inaccurate, feel free to override and cq+.
Comment 7 Myles C. Maxfield 2019-10-10 12:38:52 PDT
Comment on attachment 380169 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=380169&action=review

>> Source/WebCore/css/CSSFontFace.cpp:740
>> +                if (policy == ExternalResourceDownloadPolicy::Allow && m_status == Status::Pending)
> 
> I have one concern.
> 
> Imagine the case where this is a local font and this local font ends up being successful and Policy is ExternalResourceDownloadPolicy::Forbid. Previously, we would do:
> 1. Status update: Pending -> Loading
> 2. Call load()
> 3. Then below, source->status() returns Success
> 4. Status update: Loading -> Success (See switch below).
> 
> After your change:
> 1. Call load() without status update, so we are Pending
> 2. Then below, source->status() returns Success
> 3. Status update: Pending -> Loading
> 
> Now we're stuck in Loading state even though our source successfully loaded, right?

The next two lines after the Pending -> Loading transition is

            if (m_status == Status::Loading || m_status == Status::TimedOut)
                setStatus(Status::Success);

We just set the status to Loading, so we take this "if" branch and set the status to success.

I'll add a test.
Comment 8 Myles C. Maxfield 2019-10-10 12:42:25 PDT
Committed r250983: <https://trac.webkit.org/changeset/250983>
Comment 9 Radar WebKit Bug Importer 2019-10-10 12:43:17 PDT
<rdar://problem/56165070>
Comment 10 Chris Dumez 2019-10-10 12:53:02 PDT
Comment on attachment 380169 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=380169&action=review

>>> Source/WebCore/css/CSSFontFace.cpp:740
>>> +                if (policy == ExternalResourceDownloadPolicy::Allow && m_status == Status::Pending)
>> 
>> I have one concern.
>> 
>> Imagine the case where this is a local font and this local font ends up being successful and Policy is ExternalResourceDownloadPolicy::Forbid. Previously, we would do:
>> 1. Status update: Pending -> Loading
>> 2. Call load()
>> 3. Then below, source->status() returns Success
>> 4. Status update: Loading -> Success (See switch below).
>> 
>> After your change:
>> 1. Call load() without status update, so we are Pending
>> 2. Then below, source->status() returns Success
>> 3. Status update: Pending -> Loading
>> 
>> Now we're stuck in Loading state even though our source successfully loaded, right?
> 
> The next two lines after the Pending -> Loading transition is
> 
>             if (m_status == Status::Loading || m_status == Status::TimedOut)
>                 setStatus(Status::Success);
> 
> We just set the status to Loading, so we take this "if" branch and set the status to success.
> 
> I'll add a test.

Oh, duh. Sorry about the noise.