LibWeb: Resolve document.fonts.ready() after fonts defined in CSS loaded

This is an ad-hoc implementation that resolves the ready() promise once
the document and all fonts collected by the style computer are done
loading. A spec-compliant implementation would include creating a proxy
CSS::FontFace for each @font-face and correctly implementing the
specification steps for font fetching, but we are far from there yet.

This hackish implementation should yield good WPT progress because it
will actually start waiting for the Ahem font to load before capturing
layout measurements. For example, it makes
https://wpt.live/css/css-grid/abspos/positioned-grid-descendants-001.html
go from 0/100 to 36/100 passing subtests.
This commit is contained in:
Aliaksandr Kalenik 2024-09-29 23:38:49 +02:00 committed by Andreas Kling
parent 814fa0682a
commit 5faca4f027
Notes: github-actions[bot] 2024-09-30 06:08:51 +00:00
5 changed files with 35 additions and 5 deletions

View file

@ -29,9 +29,6 @@ JS::NonnullGCPtr<FontFaceSet> FontFaceSet::construct_impl(JS::Realm& realm, Vect
for (auto const& face : initial_faces)
set_entries->set_add(face);
if (set_entries->set_size() == 0)
WebIDL::resolve_promise(realm, *ready_promise);
return realm.heap().allocate<FontFaceSet>(realm, realm, ready_promise, set_entries);
}
@ -44,9 +41,8 @@ FontFaceSet::FontFaceSet(JS::Realm& realm, JS::NonnullGCPtr<WebIDL::Promise> rea
: DOM::EventTarget(realm)
, m_set_entries(set_entries)
, m_ready_promise(ready_promise)
, m_status(Bindings::FontFaceSetLoadStatus::Loaded)
{
bool const is_ready = ready()->state() == JS::Promise::State::Fulfilled;
m_status = is_ready ? Bindings::FontFaceSetLoadStatus::Loaded : Bindings::FontFaceSetLoadStatus::Loading;
}
void FontFaceSet::initialize(JS::Realm& realm)
@ -162,4 +158,9 @@ JS::NonnullGCPtr<JS::Promise> FontFaceSet::ready() const
return verify_cast<JS::Promise>(*m_ready_promise->promise());
}
void FontFaceSet::resolve_ready_promise()
{
WebIDL::resolve_promise(realm(), *m_ready_promise);
}
}

View file

@ -43,6 +43,8 @@ public:
JS::NonnullGCPtr<JS::Promise> ready() const;
Bindings::FontFaceSetLoadStatus status() const { return m_status; }
void resolve_ready_promise();
private:
FontFaceSet(JS::Realm&, JS::NonnullGCPtr<WebIDL::Promise> ready_promise, JS::NonnullGCPtr<JS::Set> set_entries);

View file

@ -2863,4 +2863,16 @@ void StyleComputer::pop_ancestor(DOM::Element const& element)
});
}
size_t StyleComputer::number_of_css_font_faces_with_loading_in_progress() const
{
size_t count = 0;
for (auto const& [_, loaders] : m_loaded_fonts) {
for (auto const& loader : loaders) {
if (loader->is_loading())
++count;
}
}
return count;
}
}

View file

@ -159,6 +159,8 @@ public:
[[nodiscard]] bool has_has_selectors() const { return m_has_has_selectors; }
size_t number_of_css_font_faces_with_loading_in_progress() const;
private:
enum class ComputeStyleMode {
Normal,
@ -261,6 +263,8 @@ public:
RefPtr<Gfx::Font> font_with_point_size(float point_size);
void start_loading_next_url();
bool is_loading() const { return resource() && resource()->is_pending(); }
private:
// ^ResourceClient
virtual void resource_did_load() override;

View file

@ -8,10 +8,13 @@
#include <LibCore/EventLoop.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/CSS/FontFaceSet.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HighResolutionTime/Performance.h>
@ -381,6 +384,14 @@ void EventLoop::process()
for_each_fully_active_document_in_docs([&](DOM::Document& document) {
document.process_top_layer_removals();
});
// Not in the spec:
for_each_fully_active_document_in_docs([&](DOM::Document& document) {
if (document.readiness() == HTML::DocumentReadyState::Complete && document.style_computer().number_of_css_font_faces_with_loading_in_progress() == 0) {
HTML::TemporaryExecutionContext context(HTML::relevant_settings_object(document), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
document.fonts()->resolve_ready_promise();
}
});
}
// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task