ladybird/Libraries/LibWeb/CSS/MediaQuery.h
Sam Atkins a4d3c62524 LibWeb/CSS: Return a MatchResult from MediaFeature::compare()
This commit on its own has no observable behaviour changes, as we still
only return True or False, but for the next commit, we'll need to be
able to return the Unknown state here, and without this change we'd get
regressions.
2025-05-23 10:17:58 +01:00

247 lines
7 KiB
C++

/*
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
#include <AK/RefCounted.h>
#include <LibWeb/CSS/BooleanExpression.h>
#include <LibWeb/CSS/CalculatedOr.h>
#include <LibWeb/CSS/MediaFeatureID.h>
#include <LibWeb/CSS/Ratio.h>
namespace Web::CSS {
// https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
class MediaFeatureValue {
public:
explicit MediaFeatureValue(Keyword ident)
: m_value(move(ident))
{
}
explicit MediaFeatureValue(LengthOrCalculated length)
: m_value(move(length))
{
}
explicit MediaFeatureValue(Ratio ratio)
: m_value(move(ratio))
{
}
explicit MediaFeatureValue(ResolutionOrCalculated resolution)
: m_value(move(resolution))
{
}
explicit MediaFeatureValue(IntegerOrCalculated integer)
: m_value(move(integer))
{
}
explicit MediaFeatureValue(i64 integer)
: m_value(IntegerOrCalculated(integer))
{
}
String to_string() const;
bool is_ident() const { return m_value.has<Keyword>(); }
bool is_length() const { return m_value.has<LengthOrCalculated>(); }
bool is_integer() const { return m_value.has<IntegerOrCalculated>(); }
bool is_ratio() const { return m_value.has<Ratio>(); }
bool is_resolution() const { return m_value.has<ResolutionOrCalculated>(); }
bool is_same_type(MediaFeatureValue const& other) const;
Keyword const& ident() const
{
VERIFY(is_ident());
return m_value.get<Keyword>();
}
LengthOrCalculated const& length() const
{
VERIFY(is_length());
return m_value.get<LengthOrCalculated>();
}
Ratio const& ratio() const
{
VERIFY(is_ratio());
return m_value.get<Ratio>();
}
ResolutionOrCalculated const& resolution() const
{
VERIFY(is_resolution());
return m_value.get<ResolutionOrCalculated>();
}
IntegerOrCalculated integer() const
{
VERIFY(is_integer());
return m_value.get<IntegerOrCalculated>();
}
private:
Variant<Keyword, LengthOrCalculated, Ratio, ResolutionOrCalculated, IntegerOrCalculated> m_value;
};
// https://www.w3.org/TR/mediaqueries-4/#mq-features
class MediaFeature final : public BooleanExpression {
public:
enum class Comparison : u8 {
Equal,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
};
// Corresponds to `<mf-boolean>` grammar
static NonnullOwnPtr<MediaFeature> boolean(MediaFeatureID id)
{
return adopt_own(*new MediaFeature(Type::IsTrue, id));
}
// Corresponds to `<mf-plain>` grammar
static NonnullOwnPtr<MediaFeature> plain(MediaFeatureID id, MediaFeatureValue value)
{
return adopt_own(*new MediaFeature(Type::ExactValue, move(id), move(value)));
}
static NonnullOwnPtr<MediaFeature> min(MediaFeatureID id, MediaFeatureValue value)
{
return adopt_own(*new MediaFeature(Type::MinValue, id, move(value)));
}
static NonnullOwnPtr<MediaFeature> max(MediaFeatureID id, MediaFeatureValue value)
{
return adopt_own(*new MediaFeature(Type::MaxValue, id, move(value)));
}
// Corresponds to `<mf-range>` grammar, with a single comparison
static NonnullOwnPtr<MediaFeature> half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id)
{
return adopt_own(*new MediaFeature(Type::Range, id,
Range {
.left_value = move(value),
.left_comparison = comparison,
}));
}
// Corresponds to `<mf-range>` grammar, with two comparisons
static NonnullOwnPtr<MediaFeature> range(MediaFeatureValue left_value, Comparison left_comparison, MediaFeatureID id, Comparison right_comparison, MediaFeatureValue right_value)
{
return adopt_own(*new MediaFeature(Type::Range, id,
Range {
.left_value = move(left_value),
.left_comparison = left_comparison,
.right_comparison = right_comparison,
.right_value = move(right_value),
}));
}
virtual MatchResult evaluate(HTML::Window const*) const override;
virtual String to_string() const override;
virtual void dump(StringBuilder&, int indent_levels = 0) const override;
private:
enum class Type : u8 {
IsTrue,
ExactValue,
MinValue,
MaxValue,
Range,
};
struct Range {
MediaFeatureValue left_value;
Comparison left_comparison;
Optional<Comparison> right_comparison {};
Optional<MediaFeatureValue> right_value {};
};
MediaFeature(Type type, MediaFeatureID id, Variant<Empty, MediaFeatureValue, Range> value = {})
: m_type(type)
, m_id(move(id))
, m_value(move(value))
{
}
static MatchResult compare(HTML::Window const& window, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right);
MediaFeatureValue const& value() const { return m_value.get<MediaFeatureValue>(); }
Range const& range() const { return m_value.get<Range>(); }
Type m_type;
MediaFeatureID m_id;
Variant<Empty, MediaFeatureValue, Range> m_value {};
};
class MediaQuery : public RefCounted<MediaQuery> {
friend class Parser::Parser;
public:
~MediaQuery() = default;
// https://www.w3.org/TR/mediaqueries-4/#media-types
enum class KnownMediaType : u8 {
All,
Print,
Screen,
};
struct MediaType {
FlyString name;
Optional<KnownMediaType> known_type;
};
static NonnullRefPtr<MediaQuery> create_not_all();
static NonnullRefPtr<MediaQuery> create() { return adopt_ref(*new MediaQuery); }
bool matches() const { return m_matches; }
bool evaluate(HTML::Window const&);
String to_string() const;
private:
MediaQuery() = default;
// https://www.w3.org/TR/mediaqueries-4/#mq-not
bool m_negated { false };
MediaType m_media_type { .name = "all"_fly_string, .known_type = KnownMediaType::All };
OwnPtr<BooleanExpression> m_media_condition { nullptr };
// Cached value, updated by evaluate()
bool m_matches { false };
};
String serialize_a_media_query_list(Vector<NonnullRefPtr<MediaQuery>> const&);
Optional<MediaQuery::KnownMediaType> media_type_from_string(StringView);
StringView to_string(MediaQuery::KnownMediaType);
}
namespace AK {
template<>
struct Formatter<Web::CSS::MediaFeature> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaFeature const& media_feature)
{
return Formatter<StringView>::format(builder, media_feature.to_string());
}
};
template<>
struct Formatter<Web::CSS::MediaQuery> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, Web::CSS::MediaQuery const& media_query)
{
return Formatter<StringView>::format(builder, media_query.to_string());
}
};
}