ladybird/Userland/Libraries/LibWeb/CSS/FontFaceSet.cpp
Aliaksandr Kalenik 5faca4f027 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.
2024-09-30 08:07:59 +02:00

166 lines
5.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Set.h>
#include <LibWeb/Bindings/FontFaceSetPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/FontFaceSet.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::CSS {
JS_DEFINE_ALLOCATOR(FontFaceSet);
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-fontfaceset
JS::NonnullGCPtr<FontFaceSet> FontFaceSet::construct_impl(JS::Realm& realm, Vector<JS::Handle<FontFace>> const& initial_faces)
{
auto ready_promise = WebIDL::create_promise(realm);
auto set_entries = JS::Set::create(realm);
// The FontFaceSet constructor, when called, must iterate its initialFaces argument and add each value to its set entries.
for (auto const& face : initial_faces)
set_entries->set_add(face);
return realm.heap().allocate<FontFaceSet>(realm, realm, ready_promise, set_entries);
}
JS::NonnullGCPtr<FontFaceSet> FontFaceSet::create(JS::Realm& realm)
{
return construct_impl(realm, {});
}
FontFaceSet::FontFaceSet(JS::Realm& realm, JS::NonnullGCPtr<WebIDL::Promise> ready_promise, JS::NonnullGCPtr<JS::Set> set_entries)
: DOM::EventTarget(realm)
, m_set_entries(set_entries)
, m_ready_promise(ready_promise)
, m_status(Bindings::FontFaceSetLoadStatus::Loaded)
{
}
void FontFaceSet::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(FontFaceSet);
}
void FontFaceSet::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_set_entries);
visitor.visit(m_ready_promise);
visitor.visit(m_loading_fonts);
visitor.visit(m_loaded_fonts);
visitor.visit(m_failed_fonts);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-add
WebIDL::ExceptionOr<JS::NonnullGCPtr<FontFaceSet>>
FontFaceSet::add(JS::Handle<FontFace> face)
{
// 1. If font is already in the FontFaceSets set entries, skip to the last step of this algorithm immediately.
if (m_set_entries->set_has(face))
return JS::NonnullGCPtr<FontFaceSet>(*this);
// 2. If font is CSS-connected, throw an InvalidModificationError exception and exit this algorithm immediately.
if (face->is_css_connected()) {
return WebIDL::InvalidModificationError::create(realm(), "Cannot add a CSS-connected FontFace to a FontFaceSet"_fly_string);
}
// 3. Add the font argument to the FontFaceSets set entries.
m_set_entries->set_add(face);
// 4. If fonts status attribute is "loading"
if (face->status() == Bindings::FontFaceLoadStatus::Loading) {
// 1. If the FontFaceSets [[LoadingFonts]] list is empty, switch the FontFaceSet to loading.
if (m_loading_fonts.is_empty()) {
m_status = Bindings::FontFaceSetLoadStatus::Loading;
}
// 2. Append font to the FontFaceSets [[LoadingFonts]] list.
m_loading_fonts.append(*face);
}
// 5. Return the FontFaceSet.
return JS::NonnullGCPtr<FontFaceSet>(*this);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-delete
bool FontFaceSet::delete_(JS::Handle<FontFace> face)
{
// FIXME: Do the actual spec steps
return m_set_entries->set_remove(face);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-clear
void FontFaceSet::clear()
{
// FIXME: Do the actual spec steps
m_set_entries->set_clear();
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-onloading
void FontFaceSet::set_onloading(WebIDL::CallbackType* event_handler)
{
set_event_handler_attribute(HTML::EventNames::loading, event_handler);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-onloading
WebIDL::CallbackType* FontFaceSet::onloading()
{
return event_handler_attribute(HTML::EventNames::loading);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-onloadingdone
void FontFaceSet::set_onloadingdone(WebIDL::CallbackType* event_handler)
{
set_event_handler_attribute(HTML::EventNames::loadingdone, event_handler);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-onloadingdone
WebIDL::CallbackType* FontFaceSet::onloadingdone()
{
return event_handler_attribute(HTML::EventNames::loadingdone);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-onloadingerror
void FontFaceSet::set_onloadingerror(WebIDL::CallbackType* event_handler)
{
set_event_handler_attribute(HTML::EventNames::loadingerror, event_handler);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-onloadingerror
WebIDL::CallbackType* FontFaceSet::onloadingerror()
{
return event_handler_attribute(HTML::EventNames::loadingerror);
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-load
JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Promise>> FontFaceSet::load(String const&, String const&)
{
// FIXME: Do the steps
auto promise = WebIDL::create_rejected_promise(realm(), WebIDL::NotSupportedError::create(realm(), "FontFaceSet::load is not yet implemented"_fly_string));
return verify_cast<JS::Promise>(*promise->promise());
}
// https://drafts.csswg.org/css-font-loading/#font-face-set-ready
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);
}
}