mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-11 02:13:56 +09:00
LibWeb/CSS: Load fonts using fetch
Convert FontLoader to use fetch_a_style_resource(). ResourceLoader used to keep its downloaded data around for us, but fetch doesn't, so we use Gfx::Typeface::try_load_from_temporary_memory() so that the font has a permanent copy of that data.
This commit is contained in:
parent
1967d5bc75
commit
ffc01626f7
Notes:
github-actions[bot]
2025-05-03 11:03:22 +00:00
Author: https://github.com/AtkinsSJ
Commit: ffc01626f7
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4536
3 changed files with 102 additions and 92 deletions
|
@ -450,32 +450,28 @@ GC::Ref<WebIDL::Promise> 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<Gfx::Typeface const> 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<WebIDL::Promise> 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
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <LibWeb/CSS/CSSStyleRule.h>
|
||||
#include <LibWeb/CSS/CSSTransition.h>
|
||||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
#include <LibWeb/CSS/Fetch.h>
|
||||
#include <LibWeb/CSS/Interpolation.h>
|
||||
#include <LibWeb/CSS/InvalidationSet.h>
|
||||
#include <LibWeb/CSS/Parser/Parser.h>
|
||||
|
@ -74,6 +75,8 @@
|
|||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
|
||||
#include <LibWeb/Fetch/Response.h>
|
||||
#include <LibWeb/HTML/HTMLBRElement.h>
|
||||
#include <LibWeb/HTML/HTMLHtmlElement.h>
|
||||
#include <LibWeb/HTML/Parser/HTMLParser.h>
|
||||
|
@ -184,54 +187,26 @@ StyleComputer::StyleComputer(DOM::Document& document)
|
|||
|
||||
StyleComputer::~StyleComputer() = default;
|
||||
|
||||
FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<::URL::URL> urls, Function<void(FontLoader const&)> on_load, Function<void()> on_fail)
|
||||
FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<::URL::URL> urls, Function<void(RefPtr<Gfx::Typeface const>)> 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<Gfx::Font const> 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<Gfx::Font const> 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> 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<Gfx::Typeface const> typeface;
|
||||
if (auto* bytes = stream.template get_pointer<ByteBuffer>()) {
|
||||
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<NonnullRefPtr<Gfx::Typeface const>> FontLoader::try_load_font()
|
||||
void FontLoader::font_did_load_or_fail(RefPtr<Gfx::Typeface const> 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<NonnullRefPtr<Gfx::Typeface const>> 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<FontLoader&> StyleComputer::load_font_face(ParsedFontFace const& font_face, Function<void(FontLoader const&)> on_load, Function<void()> on_fail)
|
||||
Optional<FontLoader&> StyleComputer::load_font_face(ParsedFontFace const& font_face, Function<void(RefPtr<Gfx::Typeface const>)> on_load)
|
||||
{
|
||||
if (font_face.sources().is_empty()) {
|
||||
if (on_fail)
|
||||
on_fail();
|
||||
if (on_load)
|
||||
on_load({});
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -3031,6 +3048,7 @@ Optional<FontLoader&> 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<FontLoader&> 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<FontLoader>(const_cast<StyleComputer&>(*this), font_face.font_family(), font_face.unicode_ranges(), move(urls), move(on_load), move(on_fail));
|
||||
auto loader = make<FontLoader>(*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<StyleComputer&>(*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<StyleComputer&>(*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;
|
||||
|
|
|
@ -174,7 +174,7 @@ public:
|
|||
|
||||
void did_load_font(FlyString const& family_name);
|
||||
|
||||
Optional<FontLoader&> load_font_face(ParsedFontFace const&, ESCAPING Function<void(FontLoader const&)> on_load = {}, ESCAPING Function<void()> on_fail = {});
|
||||
Optional<FontLoader&> load_font_face(ParsedFontFace const&, ESCAPING Function<void(RefPtr<Gfx::Typeface const>)> on_load = {});
|
||||
|
||||
void load_fonts_from_sheet(CSSStyleSheet&);
|
||||
void unload_fonts_from_sheet(CSSStyleSheet&);
|
||||
|
@ -313,11 +313,11 @@ private:
|
|||
CountingBloomFilter<u8, 14> m_ancestor_filter;
|
||||
};
|
||||
|
||||
class FontLoader : public ResourceClient {
|
||||
class FontLoader : public Weakable<FontLoader> {
|
||||
public:
|
||||
FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<::URL::URL> urls, ESCAPING Function<void(FontLoader const&)> on_load = {}, ESCAPING Function<void()> on_fail = {});
|
||||
FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<::URL::URL> urls, ESCAPING Function<void(RefPtr<Gfx::Typeface const>)> on_load = {});
|
||||
|
||||
virtual ~FontLoader() override;
|
||||
virtual ~FontLoader();
|
||||
|
||||
Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
|
||||
RefPtr<Gfx::Typeface const> vector_font() const { return m_vector_font; }
|
||||
|
@ -325,24 +325,20 @@ public:
|
|||
RefPtr<Gfx::Font const> 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<NonnullRefPtr<Gfx::Typeface const>> try_load_font(Fetch::Infrastructure::Response const&, ByteBuffer const&);
|
||||
|
||||
void resource_did_load_or_fail();
|
||||
|
||||
ErrorOr<NonnullRefPtr<Gfx::Typeface const>> try_load_font();
|
||||
void font_did_load_or_fail(RefPtr<Gfx::Typeface const>);
|
||||
|
||||
StyleComputer& m_style_computer;
|
||||
FlyString m_family_name;
|
||||
Vector<Gfx::UnicodeRange> m_unicode_ranges;
|
||||
RefPtr<Gfx::Typeface const> m_vector_font;
|
||||
Vector<::URL::URL> m_urls;
|
||||
Function<void(FontLoader const&)> m_on_load;
|
||||
Function<void()> m_on_fail;
|
||||
GC::Root<Fetch::Infrastructure::FetchController> m_fetch_controller;
|
||||
Function<void(RefPtr<Gfx::Typeface const>)> m_on_load;
|
||||
};
|
||||
|
||||
inline bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector) const
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue