mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:47:34 +00:00
LibWeb: Support range syntax for media queries
This means you can now do queries like: ```css @media (400px <= width < 800px) { } ``` Chromium and Firefox which I tested with both don't support this yet, so that's cool. :^)
This commit is contained in:
parent
c3bf9e5b79
commit
416033a660
4 changed files with 366 additions and 43 deletions
|
@ -41,6 +41,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only all and (width > 399px) {
|
||||||
|
.size-min-range {
|
||||||
|
background-color: lime;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
.size-max {
|
.size-max {
|
||||||
background-color: lime;
|
background-color: lime;
|
||||||
|
@ -48,6 +55,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (1001px > width) {
|
||||||
|
.size-max-range {
|
||||||
|
background-color: lime;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 400px) and (max-width: 1000px) {
|
@media (min-width: 400px) and (max-width: 1000px) {
|
||||||
.size-range {
|
.size-range {
|
||||||
background-color: lime;
|
background-color: lime;
|
||||||
|
@ -55,6 +69,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (400px <= width <= 1000px) {
|
||||||
|
.size-range-syntax {
|
||||||
|
background-color: lime;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (color) {
|
@media (color) {
|
||||||
.color {
|
.color {
|
||||||
background-color: lime;
|
background-color: lime;
|
||||||
|
@ -90,12 +111,21 @@
|
||||||
<p class="size-min">
|
<p class="size-min">
|
||||||
This should be green, with a black border and black text, if the window is at least 400px wide.
|
This should be green, with a black border and black text, if the window is at least 400px wide.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="size-min-range">
|
||||||
|
This should be green, with a black border and black text, if the window is at least 400px wide, and we understand range syntax.
|
||||||
|
</p>
|
||||||
<p class="size-max">
|
<p class="size-max">
|
||||||
This should be green, with a black border and black text, if the window is at most 1000px wide.
|
This should be green, with a black border and black text, if the window is at most 1000px wide.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="size-max-range">
|
||||||
|
This should be green, with a black border and black text, if the window is at most 1000px wide, and we understand range syntax.
|
||||||
|
</p>
|
||||||
<p class="size-range">
|
<p class="size-range">
|
||||||
This should be green, with a black border and black text, if the window is between 400px and 1000px wide.
|
This should be green, with a black border and black text, if the window is between 400px and 1000px wide.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="size-range-syntax">
|
||||||
|
This should be green, with a black border and black text, if the window is between 400px and 1000px wide, and we understand range syntax.
|
||||||
|
</p>
|
||||||
<p class="color">
|
<p class="color">
|
||||||
This should be green, with a black border and black text, if we detected the <code>color</code> feature.
|
This should be green, with a black border and black text, if we detected the <code>color</code> feature.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -61,15 +61,36 @@ bool MediaFeatureValue::equals(MediaFeatureValue const& other) const
|
||||||
|
|
||||||
String MediaFeature::to_string() const
|
String MediaFeature::to_string() const
|
||||||
{
|
{
|
||||||
|
auto comparison_string = [](Comparison comparison) -> StringView {
|
||||||
|
switch (comparison) {
|
||||||
|
case Comparison::Equal:
|
||||||
|
return "="sv;
|
||||||
|
case Comparison::LessThan:
|
||||||
|
return "<"sv;
|
||||||
|
case Comparison::LessThanOrEqual:
|
||||||
|
return "<="sv;
|
||||||
|
case Comparison::GreaterThan:
|
||||||
|
return ">"sv;
|
||||||
|
case Comparison::GreaterThanOrEqual:
|
||||||
|
return ">="sv;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
|
||||||
switch (m_type) {
|
switch (m_type) {
|
||||||
case Type::IsTrue:
|
case Type::IsTrue:
|
||||||
return m_name;
|
return serialize_an_identifier(m_name);
|
||||||
case Type::ExactValue:
|
case Type::ExactValue:
|
||||||
return String::formatted("{}:{}", m_name, m_value->to_string());
|
return String::formatted("{}:{}", serialize_an_identifier(m_name), m_value->to_string());
|
||||||
case Type::MinValue:
|
case Type::MinValue:
|
||||||
return String::formatted("min-{}:{}", m_name, m_value->to_string());
|
return String::formatted("min-{}:{}", serialize_an_identifier(m_name), m_value->to_string());
|
||||||
case Type::MaxValue:
|
case Type::MaxValue:
|
||||||
return String::formatted("max-{}:{}", m_name, m_value->to_string());
|
return String::formatted("max-{}:{}", serialize_an_identifier(m_name), m_value->to_string());
|
||||||
|
case Type::Range:
|
||||||
|
if (!m_range->right_comparison.has_value())
|
||||||
|
return String::formatted("{} {} {}", m_range->left_value.to_string(), comparison_string(m_range->left_comparison), serialize_an_identifier(m_name));
|
||||||
|
|
||||||
|
return String::formatted("{} {} {} {} {}", m_range->left_value.to_string(), comparison_string(m_range->left_comparison), serialize_an_identifier(m_name), comparison_string(*m_range->right_comparison), m_range->right_value->to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
@ -93,47 +114,79 @@ bool MediaFeature::evaluate(DOM::Window const& window) const
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case Type::ExactValue:
|
case Type::ExactValue:
|
||||||
return queried_value.equals(*m_value);
|
return compare(*m_value, Comparison::Equal, queried_value);
|
||||||
|
|
||||||
case Type::MinValue:
|
case Type::MinValue:
|
||||||
if (!m_value->is_same_type(queried_value))
|
return compare(queried_value, Comparison::GreaterThanOrEqual, *m_value);
|
||||||
return false;
|
|
||||||
|
|
||||||
if (m_value->is_number())
|
|
||||||
return queried_value.number() >= m_value->number();
|
|
||||||
|
|
||||||
if (m_value->is_length()) {
|
|
||||||
auto& queried_length = queried_value.length();
|
|
||||||
auto& value_length = m_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! {}", value_length.to_string());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return queried_length.absolute_length_to_px() >= value_length.absolute_length_to_px();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case Type::MaxValue:
|
case Type::MaxValue:
|
||||||
if (!m_value->is_same_type(queried_value))
|
return compare(queried_value, Comparison::LessThanOrEqual, *m_value);
|
||||||
|
|
||||||
|
case Type::Range:
|
||||||
|
if (!compare(m_range->left_value, m_range->left_comparison, queried_value))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (m_value->is_number())
|
if (m_range->right_comparison.has_value())
|
||||||
return queried_value.number() <= m_value->number();
|
if (!compare(queried_value, *m_range->right_comparison, *m_range->right_value))
|
||||||
|
|
||||||
if (m_value->is_length()) {
|
|
||||||
auto& queried_length = queried_value.length();
|
|
||||||
auto& value_length = m_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! {}", value_length.to_string());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return queried_length.absolute_length_to_px() <= value_length.absolute_length_to_px();
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MediaFeature::compare(MediaFeatureValue left, Comparison comparison, MediaFeatureValue right)
|
||||||
|
{
|
||||||
|
if (!left.is_same_type(right))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (left.is_ident()) {
|
||||||
|
if (comparison == Comparison::Equal)
|
||||||
|
return left.ident().equals_ignoring_case(right.ident());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.is_number()) {
|
||||||
|
switch (comparison) {
|
||||||
|
case Comparison::Equal:
|
||||||
|
return left.number() == right.number();
|
||||||
|
case Comparison::LessThan:
|
||||||
|
return left.number() < right.number();
|
||||||
|
case Comparison::LessThanOrEqual:
|
||||||
|
return left.number() <= right.number();
|
||||||
|
case Comparison::GreaterThan:
|
||||||
|
return left.number() > right.number();
|
||||||
|
case Comparison::GreaterThanOrEqual:
|
||||||
|
return left.number() >= right.number();
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.is_length()) {
|
||||||
|
// FIXME: Handle relative lengths. https://www.w3.org/TR/mediaqueries-4/#ref-for-relative-length
|
||||||
|
if (!left.length().is_absolute() || !right.length().is_absolute()) {
|
||||||
|
dbgln("TODO: Support relative lengths in media queries!");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
auto left_px = left.length().absolute_length_to_px();
|
||||||
|
auto right_px = right.length().absolute_length_to_px();
|
||||||
|
|
||||||
|
switch (comparison) {
|
||||||
|
case Comparison::Equal:
|
||||||
|
return left_px == right_px;
|
||||||
|
case Comparison::LessThan:
|
||||||
|
return left_px < right_px;
|
||||||
|
case Comparison::LessThanOrEqual:
|
||||||
|
return left_px <= right_px;
|
||||||
|
case Comparison::GreaterThan:
|
||||||
|
return left_px > right_px;
|
||||||
|
case Comparison::GreaterThanOrEqual:
|
||||||
|
return left_px >= right_px;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
@ -333,4 +386,58 @@ String serialize_a_media_query_list(NonnullRefPtrVector<MediaQuery> const& media
|
||||||
return builder.to_string();
|
return builder.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_media_feature_name(StringView name)
|
||||||
|
{
|
||||||
|
// MEDIAQUERIES-4 - https://www.w3.org/TR/mediaqueries-4/#media-descriptor-table
|
||||||
|
if (name.equals_ignoring_case("any-hover"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("any-pointer"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("aspect-ratio"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("color"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("color-gamut"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("color-index"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("device-aspect-ratio"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("device-height"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("device-width"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("grid"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("height"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("hover"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("monochrome"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("orientation"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("overflow-block"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("overflow-inline"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("pointer"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("resolution"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("scan"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("update"sv))
|
||||||
|
return true;
|
||||||
|
if (name.equals_ignoring_case("width"sv))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// MEDIAQUERIES-5 - https://www.w3.org/TR/mediaqueries-5/#media-descriptor-table
|
||||||
|
if (name.equals_ignoring_case("prefers-color-scheme"sv))
|
||||||
|
return true;
|
||||||
|
// FIXME: Add other level 5 feature names
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,14 @@ private:
|
||||||
// https://www.w3.org/TR/mediaqueries-4/#mq-features
|
// https://www.w3.org/TR/mediaqueries-4/#mq-features
|
||||||
class MediaFeature {
|
class MediaFeature {
|
||||||
public:
|
public:
|
||||||
|
enum class Comparison {
|
||||||
|
Equal,
|
||||||
|
LessThan,
|
||||||
|
LessThanOrEqual,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
};
|
||||||
|
|
||||||
// Corresponds to `<mf-boolean>` grammar
|
// Corresponds to `<mf-boolean>` grammar
|
||||||
static MediaFeature boolean(String const& name)
|
static MediaFeature boolean(String const& name)
|
||||||
{
|
{
|
||||||
|
@ -88,16 +96,40 @@ public:
|
||||||
return MediaFeature(Type::ExactValue, move(name), move(value));
|
return MediaFeature(Type::ExactValue, move(name), move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Corresponds to `<mf-range>` grammar, with a single comparison
|
||||||
|
static MediaFeature half_range(MediaFeatureValue value, Comparison comparison, String const& name)
|
||||||
|
{
|
||||||
|
MediaFeature feature { Type::Range, name };
|
||||||
|
feature.m_range = Range {
|
||||||
|
.left_value = value,
|
||||||
|
.left_comparison = comparison,
|
||||||
|
};
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Corresponds to `<mf-range>` grammar, with two comparisons
|
||||||
|
static MediaFeature range(MediaFeatureValue left_value, Comparison left_comparison, String const& name, Comparison right_comparison, MediaFeatureValue right_value)
|
||||||
|
{
|
||||||
|
MediaFeature feature { Type::Range, name };
|
||||||
|
feature.m_range = Range {
|
||||||
|
.left_value = left_value,
|
||||||
|
.left_comparison = left_comparison,
|
||||||
|
.right_comparison = right_comparison,
|
||||||
|
.right_value = right_value,
|
||||||
|
};
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
bool evaluate(DOM::Window const&) const;
|
bool evaluate(DOM::Window const&) const;
|
||||||
String to_string() const;
|
String to_string() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// FIXME: Implement range syntax: https://www.w3.org/TR/mediaqueries-4/#mq-ranges
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
IsTrue,
|
IsTrue,
|
||||||
ExactValue,
|
ExactValue,
|
||||||
MinValue,
|
MinValue,
|
||||||
MaxValue,
|
MaxValue,
|
||||||
|
Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaFeature(Type type, FlyString name, Optional<MediaFeatureValue> value = {})
|
MediaFeature(Type type, FlyString name, Optional<MediaFeatureValue> value = {})
|
||||||
|
@ -107,9 +139,19 @@ private:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool compare(MediaFeatureValue left, Comparison comparison, MediaFeatureValue right);
|
||||||
|
|
||||||
|
struct Range {
|
||||||
|
MediaFeatureValue left_value;
|
||||||
|
Comparison left_comparison;
|
||||||
|
Optional<Comparison> right_comparison {};
|
||||||
|
Optional<MediaFeatureValue> right_value {};
|
||||||
|
};
|
||||||
|
|
||||||
Type m_type;
|
Type m_type;
|
||||||
FlyString m_name;
|
FlyString m_name;
|
||||||
Optional<MediaFeatureValue> m_value {};
|
Optional<MediaFeatureValue> m_value {};
|
||||||
|
Optional<Range> m_range {};
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://www.w3.org/TR/mediaqueries-4/#media-conditions
|
// https://www.w3.org/TR/mediaqueries-4/#media-conditions
|
||||||
|
@ -189,6 +231,8 @@ private:
|
||||||
|
|
||||||
String serialize_a_media_query_list(NonnullRefPtrVector<MediaQuery> const&);
|
String serialize_a_media_query_list(NonnullRefPtrVector<MediaQuery> const&);
|
||||||
|
|
||||||
|
bool is_media_feature_name(StringView name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace AK {
|
namespace AK {
|
||||||
|
|
|
@ -869,9 +869,23 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<StyleComponentVal
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
|
|
||||||
// `<mf-name> = <ident>`
|
// `<mf-name> = <ident>`
|
||||||
auto parse_mf_name = [](StyleComponentValueRule const& token) -> Optional<String> {
|
auto parse_mf_name = [](auto& tokens, bool allow_min_max_prefix) -> Optional<String> {
|
||||||
if (token.is(Token::Type::Ident))
|
auto& token = tokens.peek_token();
|
||||||
return token.token().ident();
|
if (token.is(Token::Type::Ident)) {
|
||||||
|
auto name = token.token().ident();
|
||||||
|
if (is_media_feature_name(name)) {
|
||||||
|
tokens.next_token();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allow_min_max_prefix && (name.starts_with("min-", CaseSensitivity::CaseInsensitive) || name.starts_with("max-", CaseSensitivity::CaseInsensitive))) {
|
||||||
|
auto adjusted_name = name.substring_view(4);
|
||||||
|
if (is_media_feature_name(adjusted_name)) {
|
||||||
|
tokens.next_token();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -880,7 +894,7 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<StyleComponentVal
|
||||||
auto position = tokens.position();
|
auto position = tokens.position();
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
|
|
||||||
auto maybe_name = parse_mf_name(tokens.next_token());
|
auto maybe_name = parse_mf_name(tokens, false);
|
||||||
if (maybe_name.has_value()) {
|
if (maybe_name.has_value()) {
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
if (!tokens.has_next_token())
|
if (!tokens.has_next_token())
|
||||||
|
@ -896,7 +910,7 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<StyleComponentVal
|
||||||
auto position = tokens.position();
|
auto position = tokens.position();
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
|
|
||||||
if (auto maybe_name = parse_mf_name(tokens.next_token()); maybe_name.has_value()) {
|
if (auto maybe_name = parse_mf_name(tokens, true); maybe_name.has_value()) {
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
if (tokens.next_token().is(Token::Type::Colon)) {
|
if (tokens.next_token().is(Token::Type::Colon)) {
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
|
@ -912,13 +926,141 @@ Optional<MediaFeature> Parser::parse_media_feature(TokenStream<StyleComponentVal
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// `<mf-lt> = '<' '='?
|
||||||
|
// <mf-gt> = '>' '='?
|
||||||
|
// <mf-eq> = '='
|
||||||
|
// <mf-comparison> = <mf-lt> | <mf-gt> | <mf-eq>`
|
||||||
|
auto parse_comparison = [](auto& tokens) -> Optional<MediaFeature::Comparison> {
|
||||||
|
auto position = tokens.position();
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
|
||||||
|
auto& first = tokens.next_token();
|
||||||
|
if (first.is(Token::Type::Delim)) {
|
||||||
|
auto first_delim = first.token().delim();
|
||||||
|
if (first_delim == "="sv)
|
||||||
|
return MediaFeature::Comparison::Equal;
|
||||||
|
if (first_delim == "<"sv) {
|
||||||
|
auto& second = tokens.peek_token();
|
||||||
|
if (second.is(Token::Type::Delim) && second.token().delim() == "="sv) {
|
||||||
|
tokens.next_token();
|
||||||
|
return MediaFeature::Comparison::LessThanOrEqual;
|
||||||
|
}
|
||||||
|
return MediaFeature::Comparison::LessThan;
|
||||||
|
}
|
||||||
|
if (first_delim == ">"sv) {
|
||||||
|
auto& second = tokens.peek_token();
|
||||||
|
if (second.is(Token::Type::Delim) && second.token().delim() == "="sv) {
|
||||||
|
tokens.next_token();
|
||||||
|
return MediaFeature::Comparison::GreaterThanOrEqual;
|
||||||
|
}
|
||||||
|
return MediaFeature::Comparison::GreaterThan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.rewind_to_position(position);
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
auto flip = [](MediaFeature::Comparison comparison) {
|
||||||
|
switch (comparison) {
|
||||||
|
case MediaFeature::Comparison::Equal:
|
||||||
|
return MediaFeature::Comparison::Equal;
|
||||||
|
case MediaFeature::Comparison::LessThan:
|
||||||
|
return MediaFeature::Comparison::GreaterThan;
|
||||||
|
case MediaFeature::Comparison::LessThanOrEqual:
|
||||||
|
return MediaFeature::Comparison::GreaterThanOrEqual;
|
||||||
|
case MediaFeature::Comparison::GreaterThan:
|
||||||
|
return MediaFeature::Comparison::LessThan;
|
||||||
|
case MediaFeature::Comparison::GreaterThanOrEqual:
|
||||||
|
return MediaFeature::Comparison::LessThanOrEqual;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto comparisons_match = [](MediaFeature::Comparison a, MediaFeature::Comparison b) -> bool {
|
||||||
|
switch (a) {
|
||||||
|
case MediaFeature::Comparison::Equal:
|
||||||
|
return b == MediaFeature::Comparison::Equal;
|
||||||
|
case MediaFeature::Comparison::LessThan:
|
||||||
|
case MediaFeature::Comparison::LessThanOrEqual:
|
||||||
|
return b == MediaFeature::Comparison::LessThan || b == MediaFeature::Comparison::LessThanOrEqual;
|
||||||
|
case MediaFeature::Comparison::GreaterThan:
|
||||||
|
case MediaFeature::Comparison::GreaterThanOrEqual:
|
||||||
|
return b == MediaFeature::Comparison::GreaterThan || b == MediaFeature::Comparison::GreaterThanOrEqual;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
|
||||||
|
// `<mf-range> = <mf-name> <mf-comparison> <mf-value>
|
||||||
|
// | <mf-value> <mf-comparison> <mf-name>
|
||||||
|
// | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
|
||||||
|
// | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
|
||||||
|
auto parse_mf_range = [&](auto& tokens) -> Optional<MediaFeature> {
|
||||||
|
auto position = tokens.position();
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
|
||||||
|
// `<mf-name> <mf-comparison> <mf-value>`
|
||||||
|
// NOTE: We have to check for <mf-name> first, since all <mf-name>s will also parse as <mf-value>.
|
||||||
|
if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (auto maybe_comparison = parse_comparison(tokens); maybe_comparison.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (auto maybe_value = parse_media_feature_value(tokens); maybe_value.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (!tokens.has_next_token() && !maybe_value->is_ident())
|
||||||
|
return MediaFeature::half_range(maybe_value.release_value(), flip(maybe_comparison.release_value()), maybe_name.release_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `<mf-value> <mf-comparison> <mf-name>
|
||||||
|
// | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
|
||||||
|
// | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>`
|
||||||
|
if (auto maybe_left_value = parse_media_feature_value(tokens); maybe_left_value.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (auto maybe_left_comparison = parse_comparison(tokens); maybe_left_comparison.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (auto maybe_name = parse_mf_name(tokens, false); maybe_name.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
|
||||||
|
if (!tokens.has_next_token())
|
||||||
|
return MediaFeature::half_range(maybe_left_value.release_value(), maybe_left_comparison.release_value(), maybe_name.release_value());
|
||||||
|
|
||||||
|
if (auto maybe_right_comparison = parse_comparison(tokens); maybe_right_comparison.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
if (auto maybe_right_value = parse_media_feature_value(tokens); maybe_right_value.has_value()) {
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
// For this to be valid, the following must be true:
|
||||||
|
// - Comparisons must either both be >/>= or both be </<=.
|
||||||
|
// - Neither comparison can be `=`.
|
||||||
|
// - Neither value can be an ident.
|
||||||
|
auto left_comparison = maybe_left_comparison.release_value();
|
||||||
|
auto right_comparison = maybe_right_comparison.release_value();
|
||||||
|
|
||||||
|
if (!tokens.has_next_token()
|
||||||
|
&& comparisons_match(left_comparison, right_comparison)
|
||||||
|
&& left_comparison != MediaFeature::Comparison::Equal
|
||||||
|
&& !maybe_left_value->is_ident() && !maybe_right_value->is_ident()) {
|
||||||
|
return MediaFeature::range(maybe_left_value.release_value(), left_comparison, maybe_name.release_value(), right_comparison, maybe_right_value.release_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.rewind_to_position(position);
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
if (auto maybe_mf_boolean = parse_mf_boolean(tokens); maybe_mf_boolean.has_value())
|
if (auto maybe_mf_boolean = parse_mf_boolean(tokens); maybe_mf_boolean.has_value())
|
||||||
return maybe_mf_boolean.release_value();
|
return maybe_mf_boolean.release_value();
|
||||||
|
|
||||||
if (auto maybe_mf_plain = parse_mf_plain(tokens); maybe_mf_plain.has_value())
|
if (auto maybe_mf_plain = parse_mf_plain(tokens); maybe_mf_plain.has_value())
|
||||||
return maybe_mf_plain.release_value();
|
return maybe_mf_plain.release_value();
|
||||||
|
|
||||||
// FIXME: Implement range syntax
|
if (auto maybe_mf_range = parse_mf_range(tokens); maybe_mf_range.has_value())
|
||||||
|
return maybe_mf_range.release_value();
|
||||||
|
|
||||||
tokens.rewind_to_position(position);
|
tokens.rewind_to_position(position);
|
||||||
return {};
|
return {};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue