ladybird/Libraries/LibWeb/CSS/BooleanExpression.h
Sam Atkins 585849fcf0 LibWeb: Parse and evaluate @container <size-feature>s
e.g., `@container (width >= 300px) {}` and similar.

During style computation, flag any elements whose style depends on a
size container. Then re-evaluate their style after the initial layout
has been computed and size containers have a size. This may take
multiple passes, as these may have further descendants that depend on
their size, etc. We limit this to 8 passes currently.

SizeFeature itself is very similar to MediaFeature, but queries the
container element instead. There are only 6 size features specified, so
they're hard-coded instead of generated from JSON.

Also add a counter test for the narrower restyle path.
2026-05-20 13:00:50 +01:00

259 lines
8.5 KiB
C++

/*
* Copyright (c) 2021-2026, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibGC/Ptr.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
// Corresponds to Kleene 3-valued logic.
enum class MatchResult : u8 {
False,
True,
Unknown,
};
constexpr MatchResult operator&&(MatchResult const a, MatchResult const b)
{
// If either is false, false.
if (a == MatchResult::False || b == MatchResult::False)
return MatchResult::False;
// If both are true, true.
if (a == MatchResult::True && b == MatchResult::True)
return MatchResult::True;
// Otherwise, unknown.
return MatchResult::Unknown;
}
constexpr MatchResult operator||(MatchResult const a, MatchResult const b)
{
// If either is true, true.
if (a == MatchResult::True || b == MatchResult::True)
return MatchResult::True;
// If both are false, false.
if (a == MatchResult::False && b == MatchResult::False)
return MatchResult::False;
// Otherwise, unknown.
return MatchResult::Unknown;
}
constexpr MatchResult as_match_result(bool value)
{
return value ? MatchResult::True : MatchResult::False;
}
constexpr MatchResult negate(MatchResult value)
{
switch (value) {
case MatchResult::False:
return MatchResult::True;
case MatchResult::True:
return MatchResult::False;
case MatchResult::Unknown:
return MatchResult::Unknown;
}
VERIFY_NOT_REACHED();
}
constexpr StringView to_string(MatchResult result)
{
switch (result) {
case MatchResult::False:
return "false"sv;
case MatchResult::True:
return "true"sv;
case MatchResult::Unknown:
return "unknown"sv;
}
VERIFY_NOT_REACHED();
}
struct BooleanExpressionEvaluationContext {
GC::Ptr<DOM::Document const> document { nullptr };
GC::Ptr<DOM::Element const> query_container { nullptr };
};
struct ContainerQueryFeatureRequirements {
bool requires_width_container : 1 { false };
bool requires_height_container : 1 { false };
bool requires_inline_size_container : 1 { false };
bool requires_block_size_container : 1 { false };
bool requires_style_container : 1 { false };
bool requires_scroll_state_container : 1 { false };
bool has_unknown_or_unsupported_feature : 1 { false };
bool contains_size_feature() const
{
return requires_width_container
|| requires_height_container
|| requires_inline_size_container
|| requires_block_size_container;
}
};
// The contents of this file implement the `<boolean-expr>` concept.
// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr
class BooleanExpression {
public:
virtual ~BooleanExpression() = default;
bool evaluate_to_boolean(BooleanExpressionEvaluationContext const&) const;
static void indent(StringBuilder& builder, int levels);
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const = 0;
virtual void collect_container_query_feature_requirements(ContainerQueryFeatureRequirements&) const { }
virtual String to_string() const = 0;
virtual void dump(StringBuilder&, int indent_levels = 0) const = 0;
};
// https://www.w3.org/TR/mediaqueries-4/#typedef-general-enclosed
class GeneralEnclosed final : public BooleanExpression {
public:
static NonnullOwnPtr<GeneralEnclosed> create(String serialized_contents, MatchResult matches = MatchResult::Unknown)
{
return adopt_own(*new GeneralEnclosed(move(serialized_contents), matches));
}
virtual ~GeneralEnclosed() override = default;
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const override { return m_matches; }
virtual void collect_container_query_feature_requirements(ContainerQueryFeatureRequirements&) const override;
virtual String to_string() const override { return m_serialized_contents; }
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
GeneralEnclosed(String serialized_contents, MatchResult matches)
: m_serialized_contents(move(serialized_contents))
, m_matches(matches)
{
}
String m_serialized_contents;
MatchResult m_matches;
};
class BooleanNotExpression final : public BooleanExpression {
public:
static NonnullOwnPtr<BooleanNotExpression> create(NonnullOwnPtr<BooleanExpression>&& child)
{
return adopt_own(*new BooleanNotExpression(move(child)));
}
virtual ~BooleanNotExpression() override = default;
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const override;
virtual void collect_container_query_feature_requirements(ContainerQueryFeatureRequirements&) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
BooleanNotExpression(NonnullOwnPtr<BooleanExpression>&& child)
: m_child(move(child))
{
}
NonnullOwnPtr<BooleanExpression> m_child;
};
class BooleanExpressionInParens final : public BooleanExpression {
public:
static NonnullOwnPtr<BooleanExpressionInParens> create(NonnullOwnPtr<BooleanExpression>&& child)
{
return adopt_own(*new BooleanExpressionInParens(move(child)));
}
virtual ~BooleanExpressionInParens() override = default;
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const override;
virtual void collect_container_query_feature_requirements(ContainerQueryFeatureRequirements&) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
BooleanExpressionInParens(NonnullOwnPtr<BooleanExpression>&& child)
: m_child(move(child))
{
}
NonnullOwnPtr<BooleanExpression> m_child;
};
class BooleanAndExpression final : public BooleanExpression {
public:
static NonnullOwnPtr<BooleanAndExpression> create(Vector<NonnullOwnPtr<BooleanExpression>>&& children)
{
return adopt_own(*new BooleanAndExpression(move(children)));
}
virtual ~BooleanAndExpression() override = default;
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const override;
virtual void collect_container_query_feature_requirements(ContainerQueryFeatureRequirements&) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
BooleanAndExpression(Vector<NonnullOwnPtr<BooleanExpression>>&& children)
: m_children(move(children))
{
}
Vector<NonnullOwnPtr<BooleanExpression>> m_children;
};
class BooleanOrExpression final : public BooleanExpression {
public:
static NonnullOwnPtr<BooleanOrExpression> create(Vector<NonnullOwnPtr<BooleanExpression>>&& children)
{
return adopt_own(*new BooleanOrExpression(move(children)));
}
virtual ~BooleanOrExpression() override = default;
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const override;
virtual void collect_container_query_feature_requirements(ContainerQueryFeatureRequirements&) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
BooleanOrExpression(Vector<NonnullOwnPtr<BooleanExpression>>&& children)
: m_children(move(children))
{
}
Vector<NonnullOwnPtr<BooleanExpression>> m_children;
};
class ConstantBooleanExpression final : public BooleanExpression {
public:
static NonnullOwnPtr<ConstantBooleanExpression> create(MatchResult value)
{
return adopt_own(*new ConstantBooleanExpression(value));
}
virtual ~ConstantBooleanExpression() override = default;
virtual MatchResult evaluate(BooleanExpressionEvaluationContext const&) const override { return m_value; }
virtual String to_string() const override { return MUST(String::from_utf8(CSS::to_string(m_value))); }
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
ConstantBooleanExpression(MatchResult value)
: m_value(value)
{
}
MatchResult m_value;
};
}
template<>
struct AK::Formatter<Web::CSS::BooleanExpression> : AK::Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::BooleanExpression const& expression)
{
return Formatter<StringView>::format(builder, expression.to_string());
}
};