LibWeb: Implement generic boolean logic for media/supports queries

CSS Values 5 now defines a `<boolean-expr[]>` type that is used in place
of the bespoke grammar that previously existed for `@media` and
`@supports` queries. This commit implements some BooleanExpression
types to represent the nodes in a `<boolean-expr[]>`, and reimplements
`@media` and `@supports` queries using this.

The one part of this implementation I'm not convinced on is that the
`evaluate()` methods take a `HTML::Window*`. This is a compromise
because `@media` requires a Window, and `@supports` does not require
anything at all. As more users of `<boolean-expr[]>` get implemented in
the future, it will become clear if this is sufficient, or if we need
to do something smarter.

As a bonus, this actually improves our serialization of media queries!
This commit is contained in:
Sam Atkins 2025-03-14 10:31:27 +00:00
commit 0f5e054f97
Notes: github-actions[bot] 2025-03-17 10:01:42 +00:00
13 changed files with 526 additions and 663 deletions

View file

@ -9,62 +9,58 @@
#include <AK/NonnullOwnPtr.h>
#include <AK/RefCounted.h>
#include <AK/String.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/GeneralEnclosed.h>
#include <LibWeb/CSS/BooleanExpression.h>
namespace Web::CSS {
// https://www.w3.org/TR/css-conditional-3/#at-supports
class Supports final : public RefCounted<Supports> {
public:
struct Declaration {
String declaration;
bool matches;
[[nodiscard]] bool evaluate() const { return matches; }
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
class Declaration final : public BooleanExpression {
public:
static NonnullOwnPtr<Declaration> create(String declaration, bool matches)
{
return adopt_own(*new Declaration(move(declaration), matches));
}
virtual ~Declaration() 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:
Declaration(String declaration, bool matches)
: m_declaration(move(declaration))
, m_matches(matches)
{
}
String m_declaration;
bool m_matches;
};
struct Selector {
String selector;
bool matches;
[[nodiscard]] bool evaluate() const { return matches; }
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
class Selector final : public BooleanExpression {
public:
static NonnullOwnPtr<Selector> create(String selector, bool matches)
{
return adopt_own(*new Selector(move(selector), matches));
}
virtual ~Selector() 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:
Selector(String selector, bool matches)
: m_selector(move(selector))
, m_matches(matches)
{
}
String m_selector;
bool m_matches;
};
struct Feature {
Variant<Declaration, Selector> value;
[[nodiscard]] bool evaluate() const;
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
struct Condition;
struct InParens {
Variant<NonnullOwnPtr<Condition>, Feature, GeneralEnclosed> value;
[[nodiscard]] bool evaluate() const;
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
struct Condition {
enum class Type {
Not,
And,
Or,
};
Type type;
Vector<InParens> children;
[[nodiscard]] bool evaluate() const;
String to_string() const;
void dump(StringBuilder&, int indent_levels = 0) const;
};
static NonnullRefPtr<Supports> create(NonnullOwnPtr<Condition>&& condition)
static NonnullRefPtr<Supports> create(NonnullOwnPtr<BooleanExpression>&& condition)
{
return adopt_ref(*new Supports(move(condition)));
}
@ -75,18 +71,10 @@ public:
void dump(StringBuilder&, int indent_levels = 0) const;
private:
Supports(NonnullOwnPtr<Condition>&&);
Supports(NonnullOwnPtr<BooleanExpression>&&);
NonnullOwnPtr<Condition> m_condition;
NonnullOwnPtr<BooleanExpression> m_condition;
bool m_matches { false };
};
}
template<>
struct AK::Formatter<Web::CSS::Supports::InParens> : AK::Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::Supports::InParens const& in_parens)
{
return Formatter<StringView>::format(builder, in_parens.to_string());
}
};