Bläddra i källkod

LibWeb/CSS: Parse nested rules in style blocks

Nested lists of declarations become CSSNestedDeclarations; at-rules are
allowed as long as they are CSSGroupingRules.
Sam Atkins 9 månader sedan
förälder
incheckning
36afff97d1

+ 1 - 1
Userland/Libraries/LibWeb/CSS/CSSRuleList.cpp

@@ -135,6 +135,7 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function<void(We
 
         case CSSRule::Type::LayerBlock:
         case CSSRule::Type::Media:
+        case CSSRule::Type::Style:
         case CSSRule::Type::Supports:
             static_cast<CSSGroupingRule const&>(*rule).for_each_effective_rule(order, callback);
             break;
@@ -145,7 +146,6 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function<void(We
         case CSSRule::Type::LayerStatement:
         case CSSRule::Type::Namespace:
         case CSSRule::Type::NestedDeclarations:
-        case CSSRule::Type::Style:
             break;
         }
 

+ 2 - 2
Userland/Libraries/LibWeb/CSS/Parser/MediaParsing.cpp

@@ -620,7 +620,7 @@ Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID med
     return {};
 }
 
-JS::GCPtr<CSSMediaRule> Parser::convert_to_media_rule(AtRule const& rule)
+JS::GCPtr<CSSMediaRule> 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<CSSMediaRule> Parser::convert_to_media_rule(AtRule const& rule)
 
     JS::MarkedVector<CSSRule*> 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);

+ 66 - 37
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -25,6 +25,7 @@
 #include <LibWeb/CSS/CSSLayerStatementRule.h>
 #include <LibWeb/CSS/CSSMediaRule.h>
 #include <LibWeb/CSS/CSSNamespaceRule.h>
+#include <LibWeb/CSS/CSSNestedDeclarations.h>
 #include <LibWeb/CSS/CSSStyleDeclaration.h>
 #include <LibWeb/CSS/CSSStyleRule.h>
 #include <LibWeb/CSS/CSSStyleSheet.h>
@@ -164,7 +165,7 @@ CSSStyleSheet* Parser::parse_as_css_stylesheet(Optional<URL::URL> location)
     // Interpret all of the resulting top-level qualified rules as style rules, defined below.
     JS::MarkedVector<CSSRule*> 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<T>& 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<CSSRule> Parser::convert_to_rule(Rule const& rule)
+JS::GCPtr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
 {
     return rule.visit(
-        [this](AtRule const& at_rule) -> JS::GCPtr<CSSRule> {
+        [this, nested](AtRule const& at_rule) -> JS::GCPtr<CSSRule> {
             if (has_ignored_vendor_prefix(at_rule.name))
                 return {};
 
@@ -1350,51 +1351,79 @@ JS::GCPtr<CSSRule> 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<CSSRule> {
-            TokenStream prelude_stream { qualified_rule.prelude };
-            auto selectors = parse_a_selector_list(prelude_stream, 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 {};
-            }
+        [this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
+            return convert_to_style_rule(qualified_rule, nested);
+        });
+}
 
-            if (selectors.value().is_empty()) {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
-                return {};
-            }
+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,
+        nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone);
 
-            auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
-            if (!declaration) {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
-                return {};
+    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 {};
+    }
 
-            // TODO: Implement this properly
-            JS::MarkedVector<CSSRule*> 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);
-        });
+    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<CSSRule*> 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<CSSGroupingRule>(*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());
+                    }
+                }
+            },
+            [&](Vector<Declaration> 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<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
@@ -1442,7 +1471,7 @@ JS::GCPtr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
     return CSSImportRule::create(url.value(), const_cast<DOM::Document&>(*m_context.document()));
 }
 
-JS::GCPtr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule)
+JS::GCPtr<CSSRule> 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<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule)
         // Then the rules
         JS::MarkedVector<CSSRule*> 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<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule
     return CSSNamespaceRule::create(m_context.realm(), prefix, namespace_uri);
 }
 
-JS::GCPtr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule)
+JS::GCPtr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule, Nested nested)
 {
     // https://drafts.csswg.org/css-conditional-3/#at-supports
     // @supports <supports-condition> {
@@ -1681,7 +1710,7 @@ JS::GCPtr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule)
 
     JS::MarkedVector<CSSRule*> 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);
     });
 

+ 5 - 4
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<CSSRule> convert_to_rule(Rule const&);
+    JS::GCPtr<CSSRule> convert_to_rule(Rule const&, Nested);
+    JS::GCPtr<CSSStyleRule> convert_to_style_rule(QualifiedRule const&, Nested);
     JS::GCPtr<CSSFontFaceRule> convert_to_font_face_rule(AtRule const&);
     JS::GCPtr<CSSKeyframesRule> convert_to_keyframes_rule(AtRule const&);
     JS::GCPtr<CSSImportRule> convert_to_import_rule(AtRule const&);
-    JS::GCPtr<CSSRule> convert_to_layer_rule(AtRule const&);
-    JS::GCPtr<CSSMediaRule> convert_to_media_rule(AtRule const&);
+    JS::GCPtr<CSSRule> convert_to_layer_rule(AtRule const&, Nested);
+    JS::GCPtr<CSSMediaRule> convert_to_media_rule(AtRule const&, Nested);
     JS::GCPtr<CSSNamespaceRule> convert_to_namespace_rule(AtRule const&);
-    JS::GCPtr<CSSSupportsRule> convert_to_supports_rule(AtRule const&);
+    JS::GCPtr<CSSSupportsRule> convert_to_supports_rule(AtRule const&, Nested);
 
     PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector<Declaration> const&);
     Optional<StyleProperty> convert_to_style_property(Declaration const&);

+ 5 - 0
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)