LibWeb/CSS: Implement the `@scope` rule
`@scope (a) to (b) {}` applies its contained style rules to elements
that have `a` as a parent, and do not have `a b` as a parent. Both the
`a` and `b` selector lists are optional.
Because it's situational whether a `@scope` will apply to a given
element, we store the ancestor scope on the `MatchingRule`, similar to
`@container`, and then determine during matching whether all the parent
`@scope`s match or not.
The rules for how selectors inside `@scope` are adjusted and interpreted
are a bit confusing. Unlike for other at-rules, nested style rules
inside `@scope` do not get a leading `&` added during parsing. To
support this, `adapt_nested_relative_selector_list()` now takes a flag
for whether its parent is a `@scope` or not.
`@scope` can also contain nested declarations without itself being
nested inside a style rule.
When determining their selectors, nested declarations rules adopt the
`@scope`'s scoping root if it has one, or otherwise fall back to the
parent element of the `<style>` element (not implemented here,) or the
`:root`. These are required to have zero specificity, so we wrap the
selector in `:where()`.
2026-05-15 13:11:08 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026, Sam Atkins <sam@ladybird.org>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <AK/Optional.h>
|
|
|
|
|
#include <LibWeb/CSS/CSSGroupingRule.h>
|
|
|
|
|
#include <LibWeb/CSS/Selector.h>
|
|
|
|
|
#include <LibWeb/Forward.h>
|
|
|
|
|
|
|
|
|
|
namespace Web::CSS {
|
|
|
|
|
|
|
|
|
|
// https://drafts.csswg.org/css-cascade-6/#the-cssscoperule-interface
|
|
|
|
|
class CSSScopeRule final : public CSSGroupingRule {
|
|
|
|
|
WEB_PLATFORM_OBJECT(CSSScopeRule, CSSGroupingRule);
|
|
|
|
|
GC_DECLARE_ALLOCATOR(CSSScopeRule);
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
[[nodiscard]] static GC::Ref<CSSScopeRule> create(JS::Realm&, Optional<SelectorList>&& start_selectors, Optional<SelectorList>&& end_selectors, CSSRuleList&);
|
|
|
|
|
|
|
|
|
|
virtual ~CSSScopeRule() override;
|
|
|
|
|
|
|
|
|
|
Optional<SelectorList> const& start_selectors() const { return m_start_selectors; }
|
|
|
|
|
Optional<SelectorList> const& end_selectors() const { return m_end_selectors; }
|
|
|
|
|
Optional<SelectorList> const& start_selectors_for_matching() const;
|
|
|
|
|
Optional<SelectorList> const& end_selectors_for_matching() const;
|
|
|
|
|
GC::Ptr<CSSScopeRule const> nearest_ancestor_scope_rule() const;
|
|
|
|
|
|
|
|
|
|
Optional<String> start() const;
|
|
|
|
|
Optional<String> end() const;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
CSSScopeRule(JS::Realm&, Optional<SelectorList>&& start_selectors, Optional<SelectorList>&& end_selectors, CSSRuleList&);
|
|
|
|
|
|
|
|
|
|
virtual void initialize(JS::Realm&) override;
|
|
|
|
|
virtual void visit_edges(Cell::Visitor&) override;
|
|
|
|
|
virtual void clear_caches() override;
|
|
|
|
|
virtual String serialized() const override;
|
|
|
|
|
virtual void dump(StringBuilder&, int indent_levels) const override;
|
|
|
|
|
|
|
|
|
|
Optional<SelectorList> m_start_selectors;
|
|
|
|
|
Optional<SelectorList> m_end_selectors;
|
|
|
|
|
mutable Optional<SelectorList> m_cached_start_selectors_for_matching;
|
|
|
|
|
mutable Optional<SelectorList> m_cached_end_selectors_for_matching;
|
|
|
|
|
mutable Optional<GC::Ptr<CSSScopeRule const>> m_cached_nearest_ancestor_scope_rule;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
inline bool CSSRule::fast_is<CSSScopeRule>() const { return type() == CSSRule::Type::Scope; }
|
|
|
|
|
|
2026-06-04 13:22:45 -03:00
|
|
|
SelectorList adapt_scope_end_selectors_for_matching(SelectorList const&);
|
|
|
|
|
Optional<SelectorList> const& scope_start_selectors_for_matching(CSSRule const&);
|
|
|
|
|
Optional<SelectorList> const& scope_end_selectors_for_matching(CSSRule const&);
|
|
|
|
|
GC::Ptr<CSSImportRule const> nearest_scoped_owner_import(CSSStyleSheet const*);
|
|
|
|
|
GC::Ptr<CSSRule const> nearest_ancestor_scope_rule_for_matching(CSSRule const&);
|
|
|
|
|
|
LibWeb/CSS: Implement the `@scope` rule
`@scope (a) to (b) {}` applies its contained style rules to elements
that have `a` as a parent, and do not have `a b` as a parent. Both the
`a` and `b` selector lists are optional.
Because it's situational whether a `@scope` will apply to a given
element, we store the ancestor scope on the `MatchingRule`, similar to
`@container`, and then determine during matching whether all the parent
`@scope`s match or not.
The rules for how selectors inside `@scope` are adjusted and interpreted
are a bit confusing. Unlike for other at-rules, nested style rules
inside `@scope` do not get a leading `&` added during parsing. To
support this, `adapt_nested_relative_selector_list()` now takes a flag
for whether its parent is a `@scope` or not.
`@scope` can also contain nested declarations without itself being
nested inside a style rule.
When determining their selectors, nested declarations rules adopt the
`@scope`'s scoping root if it has one, or otherwise fall back to the
parent element of the `<style>` element (not implemented here,) or the
`:root`. These are required to have zero specificity, so we wrap the
selector in `:where()`.
2026-05-15 13:11:08 -03:00
|
|
|
}
|