mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-05 08:31:51 +00:00
LibWeb/CSS: Parse media-feature values forgivingly
Instead of rejecting invalid media-feature values at parse time, we are expected to parse them, and then treat them as Unknown when evaluating their media query. To implement this, we first try to parse a valid value as before. If that fails, or we have any trailing tokens that could be part of a value, we then scoop up all the possible ComponentValues and treat that as an "unknown"-type MediaFeatureValue. This gets us 66 WPT passes.
This commit is contained in:
parent
72f50217b0
commit
9b8dc6b8d0
Notes:
github-actions[bot]
2025-05-23 09:19:18 +00:00
Author: https://github.com/AtkinsSJ
Commit: 9b8dc6b8d0
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4817
15 changed files with 228 additions and 146 deletions
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
||||
* 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) 2022, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
|
@ -314,7 +314,7 @@ OwnPtr<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& to
|
|||
|
||||
// `<mf-name> <mf-comparison> <mf-value>`
|
||||
// NOTE: We have to check for <mf-name> first, since all <mf-name>s will also parse as <mf-value>.
|
||||
if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value() && media_feature_type_is_range(maybe_name->id)) {
|
||||
if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value()) {
|
||||
tokens.discard_whitespace();
|
||||
if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
|
||||
tokens.discard_whitespace();
|
||||
|
@ -352,7 +352,7 @@ OwnPtr<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& to
|
|||
}
|
||||
|
||||
// Now, we can parse the range properly.
|
||||
if (maybe_name.has_value() && media_feature_type_is_range(maybe_name->id)) {
|
||||
if (maybe_name.has_value()) {
|
||||
if (auto maybe_left_value = parse_media_feature_value(maybe_name->id, tokens); maybe_left_value.has_value()) {
|
||||
tokens.discard_whitespace();
|
||||
if (auto maybe_left_comparison = parse_comparison(tokens); maybe_left_comparison.has_value()) {
|
||||
|
@ -428,71 +428,148 @@ Optional<MediaQuery::MediaType> Parser::parse_media_type(TokenStream<ComponentVa
|
|||
};
|
||||
}
|
||||
|
||||
static bool is_media_feature_value_token(ComponentValue const& component_value)
|
||||
{
|
||||
if (!component_value.is_token())
|
||||
return true;
|
||||
switch (component_value.token().type()) {
|
||||
case Token::Type::Ident:
|
||||
case Token::Type::Function:
|
||||
case Token::Type::AtKeyword:
|
||||
case Token::Type::Hash:
|
||||
case Token::Type::String:
|
||||
case Token::Type::BadString:
|
||||
case Token::Type::Url:
|
||||
case Token::Type::BadUrl:
|
||||
case Token::Type::Number:
|
||||
case Token::Type::Percentage:
|
||||
case Token::Type::Dimension:
|
||||
case Token::Type::Whitespace:
|
||||
case Token::Type::Comma:
|
||||
return true;
|
||||
case Token::Type::Delim:
|
||||
// FIXME: What list of delimiters should we actually allow here?
|
||||
return !first_is_one_of(component_value.token().delim(), static_cast<u32>('<'), static_cast<u32>('>'), static_cast<u32>('='));
|
||||
case Token::Type::Invalid:
|
||||
case Token::Type::EndOfFile:
|
||||
case Token::Type::CDO:
|
||||
case Token::Type::CDC:
|
||||
case Token::Type::Colon:
|
||||
case Token::Type::Semicolon:
|
||||
case Token::Type::OpenSquare:
|
||||
case Token::Type::CloseSquare:
|
||||
case Token::Type::OpenParen:
|
||||
case Token::Type::CloseParen:
|
||||
case Token::Type::OpenCurly:
|
||||
case Token::Type::CloseCurly:
|
||||
return false;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// `<mf-value>`, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
|
||||
Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID media_feature, TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// Identifiers
|
||||
if (tokens.next_token().is(Token::Type::Ident)) {
|
||||
{
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
auto keyword = keyword_from_string(tokens.consume_a_token().token().ident());
|
||||
if (keyword.has_value() && media_feature_accepts_keyword(media_feature, keyword.value())) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(keyword.value());
|
||||
}
|
||||
}
|
||||
auto value = [this](MediaFeatureID media_feature, TokenStream<ComponentValue>& tokens) -> Optional<MediaFeatureValue> {
|
||||
// One branch for each member of the MediaFeatureValueType enum:
|
||||
// Identifiers
|
||||
if (tokens.next_token().is(Token::Type::Ident)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
auto keyword = keyword_from_string(tokens.consume_a_token().token().ident());
|
||||
if (keyword.has_value() && media_feature_accepts_keyword(media_feature, keyword.value())) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(keyword.value());
|
||||
}
|
||||
}
|
||||
|
||||
// One branch for each member of the MediaFeatureValueType enum:
|
||||
// Boolean (<mq-boolean> in the spec: a 1 or 0)
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto integer = parse_integer(tokens); integer.has_value()) {
|
||||
if (integer.value().is_calculated() || integer->value() == 0 || integer->value() == 1) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(integer.release_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Boolean (<mq-boolean> in the spec: a 1 or 0)
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto integer = parse_integer(tokens); integer.has_value()) {
|
||||
if (integer.value().is_calculated() || integer->value() == 0 || integer->value() == 1) {
|
||||
// Integer
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
if (auto integer = parse_integer(tokens); integer.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(integer.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Length
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Length)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto length = parse_length(tokens); length.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(length.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Ratio
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Ratio)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto ratio = parse_ratio(tokens); ratio.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(ratio.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Resolution
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Resolution)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto resolution = parse_resolution(tokens); resolution.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(resolution.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}(media_feature, tokens);
|
||||
|
||||
if (value.has_value()) {
|
||||
tokens.discard_whitespace();
|
||||
|
||||
// Only returned the value if there are no trailing tokens.
|
||||
// Otherwise, the transaction gets reverted and we consume all the value tokens below.
|
||||
if (!is_media_feature_value_token(tokens.next_token())) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(integer.release_value());
|
||||
return value.release_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Integer
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
if (auto integer = parse_integer(tokens); integer.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(integer.release_value());
|
||||
// Parsing failed somehow, so wrap all the tokens into an "unknown" MediaFeatureValue if possible.
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
Vector<ComponentValue> unknown_tokens;
|
||||
|
||||
// Consume any tokens that could be part of a value.
|
||||
while (tokens.has_next_token()) {
|
||||
if (is_media_feature_value_token(tokens.next_token())) {
|
||||
unknown_tokens.append(tokens.consume_a_token());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Length
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Length)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto length = parse_length(tokens); length.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(length.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Ratio
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Ratio)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto ratio = parse_ratio(tokens); ratio.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(ratio.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Resolution
|
||||
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Resolution)) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_whitespace();
|
||||
if (auto resolution = parse_resolution(tokens); resolution.has_value()) {
|
||||
transaction.commit();
|
||||
return MediaFeatureValue(resolution.release_value());
|
||||
}
|
||||
if (!unknown_tokens.is_empty()) {
|
||||
transaction.commit();
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Creating unknown media value: `{}`", String::join(""sv, unknown_tokens));
|
||||
return MediaFeatureValue(move(unknown_tokens));
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue