mirror of
https://github.com/RGBCube/serenity
synced 2025-06-01 06:18:12 +00:00
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.
This commit is contained in:
parent
0a8e289f37
commit
6299d68e45
6 changed files with 176 additions and 46 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>&);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue