diff --git a/Libraries/LibWeb/CSS/FontFace.cpp b/Libraries/LibWeb/CSS/FontFace.cpp index 28e740fb475..60df8b1308e 100644 --- a/Libraries/LibWeb/CSS/FontFace.cpp +++ b/Libraries/LibWeb/CSS/FontFace.cpp @@ -450,32 +450,28 @@ GC::Ref FontFace::load() // 4. Using the value of font face’s [[Urls]] slot, attempt to load a font as defined in [CSS-FONTS-3], // as if it was the value of a @font-face rule’s src descriptor. - // 5. When the load operation completes, successfully or not, queue a task to run the following steps synchronously: - auto on_error = [font] { - HTML::queue_global_task(HTML::Task::Source::FontLoading, HTML::relevant_global_object(*font), GC::create_function(font->heap(), [font = GC::Ref(*font)] { + // 5. When the load operation completes, successfully or not, queue a task to run the follsowing steps synchronously: + auto on_load = [font](RefPtr maybe_typeface) { + HTML::queue_global_task(HTML::Task::Source::FontLoading, HTML::relevant_global_object(*font), GC::create_function(font->heap(), [font = GC::Ref(*font), maybe_typeface] { HTML::TemporaryExecutionContext context(font->realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + // 1. If the attempt to load fails, reject font face’s [[FontStatusPromise]] with a DOMException whose name + // is "NetworkError" and set font face’s status attribute to "error". + if (!maybe_typeface) { + font->m_status = Bindings::FontFaceLoadStatus::Error; + WebIDL::reject_promise(font->realm(), font->m_font_status_promise, WebIDL::NetworkError::create(font->realm(), "Failed to load font"_string)); - // 1. If the attempt to load fails, reject font face’s [[FontStatusPromise]] with a DOMException whose name - // is "NetworkError" and set font face’s status attribute to "error". - font->m_status = Bindings::FontFaceLoadStatus::Error; - WebIDL::reject_promise(font->realm(), font->m_font_status_promise, WebIDL::NetworkError::create(font->realm(), "Failed to load font"_string)); - - // FIXME: For each FontFaceSet font face is in: - })); - }; - - auto on_load = [font](FontLoader const& loader) { - // FIXME: We are assuming that the font loader will live as long as the document! This is an unsafe capture - HTML::queue_global_task(HTML::Task::Source::FontLoading, HTML::relevant_global_object(*font), GC::create_function(font->heap(), [font = GC::Ref(*font), &loader] { - HTML::TemporaryExecutionContext context(font->realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + // FIXME: For each FontFaceSet font face is in: + } // 2. Otherwise, font face now represents the loaded font; fulfill font face’s [[FontStatusPromise]] with font face // and set font face’s status attribute to "loaded". - font->m_parsed_font = loader.vector_font(); - font->m_status = Bindings::FontFaceLoadStatus::Loaded; - WebIDL::resolve_promise(font->realm(), font->m_font_status_promise, font); + else { + font->m_parsed_font = maybe_typeface; + font->m_status = Bindings::FontFaceLoadStatus::Loaded; + WebIDL::resolve_promise(font->realm(), font->m_font_status_promise, font); - // FIXME: For each FontFaceSet font face is in: + // FIXME: For each FontFaceSet font face is in: + } })); }; @@ -502,7 +498,7 @@ GC::Ref FontFace::load() {}, // FIXME: feature_settings {}, // FIXME: variation_settings }; - if (auto loader = style_computer.load_font_face(parsed_font_face, move(on_load), move(on_error)); loader.has_value()) + if (auto loader = style_computer.load_font_face(parsed_font_face, move(on_load)); loader.has_value()) loader->start_loading_next_url(); } else { // FIXME: Don't know how to load fonts in workers! They don't have a StyleComputer diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 3da39fd3874..7a6fc8682d3 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,8 @@ #include #include #include +#include +#include #include #include #include @@ -184,54 +187,26 @@ StyleComputer::StyleComputer(DOM::Document& document) StyleComputer::~StyleComputer() = default; -FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector<::URL::URL> urls, Function on_load, Function on_fail) +FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector<::URL::URL> urls, Function)> on_load) : m_style_computer(style_computer) , m_family_name(move(family_name)) , m_unicode_ranges(move(unicode_ranges)) , m_urls(move(urls)) , m_on_load(move(on_load)) - , m_on_fail(move(on_fail)) { } FontLoader::~FontLoader() = default; -void FontLoader::resource_did_load() +bool FontLoader::is_loading() const { - resource_did_load_or_fail(); - if (m_on_load) - m_on_load(*this); -} - -void FontLoader::resource_did_fail() -{ - resource_did_load_or_fail(); - if (m_on_fail) { - m_on_fail(); - } -} - -void FontLoader::resource_did_load_or_fail() -{ - // NOTE: Even if the resource "failed" to load, we still want to try to parse it as a font. - // This is necessary for https://wpt.live/ to work correctly, as it just drops the connection - // after sending a resource, which looks like an error, but is actually recoverable. - // FIXME: It would be nice to solve this in the network layer instead. - // It would also be nice to move font loading to using fetch primitives. - auto result = try_load_font(); - if (result.is_error()) { - dbgln("Failed to parse font: {}", result.error()); - start_loading_next_url(); - return; - } - m_vector_font = result.release_value(); - m_style_computer.did_load_font(m_family_name); + return m_fetch_controller && !m_vector_font; } RefPtr FontLoader::font_with_point_size(float point_size) { if (!m_vector_font) { - if (!resource()) + if (!m_fetch_controller) start_loading_next_url(); return nullptr; } @@ -240,46 +215,88 @@ RefPtr FontLoader::font_with_point_size(float point_size) void FontLoader::start_loading_next_url() { - if (resource() && resource()->is_pending()) + // FIXME: Load local() fonts somehow. + if (m_fetch_controller && m_fetch_controller->state() == Fetch::Infrastructure::FetchController::State::Ongoing) return; if (m_urls.is_empty()) return; - auto& style_computer_realm = m_style_computer.document().realm(); - auto& page = Bindings::principal_host_defined_page(HTML::principal_realm(style_computer_realm)); - LoadRequest request; - request.set_url(m_urls.take_first()); - request.set_page(page); + // https://drafts.csswg.org/css-fonts-4/#fetch-a-font + // To fetch a font given a selected url for @font-face rule, fetch url, with stylesheet being rule’s parent + // CSS style sheet, destination "font", CORS mode "cors", and processResponse being the following steps given + // response res and null, failure or a byte stream stream: + // FIXME: Get the rule's parent style sheet from somewhere + auto maybe_fetch_controller = fetch_a_style_resource(m_urls.take_first(), GC::Ref { m_style_computer.document() }, Fetch::Infrastructure::Request::Destination::Font, CorsMode::Cors, + [weak_loader = make_weak_ptr()](auto response, auto stream) { + // NB: If the FontLoader died before this fetch completed, nobody wants the data. + if (weak_loader.is_null()) + return; + auto& loader = *weak_loader; - // HACK: We're crudely computing the referer value and shoving it into the - // request until fetch infrastructure is used here. - auto referrer_url = ReferrerPolicy::strip_url_for_use_as_referrer(m_style_computer.document().url()); - if (referrer_url.has_value() && !request.headers().contains("Referer")) - request.set_header("Referer", referrer_url->serialize().to_byte_string()); + // 1. If stream is null, return. + // 2. Load a font from stream according to its type. - set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request)); + // NB: We need to fetch the next source if this one fails to fetch OR decode. So, first try to decode it. + RefPtr typeface; + if (auto* bytes = stream.template get_pointer()) { + if (auto maybe_typeface = loader.try_load_font(response, *bytes); !maybe_typeface.is_error()) + typeface = maybe_typeface.release_value(); + } + + if (!typeface) { + // NB: If we have other sources available, try the next one. + if (loader.m_urls.is_empty()) { + loader.font_did_load_or_fail(nullptr); + } else { + loader.m_fetch_controller = nullptr; + loader.start_loading_next_url(); + } + } else { + loader.font_did_load_or_fail(move(typeface)); + } + }); + + if (maybe_fetch_controller.is_error()) { + font_did_load_or_fail(nullptr); + } else { + m_fetch_controller = maybe_fetch_controller.release_value(); + } } -ErrorOr> FontLoader::try_load_font() +void FontLoader::font_did_load_or_fail(RefPtr typeface) +{ + if (typeface) { + m_vector_font = typeface.release_nonnull(); + m_style_computer.did_load_font(m_family_name); + if (m_on_load) + m_on_load(m_vector_font); + } else { + if (m_on_load) + m_on_load(nullptr); + } + m_fetch_controller = nullptr; +} + +ErrorOr> FontLoader::try_load_font(Fetch::Infrastructure::Response const& response, ByteBuffer const& bytes) { // FIXME: This could maybe use the format() provided in @font-face as well, since often the mime type is just application/octet-stream and we have to try every format - auto mime_type = MimeSniff::MimeType::parse(resource()->mime_type()); + auto mime_type = response.header_list()->extract_mime_type(); if (!mime_type.has_value() || !mime_type->is_font()) { - mime_type = MimeSniff::Resource::sniff(resource()->encoded_data(), Web::MimeSniff::SniffingConfiguration { .sniffing_context = Web::MimeSniff::SniffingContext::Font }); + mime_type = MimeSniff::Resource::sniff(bytes, MimeSniff::SniffingConfiguration { .sniffing_context = MimeSniff::SniffingContext::Font }); } if (mime_type.has_value()) { if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv || mime_type->essence() == "font/otf"sv) { - if (auto result = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { + if (auto result = Gfx::Typeface::try_load_from_temporary_memory(bytes); !result.is_error()) { return result; } } if (mime_type->essence() == "font/woff"sv || mime_type->essence() == "application/font-woff"sv) { - if (auto result = WOFF::try_load_from_bytes(resource()->encoded_data()); !result.is_error()) { + if (auto result = WOFF::try_load_from_bytes(bytes); !result.is_error()) { return result; } } if (mime_type->essence() == "font/woff2"sv || mime_type->essence() == "application/font-woff2"sv) { - if (auto result = WOFF2::try_load_from_bytes(resource()->encoded_data()); !result.is_error()) { + if (auto result = WOFF2::try_load_from_bytes(bytes); !result.is_error()) { return result; } } @@ -3017,11 +3034,11 @@ void StyleComputer::did_load_font(FlyString const&) document().invalidate_style(DOM::StyleInvalidationReason::CSSFontLoaded); } -Optional StyleComputer::load_font_face(ParsedFontFace const& font_face, Function on_load, Function on_fail) +Optional StyleComputer::load_font_face(ParsedFontFace const& font_face, Function)> on_load) { if (font_face.sources().is_empty()) { - if (on_fail) - on_fail(); + if (on_load) + on_load({}); return {}; } @@ -3031,6 +3048,7 @@ Optional StyleComputer::load_font_face(ParsedFontFace const& font_f .slope = font_face.slope().value_or(0), }; + // FIXME: Pass the sources directly, so the font loader can make use of the format information, or load local fonts. Vector<::URL::URL> urls; for (auto const& source : font_face.sources()) { // FIXME: These should be loaded relative to the stylesheet URL instead of the document URL. @@ -3040,20 +3058,20 @@ Optional StyleComputer::load_font_face(ParsedFontFace const& font_f } if (urls.is_empty()) { - if (on_fail) - on_fail(); + if (on_load) + on_load({}); return {}; } - auto loader = make(const_cast(*this), font_face.font_family(), font_face.unicode_ranges(), move(urls), move(on_load), move(on_fail)); + auto loader = make(*this, font_face.font_family(), font_face.unicode_ranges(), move(urls), move(on_load)); auto& loader_ref = *loader; - auto maybe_font_loaders_list = const_cast(*this).m_loaded_fonts.get(key); + auto maybe_font_loaders_list = m_loaded_fonts.get(key); if (maybe_font_loaders_list.has_value()) { maybe_font_loaders_list->append(move(loader)); } else { FontLoaderList loaders; loaders.append(move(loader)); - const_cast(*this).m_loaded_fonts.set(OwnFontFaceKey(key), move(loaders)); + m_loaded_fonts.set(OwnFontFaceKey(key), move(loaders)); } // Actual object owned by font loader list inside m_loaded_fonts, this isn't use-after-move/free return loader_ref; diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index e76e6c5b177..9a9834d9e42 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -174,7 +174,7 @@ public: void did_load_font(FlyString const& family_name); - Optional load_font_face(ParsedFontFace const&, ESCAPING Function on_load = {}, ESCAPING Function on_fail = {}); + Optional load_font_face(ParsedFontFace const&, ESCAPING Function)> on_load = {}); void load_fonts_from_sheet(CSSStyleSheet&); void unload_fonts_from_sheet(CSSStyleSheet&); @@ -313,11 +313,11 @@ private: CountingBloomFilter m_ancestor_filter; }; -class FontLoader : public ResourceClient { +class FontLoader : public Weakable { public: - FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector<::URL::URL> urls, ESCAPING Function on_load = {}, ESCAPING Function on_fail = {}); + FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector<::URL::URL> urls, ESCAPING Function)> on_load = {}); - virtual ~FontLoader() override; + virtual ~FontLoader(); Vector const& unicode_ranges() const { return m_unicode_ranges; } RefPtr vector_font() const { return m_vector_font; } @@ -325,24 +325,20 @@ public: RefPtr font_with_point_size(float point_size); void start_loading_next_url(); - bool is_loading() const { return resource() && resource()->is_pending(); } + bool is_loading() const; private: - // ^ResourceClient - virtual void resource_did_load() override; - virtual void resource_did_fail() override; + ErrorOr> try_load_font(Fetch::Infrastructure::Response const&, ByteBuffer const&); - void resource_did_load_or_fail(); - - ErrorOr> try_load_font(); + void font_did_load_or_fail(RefPtr); StyleComputer& m_style_computer; FlyString m_family_name; Vector m_unicode_ranges; RefPtr m_vector_font; Vector<::URL::URL> m_urls; - Function m_on_load; - Function m_on_fail; + GC::Root m_fetch_controller; + Function)> m_on_load; }; inline bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector) const