LibWeb/CSS: Make media-type more permissive

The current spec defines this simply as `<ident>`, but does apparently
serialize as lowercase.

Because of this change, we no longer need to care about the deprecated
media types, as they all behave the same as unknown ones.

We still keep an enum around for KnownMediaType, to avoid repeated
string comparisons when evaluating it.

Gets us 2 WPT passes.
This commit is contained in:
Sam Atkins 2025-05-19 15:37:56 +01:00
commit 38aca62ef5
Notes: github-actions[bot] 2025-05-23 09:19:52 +00:00
4 changed files with 52 additions and 89 deletions

View file

@ -17,7 +17,10 @@ NonnullRefPtr<MediaQuery> MediaQuery::create_not_all()
{
auto media_query = new MediaQuery;
media_query->m_negated = true;
media_query->m_media_type = MediaType::All;
media_query->m_media_type = {
.name = "all"_fly_string,
.known_type = KnownMediaType::All,
};
return adopt_ref(*media_query);
}
@ -265,8 +268,12 @@ String MediaQuery::to_string() const
if (m_negated)
builder.append("not "sv);
if (m_negated || m_media_type != MediaType::All || !m_media_condition) {
builder.append(CSS::to_string(m_media_type));
if (m_negated || m_media_type.known_type != KnownMediaType::All || !m_media_condition) {
if (m_media_type.known_type.has_value()) {
builder.append(CSS::to_string(m_media_type.known_type.value()));
} else {
builder.append(serialize_an_identifier(m_media_type.name.to_ascii_lowercase()));
}
if (m_media_condition)
builder.append(" and "sv);
}
@ -280,28 +287,18 @@ String MediaQuery::to_string() const
bool MediaQuery::evaluate(HTML::Window const& window)
{
auto matches_media = [](MediaType media) -> MatchResult {
switch (media) {
case MediaType::All:
auto matches_media = [](MediaType const& media) -> MatchResult {
if (!media.known_type.has_value())
return MatchResult::False;
switch (media.known_type.value()) {
case KnownMediaType::All:
return MatchResult::True;
case MediaType::Print:
case KnownMediaType::Print:
// FIXME: Enable for printing, when we have printing!
return MatchResult::False;
case MediaType::Screen:
case KnownMediaType::Screen:
// FIXME: Disable for printing, when we have printing!
return MatchResult::True;
case MediaType::Unknown:
return MatchResult::False;
// Deprecated, must never match:
case MediaType::TTY:
case MediaType::TV:
case MediaType::Projection:
case MediaType::Handheld:
case MediaType::Braille:
case MediaType::Embossed:
case MediaType::Aural:
case MediaType::Speech:
return MatchResult::False;
}
VERIFY_NOT_REACHED();
};
@ -330,60 +327,26 @@ String serialize_a_media_query_list(Vector<NonnullRefPtr<MediaQuery>> const& med
return MUST(String::join(", "sv, media_queries));
}
MediaQuery::MediaType media_type_from_string(StringView name)
Optional<MediaQuery::KnownMediaType> media_type_from_string(StringView name)
{
if (name.equals_ignoring_ascii_case("all"sv))
return MediaQuery::MediaType::All;
if (name.equals_ignoring_ascii_case("aural"sv))
return MediaQuery::MediaType::Aural;
if (name.equals_ignoring_ascii_case("braille"sv))
return MediaQuery::MediaType::Braille;
if (name.equals_ignoring_ascii_case("embossed"sv))
return MediaQuery::MediaType::Embossed;
if (name.equals_ignoring_ascii_case("handheld"sv))
return MediaQuery::MediaType::Handheld;
return MediaQuery::KnownMediaType::All;
if (name.equals_ignoring_ascii_case("print"sv))
return MediaQuery::MediaType::Print;
if (name.equals_ignoring_ascii_case("projection"sv))
return MediaQuery::MediaType::Projection;
return MediaQuery::KnownMediaType::Print;
if (name.equals_ignoring_ascii_case("screen"sv))
return MediaQuery::MediaType::Screen;
if (name.equals_ignoring_ascii_case("speech"sv))
return MediaQuery::MediaType::Speech;
if (name.equals_ignoring_ascii_case("tty"sv))
return MediaQuery::MediaType::TTY;
if (name.equals_ignoring_ascii_case("tv"sv))
return MediaQuery::MediaType::TV;
return MediaQuery::MediaType::Unknown;
return MediaQuery::KnownMediaType::Screen;
return {};
}
StringView to_string(MediaQuery::MediaType media_type)
StringView to_string(MediaQuery::KnownMediaType media_type)
{
switch (media_type) {
case MediaQuery::MediaType::All:
case MediaQuery::KnownMediaType::All:
return "all"sv;
case MediaQuery::MediaType::Aural:
return "aural"sv;
case MediaQuery::MediaType::Braille:
return "braille"sv;
case MediaQuery::MediaType::Embossed:
return "embossed"sv;
case MediaQuery::MediaType::Handheld:
return "handheld"sv;
case MediaQuery::MediaType::Print:
case MediaQuery::KnownMediaType::Print:
return "print"sv;
case MediaQuery::MediaType::Projection:
return "projection"sv;
case MediaQuery::MediaType::Screen:
case MediaQuery::KnownMediaType::Screen:
return "screen"sv;
case MediaQuery::MediaType::Speech:
return "speech"sv;
case MediaQuery::MediaType::TTY:
return "tty"sv;
case MediaQuery::MediaType::TV:
return "tv"sv;
case MediaQuery::MediaType::Unknown:
return "unknown"sv;
}
VERIFY_NOT_REACHED();
}

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/FlyString.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
@ -189,21 +190,14 @@ public:
~MediaQuery() = default;
// https://www.w3.org/TR/mediaqueries-4/#media-types
enum class MediaType {
enum class KnownMediaType : u8 {
All,
Print,
Screen,
Unknown,
// Deprecated, must never match:
TTY,
TV,
Projection,
Handheld,
Braille,
Embossed,
Aural,
Speech,
};
struct MediaType {
FlyString name;
Optional<KnownMediaType> known_type;
};
static NonnullRefPtr<MediaQuery> create_not_all();
@ -218,7 +212,7 @@ private:
// https://www.w3.org/TR/mediaqueries-4/#mq-not
bool m_negated { false };
MediaType m_media_type { MediaType::All };
MediaType m_media_type { .name = "all"_fly_string, .known_type = KnownMediaType::All };
OwnPtr<BooleanExpression> m_media_condition { nullptr };
// Cached value, updated by evaluate()
@ -227,8 +221,8 @@ private:
String serialize_a_media_query_list(Vector<NonnullRefPtr<MediaQuery>> const&);
MediaQuery::MediaType media_type_from_string(StringView);
StringView to_string(MediaQuery::MediaType);
Optional<MediaQuery::KnownMediaType> media_type_from_string(StringView);
StringView to_string(MediaQuery::KnownMediaType);
}

View file

@ -112,13 +112,11 @@ NonnullRefPtr<MediaQuery> Parser::parse_media_query(TokenStream<ComponentValue>&
// `<media-type>`
if (auto media_type = parse_media_type(tokens); media_type.has_value()) {
// https://drafts.csswg.org/mediaqueries-4/#error-handling
// An unknown <media-type> must be treated as not matching.
if (media_type.value() == MediaQuery::MediaType::Unknown)
return invalid_media_query();
media_query->m_media_type = media_type.value();
media_query->m_media_type = media_type.release_value();
tokens.discard_whitespace();
} else {
// https://drafts.csswg.org/mediaqueries-4/#error-handling
// A media query that does not match the grammar in the previous section must be replaced by not all during parsing.
return invalid_media_query();
}
@ -415,10 +413,19 @@ Optional<MediaQuery::MediaType> Parser::parse_media_type(TokenStream<ComponentVa
if (!token.is(Token::Type::Ident))
return {};
// https://drafts.csswg.org/mediaqueries-3/#error-handling
// "However, an exception is made for media types layer, not, and, only, and or. Even though they do match
// the IDENT production, they must not be treated as unknown media types, but rather trigger the malformed query clause."
if (token.is_ident("layer"sv) || token.is_ident("not"sv) || token.is_ident("and"sv) || token.is_ident("only"sv) || token.is_ident("or"sv))
return {};
transaction.commit();
auto ident = token.token().ident();
return media_type_from_string(ident);
auto const& ident = token.token().ident();
return MediaQuery::MediaType {
.name = ident,
.known_type = media_type_from_string(ident),
};
}
// `<mf-value>`, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 34 tests
32 Pass
2 Fail
34 Pass
Pass Test parsing '' with matchMedia
Pass Test parsing ' ' with matchMedia
Pass Test parsing 'all' with matchMedia
@ -19,12 +18,12 @@ Pass Test parsing ' ( color ' with matchMedia
Pass Test parsing 'color)' with matchMedia
Pass Test parsing ' color)' with matchMedia
Pass Test parsing ' color ), ( color' with matchMedia
Fail Test parsing ' foo ' with matchMedia
Pass Test parsing ' foo ' with matchMedia
Pass Test parsing ',' with matchMedia
Pass Test parsing ' , ' with matchMedia
Pass Test parsing ',,' with matchMedia
Pass Test parsing ' , , ' with matchMedia
Fail Test parsing ' foo,' with matchMedia
Pass Test parsing ' foo,' with matchMedia
Pass Test parsing '(min-resolution: 1x)' with matchMedia
Pass Test parsing '(min-resolution: calc(1x))' with matchMedia
Pass Test parsing '(resolution: 2x)' with matchMedia