LibWeb/CSS: Implement @supports font-format() and font-tech()

These let authors query whether we support font formats and features.
This commit is contained in:
Sam Atkins 2025-03-14 11:36:15 +00:00
commit b6fb4baeb7
Notes: github-actions[bot] 2025-03-17 10:01:17 +00:00
6 changed files with 242 additions and 2 deletions

View file

@ -15,6 +15,7 @@
#include <AK/Debug.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/MediaList.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h>
@ -274,10 +275,11 @@ OwnPtr<BooleanExpression> Parser::parse_boolean_expression_group(TokenStream<Com
return {};
}
// https://drafts.csswg.org/css-conditional-4/#typedef-supports-feature
// https://drafts.csswg.org/css-conditional-5/#typedef-supports-feature
OwnPtr<BooleanExpression> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
{
// <supports-feature> = <supports-selector-fn> | <supports-decl>
// <supports-feature> = <supports-selector-fn> | <supports-font-tech-fn>
// | <supports-font-format-fn> | <supports-decl>
auto transaction = tokens.begin_transaction();
tokens.discard_whitespace();
auto const& first_token = tokens.consume_a_token();
@ -307,6 +309,36 @@ OwnPtr<BooleanExpression> Parser::parse_supports_feature(TokenStream<ComponentVa
!parse_a_selector_list(selector_tokens, SelectorType::Standalone).is_error());
}
// `<supports-font-tech-fn> = font-tech( <font-tech> )`
if (first_token.is_function("font-tech"sv)) {
TokenStream tech_tokens { first_token.function().value };
tech_tokens.discard_whitespace();
auto tech_token = tech_tokens.consume_a_token();
tech_tokens.discard_whitespace();
if (tech_tokens.has_next_token() || !tech_token.is(Token::Type::Ident))
return {};
transaction.commit();
auto tech_name = tech_token.token().ident();
bool matches = font_tech_is_supported(tech_name);
return Supports::FontTech::create(move(tech_name), matches);
}
// `<supports-font-format-fn> = font-format( <font-format> )`
if (first_token.is_function("font-format"sv)) {
TokenStream format_tokens { first_token.function().value };
format_tokens.discard_whitespace();
auto format_token = format_tokens.consume_a_token();
format_tokens.discard_whitespace();
if (format_tokens.has_next_token() || !format_token.is(Token::Type::Ident))
return {};
transaction.commit();
auto format_name = format_token.token().ident();
bool matches = font_format_is_supported(format_name);
return Supports::FontFormat::create(move(format_name), matches);
}
return {};
}

View file

@ -47,6 +47,38 @@ void Supports::Selector::dump(StringBuilder& builder, int indent_levels) const
builder.appendff("Selector: `{}` matches={}\n", m_selector, m_matches);
}
MatchResult Supports::FontTech::evaluate(HTML::Window const*) const
{
return as_match_result(m_matches);
}
String Supports::FontTech::to_string() const
{
return MUST(String::formatted("font-tech({})", m_tech));
}
void Supports::FontTech::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.appendff("FontTech: `{}` matches={}\n", m_tech, m_matches);
}
MatchResult Supports::FontFormat::evaluate(HTML::Window const*) const
{
return as_match_result(m_matches);
}
String Supports::FontFormat::to_string() const
{
return MUST(String::formatted("font-format({})", m_format));
}
void Supports::FontFormat::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.appendff("FontFormat: `{}` matches={}\n", m_format, m_matches);
}
String Supports::to_string() const
{
return m_condition->to_string();

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/FlyString.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefCounted.h>
#include <AK/String.h>
@ -60,6 +61,50 @@ public:
bool m_matches;
};
class FontTech final : public BooleanExpression {
public:
static NonnullOwnPtr<FontTech> create(FlyString tech, bool matches)
{
return adopt_own(*new FontTech(move(tech), matches));
}
virtual ~FontTech() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
FontTech(FlyString tech, bool matches)
: m_tech(move(tech))
, m_matches(matches)
{
}
FlyString m_tech;
bool m_matches;
};
class FontFormat final : public BooleanExpression {
public:
static NonnullOwnPtr<FontFormat> create(FlyString format, bool matches)
{
return adopt_own(*new FontFormat(move(format), matches));
}
virtual ~FontFormat() override = default;
virtual MatchResult evaluate(HTML::Window const*) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
FontFormat(FlyString format, bool matches)
: m_format(move(format))
, m_matches(matches)
{
}
FlyString m_format;
bool m_matches;
};
static NonnullRefPtr<Supports> create(NonnullOwnPtr<BooleanExpression>&& condition)
{
return adopt_ref(*new Supports(move(condition)));