ladybird/Libraries/LibWeb/CSS/BooleanExpression.cpp
Sam Atkins 0f5e054f97 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!
2025-03-17 10:00:19 +00:00

136 lines
4.1 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) 2021-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringBuilder.h>
#include <LibWeb/CSS/BooleanExpression.h>
namespace Web::CSS {
bool BooleanExpression::evaluate_to_boolean(HTML::Window const* window) const
{
return evaluate(window) == MatchResult::True;
}
void BooleanExpression::indent(StringBuilder& builder, int levels)
{
builder.append_repeated(" "sv, levels);
}
void GeneralEnclosed::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.appendff("GeneralEnclosed: {}\n", to_string());
}
MatchResult BooleanNotExpression::evaluate(HTML::Window const* window) const
{
// https://drafts.csswg.org/css-values-5/#boolean-logic
// `not test` evaluates to true if its contained test is false, false if its true, and unknown if its unknown.
switch (m_child->evaluate(window)) {
case MatchResult::False:
return MatchResult::True;
case MatchResult::True:
return MatchResult::False;
case MatchResult::Unknown:
return MatchResult::Unknown;
}
VERIFY_NOT_REACHED();
}
String BooleanNotExpression::to_string() const
{
return MUST(String::formatted("not {}", m_child->to_string()));
}
void BooleanNotExpression::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.append("NOT:\n"sv);
m_child->dump(builder, indent_levels + 1);
}
MatchResult BooleanExpressionInParens::evaluate(HTML::Window const* window) const
{
return m_child->evaluate(window);
}
String BooleanExpressionInParens::to_string() const
{
return MUST(String::formatted("({})", m_child->to_string()));
}
void BooleanExpressionInParens::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.append("(\n"sv);
m_child->dump(builder, indent_levels + 1);
indent(builder, indent_levels);
builder.append(")\n"sv);
}
MatchResult BooleanAndExpression::evaluate(HTML::Window const* window) const
{
// https://drafts.csswg.org/css-values-5/#boolean-logic
// Multiple tests connected with `and` evaluate to true if all of those tests are true, false if any of them are
// false, and unknown otherwise (i.e. if at least one unknown, but no false).
size_t true_results = 0;
for (auto const& child : m_children) {
auto child_match = child->evaluate(window);
if (child_match == MatchResult::False)
return MatchResult::False;
if (child_match == MatchResult::True)
true_results++;
}
if (true_results == m_children.size())
return MatchResult::True;
return MatchResult::Unknown;
}
String BooleanAndExpression::to_string() const
{
return MUST(String::join(" and "sv, m_children));
}
void BooleanAndExpression::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.append("AND:\n"sv);
for (auto const& child : m_children)
child->dump(builder, indent_levels + 1);
}
MatchResult BooleanOrExpression::evaluate(HTML::Window const* window) const
{
// https://drafts.csswg.org/css-values-5/#boolean-logic
// Multiple tests connected with `or` evaluate to true if any of those tests are true, false if all of them are
// false, and unknown otherwise (i.e. at least one unknown, but no true).
size_t false_results = 0;
for (auto const& child : m_children) {
auto child_match = child->evaluate(window);
if (child_match == MatchResult::True)
return MatchResult::True;
if (child_match == MatchResult::False)
false_results++;
}
if (false_results == m_children.size())
return MatchResult::False;
return MatchResult::Unknown;
}
String BooleanOrExpression::to_string() const
{
return MUST(String::join(" or "sv, m_children));
}
void BooleanOrExpression::dump(StringBuilder& builder, int indent_levels) const
{
indent(builder, indent_levels);
builder.append("OR:\n"sv);
for (auto const& child : m_children)
child->dump(builder, indent_levels + 1);
}
}