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:
Sam Atkins 2025-05-22 11:56:04 +01:00
parent 72f50217b0
commit 9b8dc6b8d0
Notes: github-actions[bot] 2025-05-23 09:19:18 +00:00
15 changed files with 228 additions and 146 deletions

View file

@ -36,6 +36,9 @@ String MediaFeatureValue::to_string() const
if (integer.is_calculated()) if (integer.is_calculated())
return integer.calculated()->to_string(SerializationMode::Normal); return integer.calculated()->to_string(SerializationMode::Normal);
return String::number(integer.value()); return String::number(integer.value());
},
[&](Vector<Parser::ComponentValue> const& values) {
return serialize_a_series_of_component_values(values);
}); });
} }
@ -46,7 +49,8 @@ bool MediaFeatureValue::is_same_type(MediaFeatureValue const& other) const
[&](LengthOrCalculated const&) { return other.is_length(); }, [&](LengthOrCalculated const&) { return other.is_length(); },
[&](Ratio const&) { return other.is_ratio(); }, [&](Ratio const&) { return other.is_ratio(); },
[&](ResolutionOrCalculated const&) { return other.is_resolution(); }, [&](ResolutionOrCalculated const&) { return other.is_resolution(); },
[&](IntegerOrCalculated const&) { return other.is_integer(); }); [&](IntegerOrCalculated const&) { return other.is_integer(); },
[&](Vector<Parser::ComponentValue> const&) { return other.is_unknown(); });
} }
String MediaFeature::to_string() const String MediaFeature::to_string() const
@ -149,6 +153,9 @@ MatchResult MediaFeature::evaluate(HTML::Window const* window) const
MatchResult MediaFeature::compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right) MatchResult MediaFeature::compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right)
{ {
if (left.is_unknown() || right.is_unknown())
return MatchResult::Unknown;
if (!left.is_same_type(right)) if (!left.is_same_type(right))
return MatchResult::False; return MatchResult::False;

View file

@ -14,6 +14,7 @@
#include <LibWeb/CSS/BooleanExpression.h> #include <LibWeb/CSS/BooleanExpression.h>
#include <LibWeb/CSS/CalculatedOr.h> #include <LibWeb/CSS/CalculatedOr.h>
#include <LibWeb/CSS/MediaFeatureID.h> #include <LibWeb/CSS/MediaFeatureID.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/Ratio.h> #include <LibWeb/CSS/Ratio.h>
namespace Web::CSS { namespace Web::CSS {
@ -51,6 +52,11 @@ public:
{ {
} }
explicit MediaFeatureValue(Vector<Parser::ComponentValue> unknown_tokens)
: m_value(move(unknown_tokens))
{
}
String to_string() const; String to_string() const;
bool is_ident() const { return m_value.has<Keyword>(); } bool is_ident() const { return m_value.has<Keyword>(); }
@ -58,6 +64,7 @@ public:
bool is_integer() const { return m_value.has<IntegerOrCalculated>(); } bool is_integer() const { return m_value.has<IntegerOrCalculated>(); }
bool is_ratio() const { return m_value.has<Ratio>(); } bool is_ratio() const { return m_value.has<Ratio>(); }
bool is_resolution() const { return m_value.has<ResolutionOrCalculated>(); } bool is_resolution() const { return m_value.has<ResolutionOrCalculated>(); }
bool is_unknown() const { return m_value.has<Vector<Parser::ComponentValue>>(); }
bool is_same_type(MediaFeatureValue const& other) const; bool is_same_type(MediaFeatureValue const& other) const;
Keyword const& ident() const Keyword const& ident() const
@ -91,7 +98,7 @@ public:
} }
private: private:
Variant<Keyword, LengthOrCalculated, Ratio, ResolutionOrCalculated, IntegerOrCalculated> m_value; Variant<Keyword, LengthOrCalculated, Ratio, ResolutionOrCalculated, IntegerOrCalculated, Vector<Parser::ComponentValue>> m_value;
}; };
// https://www.w3.org/TR/mediaqueries-4/#mq-features // https://www.w3.org/TR/mediaqueries-4/#mq-features

View file

