/* * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::CSS { // https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value class MediaFeatureValue { public: enum class Type : u8 { Ident, Length, Ratio, Resolution, Integer, Unknown, }; explicit MediaFeatureValue(Type type, NonnullRefPtr value) : m_type(type) , m_value(move(value)) { } String to_string(SerializationMode mode) const; bool is_ident() const { return m_type == Type::Ident; } bool is_length() const { return m_type == Type::Length; } bool is_integer() const { return m_type == Type::Integer; } bool is_ratio() const { return m_type == Type::Ratio; } bool is_resolution() const { return m_type == Type::Resolution; } bool is_unknown() const { return m_type == Type::Unknown; } bool is_same_type(MediaFeatureValue const& other) const { return m_type == other.m_type; } Keyword ident() const { VERIFY(is_ident()); return m_value->to_keyword(); } Length length(ComputationContext const& computation_context) const { VERIFY(is_length()); return Length::from_style_value(m_value->absolutized(computation_context), {}); } Ratio ratio(ComputationContext const& computation_context) const { VERIFY(is_ratio()); return m_value->absolutized(computation_context)->as_ratio().resolved(); } Resolution resolution(ComputationContext const& computation_context) const { VERIFY(is_resolution()); return Resolution::from_style_value(m_value->absolutized(computation_context)); } i32 integer(ComputationContext const& computation_context) const { VERIFY(is_integer()); return int_from_style_value(m_value->absolutized(computation_context)); } private: Type m_type; NonnullRefPtr 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 `` grammar static NonnullOwnPtr boolean(MediaFeatureID id) { return adopt_own(*new MediaFeature(Type::IsTrue, id)); } // Corresponds to `` grammar static NonnullOwnPtr plain(MediaFeatureID id, MediaFeatureValue value) { return adopt_own(*new MediaFeature(Type::ExactValue, move(id), move(value))); } static NonnullOwnPtr min(MediaFeatureID id, MediaFeatureValue value) { return adopt_own(*new MediaFeature(Type::MinValue, id, move(value))); } static NonnullOwnPtr max(MediaFeatureID id, MediaFeatureValue value) { return adopt_own(*new MediaFeature(Type::MaxValue, id, move(value))); } static NonnullOwnPtr half_range(MediaFeatureValue value, Comparison comparison, MediaFeatureID id) { return adopt_own(*new MediaFeature(Type::Range, id, Range { .left_value = move(value), .left_comparison = comparison, })); } static NonnullOwnPtr half_range(MediaFeatureID id, Comparison comparison, MediaFeatureValue value) { return adopt_own(*new MediaFeature(Type::Range, id, Range { .right_comparison = comparison, .right_value = move(value), })); } // Corresponds to `` grammar, with two comparisons static NonnullOwnPtr 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(DOM::Document 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 { Optional left_value {}; Optional left_comparison {}; Optional right_comparison {}; Optional right_value {}; }; MediaFeature(Type type, MediaFeatureID id, Variant value = {}) : m_type(type) , m_id(move(id)) , m_value(move(value)) { } static MatchResult compare(DOM::Document const& document, MediaFeatureValue const& left, Comparison comparison, MediaFeatureValue const& right); MediaFeatureValue const& value() const { return m_value.get(); } Range const& range() const { return m_value.get(); } Type m_type; MediaFeatureID m_id; Variant m_value {}; }; class MediaQuery : public RefCounted { 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 known_type; }; static NonnullRefPtr create_not_all(); static NonnullRefPtr create() { return adopt_ref(*new MediaQuery); } bool matches() const { return m_matches; } bool evaluate(DOM::Document const&); String to_string() const; void dump(StringBuilder&, int indent_levels = 0) 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 m_media_condition { nullptr }; // Cached value, updated by evaluate() bool m_matches { false }; }; String serialize_a_media_query_list(Vector> const&); Optional media_type_from_string(StringView); StringView to_string(MediaQuery::KnownMediaType); } namespace AK { template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::MediaFeature const& media_feature) { return Formatter::format(builder, media_feature.to_string()); } }; template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::MediaQuery const& media_query) { return Formatter::format(builder, media_query.to_string()); } }; }