mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-17 07:50:04 +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::LayerBlock:
|
||||||
case CSSRule::Type::Media:
|
case CSSRule::Type::Media:
|
||||||
|
case CSSRule::Type::Style:
|
||||||
case CSSRule::Type::Supports:
|
case CSSRule::Type::Supports:
|
||||||
static_cast<CSSGroupingRule const&>(*rule).for_each_effective_rule(order, callback);
|
static_cast<CSSGroupingRule const&>(*rule).for_each_effective_rule(order, callback);
|
||||||
break;
|
break;
|
||||||
|
@ -145,7 +146,6 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function<void(We
|
||||||
case CSSRule::Type::LayerStatement:
|
case CSSRule::Type::LayerStatement:
|
||||||
case CSSRule::Type::Namespace:
|
case CSSRule::Type::Namespace:
|
||||||
case CSSRule::Type::NestedDeclarations:
|
case CSSRule::Type::NestedDeclarations:
|
||||||
case CSSRule::Type::Style:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -620,7 +620,7 @@ Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID med
|
||||||
return {};
|
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_tokens = TokenStream { rule.prelude };
|
||||||
auto media_query_list = parse_a_media_query_list(media_query_tokens);
|
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() };
|
JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
|
||||||
rule.for_each_as_rule_list([&](auto& rule) {
|
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);
|
child_rules.append(child_rule);
|
||||||
});
|
});
|
||||||
auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
|
auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <LibWeb/CSS/CSSLayerStatementRule.h>
|
#include <LibWeb/CSS/CSSLayerStatementRule.h>
|
||||||
#include <LibWeb/CSS/CSSMediaRule.h>
|
#include <LibWeb/CSS/CSSMediaRule.h>
|
||||||
#include <LibWeb/CSS/CSSNamespaceRule.h>
|
#include <LibWeb/CSS/CSSNamespaceRule.h>
|
||||||
|
#include <LibWeb/CSS/CSSNestedDeclarations.h>
|
||||||
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
||||||
#include <LibWeb/CSS/CSSStyleRule.h>
|
#include <LibWeb/CSS/CSSStyleRule.h>
|
||||||
#include <LibWeb/CSS/CSSStyleSheet.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.
|
// Interpret all of the resulting top-level qualified rules as style rules, defined below.
|
||||||
JS::MarkedVector<CSSRule*> rules(m_context.realm().heap());
|
JS::MarkedVector<CSSRule*> rules(m_context.realm().heap());
|
||||||
for (auto const& raw_rule : style_sheet.rules) {
|
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.
|
// 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.
|
// Discard that rule.
|
||||||
if (!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()
|
CSSRule* Parser::parse_as_css_rule()
|
||||||
{
|
{
|
||||||
if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value())
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1333,10 +1334,10 @@ bool Parser::is_valid_in_the_current_context(QualifiedRule&)
|
||||||
return true;
|
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(
|
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))
|
if (has_ignored_vendor_prefix(at_rule.name))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
@ -1350,51 +1351,79 @@ JS::GCPtr<CSSRule> Parser::convert_to_rule(Rule const& rule)
|
||||||
return convert_to_keyframes_rule(at_rule);
|
return convert_to_keyframes_rule(at_rule);
|
||||||
|
|
||||||
if (at_rule.name.equals_ignoring_ascii_case("layer"sv))
|
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))
|
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))
|
if (at_rule.name.equals_ignoring_ascii_case("namespace"sv))
|
||||||
return convert_to_namespace_rule(at_rule);
|
return convert_to_namespace_rule(at_rule);
|
||||||
|
|
||||||
if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
|
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!
|
// FIXME: More at rules!
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
|
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
[this](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
|
[this, nested](QualifiedRule const& qualified_rule) -> JS::GCPtr<CSSRule> {
|
||||||
TokenStream prelude_stream { qualified_rule.prelude };
|
return convert_to_style_rule(qualified_rule, nested);
|
||||||
auto selectors = parse_a_selector_list(prelude_stream, SelectorType::Standalone);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (selectors.is_error()) {
|
JS::GCPtr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualified_rule, Nested nested)
|
||||||
if (selectors.error() == ParseError::SyntaxError) {
|
{
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding.");
|
TokenStream prelude_stream { qualified_rule.prelude };
|
||||||
if constexpr (CSS_PARSER_DEBUG) {
|
auto selectors = parse_a_selector_list(prelude_stream,
|
||||||
prelude_stream.dump_all_tokens();
|
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 {};
|
},
|
||||||
}
|
[&](Vector<Declaration> const& declarations) {
|
||||||
|
auto* declaration = convert_to_style_declaration(declarations);
|
||||||
if (selectors.value().is_empty()) {
|
if (!declaration) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding.");
|
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested declarations invalid; discarding.");
|
||||||
return {};
|
return;
|
||||||
}
|
}
|
||||||
|
child_rules.append(CSSNestedDeclarations::create(m_context.realm(), *declaration));
|
||||||
auto* declaration = convert_to_style_declaration(qualified_rule.declarations);
|
});
|
||||||
if (!declaration) {
|
}
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule declaration invalid; discarding.");
|
auto nested_rules = CSSRuleList::create(m_context.realm(), move(child_rules));
|
||||||
return {};
|
return CSSStyleRule::create(m_context.realm(), move(selectors.value()), *declaration, *nested_rules);
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JS::GCPtr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
|
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()));
|
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
|
// https://drafts.csswg.org/css-cascade-5/#at-layer
|
||||||
if (!rule.child_rules_and_lists_of_declarations.is_empty()) {
|
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
|
// Then the rules
|
||||||
JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
|
JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
|
||||||
rule.for_each_as_rule_list([&](auto& rule) {
|
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);
|
child_rules.append(child_rule);
|
||||||
});
|
});
|
||||||
auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
|
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);
|
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
|
// https://drafts.csswg.org/css-conditional-3/#at-supports
|
||||||
// @supports <supports-condition> {
|
// @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() };
|
JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
|
||||||
rule.for_each_as_rule_list([&](auto& rule) {
|
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);
|
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(Declaration&);
|
||||||
bool is_valid_in_the_current_context(AtRule&);
|
bool is_valid_in_the_current_context(AtRule&);
|
||||||
bool is_valid_in_the_current_context(QualifiedRule&);
|
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<CSSFontFaceRule> convert_to_font_face_rule(AtRule const&);
|
||||||
JS::GCPtr<CSSKeyframesRule> convert_to_keyframes_rule(AtRule const&);
|
JS::GCPtr<CSSKeyframesRule> convert_to_keyframes_rule(AtRule const&);
|
||||||
JS::GCPtr<CSSImportRule> convert_to_import_rule(AtRule const&);
|
JS::GCPtr<CSSImportRule> convert_to_import_rule(AtRule const&);
|
||||||
JS::GCPtr<CSSRule> convert_to_layer_rule(AtRule const&);
|
JS::GCPtr<CSSRule> convert_to_layer_rule(AtRule const&, Nested);
|
||||||
JS::GCPtr<CSSMediaRule> convert_to_media_rule(AtRule const&);
|
JS::GCPtr<CSSMediaRule> convert_to_media_rule(AtRule const&, Nested);
|
||||||
JS::GCPtr<CSSNamespaceRule> convert_to_namespace_rule(AtRule const&);
|
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&);
|
PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector<Declaration> const&);
|
||||||
Optional<StyleProperty> convert_to_style_property(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_selector(builder, selector, indent_levels + 1);
|
||||||
}
|
}
|
||||||
dump_declaration(builder, rule.declaration(), 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)
|
void dump_sheet(CSS::StyleSheet const& sheet)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue