2024-10-15 15:59:31 +01:00
|
|
|
/*
|
2026-05-21 13:34:03 +01:00
|
|
|
* Copyright (c) 2024-2026, Sam Atkins <sam@ladybird.org>
|
2024-10-15 15:59:31 +01:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "CSSNestedDeclarations.h"
|
2026-04-18 10:54:06 +02:00
|
|
|
#include <LibWeb/Bindings/CSSNestedDeclarations.h>
|
2024-10-15 15:59:31 +01:00
|
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
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 17:11:08 +01:00
|
|
|
#include <LibWeb/CSS/CSSScopeRule.h>
|
2024-11-06 17:43:30 +00:00
|
|
|
#include <LibWeb/CSS/CSSStyleRule.h>
|
2026-03-05 16:00:11 +13:00
|
|
|
#include <LibWeb/CSS/Parser/Parser.h>
|
2025-12-04 12:03:01 +00:00
|
|
|
#include <LibWeb/Dump.h>
|
2024-10-15 15:59:31 +01:00
|
|
|
|
|
|
|
|
namespace Web::CSS {
|
|
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
GC_DEFINE_ALLOCATOR(CSSNestedDeclarations);
|
2024-10-15 15:59:31 +01:00
|
|
|
|
2026-03-05 16:00:11 +13:00
|
|
|
GC::Ref<CSSNestedDeclarations> CSSNestedDeclarations::create(JS::Realm& realm, Parser::Parser& parser, Vector<Parser::Declaration> const& declarations)
|
|
|
|
|
{
|
|
|
|
|
return realm.create<CSSNestedDeclarations>(realm, parser.convert_to_style_declaration(declarations));
|
|
|
|
|
}
|
|
|
|
|
|
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
|
|
|
GC::Ref<CSSNestedDeclarations> CSSNestedDeclarations::create(JS::Realm& realm, CSSStyleProperties& declaration)
|
2024-10-15 15:59:31 +01:00
|
|
|
{
|
2024-11-14 05:50:17 +13:00
|
|
|
return realm.create<CSSNestedDeclarations>(realm, declaration);
|
2024-10-15 15:59:31 +01:00
|
|
|
}
|
|
|
|
|
|
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
|
|
|
CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, CSSStyleProperties& declaration)
|
2024-10-28 20:16:28 +01:00
|
|
|
: CSSRule(realm, Type::NestedDeclarations)
|
2024-10-15 15:59:31 +01:00
|
|
|
, m_declaration(declaration)
|
|
|
|
|
{
|
|
|
|
|
m_declaration->set_parent_rule(*this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CSSNestedDeclarations::initialize(JS::Realm& realm)
|
|
|
|
|
{
|
|
|
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSNestedDeclarations);
|
2025-04-20 16:22:57 +02:00
|
|
|
Base::initialize(realm);
|
2024-10-15 15:59:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CSSNestedDeclarations::visit_edges(Cell::Visitor& visitor)
|
|
|
|
|
{
|
|
|
|
|
Base::visit_edges(visitor);
|
|
|
|
|
visitor.visit(m_declaration);
|
2024-11-06 17:43:30 +00:00
|
|
|
visitor.visit(m_parent_style_rule);
|
2024-10-15 15:59:31 +01:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 13:34:03 +01:00
|
|
|
static SelectorList absolutize_parent_selectors(CSSNestedDeclarations const& nested_declarations)
|
|
|
|
|
{
|
2026-05-27 12:20:31 +01:00
|
|
|
static SelectorList s_where_scope_selector_list {
|
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 17:11:08 +01:00
|
|
|
Selector::create({
|
|
|
|
|
Selector::CompoundSelector {
|
|
|
|
|
.combinator = Selector::Combinator::None,
|
|
|
|
|
.simple_selectors = {
|
|
|
|
|
Selector::SimpleSelector {
|
|
|
|
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
|
|
|
|
.value = Selector::SimpleSelector::PseudoClassSelector {
|
2026-05-27 12:20:31 +01:00
|
|
|
.type = PseudoClass::Where,
|
|
|
|
|
.argument_selector_list = {
|
|
|
|
|
Selector::create({
|
|
|
|
|
Selector::CompoundSelector {
|
|
|
|
|
.combinator = Selector::Combinator::None,
|
|
|
|
|
.simple_selectors = {
|
|
|
|
|
Selector::SimpleSelector {
|
|
|
|
|
.type = Selector::SimpleSelector::Type::PseudoClass,
|
|
|
|
|
.value = Selector::SimpleSelector::PseudoClassSelector {
|
|
|
|
|
.type = PseudoClass::Scope,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
},
|
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 17:11:08 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-21 13:34:03 +01:00
|
|
|
for (auto const* parent_rule = nested_declarations.parent_rule(); parent_rule; parent_rule = parent_rule->parent_rule()) {
|
|
|
|
|
if (auto const* parent_style_rule = as_if<CSSStyleRule>(parent_rule))
|
|
|
|
|
return parent_style_rule->absolutized_selectors();
|
2026-05-27 12:20:31 +01:00
|
|
|
if (is<CSSScopeRule>(parent_rule)) {
|
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 17:11:08 +01:00
|
|
|
// https://drafts.csswg.org/css-cascade-6/#scoped-declarations
|
|
|
|
|
// Declarations may be used directly with the body of a @scope rule. Contiguous runs of declarations are
|
|
|
|
|
// wrapped in nested declarations rules, which match the scoping root with zero specificity.
|
2026-05-27 12:20:31 +01:00
|
|
|
return s_where_scope_selector_list;
|
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 17:11:08 +01:00
|
|
|
}
|
2026-05-21 13:34:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NB: CSSNestedDeclarations can only exist inside an ancestor rule that provides selectors, so we cannot get here
|
|
|
|
|
// unless something has gone very wrong.
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SelectorList const& CSSNestedDeclarations::absolutized_selectors() const
|
|
|
|
|
{
|
|
|
|
|
if (m_cached_absolutized_selectors.has_value())
|
|
|
|
|
return m_cached_absolutized_selectors.value();
|
|
|
|
|
|
|
|
|
|
m_cached_absolutized_selectors = absolutize_parent_selectors(*this);
|
|
|
|
|
return m_cached_absolutized_selectors.value();
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-24 22:13:14 -04:00
|
|
|
GC::Ref<CSSStyleProperties> CSSNestedDeclarations::style()
|
2024-10-15 15:59:31 +01:00
|
|
|
{
|
|
|
|
|
return m_declaration;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-06 17:43:30 +00:00
|
|
|
CSSStyleRule const& CSSNestedDeclarations::parent_style_rule() const
|
|
|
|
|
{
|
|
|
|
|
if (m_parent_style_rule)
|
|
|
|
|
return *m_parent_style_rule;
|
|
|
|
|
|
|
|
|
|
for (auto* parent = parent_rule(); parent; parent = parent->parent_rule()) {
|
|
|
|
|
if (is<CSSStyleRule>(parent)) {
|
|
|
|
|
m_parent_style_rule = static_cast<CSSStyleRule const*>(parent);
|
|
|
|
|
return *m_parent_style_rule;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbgln("CSSNestedDeclarations has no parent style rule!");
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 15:59:31 +01:00
|
|
|
String CSSNestedDeclarations::serialized() const
|
|
|
|
|
{
|
|
|
|
|
// NOTE: There's no proper spec for this yet, only this note:
|
|
|
|
|
// "The CSSNestedDeclarations rule serializes as if its declaration block had been serialized directly."
|
|
|
|
|
// - https://drafts.csswg.org/css-nesting-1/#ref-for-cssnesteddeclarations%E2%91%A1
|
|
|
|
|
// So, we'll do the simple thing and hope it's good.
|
|
|
|
|
return m_declaration->serialized();
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-06 17:43:30 +00:00
|
|
|
void CSSNestedDeclarations::clear_caches()
|
|
|
|
|
{
|
|
|
|
|
Base::clear_caches();
|
|
|
|
|
m_parent_style_rule = nullptr;
|
2026-05-21 13:34:03 +01:00
|
|
|
m_cached_absolutized_selectors.clear();
|
2024-11-06 17:43:30 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-04 12:03:01 +00:00
|
|
|
void CSSNestedDeclarations::dump(StringBuilder& builder, int indent_levels) const
|
|
|
|
|
{
|
|
|
|
|
Base::dump(builder, indent_levels);
|
|
|
|
|
|
|
|
|
|
dump_style_properties(builder, declaration(), indent_levels + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 15:59:31 +01:00
|
|
|
}
|