Pārlūkot izejas kodu

LibWeb: Introduce MediaFeatureValue type for use in media queries

Previously, we were using StyleValues for this, which was a bit of a
hack and was brittle, breaking when I modified how custom properties
were parsed. This is better and also lets us limit the kinds of value
that can be used here, to match the spec.
Sam Atkins 3 gadi atpakaļ
vecāks
revīzija
6299d68e45

+ 74 - 23
Userland/Libraries/LibWeb/CSS/MediaQuery.cpp

@@ -5,6 +5,7 @@
  */
 
 #include <LibWeb/CSS/MediaQuery.h>
+#include <LibWeb/CSS/Serialize.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Window.h>
 
@@ -19,6 +20,45 @@ NonnullRefPtr<MediaQuery> MediaQuery::create_not_all()
     return adopt_ref(*media_query);
 }
 
+String MediaFeatureValue::to_string() const
+{
+    return m_value.visit(
+        [](String& ident) { return serialize_an_identifier(ident); },
+        [](Length& length) { return length.to_string(); },
+        [](double number) { return String::number(number); });
+}
+
+bool MediaFeatureValue::is_same_type(MediaFeatureValue const& other) const
+{
+    return m_value.visit(
+        [&](String&) { return other.is_ident(); },
+        [&](Length&) { return other.is_length(); },
+        [&](double) { return other.is_number(); });
+}
+
+bool MediaFeatureValue::equals(MediaFeatureValue const& other) const
+{
+    if (!is_same_type(other))
+        return false;
+
+    if (is_ident() && other.is_ident())
+        return m_value.get<String>().equals_ignoring_case(other.m_value.get<String>());
+    if (is_length() && other.is_length()) {
+        // FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
+        auto& my_length = m_value.get<Length>();
+        auto& other_length = other.m_value.get<Length>();
+        if (!my_length.is_absolute() || !other_length.is_absolute()) {
+            dbgln("TODO: Support relative lengths in media queries!");
+            return false;
+        }
+        return my_length.absolute_length_to_px() == other_length.absolute_length_to_px();
+    }
+    if (is_number() && other.is_number())
+        return m_value.get<double>() == other.m_value.get<double>();
+
+    VERIFY_NOT_REACHED();
+}
+
 String MediaQuery::MediaFeature::to_string() const
 {
     switch (type) {
@@ -37,51 +77,62 @@ String MediaQuery::MediaFeature::to_string() const
 
 bool MediaQuery::MediaFeature::evaluate(DOM::Window const& window) const
 {
-    auto queried_value = window.query_media_feature(name);
-    if (!queried_value)
+    auto maybe_queried_value = window.query_media_feature(name);
+    if (!maybe_queried_value.has_value())
         return false;
+    auto queried_value = maybe_queried_value.release_value();
 
     switch (type) {
     case Type::IsTrue:
-        if (queried_value->has_number())
-            return queried_value->to_number() != 0;
-        if (queried_value->has_length())
-            return queried_value->to_length().raw_value() != 0;
-        if (queried_value->has_identifier())
-            return queried_value->to_identifier() != ValueID::None;
+        if (queried_value.is_number())
+            return queried_value.number() != 0;
+        if (queried_value.is_length())
+            return queried_value.length().raw_value() != 0;
+        if (queried_value.is_ident())
+            return queried_value.ident() != "none";
         return false;
 
     case Type::ExactValue:
-        return queried_value->equals(*value);
+        return queried_value.equals(*value);
 
     case Type::MinValue:
-        if (queried_value->has_number() && value->has_number())
-            return queried_value->to_number() >= value->to_number();
-        if (queried_value->has_length() && value->has_length()) {
-            auto queried_length = queried_value->to_length();
-            auto value_length = value->to_length();
-            // FIXME: We should be checking that lengths are valid during parsing
+        if (!value->is_same_type(queried_value))
+            return false;
+
+        if (value->is_number())
+            return queried_value.number() >= value->number();
+
+        if (value->is_length()) {
+            auto& queried_length = queried_value.length();
+            auto& value_length = value->length();
+            // FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
             if (!value_length.is_absolute()) {
-                dbgln("Media feature was given a non-absolute length, which is invalid! {}", value_length.to_string());
+                dbgln("Media feature was given a non-absolute length! {}", value_length.to_string());
                 return false;
             }
             return queried_length.absolute_length_to_px() >= value_length.absolute_length_to_px();
         }
+
         return false;
 
     case Type::MaxValue:
-        if (queried_value->has_number() && value->has_number())
-            return queried_value->to_number() <= value->to_number();
-        if (queried_value->has_length() && value->has_length()) {
-            auto queried_length = queried_value->to_length();
-            auto value_length = value->to_length();
-            // FIXME: We should be checking that lengths are valid during parsing
+        if (!value->is_same_type(queried_value))
+            return false;
+
+        if (value->is_number())
+            return queried_value.number() <= value->number();
+
+        if (value->is_length()) {
+            auto& queried_length = queried_value.length();
+            auto& value_length = value->length();
+            // FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
             if (!value_length.is_absolute()) {
-                dbgln("Media feature was given a non-absolute length, which is invalid! {}", value_length.to_string());
+                dbgln("Media feature was given a non-absolute length! {}", value_length.to_string());
                 return false;
             }
             return queried_length.absolute_length_to_px() <= value_length.absolute_length_to_px();
         }
+
         return false;
     }
 

+ 53 - 1
Userland/Libraries/LibWeb/CSS/MediaQuery.h

@@ -17,6 +17,58 @@
 
 namespace Web::CSS {
 
+// https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
+class MediaFeatureValue {
+public:
+    explicit MediaFeatureValue(String ident)
+        : m_value(move(ident))
+    {
+    }
+
+    explicit MediaFeatureValue(Length length)
+        : m_value(move(length))
+    {
+    }
+
+    explicit MediaFeatureValue(double number)
+        : m_value(number)
+    {
+    }
+
+    String to_string() const;
+
+    bool is_ident() const { return m_value.has<String>(); }
+    bool is_length() const { return m_value.has<Length>(); }
+    bool is_number() const { return m_value.has<double>(); }
+    bool is_same_type(MediaFeatureValue const& other) const;
+
+    String const& ident() const
+    {
+        VERIFY(is_ident());
+        return m_value.get<String>();
+    }
+
+    Length const& length() const
+    {
+        VERIFY(is_length());
+        return m_value.get<Length>();
+    }
+
+    double number() const
+    {
+        VERIFY(is_number());
+        return m_value.get<double>();
+    }
+
+    bool operator==(MediaFeatureValue const& other) const { return equals(other); }
+    bool operator!=(MediaFeatureValue const& other) const { return !(*this == other); }
+    bool equals(MediaFeatureValue const& other) const;
+
+private:
+    // TODO: Support <ratio> once we have that.
+    Variant<String, Length, double> m_value;
+};
+
 class MediaQuery : public RefCounted<MediaQuery> {
     friend class Parser;
 
@@ -52,7 +104,7 @@ public:
 
         Type type;
         FlyString name;
-        RefPtr<StyleValue> value { nullptr };
+        Optional<MediaFeatureValue> value {};
 
         bool evaluate(DOM::Window const&) const;
         String to_string() const;

+ 28 - 2
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -895,8 +895,8 @@ Optional<MediaQuery::MediaFeature> Parser::consume_media_feature(TokenStream<Sty
             return invalid_feature();
         tokens.skip_whitespace();
 
-        auto value = parse_css_value(PropertyID::Custom, tokens);
-        if (value.is_error())
+        auto value = parse_media_feature_value(tokens);
+        if (!value.has_value())
             return invalid_feature();
 
         if (tokens.has_next_token())
@@ -964,6 +964,32 @@ Optional<MediaQuery::MediaType> Parser::consume_media_type(TokenStream<StyleComp
     return {};
 }
 
+// `<mf-value>`, https://www.w3.org/TR/mediaqueries-4/#typedef-mf-value
+Optional<MediaFeatureValue> Parser::parse_media_feature_value(TokenStream<StyleComponentValueRule>& tokens)
+{
+    // `<number> | <dimension> | <ident> | <ratio>`
+    auto position = tokens.position();
+    tokens.skip_whitespace();
+    auto& first = tokens.next_token();
+
+    // `<number>`
+    if (first.is(Token::Type::Number))
+        return MediaFeatureValue(first.token().number_value());
+
+    // `<dimension>`
+    if (auto length = parse_length(first); length.has_value())
+        return MediaFeatureValue(length.release_value());
+
+    // `<ident>`
+    if (first.is(Token::Type::Ident))
+        return MediaFeatureValue(first.token().ident());
+
+    // FIXME: `<ratio>`, once we have ratios.
+
+    tokens.rewind_to_position(position);
+    return {};
+}
+
 RefPtr<Supports> Parser::parse_as_supports()
 {
     return parse_a_supports(m_token_stream);

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -238,6 +238,7 @@ private:
     OwnPtr<MediaQuery::MediaCondition> consume_media_condition(TokenStream<StyleComponentValueRule>&);
     Optional<MediaQuery::MediaFeature> consume_media_feature(TokenStream<StyleComponentValueRule>&);
     Optional<MediaQuery::MediaType> consume_media_type(TokenStream<StyleComponentValueRule>&);
+    Optional<MediaFeatureValue> parse_media_feature_value(TokenStream<StyleComponentValueRule>&);
 
     OwnPtr<Supports::Condition> parse_supports_condition(TokenStream<StyleComponentValueRule>&);
     Optional<Supports::InParens> parse_supports_in_parens(TokenStream<StyleComponentValueRule>&);

+ 19 - 19
Userland/Libraries/LibWeb/DOM/Window.cpp

@@ -291,59 +291,59 @@ NonnullRefPtr<CSS::MediaQueryList> Window::match_media(String media)
     return media_query_list;
 }
 
-RefPtr<CSS::StyleValue> Window::query_media_feature(FlyString const& name) const
+Optional<CSS::MediaFeatureValue> Window::query_media_feature(FlyString const& name) const
 {
     // FIXME: Many of these should be dependent on the hardware
 
     // MEDIAQUERIES-4 properties - https://www.w3.org/TR/mediaqueries-4/#media-descriptor-table
     if (name.equals_ignoring_case("any-hover"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Hover);
+        return CSS::MediaFeatureValue("hover");
     if (name.equals_ignoring_case("any-pointer"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Fine);
+        return CSS::MediaFeatureValue("fine");
     // FIXME: aspect-ratio
     if (name.equals_ignoring_case("color"sv))
-        return CSS::NumericStyleValue::create_integer(32);
+        return CSS::MediaFeatureValue(32);
     if (name.equals_ignoring_case("color-gamut"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Srgb);
+        return CSS::MediaFeatureValue("srgb");
     if (name.equals_ignoring_case("color-index"sv))
-        return CSS::NumericStyleValue::create_integer(0);
+        return CSS::MediaFeatureValue(0);
     // FIXME: device-aspect-ratio
     // FIXME: device-height
     // FIXME: device-width
     if (name.equals_ignoring_case("grid"sv))
-        return CSS::NumericStyleValue::create_integer(0);
+        return CSS::MediaFeatureValue(0);
     if (name.equals_ignoring_case("height"sv))
-        return CSS::LengthStyleValue::create(CSS::Length::make_px(inner_height()));
+        return CSS::MediaFeatureValue(CSS::Length::make_px(inner_height()));
     if (name.equals_ignoring_case("hover"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Hover);
+        return CSS::MediaFeatureValue("hover");
     if (name.equals_ignoring_case("monochrome"sv))
-        return CSS::NumericStyleValue::create_integer(0);
+        return CSS::MediaFeatureValue(0);
     if (name.equals_ignoring_case("hover"sv))
-        return CSS::IdentifierStyleValue::create(inner_height() >= inner_width() ? CSS::ValueID::Portrait : CSS::ValueID::Landscape);
+        return CSS::MediaFeatureValue(inner_height() >= inner_width() ? "portrait" : "landscape");
     if (name.equals_ignoring_case("overflow-block"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Scroll);
+        return CSS::MediaFeatureValue("scroll");
     // FIXME: overflow-inline
     if (name.equals_ignoring_case("pointer"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Fine);
+        return CSS::MediaFeatureValue("fine");
     // FIXME: resolution
     if (name.equals_ignoring_case("scan"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Progressive);
+        return CSS::MediaFeatureValue("progressive");
     if (name.equals_ignoring_case("update"sv))
-        return CSS::IdentifierStyleValue::create(CSS::ValueID::Fast);
+        return CSS::MediaFeatureValue("fast");
     if (name.equals_ignoring_case("width"sv))
-        return CSS::LengthStyleValue::create(CSS::Length::make_px(inner_width()));
+        return CSS::MediaFeatureValue(CSS::Length::make_px(inner_width()));
 
     // MEDIAQUERIES-5 properties - https://www.w3.org/TR/mediaqueries-5/#media-descriptor-table
     if (name.equals_ignoring_case("prefers-color-scheme")) {
         if (auto* page = this->page()) {
             switch (page->preferred_color_scheme()) {
             case CSS::PreferredColorScheme::Light:
-                return CSS::IdentifierStyleValue::create(CSS::ValueID::Light);
+                return CSS::MediaFeatureValue("light");
             case CSS::PreferredColorScheme::Dark:
-                return CSS::IdentifierStyleValue::create(CSS::ValueID::Dark);
+                return CSS::MediaFeatureValue("dark");
             case CSS::PreferredColorScheme::Auto:
             default:
-                return CSS::IdentifierStyleValue::create(page->palette().is_dark() ? CSS::ValueID::Dark : CSS::ValueID::Light);
+                return CSS::MediaFeatureValue(page->palette().is_dark() ? "dark" : "light");
             }
         }
     }

+ 1 - 1
Userland/Libraries/LibWeb/DOM/Window.h

@@ -84,7 +84,7 @@ public:
 
     NonnullRefPtr<CSS::CSSStyleDeclaration> get_computed_style(DOM::Element&) const;
     NonnullRefPtr<CSS::MediaQueryList> match_media(String);
-    RefPtr<CSS::StyleValue> query_media_feature(FlyString const&) const;
+    Optional<CSS::MediaFeatureValue> query_media_feature(FlyString const&) const;
 
     float scroll_x() const;
     float scroll_y() const;