Bug 222735 - Allow creation of a CSSFontSelector with a non-Document ScriptExecutionContext
Summary: Allow creation of a CSSFontSelector with a non-Document ScriptExecutionContext
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebCore Misc. (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Chris Lord
URL:
Keywords: InRadar
Depends on:
Blocks: 202793
  Show dependency treegraph
 
Reported: 2021-03-04 06:47 PST by Chris Lord
Modified: 2021-03-09 04:05 PST (History)
12 users (show)

See Also:


Attachments
Patch (22.79 KB, patch)
2021-03-04 06:54 PST, Chris Lord
no flags Details | Formatted Diff | Diff
Patch (29.16 KB, patch)
2021-03-05 10:25 PST, Chris Lord
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (29.46 KB, patch)
2021-03-08 02:28 PST, Chris Lord
no flags Details | Formatted Diff | Diff
Patch (29.56 KB, patch)
2021-03-08 05:05 PST, Chris Lord
no flags Details | Formatted Diff | Diff
Patch (30.62 KB, patch)
2021-03-09 01:39 PST, Chris Lord
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Chris Lord 2021-03-04 06:47:08 PST
We want to eventually be able to use fonts within workers for OffscreenCanvas - currently, the bits missing are CSSFontSelector, FontCache and then some additional support code to support loading fonts from FontFace objects (this last part not strictly being necessary, though all OffscreenCanvas font tests depend on it). These items don't necessarily make sense apart, but they are all fairly self-contained changes, so ideally we could check them in separately and enable fonts in workers in a separate patch, rather than using one monolithic patch.
Comment 1 Chris Lord 2021-03-04 06:54:41 PST
Created attachment 422223 [details]
Patch
Comment 2 Chris Lord 2021-03-04 06:58:08 PST
A perhaps dubiously-related change has crept in with this patch - the changes to the global family string usage are necessary for CSSFontSelector to work with a WorkerGlobalScope, but perhaps aren't necessary for this patch because construction with a WorkerGlobalScope isn't actually used yet.

I can remove this part if it would be preferred and include it in a later patch.
Comment 3 Darin Adler 2021-03-04 14:07:05 PST
Comment on attachment 422223 [details]
Patch

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

Generally looks good, but I have enough comments that I am not literally setting review+ on this version.

> Source/WebCore/css/CSSFontSelector.cpp:91
> +    if (is<Document>(context)) {
> +        m_cursiveFamily = makeUnique<const AtomString>(cursiveFamily);
> +        m_fantasyFamily = makeUnique<const AtomString>(fantasyFamily);
> +        m_monospaceFamily = makeUnique<const AtomString>(monospaceFamily);
> +        m_pictographFamily = makeUnique<const AtomString>(pictographFamily);
> +        m_sansSerifFamily = makeUnique<const AtomString>(sansSerifFamily);
> +        m_serifFamily = makeUnique<const AtomString>(serifFamily);
> +        m_standardFamily = makeUnique<const AtomString>(standardFamily);
> +        m_systemUiFamily = makeUnique<const AtomString>(systemUiFamily);
> +    } else {
> +        m_cursiveFamily = makeUnique<const AtomString>(&cursiveFamilyData);
> +        m_fantasyFamily = makeUnique<const AtomString>(&fantasyFamilyData);
> +        m_monospaceFamily = makeUnique<const AtomString>(&monospaceFamilyData);
> +        m_pictographFamily = makeUnique<const AtomString>(&pictographFamilyData);
> +        m_sansSerifFamily = makeUnique<const AtomString>(&sansSerifFamilyData);
> +        m_serifFamily = makeUnique<const AtomString>(&serifFamilyData);
> +        m_standardFamily = makeUnique<const AtomString>(&standardFamilyData);
> +        m_systemUiFamily = makeUnique<const AtomString>(&systemUiFamilyData);
> +    }

The repetitiveness of this code is so annoying! I really wish for a different idiom where we don’t have a long list (and it’s a bug if anything in the list is wrong, like an omitted item or copy and paste error).

I’m also not happy putting each of these into a separate memory block. I think we can find a clever way to avoid this.

> Source/WebCore/css/CSSFontSelector.cpp:284
> +    if (is<Document>(m_context))
> +        downcast<Document>(*m_context).updateStyleIfNeeded();

Can we make updateStyleIfNeeded a virtual function in ScriptExecutionContext instead? Is there any downside to doing that?

> Source/WebCore/css/CSSFontSelector.cpp:317
> +    if (familyName == *m_serifFamily)
> +        return AtomString(settings.fontGenericFamilies.serifFontFamily(script));
> +    if (familyName == *m_sansSerifFamily)
> +        return AtomString(settings.fontGenericFamilies.sansSerifFontFamily(script));
> +    if (familyName == *m_cursiveFamily)
> +        return AtomString(settings.fontGenericFamilies.cursiveFontFamily(script));
> +    if (familyName == *m_fantasyFamily)
> +        return AtomString(settings.fontGenericFamilies.fantasyFontFamily(script));
> +    if (familyName == *m_monospaceFamily)
> +        return AtomString(settings.fontGenericFamilies.fixedFontFamily(script));
> +    if (familyName == *m_pictographFamily)
> +        return AtomString(settings.fontGenericFamilies.pictographFontFamily(script));
> +    if (familyName == *m_standardFamily)
> +        return AtomString(settings.fontGenericFamilies.standardFontFamily(script));

Not new, but the same kind of frustrating repetition.

> Source/WebCore/css/CSSFontSelector.cpp:435
> +    auto& document = downcast<Document>(*m_context);

The downcast should be right next to the is<> check.

> Source/WebCore/css/CSSFontSelector.cpp:440
> +    if (m_context && document.frame())

Strange to have this null check of m_context. The code ends up assuming that the context is either the same or null in a confusing way. A better design is probably to put the document into a local reference counted pointer if we are worried about lifetime.

> Source/WebCore/css/CSSFontSelector.h:125
> -    WeakPtr<Document> m_document;
> +    ScriptExecutionContext* m_context;

Why are we weakening safety here by moving from a WeakPtr to a raw pointer?

> Source/WebCore/css/CSSFontSelector.h:153
> +    std::unique_ptr<const AtomString> m_cursiveFamily;
> +    std::unique_ptr<const AtomString> m_fantasyFamily;
> +    std::unique_ptr<const AtomString> m_monospaceFamily;
> +    std::unique_ptr<const AtomString> m_pictographFamily;
> +    std::unique_ptr<const AtomString> m_sansSerifFamily;
> +    std::unique_ptr<const AtomString> m_serifFamily;
> +    std::unique_ptr<const AtomString> m_standardFamily;
> +    std::unique_ptr<const AtomString> m_systemUiFamily;

Using pointers and extra memory blocks for these does not seem like a good pattern. If we’re doing that to make them easier to initialize, we can use Optional<AtomString> instead. But also, if we put these into a struct, we can construct the whole thing and don’t need to spend extra memory just to make them easier to initialize. Or maybe I am missing the rationale for using unique_ptr. It’s even possible that a struct or some sort of array could make the code for these less repetitive.

Also should be "systemUI", not "systemUi".
Comment 4 Chris Lord 2021-03-04 14:42:14 PST
(In reply to Darin Adler from comment #3)
> Comment on attachment 422223 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=422223&action=review
> 
> Generally looks good, but I have enough comments that I am not literally
> setting review+ on this version.
> 
> > Source/WebCore/css/CSSFontSelector.cpp:91
> > +    if (is<Document>(context)) {
> > +        m_cursiveFamily = makeUnique<const AtomString>(cursiveFamily);
> > +        m_fantasyFamily = makeUnique<const AtomString>(fantasyFamily);
> > +        m_monospaceFamily = makeUnique<const AtomString>(monospaceFamily);
> > +        m_pictographFamily = makeUnique<const AtomString>(pictographFamily);
> > +        m_sansSerifFamily = makeUnique<const AtomString>(sansSerifFamily);
> > +        m_serifFamily = makeUnique<const AtomString>(serifFamily);
> > +        m_standardFamily = makeUnique<const AtomString>(standardFamily);
> > +        m_systemUiFamily = makeUnique<const AtomString>(systemUiFamily);
> > +    } else {
> > +        m_cursiveFamily = makeUnique<const AtomString>(&cursiveFamilyData);
> > +        m_fantasyFamily = makeUnique<const AtomString>(&fantasyFamilyData);
> > +        m_monospaceFamily = makeUnique<const AtomString>(&monospaceFamilyData);
> > +        m_pictographFamily = makeUnique<const AtomString>(&pictographFamilyData);
> > +        m_sansSerifFamily = makeUnique<const AtomString>(&sansSerifFamilyData);
> > +        m_serifFamily = makeUnique<const AtomString>(&serifFamilyData);
> > +        m_standardFamily = makeUnique<const AtomString>(&standardFamilyData);
> > +        m_systemUiFamily = makeUnique<const AtomString>(&systemUiFamilyData);
> > +    }
> 
> The repetitiveness of this code is so annoying! I really wish for a
> different idiom where we don’t have a long list (and it’s a bug if anything
> in the list is wrong, like an omitted item or copy and paste error).
> 
> I’m also not happy putting each of these into a separate memory block. I
> think we can find a clever way to avoid this.

I agree... Thinking about this, perhaps I could alter the strings generator script to generate corresponding arrays for grouped strings and their data so that this could just be a pointer to that array and the array could be re-generated for the Worker case in a loop?

> > Source/WebCore/css/CSSFontSelector.cpp:284
> > +    if (is<Document>(m_context))
> > +        downcast<Document>(*m_context).updateStyleIfNeeded();
> 
> Can we make updateStyleIfNeeded a virtual function in ScriptExecutionContext
> instead? Is there any downside to doing that?

The only downside I suppose is possible confusion - This is here because font style can rely on Document style, but there's no such dependency chain in a Worker. This seems like knowledge that makes sense to embed here in the FontSelector to me, but I don't feel strongly about that.

> > Source/WebCore/css/CSSFontSelector.cpp:317
> > +    if (familyName == *m_serifFamily)
> > +        return AtomString(settings.fontGenericFamilies.serifFontFamily(script));
> > +    if (familyName == *m_sansSerifFamily)
> > +        return AtomString(settings.fontGenericFamilies.sansSerifFontFamily(script));
> > +    if (familyName == *m_cursiveFamily)
> > +        return AtomString(settings.fontGenericFamilies.cursiveFontFamily(script));
> > +    if (familyName == *m_fantasyFamily)
> > +        return AtomString(settings.fontGenericFamilies.fantasyFontFamily(script));
> > +    if (familyName == *m_monospaceFamily)
> > +        return AtomString(settings.fontGenericFamilies.fixedFontFamily(script));
> > +    if (familyName == *m_pictographFamily)
> > +        return AtomString(settings.fontGenericFamilies.pictographFontFamily(script));
> > +    if (familyName == *m_standardFamily)
> > +        return AtomString(settings.fontGenericFamilies.standardFontFamily(script));
> 
> Not new, but the same kind of frustrating repetition.

I think my idea with the generated arrays would allow turning this into a loop too.

> > Source/WebCore/css/CSSFontSelector.cpp:435
> > +    auto& document = downcast<Document>(*m_context);
> 
> The downcast should be right next to the is<> check.

Noted.

> > Source/WebCore/css/CSSFontSelector.cpp:440
> > +    if (m_context && document.frame())
> 
> Strange to have this null check of m_context. The code ends up assuming that
> the context is either the same or null in a confusing way. A better design
> is probably to put the document into a local reference counted pointer if we
> are worried about lifetime.

I think this was existing behaviour and I was wary of changing anything, but I agree. Previously, this class had a clearDocument function, but this has been replaced by stopLoadingAndClearFonts, so this no longer makes sense.

> > Source/WebCore/css/CSSFontSelector.h:125
> > -    WeakPtr<Document> m_document;
> > +    ScriptExecutionContext* m_context;
> 
> Why are we weakening safety here by moving from a WeakPtr to a raw pointer?

You can't make a WeakPtr of a ScriptExecutionContext, but this could be changed - When looking at how CSSFontSelector is used, and that the document member was explicitly cleared, it seemed safe to make this a pointer, but with the above-mentioned removal of clearDocument, this is no longer the case.

> > Source/WebCore/css/CSSFontSelector.h:153
> > +    std::unique_ptr<const AtomString> m_cursiveFamily;
> > +    std::unique_ptr<const AtomString> m_fantasyFamily;
> > +    std::unique_ptr<const AtomString> m_monospaceFamily;
> > +    std::unique_ptr<const AtomString> m_pictographFamily;
> > +    std::unique_ptr<const AtomString> m_sansSerifFamily;
> > +    std::unique_ptr<const AtomString> m_serifFamily;
> > +    std::unique_ptr<const AtomString> m_standardFamily;
> > +    std::unique_ptr<const AtomString> m_systemUiFamily;
> 
> Using pointers and extra memory blocks for these does not seem like a good
> pattern. If we’re doing that to make them easier to initialize, we can use
> Optional<AtomString> instead. But also, if we put these into a struct, we
> can construct the whole thing and don’t need to spend extra memory just to
> make them easier to initialize. Or maybe I am missing the rationale for
> using unique_ptr. It’s even possible that a struct or some sort of array
> could make the code for these less repetitive.

Yes, agreed. I'm not exactly sure why I made them unique_ptr, I think I was trying to do something that I lost track of... Ideally in the case of is<Document>(context), we would just store pointers to the existing global AtomString, rather than making a new one that just wraps the internal AtomStringImpl. I suppose having an array would make this easier too.

> Also should be "systemUI", not "systemUi".

Thanks :)
Comment 5 Darin Adler 2021-03-04 15:24:30 PST
(In reply to Chris Lord from comment #4)
> Ideally in the case of
> is<Document>(context), we would just store pointers to the existing global
> AtomString, rather than making a new one that just wraps the internal
> AtomStringImpl.

I don’t think this an important optimization. An AtomString is a pointer. So the savings in storing a pointer to an AtomString rather than an AtomString itself is only a tiny reduction in reference counting overhead for the AtomStringImpl when we assign the value, and in the destructor. That’s not enough to seem worthwhile. And there’s a cost too. If we store pointers, there’s an extra memory read every time the AtomString is accessed.

On the other hand, if we could arrange things so there’s a single pointer to an object containing all the family names, that might provide some welcome simplicity and does seem helpful.
Comment 6 Chris Lord 2021-03-05 10:25:09 PST
Created attachment 422381 [details]
Patch
Comment 7 Darin Adler 2021-03-05 12:20:36 PST
Comment on attachment 422381 [details]
Patch

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

Looks fine; not sure why it’s not building on all platforms yet.

> Source/WebCore/css/CSSFontSelector.cpp:306
> +    bool resolveGenericFamilyFirst = familyName == m_fontFamilyNames[static_cast<long int>(FamilyNamesIndex::StandardFamily)];

The static_cast should be to "unsigned" or "int". We should never need to use "long int" in our code. Also wondering why the cast is needed at all, but maybe it’s converting from an enum to an integer?

> Source/WebCore/platform/graphics/FontGenericFamilies.cpp:202
> +    case FamilyNamesIndex::SystemUiFamily:
> +    default:
> +        return WTF::nullopt;

If you leave out default then we’ll get a warning if we forget to handle any of the enumeration values; that’s a welcome tiny bit of future-proofing. Instead you can put the return WTF::nullopt after the switch statement. Unless that runs into warning problems on Windows compilers or something?

> Source/WebCore/platform/graphics/FontGenericFamilies.h:27
>  #ifndef FontGenericFamilies_h
>  #define FontGenericFamilies_h

Would be nice to move to #pragma once since we’re touching this file.

> Source/WebCore/platform/graphics/FontGenericFamilies.h:63
> +    Optional<const String&> fontFamily(WebKitFontFamilyNames::FamilyNamesIndex, UScriptCode = USCRIPT_COMMON) const;

Not sure the return type here is optimal. I’d suggest just const String& and returning a reference to a null or empty string if there’s no failure, or const String* and using nullptr.

Optional is really clear semantically, but one of those others might be nice since String already has a built in null and empty value, and references have a natural optional form (pointers) without using the Optional template.
Comment 8 Chris Lord 2021-03-08 02:28:05 PST
Created attachment 422550 [details]
Patch
Comment 9 Chris Lord 2021-03-08 05:05:38 PST
Created attachment 422555 [details]
Patch
Comment 10 EWS 2021-03-08 05:53:19 PST
Committed r274069: <https://commits.webkit.org/r274069>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 422555 [details].
Comment 11 Radar WebKit Bug Importer 2021-03-08 05:54:14 PST
<rdar://problem/75167438>
Comment 12 Ryan Haddad 2021-03-08 13:04:04 PST
(In reply to EWS from comment #10)
> Committed r274069: <https://commits.webkit.org/r274069>
This change has caused an API test to assert on iOS and macOS bots:

TestWebKitAPI.DocumentOrder.Positions
        ASSERTION FAILED: m_isConstructed
        /Volumes/Data/worker/bigsur-debug/build/WebKitBuild/Debug/usr/local/include/wtf/NeverDestroyed.h(154) : WTF::LazyNeverDestroyed::PointerType WTF::LazyNeverDestroyed<WTF::Vector<WTF::AtomStringImpl *, 8, WTF::CrashOnOverflow, 16, WTF::FastMalloc>, WTF::MainThreadAccessTraits>::storagePointerWithoutAccessCheck() const [T = WTF::Vector<WTF::AtomStringImpl *, 8, WTF::CrashOnOverflow, 16, WTF::FastMalloc>, AccessTraits = WTF::MainThreadAccessTraits]

https://bugs.webkit.org/show_bug.cgi?id=222929
Comment 13 Ryan Haddad 2021-03-08 13:14:32 PST
Reverted r274069 for reason:

Caused an assertion failure with TestWebKitAPI.DocumentOrder.Positions

Committed r274098 (235032@main): <https://commits.webkit.org/235032@main>
Comment 14 Chris Lord 2021-03-09 01:32:19 PST
(In reply to Ryan Haddad from comment #13)
> Reverted r274069 for reason:
> 
> Caused an assertion failure with TestWebKitAPI.DocumentOrder.Positions
> 
> Committed r274098 (235032@main): <https://commits.webkit.org/235032@main>

This assert also happens on Linux, thankfully - looking at it now.
Comment 15 Chris Lord 2021-03-09 01:33:33 PST
oh wait, the test doesn't exist on Linux and the error output is pretty misleading at a glance - either way, hopefully I can figure it out.
Comment 16 Chris Lord 2021-03-09 01:39:07 PST
Created attachment 422678 [details]
Patch
Comment 17 EWS 2021-03-09 04:05:35 PST
Committed r274143: <https://commits.webkit.org/r274143>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 422678 [details].