ladybird/Libraries/LibWeb/CSS/CSSContainerRule.h
Sam Atkins 67046049d9 LibWeb: Evaluate container queries when collecting matching style rules
MatchingRules now have a container_rule member which stores the nearest
ancestor CSSContainerRule, if any. When populating the rule cache, we
maintain a stack of CSSContainerRules that we are within, and use
record the last one on the MatchingRule, so that it's O(1) instead of
having to walk up the rule's ancestors each time. This does mean we
have reimplement some "for each rule" code.

When collecting rules to apply to an element, we see if the MatchingRule
has a container_rule, and if so, we evaluate that rule's query to see
if the element has a matching container. We then also match any ancestor
container rules, using the cached parent container rule.

Add a custom test to cover the case of nested name-only `@container`s,
which WPT lacks currently as far as I can tell.
2026-05-13 11:05:31 +01:00

67 lines
2 KiB
C++

/*
* Copyright (c) 2026, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/Optional.h>
#include <LibWeb/CSS/CSSConditionRule.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
// https://drafts.csswg.org/css-conditional-5/#dictdef-csscontainercondition
struct CSSContainerCondition {
String name;
String query;
};
// https://drafts.csswg.org/css-conditional-5/#the-csscontainerrule-interface
class CSSContainerRule final : public CSSConditionRule {
WEB_PLATFORM_OBJECT(CSSContainerRule, CSSConditionRule);
GC_DECLARE_ALLOCATOR(CSSContainerRule);
public:
struct Condition {
Optional<FlyString> container_name;
RefPtr<ContainerQuery> container_query;
};
[[nodiscard]] static GC::Ref<CSSContainerRule> create(JS::Realm&, Vector<Condition>&&, CSSRuleList&);
virtual ~CSSContainerRule() override;
virtual String condition_text() const override;
virtual bool condition_matches() const override;
bool matches(DOM::AbstractElement const&) const;
String container_name() const;
String container_query() const;
// FIXME: Should be FrozenArray
Vector<CSSContainerCondition> conditions() const;
virtual void for_each_effective_rule(TraversalOrder, Function<void(CSSRule const&)> const& callback) const override;
private:
CSSContainerRule(JS::Realm&, Vector<Condition>&&, CSSRuleList&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual void clear_caches() override;
virtual String serialized() const override;
CSSContainerRule const* find_parent_container_rule() const;
bool conditions_match(DOM::AbstractElement const&) const;
Vector<Condition> m_conditions;
mutable GC::Ptr<CSSContainerRule const> m_cached_parent_container_rule;
mutable bool m_parent_container_rule_cache_valid { false };
};
template<>
inline bool CSSRule::fast_is<CSSContainerRule>() const { return type() == CSSRule::Type::Container; }
}