2024-05-07 14:49:31 -06:00
/*
* Copyright ( c ) 2024 , Andrew Kaster < akaster @ serenityos . org >
2024-06-06 19:58:47 +01:00
* Copyright ( c ) 2024 , Jamie Mansfield < jmansfield @ cadixdev . org >
2024-05-07 14:49:31 -06:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-11-15 04:01:23 +13:00
# include <LibGC/Heap.h>
2024-05-07 14:49:31 -06:00
# include <LibJS/Runtime/Realm.h>
2024-05-21 21:05:36 -06:00
# include <LibJS/Runtime/Set.h>
2024-10-23 19:15:25 -04:00
# include <LibWeb/Bindings/ExceptionOrUtils.h>
2024-05-07 14:49:31 -06:00
# include <LibWeb/Bindings/FontFaceSetPrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2024-05-21 21:05:36 -06:00
# include <LibWeb/CSS/FontFace.h>
2024-05-07 14:49:31 -06:00
# include <LibWeb/CSS/FontFaceSet.h>
2024-10-23 19:15:25 -04:00
# include <LibWeb/CSS/Parser/Parser.h>
2025-08-08 10:28:41 +01:00
# include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
2024-10-23 19:15:25 -04:00
# include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
# include <LibWeb/CSS/StyleValues/StringStyleValue.h>
# include <LibWeb/CSS/StyleValues/StyleValueList.h>
# include <LibWeb/DOM/Document.h>
2024-06-06 19:58:47 +01:00
# include <LibWeb/HTML/EventNames.h>
2024-10-23 19:15:25 -04:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
# include <LibWeb/Platform/EventLoopPlugin.h>
2024-05-07 14:49:31 -06:00
# include <LibWeb/WebIDL/Promise.h>
namespace Web : : CSS {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( FontFaceSet ) ;
2024-05-07 14:49:31 -06:00
2024-05-21 21:05:36 -06:00
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-fontfaceset
2024-11-15 04:01:23 +13:00
GC : : Ref < FontFaceSet > FontFaceSet : : construct_impl ( JS : : Realm & realm , Vector < GC : : Root < FontFace > > const & initial_faces )
2024-05-07 14:49:31 -06:00
{
2024-05-21 21:05:36 -06:00
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 ) ;
2024-11-14 05:50:17 +13:00
return realm . create < FontFaceSet > ( realm , ready_promise , set_entries ) ;
2024-05-07 14:49:31 -06:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < FontFaceSet > FontFaceSet : : create ( JS : : Realm & realm )
2024-05-07 14:49:31 -06:00
{
return construct_impl ( realm , { } ) ;
}
2024-11-15 04:01:23 +13:00
FontFaceSet : : FontFaceSet ( JS : : Realm & realm , GC : : Ref < WebIDL : : Promise > ready_promise , GC : : Ref < JS : : Set > set_entries )
2024-06-06 19:55:53 +01:00
: DOM : : EventTarget ( realm )
2024-05-21 21:05:36 -06:00
, m_set_entries ( set_entries )
2024-05-13 13:55:43 -06:00
, m_ready_promise ( ready_promise )
2024-09-29 23:38:49 +02:00
, m_status ( Bindings : : FontFaceSetLoadStatus : : Loaded )
2024-05-07 14:49:31 -06:00
{
}
void FontFaceSet : : initialize ( JS : : Realm & realm )
{
WEB_SET_PROTOTYPE_FOR_INTERFACE ( FontFaceSet ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2024-05-07 14:49:31 -06:00
}
2024-05-13 13:55:43 -06:00
void FontFaceSet : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2024-05-21 21:05:36 -06:00
visitor . visit ( m_set_entries ) ;
2024-05-13 13:55:43 -06:00
visitor . visit ( m_ready_promise ) ;
2024-09-26 10:52:54 +01:00
visitor . visit ( m_loading_fonts ) ;
visitor . visit ( m_loaded_fonts ) ;
visitor . visit ( m_failed_fonts ) ;
2024-05-13 13:55:43 -06:00
}
2024-05-07 14:49:31 -06:00
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-add
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < FontFaceSet > >
FontFaceSet : : add ( GC : : Root < FontFace > face )
2024-05-07 14:49:31 -06:00
{
2024-09-26 10:52:54 +01:00
// 1. If font is already in the FontFaceSet’ s set entries, skip to the last step of this algorithm immediately.
if ( m_set_entries - > set_has ( face ) )
2024-11-15 04:01:23 +13:00
return GC : : Ref < FontFaceSet > ( * this ) ;
2024-09-26 10:52:54 +01:00
// 2. If font is CSS-connected, throw an InvalidModificationError exception and exit this algorithm immediately.
if ( face - > is_css_connected ( ) ) {
2025-08-07 19:31:52 -04:00
return WebIDL : : InvalidModificationError : : create ( realm ( ) , " Cannot add a CSS-connected FontFace to a FontFaceSet " _utf16 ) ;
2024-09-26 10:52:54 +01:00
}
// 3. Add the font argument to the FontFaceSet’ s set entries.
2024-05-21 21:05:36 -06:00
m_set_entries - > set_add ( face ) ;
2024-09-26 10:52:54 +01:00
// 4. If font’ s status attribute is "loading"
if ( face - > status ( ) = = Bindings : : FontFaceLoadStatus : : Loading ) {
// 1. If the FontFaceSet’ s [[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 FontFaceSet’ s [[LoadingFonts]] list.
m_loading_fonts . append ( * face ) ;
}
// 5. Return the FontFaceSet.
2024-11-15 04:01:23 +13:00
return GC : : Ref < FontFaceSet > ( * this ) ;
2024-05-07 14:49:31 -06:00
}
2024-05-21 21:05:36 -06:00
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-delete
2024-11-15 04:01:23 +13:00
bool FontFaceSet : : delete_ ( GC : : Root < FontFace > face )
2024-05-21 21:05:36 -06:00
{
2024-10-03 14:49:25 +01:00
// 1. If font is CSS-connected, return false and exit this algorithm immediately.
if ( face - > is_css_connected ( ) ) {
return false ;
}
// 2. Let deleted be the result of removing font from the FontFaceSet’ s set entries.
bool deleted = m_set_entries - > set_remove ( face ) ;
// 3. If font is present in the FontFaceSet’ s [[LoadedFonts]], or [[FailedFonts]] lists, remove it.
m_loaded_fonts . remove_all_matching ( [ face ] ( auto const & entry ) { return entry = = face ; } ) ;
m_failed_fonts . remove_all_matching ( [ face ] ( auto const & entry ) { return entry = = face ; } ) ;
// 4. If font is present in the FontFaceSet’ s [[LoadingFonts]] list, remove it. If font was the last item in that list (and so the list is now empty), switch the FontFaceSet to loaded.
m_loading_fonts . remove_all_matching ( [ face ] ( auto const & entry ) { return entry = = face ; } ) ;
if ( m_loading_fonts . is_empty ( ) ) {
m_status = Bindings : : FontFaceSetLoadStatus : : Loaded ;
}
return deleted ;
2024-05-21 21:05:36 -06:00
}
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-clear
void FontFaceSet : : clear ( )
{
// FIXME: Do the actual spec steps
m_set_entries - > set_clear ( ) ;
}
2024-06-06 19:58:47 +01:00
// 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 ) ;
}
2024-06-06 20:01:39 +01:00
// 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 ) ;
}
2024-06-06 20:02:55 +01:00
// 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 ) ;
}
2024-10-23 19:15:25 -04:00
// https://drafts.csswg.org/css-font-loading/#find-the-matching-font-faces
2024-11-15 04:01:23 +13:00
static WebIDL : : ExceptionOr < GC : : Ref < JS : : Set > > find_matching_font_faces ( JS : : Realm & realm , FontFaceSet & font_face_set , String const & font , String const & )
2024-10-23 19:15:25 -04:00
{
// 1. Parse font using the CSS value syntax of the font property. If a syntax error occurs, return a syntax error.
2025-02-05 12:08:27 +00:00
auto property = parse_css_value ( CSS : : Parser : : ParsingParams ( ) , font , PropertyID : : Font ) ;
2025-07-01 19:53:30 +10:00
if ( ! property | | ! property - > is_shorthand ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : SyntaxError : : create ( realm , " Unable to parse font " _utf16 ) ;
2024-10-23 19:15:25 -04:00
// If the parsed value is a CSS-wide keyword, return a syntax error.
2025-07-01 19:53:30 +10:00
// Note: This case is already caught by the is_shorthand check.
2024-10-23 19:15:25 -04:00
// FIXME: Absolutize all relative lengths against the initial values of the corresponding properties. (For example, a
// relative font weight like bolder is evaluated against the initial value normal.)
// FIXME: 2. If text was not explicitly provided, let it be a string containing a single space character (U+0020 SPACE).
// 3. Let font family list be the list of font families parsed from font, and font style be the other font style
// attributes parsed from font.
auto const & font_family_list = property - > as_shorthand ( ) . longhand ( PropertyID : : FontFamily ) - > as_value_list ( ) ;
// 4. Let available font faces be the available font faces within source. If the allow system fonts flag is specified,
// add all system fonts to available font faces.
auto available_font_faces = font_face_set . set_entries ( ) ;
// 5. Let matched font faces initially be an empty list.
auto matched_font_faces = JS : : Set : : create ( realm ) ;
// 6. For each family in font family list, use the font matching rules to select the font faces from available font
// faces that match the font style, and add them to matched font faces. The use of the unicodeRange attribute means
// that this may be more than just a single font face.
for ( auto const & font_family : font_family_list . values ( ) ) {
// FIXME: The matching below is super basic. We currently just match font family names by their string value.
if ( ! font_family - > is_string ( ) )
continue ;
auto const & font_family_name = font_family - > as_string ( ) . string_value ( ) ;
for ( auto font_face_value : * available_font_faces ) {
2025-01-21 09:12:05 -05:00
auto & font_face = as < FontFace > ( font_face_value . key . as_object ( ) ) ;
2024-10-23 19:15:25 -04:00
if ( font_face . family ( ) ! = font_family_name )
continue ;
matched_font_faces - > set_add ( font_face_value . key ) ;
}
}
// FIXME: 7. If matched font faces is empty, set the found faces flag to false. Otherwise, set it to true.
// FIXME: 8. For each font face in matched font faces, if its defined unicode-range does not include the codepoint of at
// least one character in text, remove it from the list.
// 9. Return matched font faces and the found faces flag.
return matched_font_faces ;
}
2024-05-07 14:49:31 -06:00
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-load
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ref < WebIDL : : Promise > > FontFaceSet : : load ( String const & font , String const & text )
2024-05-07 14:49:31 -06:00
{
2024-10-23 19:15:25 -04:00
auto & realm = this - > realm ( ) ;
// 1. Let font face set be the FontFaceSet object this method was called on. Let promise be a newly-created promise object.
2024-11-15 04:01:23 +13:00
GC : : Ref font_face_set = * this ;
2024-10-23 19:15:25 -04:00
auto promise = WebIDL : : create_promise ( realm ) ;
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , font_face_set , promise , font , text ] ( ) mutable {
2024-10-23 19:15:25 -04:00
// 3. Find the matching font faces from font face set using the font and text arguments passed to the function,
// and let font face list be the return value (ignoring the found faces flag). If a syntax error was returned,
// reject promise with a SyntaxError exception and terminate these steps.
auto result = find_matching_font_faces ( realm , font_face_set , font , text ) ;
if ( result . is_error ( ) ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2025-04-04 18:11:45 +02:00
WebIDL : : reject_promise ( realm , promise , Bindings : : exception_to_throw_completion ( realm . vm ( ) , result . release_error ( ) ) . release_value ( ) ) ;
2024-10-23 19:15:25 -04:00
return ;
}
auto matched_font_faces = result . release_value ( ) ;
// 4. Queue a task to run the following steps synchronously:
2024-11-15 04:01:23 +13:00
HTML : : queue_a_task ( HTML : : Task : : Source : : FontLoading , nullptr , nullptr , GC : : create_function ( realm . heap ( ) , [ & realm , promise , matched_font_faces ] {
2024-12-26 14:32:52 +01:00
GC : : RootVector < GC : : Ref < WebIDL : : Promise > > promises ( realm . heap ( ) ) ;
2024-10-23 19:15:25 -04:00
// 1. For all of the font faces in the font face list, call their load() method.
for ( auto font_face_value : * matched_font_faces ) {
2025-01-21 09:12:05 -05:00
auto & font_face = as < FontFace > ( font_face_value . key . as_object ( ) ) ;
2024-10-23 19:15:25 -04:00
font_face . load ( ) ;
promises . append ( font_face . font_status_promise ( ) ) ;
}
// 2. Resolve promise with the result of waiting for all of the [[FontStatusPromise]]s of each font face in
// the font face list, in order.
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-10-23 19:15:25 -04:00
WebIDL : : wait_for_all (
realm , promises ,
2025-07-21 13:15:47 +01:00
[ & realm , promise ] ( auto const & fonts ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2025-07-21 13:15:47 +01:00
auto fonts_array = JS : : Array : : create_from ( realm , fonts ) ;
WebIDL : : resolve_promise ( realm , promise , fonts_array ) ;
2024-10-23 19:15:25 -04:00
} ,
[ & realm , promise ] ( auto error ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-10-23 19:15:25 -04:00
WebIDL : : reject_promise ( realm , promise , error ) ;
} ) ;
} ) ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2024-10-23 19:15:25 -04:00
// 2. Return promise. Complete the rest of these steps asynchronously.
2024-10-25 12:38:19 -06:00
return promise ;
2024-05-07 14:49:31 -06:00
}
2024-05-13 13:55:43 -06:00
// https://drafts.csswg.org/css-font-loading/#font-face-set-ready
2024-11-15 04:01:23 +13:00
GC : : Ref < WebIDL : : Promise > FontFaceSet : : ready ( ) const
2024-05-13 13:55:43 -06:00
{
2024-10-25 12:38:19 -06:00
return m_ready_promise ;
2024-05-13 13:55:43 -06:00
}
2024-09-29 23:38:49 +02:00
void FontFaceSet : : resolve_ready_promise ( )
{
2024-10-25 12:38:19 -06:00
WebIDL : : resolve_promise ( realm ( ) , m_ready_promise ) ;
2024-09-29 23:38:49 +02:00
}
2024-05-07 14:49:31 -06:00
}