diff --git a/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp b/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp index dc3a92d6e00..4f21ce1d6a6 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp +++ b/Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp @@ -135,6 +135,7 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function(*rule).for_each_effective_rule(order, callback); break; @@ -145,7 +146,6 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function Parser::parse_media_feature_value(MediaFeatureID med return {}; } -JS::GCPtr Parser::convert_to_media_rule(AtRule const& rule) +JS::GCPtr Parser::convert_to_media_rule(AtRule const& rule, Nested nested) { auto media_query_tokens = TokenStream { rule.prelude }; auto media_query_list = parse_a_media_query_list(media_query_tokens); @@ -628,7 +628,7 @@ JS::GCPtr Parser::convert_to_media_rule(AtRule const& rule) JS::MarkedVector child_rules { m_context.realm().heap() }; rule.for_each_as_rule_list([&](auto& rule) { - if (auto child_rule = convert_to_rule(rule)) + if (auto child_rule = convert_to_rule(rule, nested)) child_rules.append(child_rule); }); auto rule_list = CSSRuleList::create(m_context.realm(), child_rules); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 755ecd036b9..9c192e408d4 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -164,7 +165,7 @@ CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional location) // Interpret all of the resulting top-level qualified rules as style rules, defined below. JS::MarkedVector rules(m_context.realm().heap()); for (auto const& raw_rule : style_sheet.rules) { - auto rule = convert_to_rule(raw_rule); + auto rule = convert_to_rule(raw_rule, Nested::No); // If any style rule is invalid, or any at-rule is not recognized or is invalid according to its grammar or context, it’s a parse error. // Discard that rule. if (!rule) { @@ -1019,7 +1020,7 @@ void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream& input, Ne CSSRule* Parser::parse_as_css_rule() { if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value()) - return convert_to_rule(maybe_rule.value()); + return convert_to_rule(maybe_rule.value(), Nested::No); return {}; } @@ -1333,10 +1334,10 @@ bool Parser::is_valid_in_the_current_context(QualifiedRule&) return true; } -JS::GCPtr Parser::convert_to_rule(Rule const& rule) +JS::GCPtr Parser::convert_to_rule(Rule const& rule, Nested nested) { return rule.visit( - [this](AtRule const& at_rule) -> JS::GCPtr { + [this, nested](AtRule const& at_rule) -> JS::GCPtr { if (has_ignored_vendor_prefix(at_rule.name)) return {}; @@ -1350,51 +1351,79 @@ JS::GCPtr Parser::convert_to_rule(Rule const& rule) return convert_to_keyframes_rule(at_rule); if (at_rule.name.equals_ignoring_ascii_case("layer"sv)) - return convert_to_layer_rule(at_rule); + return convert_to_layer_rule(at_rule, nested); if (at_rule.name.equals_ignoring_ascii_case("media"sv)) - return convert_to_media_rule(at_rule); + return convert_to_media_rule(at_rule, nested); if (at_rule.name.equals_ignoring_ascii_case("namespace"sv)) return convert_to_namespace_rule(at_rule); if (at_rule.name.equals_ignoring_ascii_case("supports"sv)) - return convert_to_supports_rule(at_rule); + return convert_to_supports_rule(at_rule, nested); // FIXME: More at rules! dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name); return {}; }, - [this](QualifiedRule const& qualified_rule) -> JS::GCPtr { - TokenStream prelude_stream { qualified_rule.prelude }; - auto selectors = parse_a_selector_list(prelude_stream, SelectorType::Standalone); + [this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr { + return convert_to_style_rule(qualified_rule, nested); + }); +} - if (selectors.is_error()) { - if (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(); +JS::GCPtr 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, + nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone); + + if (selectors.is_error()) { + if (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(); + } + } + return {}; + } + + if (selectors.value().is_empty()) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding."); + return {}; + } + + auto* declaration = convert_to_style_declaration(qualified_rule.declarations); + if (!declaration) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding."); + return {}; + } + + JS::MarkedVector child_rules { m_context.realm().heap() }; + for (auto& child : qualified_rule.child_rules) { + child.visit( + [&](Rule const& rule) { + // "In addition to nested style rules, this specification allows nested group rules inside of style rules: + // any at-rule whose body contains style rules can be nested inside of a style rule as well." + // https://drafts.csswg.org/css-nesting-1/#nested-group-rules + if (auto converted_rule = convert_to_rule(rule, Nested::Yes)) { + if (is(*converted_rule)) { + child_rules.append(converted_rule); + } else { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside style rule; discarding.", converted_rule->class_name()); } } - return {}; - } - - if (selectors.value().is_empty()) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding."); - return {}; - } - - auto* declaration = convert_to_style_declaration(qualified_rule.declarations); - if (!declaration) { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding."); - return {}; - } - - // TODO: Implement this properly - JS::MarkedVector child_rules { m_context.realm().heap() }; - auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules)); - return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration, *nested_rules); - }); + }, + [&](Vector const& declarations) { + auto* declaration = convert_to_style_declaration(declarations); + if (!declaration) { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding."); + return; + } + child_rules.append(CSSNestedDeclarations::create(m_context.realm(), *declaration)); + }); + } + auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules)); + return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration, *nested_rules); } JS::GCPtr Parser::convert_to_import_rule(AtRule const& rule) @@ -1442,7 +1471,7 @@ JS::GCPtr Parser::convert_to_import_rule(AtRule const& rule) return CSSImportRule::create(url.value(), const_cast(*m_context.document())); } -JS::GCPtr Parser::convert_to_layer_rule(AtRule const& rule) +JS::GCPtr Parser::convert_to_layer_rule(AtRule const& rule, Nested nested) { // https://drafts.csswg.org/css-cascade-5/#at-layer if (!rule.child_rules_and_lists_of_declarations.is_empty()) { @@ -1470,7 +1499,7 @@ JS::GCPtr Parser::convert_to_layer_rule(AtRule const& rule) // Then the rules JS::MarkedVector child_rules { m_context.realm().heap() }; rule.for_each_as_rule_list([&](auto& rule) { - if (auto child_rule = convert_to_rule(rule)) + if (auto child_rule = convert_to_rule(rule, nested)) child_rules.append(child_rule); }); auto rule_list = CSSRuleList::create(m_context.realm(), child_rules); @@ -1657,7 +1686,7 @@ JS::GCPtr Parser::convert_to_namespace_rule(AtRule const& rule return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri); } -JS::GCPtr Parser::convert_to_supports_rule(AtRule const& rule) +JS::GCPtr Parser::convert_to_supports_rule(AtRule const& rule, Nested nested) { // https://drafts.csswg.org/css-conditional-3/#at-supports // @supports { @@ -1681,7 +1710,7 @@ JS::GCPtr Parser::convert_to_supports_rule(AtRule const& rule) JS::MarkedVector child_rules { m_context.realm().heap() }; rule.for_each_as_rule_list([&](auto& rule) { - if (auto child_rule = convert_to_rule(rule)) + if (auto child_rule = convert_to_rule(rule, nested)) child_rules.append(child_rule); }); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index df7b98cbe9e..d5df355d5c4 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -178,14 +178,15 @@ private: bool is_valid_in_the_current_context(Declaration&); bool is_valid_in_the_current_context(AtRule&); bool is_valid_in_the_current_context(QualifiedRule&); - JS::GCPtr convert_to_rule(Rule const&); + JS::GCPtr convert_to_rule(Rule const&, Nested); + JS::GCPtr convert_to_style_rule(QualifiedRule const&, Nested); JS::GCPtr convert_to_font_face_rule(AtRule const&); JS::GCPtr convert_to_keyframes_rule(AtRule const&); JS::GCPtr convert_to_import_rule(AtRule const&); - JS::GCPtr convert_to_layer_rule(AtRule const&); - JS::GCPtr convert_to_media_rule(AtRule const&); + JS::GCPtr convert_to_layer_rule(AtRule const&, Nested); + JS::GCPtr convert_to_media_rule(AtRule const&, Nested); JS::GCPtr convert_to_namespace_rule(AtRule const&); - JS::GCPtr convert_to_supports_rule(AtRule const&); + JS::GCPtr convert_to_supports_rule(AtRule const&, Nested); PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector const&); Optional convert_to_style_property(Declaration const&); diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 05783471271..176ad808910 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -810,6 +810,11 @@ void dump_style_rule(StringBuilder& builder, CSS::CSSStyleRule const& rule, int dump_selector(builder, selector, indent_levels + 1); } dump_declaration(builder, rule.declaration(), indent_levels + 1); + + indent(builder, indent_levels); + builder.appendff(" Child rules ({}):\n", rule.css_rules().length()); + for (auto& child_rule : rule.css_rules()) + dump_rule(builder, child_rule, indent_levels + 2); } void dump_sheet(CSS::StyleSheet const& sheet)