LibWeb: Add & when setting nested style rule's selector text

When we first parse a nested CSSStyleRule's selectors, we treat them as
relative selectors and then patch them up with an `&` as needed.
However, we weren't doing this when assigning the `cssText` attribute.
So, let's do that!

This gives us a couple of subtest passes. :^)
This commit is contained in:
Sam Atkins 2024-11-08 17:50:38 +00:00 committed by Andreas Kling
parent 3e0c182344
commit a0403ac427
Notes: github-actions[bot] 2024-11-09 13:30:38 +00:00
9 changed files with 77 additions and 46 deletions

View file

@ -6,7 +6,8 @@ Rerun
Found 11 tests
11 Fail
1 Pass
10 Fail
Details
Result Test Name MessageFail Trailing declarations apply after any preceding rules
Fail Trailing declarations apply after any preceding rules (no leading)
@ -18,4 +19,4 @@ Fail Bare declartaion in nested grouping rule can match pseudo-element
Fail Nested group rules have top-level specificity behavior
Fail Nested @scope rules behave like :where(:scope)
Fail Nested @scope rules behave like :where(:scope) (trailing)
Fail Nested declarations rule responds to parent selector text change
Pass Nested declarations rule responds to parent selector text change

View file

@ -6,6 +6,6 @@ Rerun
Found 1 tests
1 Fail
1 Pass
Details
Result Test Name MessageFail Nested rule responds to parent selector text change
Result Test Name MessagePass Nested rule responds to parent selector text change

View file

@ -125,10 +125,18 @@ void CSSStyleRule::set_selector_text(StringView selector_text)
clear_caches();
// 1. Run the parse a group of selectors algorithm on the given value.
auto parsed_selectors = parse_selector(Parser::ParsingContext { realm() }, selector_text);
Optional<SelectorList> parsed_selectors;
if (parent_style_rule()) {
// AD-HOC: If we're a nested style rule, then we need to parse the selector as relative and then adapt it with implicit &s.
parsed_selectors = parse_selector_for_nested_style_rule(Parser::ParsingContext { realm() }, selector_text);
} else {
parsed_selectors = parse_selector(Parser::ParsingContext { realm() }, selector_text);
}
// 2. If the algorithm returns a non-null value replace the associated group of selectors with the returned value.
if (parsed_selectors.has_value()) {
// NOTE: If we have a parent style rule, we need to update the selectors to add any implicit `&`s
m_selectors = parsed_selectors.release_value();
if (auto* sheet = parent_style_sheet()) {
if (auto style_sheet_list = sheet->style_sheet_list()) {
@ -169,15 +177,8 @@ SelectorList const& CSSStyleRule::absolutized_selectors() const
// "When used in the selector of a nested style rule, the nesting selector represents the elements matched by the parent rule.
// When used in any other context, it represents the same elements as :scope in that context (unless otherwise defined)."
// https://drafts.csswg.org/css-nesting-1/#nest-selector
CSSStyleRule const* parent_style_rule = nullptr;
for (auto* parent = parent_rule(); parent; parent = parent->parent_rule()) {
if (parent->type() == CSSStyleRule::Type::Style) {
parent_style_rule = static_cast<CSSStyleRule const*>(parent);
break;
}
}
Selector::SimpleSelector parent_selector;
if (parent_style_rule) {
if (auto const* parent_style_rule = this->parent_style_rule()) {
// TODO: If there's only 1, we don't have to use `:is()` for it
parent_selector = {
.type = Selector::SimpleSelector::Type::PseudoClass,
@ -207,4 +208,13 @@ void CSSStyleRule::clear_caches()
m_cached_absolutized_selectors.clear();
}
CSSStyleRule const* CSSStyleRule::parent_style_rule() const
{
for (auto* parent = parent_rule(); parent; parent = parent->parent_rule()) {
if (parent->type() == CSSStyleRule::Type::Style)
return static_cast<CSSStyleRule const*>(parent);
}
return nullptr;
}
}

View file

@ -42,6 +42,8 @@ private:
virtual void clear_caches() override;
virtual String serialized() const override;
CSSStyleRule const* parent_style_rule() const;
SelectorList m_selectors;
mutable Optional<SelectorList> m_cached_absolutized_selectors;
JS::NonnullGCPtr<PropertyOwningCSSStyleDeclaration> m_declaration;

View file

@ -54,6 +54,17 @@ Optional<CSS::SelectorList> parse_selector(CSS::Parser::ParsingContext const& co
return CSS::Parser::Parser::create(context, selector_text).parse_as_selector();
}
Optional<CSS::SelectorList> parse_selector_for_nested_style_rule(CSS::Parser::ParsingContext const& context, StringView selector_text)
{
auto parser = CSS::Parser::Parser::create(context, selector_text);
auto maybe_selectors = parser.parse_as_relative_selector(CSS::Parser::Parser::SelectorParsingMode::Standard);
if (!maybe_selectors.has_value())
return {};
return adapt_nested_relative_selector_list(*maybe_selectors);
}
Optional<CSS::Selector::PseudoElement> parse_pseudo_element_selector(CSS::Parser::ParsingContext const& context, StringView selector_text)
{
return CSS::Parser::Parser::create(context, selector_text).parse_as_pseudo_element_selector();

View file

@ -423,6 +423,7 @@ CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingContext const&, Str
CSS::ElementInlineCSSStyleDeclaration* parse_css_style_attribute(CSS::Parser::ParsingContext const&, StringView, DOM::Element&);
RefPtr<CSS::CSSStyleValue> parse_css_value(CSS::Parser::ParsingContext const&, StringView, CSS::PropertyID property_id = CSS::PropertyID::Invalid);
Optional<CSS::SelectorList> parse_selector(CSS::Parser::ParsingContext const&, StringView);
Optional<CSS::SelectorList> parse_selector_for_nested_style_rule(CSS::Parser::ParsingContext const&, StringView);
Optional<CSS::Selector::PseudoElement> parse_pseudo_element_selector(CSS::Parser::ParsingContext const&, StringView);
CSS::CSSRule* parse_css_rule(CSS::Parser::ParsingContext const&, StringView);
RefPtr<CSS::MediaQuery> parse_media_query(CSS::Parser::ParsingContext const&, StringView);

View file

@ -98,39 +98,8 @@ JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& quali
}
SelectorList selectors = maybe_selectors.release_value();
if (nested == Nested::Yes) {
// "Nested style rules differ from non-nested rules in the following ways:
// - A nested style rule accepts a <relative-selector-list> as its prelude (rather than just a <selector-list>).
// Any relative selectors are relative to the elements represented by the nesting selector.
// - If a selector in the <relative-selector-list> does not start with a combinator but does contain the nesting
// selector, it is interpreted as a non-relative selector."
// https://drafts.csswg.org/css-nesting-1/#syntax
// NOTE: We already parsed the selectors as a <relative-selector-list>
// Nested relative selectors get a `&` inserted at the beginning.
// This is, handily, how the spec wants them serialized:
// "When serializing a relative selector in a nested style rule, the selector must be absolutized,
// with the implied nesting selector inserted."
// - https://drafts.csswg.org/css-nesting-1/#cssom
SelectorList new_list;
new_list.ensure_capacity(selectors.size());
for (auto const& selector : selectors) {
auto first_combinator = selector->compound_selectors().first().combinator;
if (!first_is_one_of(first_combinator, Selector::Combinator::None, Selector::Combinator::Descendant)
|| !selector->contains_the_nesting_selector()) {
new_list.append(selector->relative_to(Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::Nesting }));
} else if (first_combinator == Selector::Combinator::Descendant) {
// Replace leading descendant combinator (whitespace) with none, because we're not actually relative.
auto copied_compound_selectors = selector->compound_selectors();
copied_compound_selectors.first().combinator = Selector::Combinator::None;
new_list.append(Selector::create(move(copied_compound_selectors)));
} else {
new_list.append(selector);
}
}
selectors = move(new_list);
}
if (nested == Nested::Yes)
selectors = adapt_nested_relative_selector_list(selectors);
auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
if (!declaration) {

View file

@ -606,4 +606,39 @@ Selector::SimpleSelector Selector::SimpleSelector::absolutized(Selector::SimpleS
VERIFY_NOT_REACHED();
}
SelectorList adapt_nested_relative_selector_list(SelectorList const& selectors)
{
// "Nested style rules differ from non-nested rules in the following ways:
// - A nested style rule accepts a <relative-selector-list> as its prelude (rather than just a <selector-list>).
// Any relative selectors are relative to the elements represented by the nesting selector.
// - If a selector in the <relative-selector-list> does not start with a combinator but does contain the nesting
// selector, it is interpreted as a non-relative selector."
// https://drafts.csswg.org/css-nesting-1/#syntax
// NOTE: We already parsed the selectors as a <relative-selector-list>
// Nested relative selectors get a `&` inserted at the beginning.
// This is, handily, how the spec wants them serialized:
// "When serializing a relative selector in a nested style rule, the selector must be absolutized,
// with the implied nesting selector inserted."
// - https://drafts.csswg.org/css-nesting-1/#cssom
CSS::SelectorList new_list;
new_list.ensure_capacity(selectors.size());
for (auto const& selector : selectors) {
auto first_combinator = selector->compound_selectors().first().combinator;
if (!first_is_one_of(first_combinator, CSS::Selector::Combinator::None, CSS::Selector::Combinator::Descendant)
|| !selector->contains_the_nesting_selector()) {
new_list.append(selector->relative_to(CSS::Selector::SimpleSelector { .type = CSS::Selector::SimpleSelector::Type::Nesting }));
} else if (first_combinator == CSS::Selector::Combinator::Descendant) {
// Replace leading descendant combinator (whitespace) with none, because we're not actually relative.
auto copied_compound_selectors = selector->compound_selectors();
copied_compound_selectors.first().combinator = CSS::Selector::Combinator::None;
new_list.append(CSS::Selector::create(move(copied_compound_selectors)));
} else {
new_list.append(selector);
}
}
return new_list;
}
}

View file

@ -267,6 +267,8 @@ private:
String serialize_a_group_of_selectors(SelectorList const& selectors);
SelectorList adapt_nested_relative_selector_list(SelectorList const&);
}
namespace AK {