mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
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:
parent
3e0c182344
commit
a0403ac427
Notes:
github-actions[bot]
2024-11-09 13:30:38 +00:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/LadybirdBrowser/ladybird/commit/a0403ac4277 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2240
9 changed files with 77 additions and 46 deletions
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -267,6 +267,8 @@ private:
|
|||
|
||||
String serialize_a_group_of_selectors(SelectorList const& selectors);
|
||||
|
||||
SelectorList adapt_nested_relative_selector_list(SelectorList const&);
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
|
Loading…
Reference in a new issue