mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-28 21:26:22 +00:00
LibWeb/CSS: Use ErrorReporter for selector parsing errors
Also removed the error reporting from parse_a_n_plus_b_pattern() as its caller already reports that error.
This commit is contained in:
parent
2a2a1986cc
commit
d6cfd56ae6
Notes:
github-actions[bot]
2025-08-04 09:52:38 +00:00
Author: https://github.com/AtkinsSJ
Commit: d6cfd56ae6
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5576
2 changed files with 186 additions and 56 deletions
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
||||||
* Copyright (c) 2020-2021, the SerenityOS developers.
|
* 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>
|
||||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
||||||
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/Debug.h>
|
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
||||||
#include <LibWeb/CSS/Parser/Parser.h>
|
#include <LibWeb/CSS/Parser/Parser.h>
|
||||||
#include <LibWeb/Infra/Strings.h>
|
#include <LibWeb/Infra/Strings.h>
|
||||||
|
|
||||||
|
@ -290,13 +290,19 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
|
||||||
attribute_tokens.discard_whitespace();
|
attribute_tokens.discard_whitespace();
|
||||||
|
|
||||||
if (!attribute_tokens.has_next_token()) {
|
if (!attribute_tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "CSS attribute selector is empty!");
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = "Attribute selector is empty."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto maybe_qualified_name = parse_selector_qualified_name(attribute_tokens, AllowWildcardName::No);
|
auto maybe_qualified_name = parse_selector_qualified_name(attribute_tokens, AllowWildcardName::No);
|
||||||
if (!maybe_qualified_name.has_value()) {
|
if (!maybe_qualified_name.has_value()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected qualified-name for attribute name, got: '{}'", attribute_tokens.next_token().to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = MUST(String::formatted("Expected qualified-name, got: '{}'.", attribute_tokens.next_token().to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
auto qualified_name = maybe_qualified_name.release_value();
|
auto qualified_name = maybe_qualified_name.release_value();
|
||||||
|
@ -316,7 +322,10 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
|
||||||
|
|
||||||
auto const& delim_part = attribute_tokens.consume_a_token();
|
auto const& delim_part = attribute_tokens.consume_a_token();
|
||||||
if (!delim_part.is(Token::Type::Delim)) {
|
if (!delim_part.is(Token::Type::Delim)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected a delim for attribute comparison, got: '{}'", delim_part.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = MUST(String::formatted("Expected delim for attribute comparison, got: '{}'.", delim_part.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,13 +333,19 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
|
||||||
simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
|
simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
|
||||||
} else {
|
} else {
|
||||||
if (!attribute_tokens.has_next_token()) {
|
if (!attribute_tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Attribute selector ended part way through a match type.");
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = "Attribute selector ended part way through a match type."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const& delim_second_part = attribute_tokens.consume_a_token();
|
auto const& delim_second_part = attribute_tokens.consume_a_token();
|
||||||
if (!delim_second_part.is_delim('=')) {
|
if (!delim_second_part.is_delim('=')) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected a double delim for attribute comparison, got: '{}{}'", delim_part.to_debug_string(), delim_second_part.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = MUST(String::formatted("Expected a double delim for attribute comparison, got: '{}{}'.", delim_part.to_debug_string(), delim_second_part.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
switch (delim_part.token().delim()) {
|
switch (delim_part.token().delim()) {
|
||||||
|
@ -356,13 +371,19 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
|
||||||
|
|
||||||
attribute_tokens.discard_whitespace();
|
attribute_tokens.discard_whitespace();
|
||||||
if (!attribute_tokens.has_next_token()) {
|
if (!attribute_tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Attribute selector ended without a value to match.");
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = "Attribute selector ended without a value to match."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const& value_part = attribute_tokens.consume_a_token();
|
auto const& value_part = attribute_tokens.consume_a_token();
|
||||||
if (!value_part.is(Token::Type::Ident) && !value_part.is(Token::Type::String)) {
|
if (!value_part.is(Token::Type::Ident) && !value_part.is(Token::Type::String)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected a string or ident for the value to match attribute against, got: '{}'", value_part.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = MUST(String::formatted("Expected a string or ident for the value to match attribute against, got: '{}'.", value_part.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
auto const& value_string = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
|
auto const& value_string = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
|
||||||
|
@ -379,11 +400,17 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
|
||||||
} else if (case_sensitivity.equals_ignoring_ascii_case("s"sv)) {
|
} else if (case_sensitivity.equals_ignoring_ascii_case("s"sv)) {
|
||||||
simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch;
|
simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch;
|
||||||
} else {
|
} else {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected a \"i\" or \"s\" attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = MUST(String::formatted("Expected a \"i\" or \"s\" attribute selector case sensitivity identifier, got: '{}'.", case_sensitivity_part.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected an attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = MUST(String::formatted("Expected an attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,7 +418,10 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
|
||||||
attribute_tokens.discard_whitespace();
|
attribute_tokens.discard_whitespace();
|
||||||
|
|
||||||
if (attribute_tokens.has_next_token()) {
|
if (attribute_tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Was not expecting anything else inside attribute selector.");
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = first_value.to_string(),
|
||||||
|
.description = "Trailing tokens in attribute selector."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +459,10 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
pseudo_name = name_token.function().name;
|
pseudo_name = name_token.function().name;
|
||||||
is_function = true;
|
is_function = true;
|
||||||
} else {
|
} else {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected an ident or function token for pseudo-element, got: '{}'", name_token.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = MUST(String::formatted("Pseudo-element should be an ident or function, got: '{}'", name_token.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +484,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
Selector::PseudoElementSelector::Value value = Empty {};
|
Selector::PseudoElementSelector::Value value = Empty {};
|
||||||
if (is_function) {
|
if (is_function) {
|
||||||
if (!metadata.is_valid_as_function) {
|
if (!metadata.is_valid_as_function) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-element '::{}()' is not valid as a function.", pseudo_name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = "Not valid as a function."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,14 +499,22 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
switch (metadata.parameter_type) {
|
switch (metadata.parameter_type) {
|
||||||
case PseudoElementMetadata::ParameterType::None:
|
case PseudoElementMetadata::ParameterType::None:
|
||||||
if (function_tokens.has_next_token()) {
|
if (function_tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-element '::{}()' invalid: Should have no arguments.", pseudo_name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = "Should have no arguments."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PseudoElementMetadata::ParameterType::CompoundSelector: {
|
case PseudoElementMetadata::ParameterType::CompoundSelector: {
|
||||||
auto compound_selector_or_error = parse_compound_selector(function_tokens);
|
auto compound_selector_or_error = parse_compound_selector(function_tokens);
|
||||||
if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
|
if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse ::{}() parameter as a compound selector", pseudo_name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = "Failed to parse argument as a compound selector."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,12 +534,20 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
} else if (auto custom_ident = parse_custom_ident(function_tokens, {}); custom_ident.has_value()) {
|
} else if (auto custom_ident = parse_custom_ident(function_tokens, {}); custom_ident.has_value()) {
|
||||||
value = Selector::PseudoElementSelector::PTNameSelector { .value = custom_ident.release_value() };
|
value = Selector::PseudoElementSelector::PTNameSelector { .value = custom_ident.release_value() };
|
||||||
} else {
|
} else {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid <pt-name-selector> in ::{}() - expected `*` or `<custom-ident>`, got `{}`", pseudo_name, function_tokens.next_token().to_debug_string());
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = MUST(String::formatted("Invalid <pt-name-selector> - expected `*` or `<custom-ident>`, got `{}`", function_tokens.next_token().to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
function_tokens.discard_whitespace();
|
function_tokens.discard_whitespace();
|
||||||
if (function_tokens.has_next_token()) {
|
if (function_tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid <pt-name-selector> in ::{}() - trailing tokens", pseudo_name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = "Invalid <pt-name-selector> - trailing tokens."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -503,7 +556,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!metadata.is_valid_as_identifier) {
|
if (!metadata.is_valid_as_identifier) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-element '::{}' is not valid as an identifier.", pseudo_name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
.value_string = name_token.to_string(),
|
||||||
|
.description = "Only valid as a function."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -543,7 +600,9 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
if (has_ignored_vendor_prefix(pseudo_name))
|
if (has_ignored_vendor_prefix(pseudo_name))
|
||||||
return ParseError::IncludesIgnoredVendorPrefix;
|
return ParseError::IncludesIgnoredVendorPrefix;
|
||||||
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-element: '::{}'", pseudo_name);
|
ErrorReporter::the().report(UnknownPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted("::{}", pseudo_name)),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,7 +625,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
|
|
||||||
if (auto pseudo_class = pseudo_class_from_string(pseudo_name); pseudo_class.has_value()) {
|
if (auto pseudo_class = pseudo_class_from_string(pseudo_name); pseudo_class.has_value()) {
|
||||||
if (!pseudo_class_metadata(pseudo_class.value()).is_valid_as_identifier) {
|
if (!pseudo_class_metadata(pseudo_class.value()).is_valid_as_identifier) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is only valid as a function", pseudo_name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Only valid as a function."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
return make_pseudo_class_selector(pseudo_class.value());
|
return make_pseudo_class_selector(pseudo_class.value());
|
||||||
|
@ -594,7 +657,9 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name);
|
ErrorReporter::the().report(UnknownPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_name)),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,7 +668,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
auto tokens = TokenStream<ComponentValue>(function_values);
|
auto tokens = TokenStream<ComponentValue>(function_values);
|
||||||
auto nth_child_pattern = parse_a_n_plus_b_pattern(tokens);
|
auto nth_child_pattern = parse_a_n_plus_b_pattern(tokens);
|
||||||
if (!nth_child_pattern.has_value()) {
|
if (!nth_child_pattern.has_value()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid An+B format for {}", pseudo_class_name(pseudo_class));
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_class_name(pseudo_class))),
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = "Invalid An+B format."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,26 +713,40 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
auto const& pseudo_function = pseudo_class_token.function();
|
auto const& pseudo_function = pseudo_class_token.function();
|
||||||
auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name);
|
auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name);
|
||||||
if (!maybe_pseudo_class.has_value()) {
|
if (!maybe_pseudo_class.has_value()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name);
|
ErrorReporter::the().report(UnknownPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
auto pseudo_class = maybe_pseudo_class.value();
|
auto pseudo_class = maybe_pseudo_class.value();
|
||||||
auto metadata = pseudo_class_metadata(pseudo_class);
|
auto metadata = pseudo_class_metadata(pseudo_class);
|
||||||
|
|
||||||
if (!metadata.is_valid_as_function) {
|
if (!metadata.is_valid_as_function) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Not valid as a function."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pseudo_function.value.is_empty()) {
|
if (pseudo_function.value.is_empty()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Missing arguments."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "The :has() pseudo-class cannot be nested; :has() is not valid within :has()."
|
// "The :has() pseudo-class cannot be nested; :has() is not valid within :has()."
|
||||||
// https://drafts.csswg.org/selectors/#relational
|
// https://drafts.csswg.org/selectors/#relational
|
||||||
if (pseudo_class == PseudoClass::Has && m_pseudo_class_context.contains_slow(PseudoClass::Has)) {
|
if (pseudo_class == PseudoClass::Has && m_pseudo_class_context.contains_slow(PseudoClass::Has)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, ":has() is not allowed inside :has()");
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = ":has() is not allowed inside :has()."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +762,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
auto function_token_stream = TokenStream(pseudo_function.value);
|
auto function_token_stream = TokenStream(pseudo_function.value);
|
||||||
auto compound_selector_or_error = parse_compound_selector(function_token_stream);
|
auto compound_selector_or_error = parse_compound_selector(function_token_stream);
|
||||||
if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
|
if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a compound selector", pseudo_function.name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Failed to parse argument as a compound selector."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,7 +806,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
auto const& maybe_ident_token = function_token_stream.consume_a_token();
|
auto const& maybe_ident_token = function_token_stream.consume_a_token();
|
||||||
function_token_stream.discard_whitespace();
|
function_token_stream.discard_whitespace();
|
||||||
if (!maybe_ident_token.is(Token::Type::Ident) || function_token_stream.has_next_token()) {
|
if (!maybe_ident_token.is(Token::Type::Ident) || function_token_stream.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter: not an ident", pseudo_function.name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Failed to parse argument as an ident."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,7 +837,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
language_token_stream.discard_whitespace();
|
language_token_stream.discard_whitespace();
|
||||||
auto const& language_token = language_token_stream.consume_a_token();
|
auto const& language_token = language_token_stream.consume_a_token();
|
||||||
if (!(language_token.is(Token::Type::Ident) || language_token.is(Token::Type::String))) {
|
if (!(language_token.is(Token::Type::Ident) || language_token.is(Token::Type::String))) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Failed to parse argument as a language range: Not a string/ident."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,7 +850,11 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
|
|
||||||
language_token_stream.discard_whitespace();
|
language_token_stream.discard_whitespace();
|
||||||
if (language_token_stream.has_next_token()) {
|
if (language_token_stream.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name);
|
ErrorReporter::the().report(InvalidPseudoClassOrElementError {
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_function.name)),
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = "Failed to parse argument as a language range: Has trailing tokens."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -787,7 +886,10 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = pseudo_class_token.to_string(),
|
||||||
|
.description = MUST(String::formatted("Pseudo-class should be an ident or function, got: '{}'", pseudo_class_token.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -833,7 +935,10 @@ Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_se
|
||||||
|
|
||||||
auto const& class_name_value = tokens.consume_a_token();
|
auto const& class_name_value = tokens.consume_a_token();
|
||||||
if (!class_name_value.is(Token::Type::Ident)) {
|
if (!class_name_value.is(Token::Type::Ident)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Expected an ident after '.', got: {}", class_name_value.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = MUST(String::formatted("Expected an ident after '.', got: {}", class_name_value.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
return Selector::SimpleSelector {
|
return Selector::SimpleSelector {
|
||||||
|
@ -850,14 +955,20 @@ Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_se
|
||||||
tokens.reconsume_current_input_token();
|
tokens.reconsume_current_input_token();
|
||||||
return Optional<Selector::SimpleSelector> {};
|
return Optional<Selector::SimpleSelector> {};
|
||||||
default:
|
default:
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized delimiter in selector: '{}'", first_value.token().to_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = MUST(String::formatted("Unrecognized delimiter: {}", first_value.token().to_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first_value.is(Token::Type::Hash)) {
|
if (first_value.is(Token::Type::Hash)) {
|
||||||
if (first_value.token().hash_type() != Token::HashType::Id) {
|
if (first_value.token().hash_type() != Token::HashType::Id) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Selector contains hash token that is not an id: {}", first_value.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = MUST(String::formatted("Hash token is not an id: {}", first_value.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
return Selector::SimpleSelector {
|
return Selector::SimpleSelector {
|
||||||
|
@ -872,20 +983,16 @@ Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_se
|
||||||
if (first_value.is(Token::Type::Colon))
|
if (first_value.is(Token::Type::Colon))
|
||||||
return TRY(parse_pseudo_simple_selector(tokens));
|
return TRY(parse_pseudo_simple_selector(tokens));
|
||||||
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid simple selector: {}", first_value.to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = MUST(String::formatted("Invalid start of a simple selector: {}", first_value.to_debug_string())),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_pattern(TokenStream<ComponentValue>& values)
|
Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_pattern(TokenStream<ComponentValue>& values)
|
||||||
{
|
{
|
||||||
auto transaction = values.begin_transaction();
|
auto transaction = values.begin_transaction();
|
||||||
auto syntax_error = [&]() -> Optional<Selector::SimpleSelector::ANPlusBPattern> {
|
|
||||||
if constexpr (CSS_PARSER_DEBUG) {
|
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid An+B value:");
|
|
||||||
values.dump_all_tokens();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
auto is_sign = [](ComponentValue const& value) -> bool {
|
auto is_sign = [](ComponentValue const& value) -> bool {
|
||||||
return value.is(Token::Type::Delim) && (value.token().delim() == '+' || value.token().delim() == '-');
|
return value.is(Token::Type::Delim) && (value.token().delim() == '+' || value.token().delim() == '-');
|
||||||
|
@ -1025,7 +1132,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
|
||||||
return Selector::SimpleSelector::ANPlusBPattern { a, b };
|
return Selector::SimpleSelector::ANPlusBPattern { a, b };
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
// <ndashdigit-dimension>
|
// <ndashdigit-dimension>
|
||||||
if (is_ndashdigit_dimension(first_value)) {
|
if (is_ndashdigit_dimension(first_value)) {
|
||||||
|
@ -1037,7 +1144,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
|
||||||
return Selector::SimpleSelector::ANPlusBPattern { a, maybe_b.value() };
|
return Selector::SimpleSelector::ANPlusBPattern { a, maybe_b.value() };
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
// <dashndashdigit-ident>
|
// <dashndashdigit-ident>
|
||||||
if (is_dashndashdigit_ident(first_value)) {
|
if (is_dashndashdigit_ident(first_value)) {
|
||||||
|
@ -1047,7 +1154,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
|
||||||
return Selector::SimpleSelector::ANPlusBPattern { -1, maybe_b.value() };
|
return Selector::SimpleSelector::ANPlusBPattern { -1, maybe_b.value() };
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
// -n
|
// -n
|
||||||
// -n <signed-integer>
|
// -n <signed-integer>
|
||||||
|
@ -1090,7 +1197,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
|
||||||
return Selector::SimpleSelector::ANPlusBPattern { -1, b };
|
return Selector::SimpleSelector::ANPlusBPattern { -1, b };
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// All that's left now are these:
|
// All that's left now are these:
|
||||||
|
@ -1149,7 +1256,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
|
||||||
return Selector::SimpleSelector::ANPlusBPattern { 1, b };
|
return Selector::SimpleSelector::ANPlusBPattern { 1, b };
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// '+'?† <ndashdigit-ident>
|
// '+'?† <ndashdigit-ident>
|
||||||
|
@ -1160,10 +1267,10 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
|
||||||
return Selector::SimpleSelector::ANPlusBPattern { 1, maybe_b.value() };
|
return Selector::SimpleSelector::ANPlusBPattern { 1, maybe_b.value() };
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<PageSelectorList> Parser::parse_as_page_selector_list()
|
Optional<PageSelectorList> Parser::parse_as_page_selector_list()
|
||||||
|
@ -1197,21 +1304,32 @@ Parser::ParseErrorOr<PageSelectorList> Parser::parse_a_page_selector_list(TokenS
|
||||||
while (tokens.next_token().is(Token::Type::Colon)) {
|
while (tokens.next_token().is(Token::Type::Colon)) {
|
||||||
tokens.discard_a_token(); // :
|
tokens.discard_a_token(); // :
|
||||||
if (!tokens.next_token().is(Token::Type::Ident)) {
|
if (!tokens.next_token().is(Token::Type::Ident)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid @page selector: pseudo-class is not an ident: `{}`", tokens.next_token().to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.rule_name = "@page"_fly_string,
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = "Pseudo-classes must be idents."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
auto pseudo_class_name = static_cast<Token>(tokens.consume_a_token()).ident();
|
auto pseudo_class_name = static_cast<Token>(tokens.consume_a_token()).ident();
|
||||||
if (auto pseudo_class = page_pseudo_class_from_string(pseudo_class_name); pseudo_class.has_value()) {
|
if (auto pseudo_class = page_pseudo_class_from_string(pseudo_class_name); pseudo_class.has_value()) {
|
||||||
pseudo_classes.append(*pseudo_class);
|
pseudo_classes.append(*pseudo_class);
|
||||||
} else {
|
} else {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid @page selector: unrecognized pseudo-class `:{}`", pseudo_class_name);
|
ErrorReporter::the().report(UnknownPseudoClassOrElementError {
|
||||||
|
.rule_name = "@page"_fly_string,
|
||||||
|
.name = MUST(String::formatted(":{}", pseudo_class_name)),
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!maybe_ident.has_value() && pseudo_classes.is_empty()) {
|
if (!maybe_ident.has_value() && pseudo_classes.is_empty()) {
|
||||||
// Nothing parsed
|
// Nothing parsed
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid @page selector: is empty");
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.rule_name = "@page"_fly_string,
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = "Is empty."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1223,12 +1341,20 @@ Parser::ParseErrorOr<PageSelectorList> Parser::parse_a_page_selector_list(TokenS
|
||||||
tokens.discard_a_token(); // ,
|
tokens.discard_a_token(); // ,
|
||||||
tokens.discard_whitespace();
|
tokens.discard_whitespace();
|
||||||
if (!tokens.has_next_token()) {
|
if (!tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid @page selector: trailing comma");
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.rule_name = "@page"_fly_string,
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = "Trailing comma."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (tokens.has_next_token()) {
|
} else if (tokens.has_next_token()) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Invalid @page selector: trailing token `{}`", tokens.next_token().to_debug_string());
|
ErrorReporter::the().report(InvalidSelectorError {
|
||||||
|
.rule_name = "@page"_fly_string,
|
||||||
|
.value_string = tokens.dump_string(),
|
||||||
|
.description = "Trailing tokens."_string,
|
||||||
|
});
|
||||||
return ParseError::SyntaxError;
|
return ParseError::SyntaxError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "Selector.h"
|
#include "Selector.h"
|
||||||
#include <AK/GenericShorthands.h>
|
#include <AK/GenericShorthands.h>
|
||||||
|
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
||||||
#include <LibWeb/CSS/Serialize.h>
|
#include <LibWeb/CSS/Serialize.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
@ -728,7 +729,10 @@ Optional<Selector::SimpleSelector> Selector::SimpleSelector::absolutized(Selecto
|
||||||
if (pseudo_class.type == PseudoClass::Has) {
|
if (pseudo_class.type == PseudoClass::Has) {
|
||||||
for (auto const& selector : pseudo_class.argument_selector_list) {
|
for (auto const& selector : pseudo_class.argument_selector_list) {
|
||||||
if (contains_invalid_contents_for_has(selector)) {
|
if (contains_invalid_contents_for_has(selector)) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "After absolutizing, :has() would contain invalid contents; rejecting");
|
Parser::ErrorReporter::the().report(Parser::InvalidSelectorError {
|
||||||
|
.value_string = selector->serialize(),
|
||||||
|
.description = "After absolutizing, :has() would contain invalid contents."_string,
|
||||||
|
});
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue