LibWeb/CSS: Use ErrorReporter for "rule X not allowed in Y" errors

QualifiedRule::for_each_as_declaration_list() now takes a rule_name, so
that the error message can actually be useful - we only know what a
qualified rule is by context.
This commit is contained in:
Sam Atkins 2025-07-23 10:27:53 +01:00
parent d6cfd56ae6
commit 0bd83dd996
3 changed files with 80 additions and 17 deletions

View File

@ -28,6 +28,7 @@
#include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/CSSSupportsRule.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
@ -169,7 +170,10 @@ GC::Ptr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualifi
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());
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = "style"_fly_string,
.inner_rule_name = MUST(FlyString::from_utf8(converted_rule->class_name())),
});
}
}
},
@ -405,7 +409,20 @@ GC::Ptr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
GC::RootVector<GC::Ref<CSSRule>> keyframes(realm().heap());
rule.for_each_as_qualified_rule_list([&](auto& qualified_rule) {
if (!qualified_rule.child_rules.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes keyframe rule contains at-rules; discarding them.");
for (auto const& child_rule : qualified_rule.child_rules) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = "@keyframes"_fly_string,
.inner_rule_name = child_rule.visit(
[](Rule const& rule) {
return rule.visit(
[](AtRule const& at_rule) { return MUST(String::formatted("@{}", at_rule.name)); },
[](QualifiedRule const&) { return "qualified-rule"_string; });
},
[](auto&) {
return "list-of-declarations"_string;
}),
});
}
}
auto selectors = Vector<CSS::Percentage> {};
@ -447,7 +464,7 @@ GC::Ptr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
}
PropertiesAndCustomProperties properties;
qualified_rule.for_each_as_declaration_list([&](auto const& declaration) {
qualified_rule.for_each_as_declaration_list("keyframe"_fly_string, [&](auto const& declaration) {
extract_property(declaration, properties);
});
auto style = CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties));
@ -674,7 +691,10 @@ GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& page_rule)
if (is<CSSMarginRule>(*converted_rule)) {
child_rules.append(*converted_rule);
} else {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside @page rule; discarding.", converted_rule->class_name());
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = "@page"_fly_string,
.inner_rule_name = MUST(FlyString::from_utf8(converted_rule->class_name())),
});
}
}
},

View File

@ -1,12 +1,12 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/Parser/Types.h>
#include <LibWeb/CSS/Serialize.h>
@ -120,8 +120,18 @@ void AtRule::for_each_as_declaration_list(DeclarationVisitor&& visit) const
{
// <declaration-list>: only declarations are allowed; at-rules and qualified rules are automatically invalid.
for_each(
[](auto const& at_rule) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal @{} rule in `<declaration-list>`; discarding.", at_rule.name); },
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<declaration-list>`; discarding."); },
[this](auto const& at_rule) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = MUST(String::formatted("@{}", at_rule.name)),
});
},
[this](auto const&) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = "qualified-rule"_fly_string,
});
},
move(visit));
}
@ -130,9 +140,19 @@ void AtRule::for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const
{
// <qualified-rule-list>: only qualified rules are allowed; declarations and at-rules are automatically invalid.
for_each(
[](auto const& at_rule) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal @{} rule in `<qualified-rule-list>`; discarding.", at_rule.name); },
[this](auto const& at_rule) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = MUST(String::formatted("@{}", at_rule.name)),
});
},
move(visit),
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in `<qualified-rule-list>`; discarding."); });
[this](auto const&) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = "list-of-declarations"_fly_string,
});
});
}
// https://drafts.csswg.org/css-syntax/#typedef-at-rule-list
@ -141,8 +161,18 @@ void AtRule::for_each_as_at_rule_list(AtRuleVisitor&& visit) const
// <at-rule-list>: only at-rules are allowed; declarations and qualified rules are automatically invalid.
for_each(
move(visit),
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<at-rule-list>`; discarding."); },
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in `<at-rule-list>`; discarding."); });
[this](auto const&) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = "qualified-rule"_fly_string,
});
},
[this](auto const&) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = "list-of-declarations"_fly_string,
});
});
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-rule-list
@ -151,7 +181,12 @@ void AtRule::for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, De
// <declaration-rule-list>: declarations and at-rules are allowed; qualified rules are automatically invalid.
for_each(
move(visit_at_rule),
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<declaration-rule-list>`; discarding."); },
[this](auto const&) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = "qualified-rule"_fly_string,
});
},
move(visit_declaration));
}
@ -162,12 +197,17 @@ void AtRule::for_each_as_rule_list(RuleVisitor&& visit) const
for (auto const& child : child_rules_and_lists_of_declarations) {
child.visit(
[&](Rule const& rule) { visit(rule); },
[&](Vector<Declaration> const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in `<rule-list>`; discarding."); });
[&](Vector<Declaration> const&) {
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = MUST(String::formatted("@{}", name)),
.inner_rule_name = "list-of-declarations"_fly_string,
});
});
}
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-list
void QualifiedRule::for_each_as_declaration_list(DeclarationVisitor&& visit) const
void QualifiedRule::for_each_as_declaration_list(FlyString const& rule_name, DeclarationVisitor&& visit) const
{
// <declaration-list>: only declarations are allowed; at-rules and qualified rules are automatically invalid.
for (auto const& declaration : declarations)
@ -176,7 +216,10 @@ void QualifiedRule::for_each_as_declaration_list(DeclarationVisitor&& visit) con
for (auto const& child : child_rules) {
child.visit(
[&](Rule const&) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<declaration-list>`; discarding.");
ErrorReporter::the().report(InvalidRuleLocationError {
.outer_rule_name = rule_name,
.inner_rule_name = "qualified-rule"_fly_string,
});
},
[&](Vector<Declaration> const& declarations) {
for (auto const& declaration : declarations)

View File

@ -47,7 +47,7 @@ struct QualifiedRule {
Vector<Declaration> declarations;
Vector<RuleOrListOfDeclarations> child_rules;
void for_each_as_declaration_list(DeclarationVisitor&& visit) const;
void for_each_as_declaration_list(FlyString const& rule_name, DeclarationVisitor&& visit) const;
};
// https://drafts.csswg.org/css-syntax/#declaration