LibWeb/CSS: Use ErrorReporter for rule-parsing errors

Unfortunately we already have an InvalidRuleError type defined in the
CSS Parser, so it has to be qualified here, at least for now.
This commit is contained in:
Sam Atkins 2025-07-23 10:34:28 +01:00
commit e072ddfebd
Notes: github-actions[bot] 2025-08-04 09:52:25 +00:00
2 changed files with 192 additions and 66 deletions

View file

@ -14,6 +14,7 @@
#include <LibWeb/CSS/CalculatedOr.h> #include <LibWeb/CSS/CalculatedOr.h>
#include <LibWeb/CSS/MediaList.h> #include <LibWeb/CSS/MediaList.h>
#include <LibWeb/CSS/MediaQuery.h> #include <LibWeb/CSS/MediaQuery.h>
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/Parser/Parser.h> #include <LibWeb/CSS/Parser/Parser.h>
namespace Web::CSS::Parser { namespace Web::CSS::Parser {
@ -566,7 +567,11 @@ GC::Ptr<CSSMediaRule> Parser::convert_to_media_rule(AtRule const& rule, Nested n
// <rule-list> // <rule-list>
// } // }
if (!rule.is_block_rule) { if (!rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @media rule: Expected a block."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@media"_fly_string,
.prelude = MUST(String::join(""sv, rule.prelude)),
.description = "Expected a block."_string,
});
return nullptr; return nullptr;
} }

View file

@ -123,7 +123,7 @@ GC::Ptr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
return convert_to_supports_rule(at_rule, nested); 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); ErrorReporter::the().report(UnknownRuleError { .rule_name = MUST(String::formatted("@{}", at_rule.name)) });
return {}; return {};
}, },
[this, nested](QualifiedRule const& qualified_rule) -> GC::Ptr<CSSRule> { [this, nested](QualifiedRule const& qualified_rule) -> GC::Ptr<CSSRule> {
@ -140,16 +140,21 @@ GC::Ptr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualifi
if (maybe_selectors.is_error()) { if (maybe_selectors.is_error()) {
if (maybe_selectors.error() == ParseError::SyntaxError) { if (maybe_selectors.error() == ParseError::SyntaxError) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: style rule selectors invalid; discarding."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
if constexpr (CSS_PARSER_DEBUG) { .rule_name = "style"_fly_string,
prelude_stream.dump_all_tokens(); .prelude = prelude_stream.dump_string(),
} .description = "Selectors invalid."_string,
});
} }
return {}; return {};
} }
if (maybe_selectors.value().is_empty()) { if (maybe_selectors.value().is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: empty selector; discarding."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "style"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Empty selector."_string,
});
return {}; return {};
} }
@ -194,17 +199,26 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
// //
// <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]? // <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
// <media-query-list>? // <media-query-list>?
TokenStream tokens { rule.prelude };
if (rule.is_block_rule) { if (rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Block is not allowed."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@import"_fly_string,
.prelude = tokens.dump_string(),
.description = "Must be a statement, not a block."_string,
});
return {}; return {};
} }
if (rule.prelude.is_empty()) { if (rule.prelude.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Empty prelude."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@import"_fly_string,
.prelude = tokens.dump_string(),
.description = "Empty prelude."_string,
});
return {}; return {};
} }
TokenStream tokens { rule.prelude };
tokens.discard_whitespace(); tokens.discard_whitespace();
Optional<URL> url = parse_url_function(tokens); Optional<URL> url = parse_url_function(tokens);
@ -212,7 +226,11 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
url = URL { tokens.consume_a_token().token().string().to_string() }; url = URL { tokens.consume_a_token().token().string().to_string() };
if (!url.has_value()) { if (!url.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @import rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string()); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@import"_fly_string,
.prelude = tokens.dump_string(),
.description = MUST(String::formatted("Unable to parse `{}` as URL.", tokens.next_token().to_debug_string())),
});
return {}; return {};
} }
@ -238,10 +256,11 @@ GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule)
auto media_query_list = parse_a_media_query_list(tokens); auto media_query_list = parse_a_media_query_list(tokens);
if (tokens.has_next_token()) { if (tokens.has_next_token()) {
if constexpr (CSS_PARSER_DEBUG) { ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
dbgln("Failed to parse @import rule:"); .rule_name = "@import"_fly_string,
tokens.dump_all_tokens(); .prelude = tokens.dump_string(),
} .description = "Trailing tokens in prelude."_string,
});
return {}; return {};
} }
@ -303,13 +322,21 @@ GC::Ptr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule, Nested nested
if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) { if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) {
layer_name = maybe_name.release_value(); layer_name = maybe_name.release_value();
} else { } else {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (not a valid layer name) prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@layer"_fly_string,
.prelude = prelude_tokens.dump_string(),
.description = "Not a valid layer name."_string,
});
return {}; return {};
} }
prelude_tokens.discard_whitespace(); prelude_tokens.discard_whitespace();
if (prelude_tokens.has_next_token()) { if (prelude_tokens.has_next_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer has invalid prelude, (tokens after layer name) prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@layer"_fly_string,
.prelude = prelude_tokens.dump_string(),
.description = "Trailing tokens after name in prelude."_string,
});
return {}; return {};
} }
@ -331,30 +358,42 @@ GC::Ptr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule, Nested nested
// CSSLayerStatementRule // CSSLayerStatementRule
// @layer <layer-name>#; // @layer <layer-name>#;
auto tokens = TokenStream { rule.prelude }; auto prelude_tokens = TokenStream { rule.prelude };
tokens.discard_whitespace(); prelude_tokens.discard_whitespace();
Vector<FlyString> layer_names; Vector<FlyString> layer_names;
while (tokens.has_next_token()) { while (prelude_tokens.has_next_token()) {
// Comma // Comma
if (!layer_names.is_empty()) { if (!layer_names.is_empty()) {
if (auto comma = tokens.consume_a_token(); !comma.is(Token::Type::Comma)) { if (auto comma = prelude_tokens.consume_a_token(); !comma.is(Token::Type::Comma)) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer missing separating comma, ({}) prelude = {}; discarding.", comma.to_debug_string(), rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@layer"_fly_string,
.prelude = prelude_tokens.dump_string(),
.description = "Missing comma between layer names."_string,
});
return {}; return {};
} }
tokens.discard_whitespace(); prelude_tokens.discard_whitespace();
} }
if (auto name = parse_layer_name(tokens, AllowBlankLayerName::No); name.has_value()) { if (auto name = parse_layer_name(prelude_tokens, AllowBlankLayerName::No); name.has_value()) {
layer_names.append(name.release_value()); layer_names.append(name.release_value());
} else { } else {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer contains invalid name, prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@layer"_fly_string,
.prelude = prelude_tokens.dump_string(),
.description = "Contains invalid layer name."_string,
});
return {}; return {};
} }
tokens.discard_whitespace(); prelude_tokens.discard_whitespace();
} }
if (layer_names.is_empty()) { if (layer_names.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @layer statement has no layer names, prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@layer"_fly_string,
.prelude = prelude_tokens.dump_string(),
.description = "No layer names provided."_string,
});
return {}; return {};
} }
@ -368,21 +407,33 @@ GC::Ptr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
// <keyframes-name> = <custom-ident> | <string> // <keyframes-name> = <custom-ident> | <string>
// <keyframe-block> = <keyframe-selector># { <declaration-list> } // <keyframe-block> = <keyframe-selector># { <declaration-list> }
// <keyframe-selector> = from | to | <percentage [0,100]> // <keyframe-selector> = from | to | <percentage [0,100]>
auto prelude_stream = TokenStream { rule.prelude };
if (!rule.is_block_rule) { if (!rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: Expected a block."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@keyframes"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Must be a block, not a statement."_string,
});
return nullptr; return nullptr;
} }
if (rule.prelude.is_empty()) { if (rule.prelude.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @keyframes rule: Empty prelude."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@keyframes"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Empty prelude."_string,
});
return {}; return {};
} }
auto prelude_stream = TokenStream { rule.prelude };
prelude_stream.discard_whitespace(); prelude_stream.discard_whitespace();
auto& token = prelude_stream.consume_a_token(); auto& token = prelude_stream.consume_a_token();
if (!token.is_token()) { if (!token.is_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@keyframes"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Name must be a <string> or <ident>."_string,
});
return {}; return {};
} }
@ -390,17 +441,29 @@ GC::Ptr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
prelude_stream.discard_whitespace(); prelude_stream.discard_whitespace();
if (prelude_stream.has_next_token()) { if (prelude_stream.has_next_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes has invalid prelude, prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@keyframes"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Trailing tokens after name in prelude."_string,
});
return {}; return {};
} }
if (name_token.is(Token::Type::Ident) && (is_css_wide_keyword(name_token.ident()) || name_token.ident().is_one_of_ignoring_ascii_case("none"sv, "default"sv))) { if (name_token.is(Token::Type::Ident) && (is_css_wide_keyword(name_token.ident()) || name_token.ident().is_one_of_ignoring_ascii_case("none"sv, "default"sv))) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.ident()); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@keyframes"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Invalid name."_string,
});
return {}; return {};
} }
if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) { if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule name is invalid: {}; discarding.", name_token.to_debug_string()); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@keyframes"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Name must be a <string> or <ident>."_string,
});
return {}; return {};
} }
@ -433,7 +496,11 @@ GC::Ptr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule)
break; break;
auto tok = child_tokens.consume_a_token(); auto tok = child_tokens.consume_a_token();
if (!tok.is_token()) { if (!tok.is_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @keyframes rule has invalid selector: {}; discarding.", tok.to_debug_string()); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "keyframe"_fly_string,
.prelude = child_tokens.dump_string(),
.description = "Invalid selector."_string,
});
child_tokens.reconsume_current_input_token(); child_tokens.reconsume_current_input_token();
break; break;
} }
@ -482,17 +549,25 @@ GC::Ptr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
// https://drafts.csswg.org/css-namespaces/#syntax // https://drafts.csswg.org/css-namespaces/#syntax
// @namespace <namespace-prefix>? [ <string> | <url> ] ; // @namespace <namespace-prefix>? [ <string> | <url> ] ;
// <namespace-prefix> = <ident> // <namespace-prefix> = <ident>
auto tokens = TokenStream { rule.prelude };
if (rule.is_block_rule) { if (rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Block is not allowed."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@namespace"_fly_string,
.prelude = tokens.dump_string(),
.description = "Must be a statement, not a block."_string,
});
return {}; return {};
} }
if (rule.prelude.is_empty()) { if (rule.prelude.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Empty prelude."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@namespace"_fly_string,
.prelude = tokens.dump_string(),
.description = "Empty prelude."_string,
});
return {}; return {};
} }
auto tokens = TokenStream { rule.prelude };
tokens.discard_whitespace(); tokens.discard_whitespace();
Optional<FlyString> prefix = {}; Optional<FlyString> prefix = {};
@ -510,16 +585,21 @@ GC::Ptr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule)
} else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) { } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) {
namespace_uri = url_token.token().string(); namespace_uri = url_token.token().string();
} else { } else {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @namespace rule: Unable to parse `{}` as URL.", tokens.next_token().to_debug_string()); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@namespace"_fly_string,
.prelude = tokens.dump_string(),
.description = "Unable to parse <url>."_string,
});
return {}; return {};
} }
tokens.discard_whitespace(); tokens.discard_whitespace();
if (tokens.has_next_token()) { if (tokens.has_next_token()) {
if constexpr (CSS_PARSER_DEBUG) { ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
dbgln("Failed to parse @namespace rule: Trailing tokens after URL."); .rule_name = "@namespace"_fly_string,
tokens.dump_all_tokens(); .prelude = tokens.dump_string(),
} .description = "Trailing tokens after <url> in prelude."_string,
});
return {}; return {};
} }
@ -532,23 +612,32 @@ GC::Ptr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule, Ne
// @supports <supports-condition> { // @supports <supports-condition> {
// <rule-list> // <rule-list>
// } // }
auto supports_tokens = TokenStream { rule.prelude };
if (!rule.is_block_rule) { if (!rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: Expected a block."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@supports"_fly_string,
.prelude = supports_tokens.dump_string(),
.description = "Must be a block, not a statement."_string,
});
return {}; return {};
} }
if (rule.prelude.is_empty()) { if (rule.prelude.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @supports rule: Empty prelude."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@supports"_fly_string,
.prelude = supports_tokens.dump_string(),
.description = "Empty prelude."_string,
});
return {}; return {};
} }
auto supports_tokens = TokenStream { rule.prelude };
auto supports = parse_a_supports(supports_tokens); auto supports = parse_a_supports(supports_tokens);
if (!supports) { if (!supports) {
if constexpr (CSS_PARSER_DEBUG) { ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
dbgln("Failed to parse @supports rule: supports clause invalid."); .rule_name = "@supports"_fly_string,
supports_tokens.dump_all_tokens(); .prelude = supports_tokens.dump_string(),
} .description = "Supports clause invalid."_string,
});
return {}; return {};
} }
@ -574,21 +663,33 @@ GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
// @property <custom-property-name> { // @property <custom-property-name> {
// <declaration-list> // <declaration-list>
// } // }
auto prelude_stream = TokenStream { rule.prelude };
if (!rule.is_block_rule) { if (!rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @property rule: Expected a block."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@property"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Must be a block, not a statement."_string,
});
return {}; return {};
} }
if (rule.prelude.is_empty()) { if (rule.prelude.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @property rule: Empty prelude."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@property"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Empty prelude."_string,
});
return {}; return {};
} }
auto prelude_stream = TokenStream { rule.prelude };
prelude_stream.discard_whitespace(); prelude_stream.discard_whitespace();
auto const& token = prelude_stream.consume_a_token(); auto const& token = prelude_stream.consume_a_token();
if (!token.is_token()) { if (!token.is_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@property"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Name must be an ident."_string,
});
return {}; return {};
} }
@ -596,17 +697,20 @@ GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
prelude_stream.discard_whitespace(); prelude_stream.discard_whitespace();
if (prelude_stream.has_next_token()) { if (prelude_stream.has_next_token()) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@property"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Trailing tokens after name in prelude."_string,
});
return {}; return {};
} }
if (!name_token.is(Token::Type::Ident)) { if (!name_token.is(Token::Type::Ident) || !is_a_custom_property_name_string(name_token.ident())) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name is invalid: {}; discarding.", name_token.to_debug_string()); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
return {}; .rule_name = "@property"_fly_string,
} .prelude = prelude_stream.dump_string(),
.description = "Name must be an ident starting with '--'."_string,
if (!is_a_custom_property_name_string(name_token.ident())) { });
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name doesn't start with '--': {}; discarding.", name_token.ident());
return {}; return {};
} }
@ -654,10 +758,16 @@ GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
GC::Ptr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule) GC::Ptr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
{ {
// https://drafts.csswg.org/css-fonts/#font-face-rule // https://drafts.csswg.org/css-fonts/#font-face-rule
TokenStream prelude_stream { rule.prelude };
if (!rule.is_block_rule) { if (!rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @font-face rule: Expected a block."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@font-face"_fly_string,
.prelude = prelude_stream.dump_string(),
.description = "Must be a block, not a statement."_string,
});
return nullptr; return nullptr;
} }
// FIXME: Prelude must be empty
DescriptorList descriptors { AtRuleID::FontFace }; DescriptorList descriptors { AtRuleID::FontFace };
rule.for_each_as_declaration_list([&](auto& declaration) { rule.for_each_as_declaration_list([&](auto& declaration) {
@ -673,12 +783,16 @@ GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& page_rule)
{ {
// https://drafts.csswg.org/css-page-3/#syntax-page-selector // https://drafts.csswg.org/css-page-3/#syntax-page-selector
// @page = @page <page-selector-list>? { <declaration-rule-list> } // @page = @page <page-selector-list>? { <declaration-rule-list> }
TokenStream tokens { page_rule.prelude };
if (!page_rule.is_block_rule) { if (!page_rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @property rule: Expected a block."); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = "@page"_fly_string,
.prelude = tokens.dump_string(),
.description = "Must be a block, not a statement."_string,
});
return nullptr; return nullptr;
} }
TokenStream tokens { page_rule.prelude };
auto page_selectors = parse_a_page_selector_list(tokens); auto page_selectors = parse_a_page_selector_list(tokens);
if (page_selectors.is_error()) if (page_selectors.is_error())
return nullptr; return nullptr;
@ -710,11 +824,18 @@ GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& page_rule)
GC::Ptr<CSSMarginRule> Parser::convert_to_margin_rule(AtRule const& rule) GC::Ptr<CSSMarginRule> Parser::convert_to_margin_rule(AtRule const& rule)
{ {
TokenStream prelude_stream { rule.prelude };
if (!rule.is_block_rule) { if (!rule.is_block_rule) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse margin rule @{}: Expected a block.", rule.name); ErrorReporter::the().report(CSS::Parser::InvalidRuleError {
.rule_name = MUST(String::formatted("@{}", rule.name)),
.prelude = prelude_stream.dump_string(),
.description = "Must be a block, not a statement."_string,
});
return nullptr; return nullptr;
} }
// FIXME: Reject if there's a prelude
// https://drafts.csswg.org/css-page-3/#syntax-page-selector // https://drafts.csswg.org/css-page-3/#syntax-page-selector
// There are lots of these, but they're all in the format: // There are lots of these, but they're all in the format:
// @foo = @foo { <declaration-list> }; // @foo = @foo { <declaration-list> };