@ -1,7 +1,7 @@
/* /*
* 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>
* *
@ -314,7 +314,7 @@ OwnPtr<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& to
// `<mf-name> <mf-comparison> <mf-value>` // `<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>. // 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(); tokens.discard_whitespace();
if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) { if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
tokens.discard_whitespace(); tokens.discard_whitespace();
@ -352,7 +352,7 @@ OwnPtr<MediaFeature> Parser::parse_media_feature(TokenStream<ComponentValue>& to
} }
// Now, we can parse the range properly. // 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()) { if (auto maybe_left_value = parse_media_feature_value(maybe_name->id, tokens); maybe_left_value.has_value()) {
tokens.discard_whitespace(); tokens.discard_whitespace();
if (auto maybe_left_comparison = parse_comparison(tokens); maybe_left_comparison.has_value()) { 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 // `<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) 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(); auto transaction = tokens.begin_transaction();
tokens.discard_whitespace(); auto value = [this](MediaFeatureID media_feature, TokenStream<ComponentValue>& tokens) -> Optional<MediaFeatureValue> {
auto keyword = keyword_from_string(tokens.consume_a_token().token().ident()); // One branch for each member of the MediaFeatureValueType enum:
if (keyword.has_value() && media_feature_accepts_keyword(media_feature, keyword.value())) { // Identifiers
transaction.commit(); if (tokens.next_token().is(Token::Type::Ident)) {
return MediaFeatureValue(keyword.value()); 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) // Integer
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) { if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
auto transaction = tokens.begin_transaction(); auto transaction = tokens.begin_transaction();
tokens.discard_whitespace(); if (auto integer = parse_integer(tokens); integer.has_value()) {
if (auto integer = parse_integer(tokens); integer.has_value()) { transaction.commit();
if (integer.value().is_calculated() || integer->value() == 0 || integer->value() == 1) { 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(); transaction.commit();
return MediaFeatureValue(integer.release_value()); return value.release_value();
} }
} }
} }
// Integer // Parsing failed somehow, so wrap all the tokens into an "unknown" MediaFeatureValue if possible.
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
auto transaction = tokens.begin_transaction(); auto transaction = tokens.begin_transaction();
if (auto integer = parse_integer(tokens); integer.has_value()) { tokens.discard_whitespace();
transaction.commit(); Vector<ComponentValue> unknown_tokens;
return MediaFeatureValue(integer.release_value());
// 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 (!unknown_tokens.is_empty()) {
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Length)) { transaction.commit();
auto transaction = tokens.begin_transaction(); dbgln_if(CSS_PARSER_DEBUG, "Creating unknown media value: `{}`", String::join(""sv, unknown_tokens));
tokens.discard_whitespace(); return MediaFeatureValue(move(unknown_tokens));
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 {}; return {};

View file

@ -2,22 +2,21 @@ Harness status: OK
Found 17 tests Found 17 tests
12 Pass 17 Pass
5 Fail
Pass Should be known: '(display-mode)' Pass Should be known: '(display-mode)'
Pass Should be known: '(display-mode: standalone)' Pass Should be known: '(display-mode: standalone)'
Pass Should be known: '(display-mode: browser)' Pass Should be known: '(display-mode: browser)'
Pass Should be known: '(display-mode: minimal-ui)' Pass Should be known: '(display-mode: minimal-ui)'
Pass Should be known: '(display-mode: fullscreen)' Pass Should be known: '(display-mode: fullscreen)'
Pass Should be known: '(display-mode: picture-in-picture)' Pass Should be known: '(display-mode: picture-in-picture)'
Fail Should be parseable: '(display-mode: 0)' Pass Should be parseable: '(display-mode: 0)'
Pass Should be unknown: '(display-mode: 0)' Pass Should be unknown: '(display-mode: 0)'
Fail Should be parseable: '(display-mode: none)' Pass Should be parseable: '(display-mode: none)'
Pass Should be unknown: '(display-mode: none)' Pass Should be unknown: '(display-mode: none)'
Fail Should be parseable: '(display-mode: random)' Pass Should be parseable: '(display-mode: random)'
Pass Should be unknown: '(display-mode: random)' Pass Should be unknown: '(display-mode: random)'
Fail Should be parseable: '(display-mode: 10px)' Pass Should be parseable: '(display-mode: 10px)'
Pass Should be unknown: '(display-mode: 10px)' Pass Should be unknown: '(display-mode: 10px)'
Fail Should be parseable: '(display-mode: 1%)' Pass Should be parseable: '(display-mode: 1%)'
Pass Should be unknown: '(display-mode: 1%)' Pass Should be unknown: '(display-mode: 1%)'
Pass Check that display-mode evaluates to true in the boolean context Pass Check that display-mode evaluates to true in the boolean context

View file

@ -2,25 +2,25 @@ Harness status: OK
Found 23 tests Found 23 tests
15 Pass 21 Pass
8 Fail 2 Fail
Pass Should be known: '(dynamic-range: standard)' Pass Should be known: '(dynamic-range: standard)'
Pass Should be known: '(dynamic-range: high)' Pass Should be known: '(dynamic-range: high)'
Pass Should be known: '(video-dynamic-range: standard)' Pass Should be known: '(video-dynamic-range: standard)'
Pass Should be known: '(video-dynamic-range: high)' Pass Should be known: '(video-dynamic-range: high)'
Pass Should be known: '(dynamic-range)' Pass Should be known: '(dynamic-range)'
Pass Should be known: '(video-dynamic-range)' Pass Should be known: '(video-dynamic-range)'
Fail Should be parseable: '(dynamic-range: 0)' Pass Should be parseable: '(dynamic-range: 0)'
Pass Should be unknown: '(dynamic-range: 0)' Pass Should be unknown: '(dynamic-range: 0)'
Fail Should be parseable: '(dynamic-range: 10px)' Pass Should be parseable: '(dynamic-range: 10px)'
Pass Should be unknown: '(dynamic-range: 10px)' Pass Should be unknown: '(dynamic-range: 10px)'
Fail Should be parseable: '(dynamic-range: invalid)' Pass Should be parseable: '(dynamic-range: invalid)'
Pass Should be unknown: '(dynamic-range: invalid)' Pass Should be unknown: '(dynamic-range: invalid)'
Fail Should be parseable: '(video-dynamic-range: 0)' Pass Should be parseable: '(video-dynamic-range: 0)'
Pass Should be unknown: '(video-dynamic-range: 0)' Pass Should be unknown: '(video-dynamic-range: 0)'
Fail Should be parseable: '(video-dynamic-range: 10px)' Pass Should be parseable: '(video-dynamic-range: 10px)'
Pass Should be unknown: '(video-dynamic-range: 10px)' Pass Should be unknown: '(video-dynamic-range: 10px)'
Fail Should be parseable: '(video-dynamic-range: invalid)' Pass Should be parseable: '(video-dynamic-range: invalid)'
Pass Should be unknown: '(video-dynamic-range: invalid)' Pass Should be unknown: '(video-dynamic-range: invalid)'
Fail Check that dynamic-range evaluates to false in the boolean context Fail Check that dynamic-range evaluates to false in the boolean context
Fail Check that video-dynamic-range evaluates to false in the boolean context Fail Check that video-dynamic-range evaluates to false in the boolean context

View file

@ -2,21 +2,20 @@ Harness status: OK
Found 16 tests Found 16 tests
10 Pass 16 Pass
6 Fail
Pass Should be known: '(forced-colors)' Pass Should be known: '(forced-colors)'
Pass Should be known: '(forced-colors: none)' Pass Should be known: '(forced-colors: none)'
Pass Should be known: '(forced-colors: active)' Pass Should be known: '(forced-colors: active)'
Fail Should be parseable: '(forced-colors: 0)' Pass Should be parseable: '(forced-colors: 0)'
Pass Should be unknown: '(forced-colors: 0)' Pass Should be unknown: '(forced-colors: 0)'
Fail Should be parseable: '(forced-colors: no-preference)' Pass Should be parseable: '(forced-colors: no-preference)'
Pass Should be unknown: '(forced-colors: no-preference)' Pass Should be unknown: '(forced-colors: no-preference)'
Fail Should be parseable: '(forced-colors: 10px)' Pass Should be parseable: '(forced-colors: 10px)'
Pass Should be unknown: '(forced-colors: 10px)' Pass Should be unknown: '(forced-colors: 10px)'
Fail Should be parseable: '(forced-colors: active 0)' Pass Should be parseable: '(forced-colors: active 0)'
Pass Should be unknown: '(forced-colors: active 0)' Pass Should be unknown: '(forced-colors: active 0)'
Fail Should be parseable: '(forced-colors: none active)' Pass Should be parseable: '(forced-colors: none active)'
Pass Should be unknown: '(forced-colors: none active)' Pass Should be unknown: '(forced-colors: none active)'
Fail Should be parseable: '(forced-colors: active/none)' Pass Should be parseable: '(forced-colors: active/none)'
Pass Should be unknown: '(forced-colors: active/none)' Pass Should be unknown: '(forced-colors: active/none)'
Pass Check that none evaluates to false in the boolean context Pass Check that none evaluates to false in the boolean context

View file

@ -2,20 +2,19 @@ Harness status: OK
Found 15 tests Found 15 tests
10 Pass 15 Pass
5 Fail
Pass Should be known: '(inverted-colors)' Pass Should be known: '(inverted-colors)'
Pass Should be known: '(inverted-colors: none)' Pass Should be known: '(inverted-colors: none)'
Pass Should be known: '(inverted-colors: inverted)' Pass Should be known: '(inverted-colors: inverted)'
Fail Should be parseable: '(inverted-colors: 0)' Pass Should be parseable: '(inverted-colors: 0)'
Pass Should be unknown: '(inverted-colors: 0)' Pass Should be unknown: '(inverted-colors: 0)'
Fail Should be parseable: '(inverted-colors: no-preference)' Pass Should be parseable: '(inverted-colors: no-preference)'
Pass Should be unknown: '(inverted-colors: no-preference)' Pass Should be unknown: '(inverted-colors: no-preference)'
Fail Should be parseable: '(inverted-colors: 10px)' Pass Should be parseable: '(inverted-colors: 10px)'
Pass Should be unknown: '(inverted-colors: 10px)' Pass Should be unknown: '(inverted-colors: 10px)'
Fail Should be parseable: '(inverted-colors: none inverted)' Pass Should be parseable: '(inverted-colors: none inverted)'
Pass Should be unknown: '(inverted-colors: none inverted)' Pass Should be unknown: '(inverted-colors: none inverted)'
Fail Should be parseable: '(inverted-colors: none/inverted)' Pass Should be parseable: '(inverted-colors: none/inverted)'
Pass Should be unknown: '(inverted-colors: none/inverted)' Pass Should be unknown: '(inverted-colors: none/inverted)'
Pass Check that none evaluates to false in the boolean context Pass Check that none evaluates to false in the boolean context
Pass Check that invalid evaluates to false Pass Check that invalid evaluates to false

View file

@ -2,18 +2,18 @@ Harness status: OK
Found 28 tests Found 28 tests
20 Pass 26 Pass
8 Fail 2 Fail
Pass Should be known: '(overflow-inline)' Pass Should be known: '(overflow-inline)'
Pass Should be known: '(overflow-inline: none)' Pass Should be known: '(overflow-inline: none)'
Pass Should be known: '(overflow-inline: scroll)' Pass Should be known: '(overflow-inline: scroll)'
Pass Should be parseable: 'overflow-inline' Pass Should be parseable: 'overflow-inline'
Fail Should be unknown: 'overflow-inline' Fail Should be unknown: 'overflow-inline'
Fail Should be parseable: '(overflow-inline: ?)' Pass Should be parseable: '(overflow-inline: ?)'
Pass Should be unknown: '(overflow-inline: ?)' Pass Should be unknown: '(overflow-inline: ?)'
Fail Should be parseable: '(overflow-inline: 10px)' Pass Should be parseable: '(overflow-inline: 10px)'
Pass Should be unknown: '(overflow-inline: 10px)' Pass Should be unknown: '(overflow-inline: 10px)'
Fail Should be parseable: '(overflow-inline: 0)' Pass Should be parseable: '(overflow-inline: 0)'
Pass Should be unknown: '(overflow-inline: 0)' Pass Should be unknown: '(overflow-inline: 0)'
Pass Should be known: '(overflow-block)' Pass Should be known: '(overflow-block)'
Pass Should be known: '(overflow-block: none)' Pass Should be known: '(overflow-block: none)'
@ -21,11 +21,11 @@ Pass Should be known: '(overflow-block: scroll)'
Pass Should be known: '(overflow-block: paged)' Pass Should be known: '(overflow-block: paged)'
Pass Should be parseable: 'overflow-block' Pass Should be parseable: 'overflow-block'
Fail Should be unknown: 'overflow-block' Fail Should be unknown: 'overflow-block'
Fail Should be parseable: '(overflow-block: ?)' Pass Should be parseable: '(overflow-block: ?)'
Pass Should be unknown: '(overflow-block: ?)' Pass Should be unknown: '(overflow-block: ?)'
Fail Should be parseable: '(overflow-block: 10px)' Pass Should be parseable: '(overflow-block: 10px)'
Pass Should be unknown: '(overflow-block: 10px)' Pass Should be unknown: '(overflow-block: 10px)'
Fail Should be parseable: '(overflow-block: 0)' Pass Should be parseable: '(overflow-block: 0)'
Pass Should be unknown: '(overflow-block: 0)' Pass Should be unknown: '(overflow-block: 0)'
Pass Check that overflow-inline: scroll always matches non printing documents Pass Check that overflow-inline: scroll always matches non printing documents
Pass Check that overflow-block: scroll always matches non printing documents Pass Check that overflow-block: scroll always matches non printing documents

View file

@ -2,23 +2,22 @@ Harness status: OK
Found 18 tests Found 18 tests
11 Pass 18 Pass
7 Fail
Pass Should be known: '(prefers-color-scheme)' Pass Should be known: '(prefers-color-scheme)'
Pass Should be known: '(prefers-color-scheme: light)' Pass Should be known: '(prefers-color-scheme: light)'
Pass Should be known: '(prefers-color-scheme: dark)' Pass Should be known: '(prefers-color-scheme: dark)'
Fail Should be parseable: '(prefers-color-scheme: 0)' Pass Should be parseable: '(prefers-color-scheme: 0)'
Pass Should be unknown: '(prefers-color-scheme: 0)' Pass Should be unknown: '(prefers-color-scheme: 0)'
Fail Should be parseable: '(prefers-color-scheme: none)' Pass Should be parseable: '(prefers-color-scheme: none)'
Pass Should be unknown: '(prefers-color-scheme: none)' Pass Should be unknown: '(prefers-color-scheme: none)'
Fail Should be parseable: '(prefers-color-scheme: 10px)' Pass Should be parseable: '(prefers-color-scheme: 10px)'
Pass Should be unknown: '(prefers-color-scheme: 10px)' Pass Should be unknown: '(prefers-color-scheme: 10px)'
Fail Should be parseable: '(prefers-color-scheme: dark 0)' Pass Should be parseable: '(prefers-color-scheme: dark 0)'
Pass Should be unknown: '(prefers-color-scheme: dark 0)' Pass Should be unknown: '(prefers-color-scheme: dark 0)'
Fail Should be parseable: '(prefers-color-scheme: dark light)' Pass Should be parseable: '(prefers-color-scheme: dark light)'
Pass Should be unknown: '(prefers-color-scheme: dark light)' Pass Should be unknown: '(prefers-color-scheme: dark light)'
Fail Should be parseable: '(prefers-color-scheme: light/dark)' Pass Should be parseable: '(prefers-color-scheme: light/dark)'
Pass Should be unknown: '(prefers-color-scheme: light/dark)' Pass Should be unknown: '(prefers-color-scheme: light/dark)'
Fail Should be parseable: '(prefers-color-scheme: no-preference)' Pass Should be parseable: '(prefers-color-scheme: no-preference)'
Pass Should be unknown: '(prefers-color-scheme: no-preference)' Pass Should be unknown: '(prefers-color-scheme: no-preference)'
Pass Check that prefer-color-scheme evaluates to true in the boolean context Pass Check that prefer-color-scheme evaluates to true in the boolean context

View file

@ -2,20 +2,20 @@ Harness status: OK
Found 26 tests Found 26 tests
18 Pass 25 Pass
8 Fail 1 Fail
Pass Should be known: '(prefers-contrast)' Pass Should be known: '(prefers-contrast)'
Pass Should be known: '(prefers-contrast: no-preference)' Pass Should be known: '(prefers-contrast: no-preference)'
Pass Should be known: '(prefers-contrast: more)' Pass Should be known: '(prefers-contrast: more)'
Pass Should be known: '(prefers-contrast: less)' Pass Should be known: '(prefers-contrast: less)'
Pass Should be known: '(prefers-contrast: custom)' Pass Should be known: '(prefers-contrast: custom)'
Fail Should be parseable: '(prefers-contrast: increase)' Pass Should be parseable: '(prefers-contrast: increase)'
Pass Should be unknown: '(prefers-contrast: increase)' Pass Should be unknown: '(prefers-contrast: increase)'
Fail Should be parseable: '(prefers-contrast: none)' Pass Should be parseable: '(prefers-contrast: none)'
Pass Should be unknown: '(prefers-contrast: none)' Pass Should be unknown: '(prefers-contrast: none)'
Fail Should be parseable: '(prefers-contrast: forced high)' Pass Should be parseable: '(prefers-contrast: forced high)'
Pass Should be unknown: '(prefers-contrast: forced high)' Pass Should be unknown: '(prefers-contrast: forced high)'
Fail Should be parseable: '(prefers-contrast: forced low)' Pass Should be parseable: '(prefers-contrast: forced low)'
Pass Should be unknown: '(prefers-contrast: forced low)' Pass Should be unknown: '(prefers-contrast: forced low)'
Fail Should be parseable: '(prefers-contrast > increase)' Fail Should be parseable: '(prefers-contrast > increase)'
Pass Should be unknown: '(prefers-contrast > increase)' Pass Should be unknown: '(prefers-contrast > increase)'
@ -23,10 +23,10 @@ Pass Should be parseable: '(prefers-increased-contrast)'
Pass Should be unknown: '(prefers-increased-contrast)' Pass Should be unknown: '(prefers-increased-contrast)'
Pass Should be parseable: '(prefers-decreased-contrast)' Pass Should be parseable: '(prefers-decreased-contrast)'
Pass Should be unknown: '(prefers-decreased-contrast)' Pass Should be unknown: '(prefers-decreased-contrast)'
Fail Should be parseable: '(prefers-contrast: high)' Pass Should be parseable: '(prefers-contrast: high)'
Pass Should be unknown: '(prefers-contrast: high)' Pass Should be unknown: '(prefers-contrast: high)'
Fail Should be parseable: '(prefers-contrast: low)' Pass Should be parseable: '(prefers-contrast: low)'
Pass Should be unknown: '(prefers-contrast: low)' Pass Should be unknown: '(prefers-contrast: low)'
Fail Should be parseable: '(prefers-contrast: forced)' Pass Should be parseable: '(prefers-contrast: forced)'
Pass Should be unknown: '(prefers-contrast: forced)' Pass Should be unknown: '(prefers-contrast: forced)'
Pass Check boolean context evaluation. Pass Check boolean context evaluation.

View file

@ -2,22 +2,21 @@ Harness status: OK
Found 17 tests Found 17 tests
11 Pass 17 Pass
6 Fail
Pass Should be known: '(prefers-reduced-data)' Pass Should be known: '(prefers-reduced-data)'
Pass Should be known: '(prefers-reduced-data: no-preference)' Pass Should be known: '(prefers-reduced-data: no-preference)'
Pass Should be known: '(prefers-reduced-data: reduce)' Pass Should be known: '(prefers-reduced-data: reduce)'
Fail Should be parseable: '(prefers-reduced-data: 0)' Pass Should be parseable: '(prefers-reduced-data: 0)'
Pass Should be unknown: '(prefers-reduced-data: 0)' Pass Should be unknown: '(prefers-reduced-data: 0)'
Fail Should be parseable: '(prefers-reduced-data: none)' Pass Should be parseable: '(prefers-reduced-data: none)'
Pass Should be unknown: '(prefers-reduced-data: none)' Pass Should be unknown: '(prefers-reduced-data: none)'
Fail Should be parseable: '(prefers-reduced-data: 10px)' Pass Should be parseable: '(prefers-reduced-data: 10px)'
Pass Should be unknown: '(prefers-reduced-data: 10px)' Pass Should be unknown: '(prefers-reduced-data: 10px)'
Fail Should be parseable: '(prefers-reduced-data: no-preference reduce)' Pass Should be parseable: '(prefers-reduced-data: no-preference reduce)'
Pass Should be unknown: '(prefers-reduced-data: no-preference reduce)' Pass Should be unknown: '(prefers-reduced-data: no-preference reduce)'
Fail Should be parseable: '(prefers-reduced-data: reduced)' Pass Should be parseable: '(prefers-reduced-data: reduced)'
Pass Should be unknown: '(prefers-reduced-data: reduced)' Pass Should be unknown: '(prefers-reduced-data: reduced)'
Fail Should be parseable: '(prefers-reduced-data: no-preference/reduce)' Pass Should be parseable: '(prefers-reduced-data: no-preference/reduce)'
Pass Should be unknown: '(prefers-reduced-data: no-preference/reduce)' Pass Should be unknown: '(prefers-reduced-data: no-preference/reduce)'
Pass Check that no-preference evaluates to false in the boolean context Pass Check that no-preference evaluates to false in the boolean context
Pass Check that invalid evaluates to false Pass Check that invalid evaluates to false

View file

@ -2,21 +2,20 @@ Harness status: OK
Found 16 tests Found 16 tests
10 Pass 16 Pass
6 Fail
Pass Should be known: '(prefers-reduced-motion)' Pass Should be known: '(prefers-reduced-motion)'
Pass Should be known: '(prefers-reduced-motion: no-preference)' Pass Should be known: '(prefers-reduced-motion: no-preference)'
Pass Should be known: '(prefers-reduced-motion: reduce)' Pass Should be known: '(prefers-reduced-motion: reduce)'
Fail Should be parseable: '(prefers-reduced-motion: 0)' Pass Should be parseable: '(prefers-reduced-motion: 0)'
Pass Should be unknown: '(prefers-reduced-motion: 0)' Pass Should be unknown: '(prefers-reduced-motion: 0)'
Fail Should be parseable: '(prefers-reduced-motion: none)' Pass Should be parseable: '(prefers-reduced-motion: none)'
Pass Should be unknown: '(prefers-reduced-motion: none)' Pass Should be unknown: '(prefers-reduced-motion: none)'
Fail Should be parseable: '(prefers-reduced-motion: 10px)' Pass Should be parseable: '(prefers-reduced-motion: 10px)'
Pass Should be unknown: '(prefers-reduced-motion: 10px)' Pass Should be unknown: '(prefers-reduced-motion: 10px)'
Fail Should be parseable: '(prefers-reduced-motion: no-preference reduce)' Pass Should be parseable: '(prefers-reduced-motion: no-preference reduce)'
Pass Should be unknown: '(prefers-reduced-motion: no-preference reduce)' Pass Should be unknown: '(prefers-reduced-motion: no-preference reduce)'
Fail Should be parseable: '(prefers-reduced-motion: reduced)' Pass Should be parseable: '(prefers-reduced-motion: reduced)'
Pass Should be unknown: '(prefers-reduced-motion: reduced)' Pass Should be unknown: '(prefers-reduced-motion: reduced)'
Fail Should be parseable: '(prefers-reduced-motion: no-preference/reduce)' Pass Should be parseable: '(prefers-reduced-motion: no-preference/reduce)'
Pass Should be unknown: '(prefers-reduced-motion: no-preference/reduce)' Pass Should be unknown: '(prefers-reduced-motion: no-preference/reduce)'
Pass Check that no-preference evaluates to false in the boolean context Pass Check that no-preference evaluates to false in the boolean context

View file

@ -2,22 +2,21 @@ Harness status: OK
Found 17 tests Found 17 tests
11 Pass 17 Pass
6 Fail
Pass Should be known: '(prefers-reduced-transparency)' Pass Should be known: '(prefers-reduced-transparency)'
Pass Should be known: '(prefers-reduced-transparency: no-preference)' Pass Should be known: '(prefers-reduced-transparency: no-preference)'
Pass Should be known: '(prefers-reduced-transparency: reduce)' Pass Should be known: '(prefers-reduced-transparency: reduce)'
Fail Should be parseable: '(prefers-reduced-transparency: 0)' Pass Should be parseable: '(prefers-reduced-transparency: 0)'
Pass Should be unknown: '(prefers-reduced-transparency: 0)' Pass Should be unknown: '(prefers-reduced-transparency: 0)'
Fail Should be parseable: '(prefers-reduced-transparency: none)' Pass Should be parseable: '(prefers-reduced-transparency: none)'
Pass Should be unknown: '(prefers-reduced-transparency: none)' Pass Should be unknown: '(prefers-reduced-transparency: none)'
Fail Should be parseable: '(prefers-reduced-transparency: 10px)' Pass Should be parseable: '(prefers-reduced-transparency: 10px)'
Pass Should be unknown: '(prefers-reduced-transparency: 10px)' Pass Should be unknown: '(prefers-reduced-transparency: 10px)'
Fail Should be parseable: '(prefers-reduced-transparency: no-preference reduce)' Pass Should be parseable: '(prefers-reduced-transparency: no-preference reduce)'
Pass Should be unknown: '(prefers-reduced-transparency: no-preference reduce)' Pass Should be unknown: '(prefers-reduced-transparency: no-preference reduce)'
Fail Should be parseable: '(prefers-reduced-transparency: reduced)' Pass Should be parseable: '(prefers-reduced-transparency: reduced)'
Pass Should be unknown: '(prefers-reduced-transparency: reduced)' Pass Should be unknown: '(prefers-reduced-transparency: reduced)'
Fail Should be parseable: '(prefers-reduced-transparency: no-preference/reduce)' Pass Should be parseable: '(prefers-reduced-transparency: no-preference/reduce)'
Pass Should be unknown: '(prefers-reduced-transparency: no-preference/reduce)' Pass Should be unknown: '(prefers-reduced-transparency: no-preference/reduce)'
Pass Check that no-preference evaluates to false in the boolean context Pass Check that no-preference evaluates to false in the boolean context
Pass Check that invalid evaluates to false Pass Check that invalid evaluates to false

View file

@ -2,17 +2,16 @@ Harness status: OK
Found 12 tests Found 12 tests
9 Pass 12 Pass
3 Fail
Pass Should be known: '(scripting)' Pass Should be known: '(scripting)'
Pass Should be known: '(scripting: enabled)' Pass Should be known: '(scripting: enabled)'
Pass Should be known: '(scripting: initial-only)' Pass Should be known: '(scripting: initial-only)'
Pass Should be known: '(scripting: none)' Pass Should be known: '(scripting: none)'
Fail Should be parseable: '(scripting: 0)' Pass Should be parseable: '(scripting: 0)'
Pass Should be unknown: '(scripting: 0)' Pass Should be unknown: '(scripting: 0)'
Fail Should be parseable: '(scripting: 10px)' Pass Should be parseable: '(scripting: 10px)'
Pass Should be unknown: '(scripting: 10px)' Pass Should be unknown: '(scripting: 10px)'
Fail Should be parseable: '(scripting: invalid)' Pass Should be parseable: '(scripting: invalid)'
Pass Should be unknown: '(scripting: invalid)' Pass Should be unknown: '(scripting: invalid)'
Pass Check that scripting currently matches 'enabled' Pass Check that scripting currently matches 'enabled'
Pass Check that scripting currently evaluates to true in the boolean context Pass Check that scripting currently evaluates to true in the boolean context

View file

@ -2,17 +2,16 @@ Harness status: OK
Found 14 tests Found 14 tests
11 Pass 14 Pass
3 Fail
Pass Should be known: '(update)' Pass Should be known: '(update)'
Pass Should be known: '(update: none)' Pass Should be known: '(update: none)'
Pass Should be known: '(update: slow)' Pass Should be known: '(update: slow)'
Pass Should be known: '(update: fast)' Pass Should be known: '(update: fast)'
Fail Should be parseable: '(update: ?)' Pass Should be parseable: '(update: ?)'
Pass Should be unknown: '(update: ?)' Pass Should be unknown: '(update: ?)'
Fail Should be parseable: '(update: 10px)' Pass Should be parseable: '(update: 10px)'
Pass Should be unknown: '(update: 10px)' Pass Should be unknown: '(update: 10px)'
Fail Should be parseable: '(update: 0)' Pass Should be parseable: '(update: 0)'
Pass Should be unknown: '(update: 0)' Pass Should be unknown: '(update: 0)'
Pass Check that update: fast always matches fast displays Pass Check that update: fast always matches fast displays
Pass Check that update: slow doesn't match fast displays Pass Check that update: slow doesn't match fast displays