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.
67 lines
2 KiB
C++
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; }
|
|
|
|
}
|