mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-15 23:09:05 +00:00
LibWeb/CSS: Parse nested rules in style blocks
Nested lists of declarations become CSSNestedDeclarations; at-rules are allowed as long as they are CSSGroupingRules.
This commit is contained in:
parent
9c66ab356a
commit
36afff97d1
Notes:
github-actions[bot]
2024-10-17 18:57:33 +00:00
Author: https://github.com/AtkinsSJ
Commit: 36afff97d1
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1842
Reviewed-by: https://github.com/awesomekling
5 changed files with 80 additions and 45 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
[this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
|
||||
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<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);
|
||||
|
||||
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<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());
|
||||
}
|
||||
}
|
||||
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<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);
|
||||
});
|
||||
},
|
||||
[&](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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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&);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue