RESOLVED FIXED Bug 222735
Allow creation of a CSSFontSelector with a non-Document ScriptExecutionContext
https://bugs.webkit.org/show_bug.cgi?id=222735
Summary Allow creation of a CSSFontSelector with a non-Document ScriptExecutionContext
Chris Lord
Reported 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.
Attachments
Patch (22.79 KB, patch)
2021-03-04 06:54 PST, Chris Lord
no flags
Patch (29.16 KB, patch)
2021-03-05 10:25 PST, Chris Lord
ews-feeder: commit-queue-
Patch (29.46 KB, patch)
2021-03-08 02:28 PST, Chris Lord
no flags
Patch (29.56 KB, patch)
2021-03-08 05:05 PST, Chris Lord
no flags
Patch (30.62 KB, patch)
2021-03-09 01:39 PST, Chris Lord
no flags
Chris Lord
Comment 1 2021-03-04 06:54:41 PST
Chris Lord
Comment 2 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.
Darin Adler
Comment 3 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".
Chris Lord
Comment 4 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 :)
Darin Adler
Comment 5 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.
Chris Lord
Comment 6 2021-03-05 10:25:09 PST
Darin Adler
Comment 7 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.
Chris Lord
Comment 8 2021-03-08 02:28:05 PST
Chris Lord
Comment 9 2021-03-08 05:05:38 PST
EWS
Comment 10 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].
Radar WebKit Bug Importer
Comment 11 2021-03-08 05:54:14 PST
Ryan Haddad
Comment 12 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
Ryan Haddad
Comment 13 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>
Chris Lord
Comment 14 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.
Chris Lord
Comment 15 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.
Chris Lord
Comment 16 2021-03-09 01:39:07 PST
EWS
Comment 17 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].
Note You need to log in before you can comment on or make changes to this bug.