mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-08 09:09:43 +00:00
LibWeb/CSS: Parse and use nested style rules
For example, this: ```css .foo { color: red; &:hover { color: green; } } ``` now has the same effect as this: ```css .foo { color: red; } .foo:hover { color: green; } ``` CSSStyleRule now has "absolutized selectors", which are its selectors with any `&`s resolved. We use these instead of the "real" selectors when matching them, meaning the style computer doesn't have to know or care about where the selector appears in the CSS document.
This commit is contained in:
parent
74c448d744
commit
53f99e51f8
Notes:
github-actions[bot]
2024-10-17 18:57:13 +00:00
Author: https://github.com/AtkinsSJ
Commit: 53f99e51f8
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1842
Reviewed-by: https://github.com/awesomekling
8 changed files with 345 additions and 10 deletions
|
@ -1374,11 +1374,12 @@ JS::GCPtr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
|
|||
JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualified_rule, Nested nested)
|
||||
{
|
||||
TokenStream prelude_stream { qualified_rule.prelude };
|
||||
auto selectors = parse_a_selector_list(prelude_stream,
|
||||
|
||||
auto maybe_selectors = parse_a_selector_list(prelude_stream,
|
||||
nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone);
|
||||
|
||||
if (selectors.is_error()) {
|
||||
if (selectors.error() == ParseError::SyntaxError) {
|
||||
if (maybe_selectors.is_error()) {
|
||||
if (maybe_selectors.error() == ParseError::SyntaxError) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding.");
|
||||
if constexpr (CSS_PARSER_DEBUG) {
|
||||
prelude_stream.dump_all_tokens();
|
||||
|
@ -1387,11 +1388,46 @@ JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& quali
|
|||
return {};
|
||||
}
|
||||
|
||||
if (selectors.value().is_empty()) {
|
||||
if (maybe_selectors.value().is_empty()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
|
||||
return {};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
|
||||
if (!declaration) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
|
||||
|
@ -1423,7 +1459,7 @@ JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& quali
|
|||
});
|
||||
}
|
||||
auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules));
|
||||
return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration, *nested_rules);
|
||||
return CSSStyleRule::create(m_context.realm(), move(selectors), *declaration, *nested_rules);
|
||||
}
|
||||
|
||||
JS::GCPtr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue