mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
style: Move some of the media query code to a more generic queries module
No behavior change, just moving and renaming files. The code in the "queries" module will be shared between @media and @container. @media has some other code that container queries doesn't need like MediaList / MediaType / etc. That remains in the media_queries module. Differential Revision: https://phabricator.services.mozilla.com/D144435
This commit is contained in:
parent
3eed093e33
commit
453e7d03d4
10 changed files with 127 additions and 118 deletions
|
@ -1,185 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A media query condition:
|
||||
//!
|
||||
//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
|
||||
|
||||
use super::MediaFeatureExpression;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::values::computed;
|
||||
use cssparser::{Parser, Token};
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
||||
/// A binary `and` or `or` operator.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Operator {
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
/// Whether to allow an `or` condition or not during parsing.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
|
||||
enum AllowOr {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// Represents a media condition.
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum MediaCondition {
|
||||
/// A simple media feature expression, implicitly parenthesized.
|
||||
Feature(MediaFeatureExpression),
|
||||
/// A negation of a condition.
|
||||
Not(Box<MediaCondition>),
|
||||
/// A set of joint operations.
|
||||
Operation(Box<[MediaCondition]>, Operator),
|
||||
/// A condition wrapped in parenthesis.
|
||||
InParens(Box<MediaCondition>),
|
||||
}
|
||||
|
||||
impl ToCss for MediaCondition {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
match *self {
|
||||
// NOTE(emilio): MediaFeatureExpression already includes the
|
||||
// parenthesis.
|
||||
MediaCondition::Feature(ref f) => f.to_css(dest),
|
||||
MediaCondition::Not(ref c) => {
|
||||
dest.write_str("not ")?;
|
||||
c.to_css(dest)
|
||||
},
|
||||
MediaCondition::InParens(ref c) => {
|
||||
dest.write_char('(')?;
|
||||
c.to_css(dest)?;
|
||||
dest.write_char(')')
|
||||
},
|
||||
MediaCondition::Operation(ref list, op) => {
|
||||
let mut iter = list.iter();
|
||||
iter.next().unwrap().to_css(dest)?;
|
||||
for item in iter {
|
||||
dest.write_char(' ')?;
|
||||
op.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
item.to_css(dest)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaCondition {
|
||||
/// Parse a single media condition.
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
Self::parse_internal(context, input, AllowOr::Yes)
|
||||
}
|
||||
|
||||
/// Parse a single media condition, disallowing `or` expressions.
|
||||
///
|
||||
/// To be used from the legacy media query syntax.
|
||||
pub fn parse_disallow_or<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
Self::parse_internal(context, input, AllowOr::No)
|
||||
}
|
||||
|
||||
fn parse_internal<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
allow_or: AllowOr,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let location = input.current_source_location();
|
||||
|
||||
// FIXME(emilio): This can be cleaner with nll.
|
||||
let is_negation = match *input.next()? {
|
||||
Token::ParenthesisBlock => false,
|
||||
Token::Ident(ref ident) if ident.eq_ignore_ascii_case("not") => true,
|
||||
ref t => return Err(location.new_unexpected_token_error(t.clone())),
|
||||
};
|
||||
|
||||
if is_negation {
|
||||
let inner_condition = Self::parse_in_parens(context, input)?;
|
||||
return Ok(MediaCondition::Not(Box::new(inner_condition)));
|
||||
}
|
||||
|
||||
// ParenthesisBlock.
|
||||
let first_condition = Self::parse_paren_block(context, input)?;
|
||||
let operator = match input.try_parse(Operator::parse) {
|
||||
Ok(op) => op,
|
||||
Err(..) => return Ok(first_condition),
|
||||
};
|
||||
|
||||
if allow_or == AllowOr::No && operator == Operator::Or {
|
||||
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
let mut conditions = vec![];
|
||||
conditions.push(first_condition);
|
||||
conditions.push(Self::parse_in_parens(context, input)?);
|
||||
|
||||
let delim = match operator {
|
||||
Operator::And => "and",
|
||||
Operator::Or => "or",
|
||||
};
|
||||
|
||||
loop {
|
||||
if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
|
||||
return Ok(MediaCondition::Operation(
|
||||
conditions.into_boxed_slice(),
|
||||
operator,
|
||||
));
|
||||
}
|
||||
|
||||
conditions.push(Self::parse_in_parens(context, input)?);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a media condition in parentheses.
|
||||
pub fn parse_in_parens<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
Self::parse_paren_block(context, input)
|
||||
}
|
||||
|
||||
fn parse_paren_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.parse_nested_block(|input| {
|
||||
// Base case.
|
||||
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) {
|
||||
return Ok(MediaCondition::InParens(Box::new(inner)));
|
||||
}
|
||||
let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?;
|
||||
Ok(MediaCondition::Feature(expr))
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this condition matches the device and quirks mode.
|
||||
pub fn matches(&self, context: &computed::Context) -> bool {
|
||||
match *self {
|
||||
MediaCondition::Feature(ref f) => f.matches(context),
|
||||
MediaCondition::InParens(ref c) => c.matches(context),
|
||||
MediaCondition::Not(ref c) => !c.matches(context),
|
||||
MediaCondition::Operation(ref conditions, op) => {
|
||||
let mut iter = conditions.iter();
|
||||
match op {
|
||||
Operator::And => iter.all(|c| c.matches(context)),
|
||||
Operator::Or => iter.any(|c| c.matches(context)),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Media features.
|
||||
|
||||
use crate::parser::ParserContext;
|
||||
use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio};
|
||||
use crate::Atom;
|
||||
use cssparser::Parser;
|
||||
use std::fmt;
|
||||
use style_traits::ParseError;
|
||||
|
||||
/// A generic discriminant for an enum value.
|
||||
pub type KeywordDiscriminant = u8;
|
||||
|
||||
type MediaFeatureGetter<T> = fn(device: &computed::Context) -> T;
|
||||
|
||||
/// Serializes a given discriminant.
|
||||
///
|
||||
/// FIXME(emilio): we could prevent this allocation if the ToCss code would
|
||||
/// generate a method for keywords to get the static string or something.
|
||||
pub type KeywordSerializer = fn(KeywordDiscriminant) -> String;
|
||||
|
||||
/// Parses a given identifier.
|
||||
pub type KeywordParser = for<'a, 'i, 't> fn(
|
||||
context: &'a ParserContext,
|
||||
input: &'a mut Parser<'i, 't>,
|
||||
) -> Result<KeywordDiscriminant, ParseError<'i>>;
|
||||
|
||||
/// An evaluator for a given media feature.
|
||||
///
|
||||
/// This determines the kind of values that get parsed, too.
|
||||
#[allow(missing_docs)]
|
||||
pub enum Evaluator {
|
||||
Length(MediaFeatureGetter<CSSPixelLength>),
|
||||
Integer(MediaFeatureGetter<u32>),
|
||||
Float(MediaFeatureGetter<f32>),
|
||||
BoolInteger(MediaFeatureGetter<bool>),
|
||||
/// A non-negative number ratio, such as the one from device-pixel-ratio.
|
||||
NumberRatio(MediaFeatureGetter<Ratio>),
|
||||
/// A resolution.
|
||||
Resolution(MediaFeatureGetter<Resolution>),
|
||||
/// A keyword value.
|
||||
Enumerated {
|
||||
/// The parser to get a discriminant given a string.
|
||||
parser: KeywordParser,
|
||||
/// The serializer to get a string from a discriminant.
|
||||
///
|
||||
/// This is guaranteed to be called with a keyword that `parser` has
|
||||
/// produced.
|
||||
serializer: KeywordSerializer,
|
||||
/// The evaluator itself. This is guaranteed to be called with a
|
||||
/// keyword that `parser` has produced.
|
||||
evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// A simple helper macro to create a keyword evaluator.
|
||||
///
|
||||
/// This assumes that keyword feature expressions don't accept ranges, and
|
||||
/// asserts if that's not true. As of today there's nothing like that (does that
|
||||
/// even make sense?).
|
||||
macro_rules! keyword_evaluator {
|
||||
($actual_evaluator:ident, $keyword_type:ty) => {{
|
||||
fn __parse<'i, 't>(
|
||||
context: &$crate::parser::ParserContext,
|
||||
input: &mut $crate::cssparser::Parser<'i, 't>,
|
||||
) -> Result<
|
||||
$crate::media_queries::media_feature::KeywordDiscriminant,
|
||||
::style_traits::ParseError<'i>,
|
||||
> {
|
||||
let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
|
||||
Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant)
|
||||
}
|
||||
|
||||
fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String {
|
||||
// This unwrap is ok because the only discriminants that get
|
||||
// back to us is the ones that `parse` produces.
|
||||
let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
|
||||
<$keyword_type as ::style_traits::ToCss>::to_css_string(&value)
|
||||
}
|
||||
|
||||
fn __evaluate(
|
||||
context: &$crate::values::computed::Context,
|
||||
value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>,
|
||||
) -> bool {
|
||||
// This unwrap is ok because the only discriminants that get
|
||||
// back to us is the ones that `parse` produces.
|
||||
let value: Option<$keyword_type> =
|
||||
value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
|
||||
$actual_evaluator(context, value)
|
||||
}
|
||||
|
||||
$crate::media_queries::media_feature::Evaluator::Enumerated {
|
||||
parser: __parse,
|
||||
serializer: __serialize,
|
||||
evaluator: __evaluate,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Different requirements or toggles that change how a expression is
|
||||
/// parsed.
|
||||
pub struct ParsingRequirements: u8 {
|
||||
/// The feature should only be parsed in chrome and ua sheets.
|
||||
const CHROME_AND_UA_ONLY = 1 << 0;
|
||||
/// The feature requires a -webkit- prefix.
|
||||
const WEBKIT_PREFIX = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a media feature allows ranges or not.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AllowsRanges {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// A description of a media feature.
|
||||
pub struct MediaFeatureDescription {
|
||||
/// The media feature name, in ascii lowercase.
|
||||
pub name: Atom,
|
||||
/// Whether min- / max- prefixes are allowed or not.
|
||||
pub allows_ranges: AllowsRanges,
|
||||
/// The evaluator, which we also use to determine which kind of value to
|
||||
/// parse.
|
||||
pub evaluator: Evaluator,
|
||||
/// Different requirements that need to hold for the feature to be
|
||||
/// successfully parsed.
|
||||
pub requirements: ParsingRequirements,
|
||||
}
|
||||
|
||||
impl MediaFeatureDescription {
|
||||
/// Whether this media feature allows ranges.
|
||||
#[inline]
|
||||
pub fn allows_ranges(&self) -> bool {
|
||||
self.allows_ranges == AllowsRanges::Yes
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple helper to construct a `MediaFeatureDescription`.
|
||||
macro_rules! feature {
|
||||
($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => {
|
||||
$crate::media_queries::media_feature::MediaFeatureDescription {
|
||||
name: $name,
|
||||
allows_ranges: $allows_ranges,
|
||||
evaluator: $evaluator,
|
||||
requirements: $reqs,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl fmt::Debug for MediaFeatureDescription {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("MediaFeatureExpression")
|
||||
.field("name", &self.name)
|
||||
.field("allows_ranges", &self.allows_ranges)
|
||||
.field("requirements", &self.requirements)
|
||||
.finish()
|
||||
}
|
||||
}
|
|
@ -1,531 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Parsing for media feature expressions, like `(foo: bar)` or
|
||||
//! `(width >= 400px)`.
|
||||
|
||||
use super::media_feature::{Evaluator, MediaFeatureDescription};
|
||||
use super::media_feature::{KeywordDiscriminant, ParsingRequirements};
|
||||
#[cfg(feature = "gecko")]
|
||||
use crate::gecko::media_features::MEDIA_FEATURES;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
#[cfg(feature = "servo")]
|
||||
use crate::servo::media_queries::MEDIA_FEATURES;
|
||||
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
||||
use crate::values::computed::{self, Ratio, ToComputedValue};
|
||||
use crate::values::specified::{Integer, Length, Number, Resolution};
|
||||
use crate::values::CSSFloat;
|
||||
use crate::{Atom, Zero};
|
||||
use cssparser::{Parser, Token};
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
||||
/// The kind of matching that should be performed on a media feature value.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum Range {
|
||||
/// At least the specified value.
|
||||
Min,
|
||||
/// At most the specified value.
|
||||
Max,
|
||||
}
|
||||
|
||||
/// The operator that was specified in this media feature.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum Operator {
|
||||
/// =
|
||||
Equal,
|
||||
/// >
|
||||
GreaterThan,
|
||||
/// >=
|
||||
GreaterThanEqual,
|
||||
/// <
|
||||
LessThan,
|
||||
/// <=
|
||||
LessThanEqual,
|
||||
}
|
||||
|
||||
impl ToCss for Operator {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str(match *self {
|
||||
Operator::Equal => "=",
|
||||
Operator::LessThan => "<",
|
||||
Operator::LessThanEqual => "<=",
|
||||
Operator::GreaterThan => ">",
|
||||
Operator::GreaterThanEqual => ">=",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a `Range` or an `Operator`.
|
||||
///
|
||||
/// Ranged media features are not allowed with operations (that'd make no
|
||||
/// sense).
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum RangeOrOperator {
|
||||
/// A `Range`.
|
||||
Range(Range),
|
||||
/// An `Operator`.
|
||||
Operator(Operator),
|
||||
}
|
||||
|
||||
impl RangeOrOperator {
|
||||
/// Evaluate a given range given an optional query value and a value from
|
||||
/// the browser.
|
||||
fn evaluate<T>(range_or_op: Option<Self>, query_value: Option<T>, value: T) -> bool
|
||||
where
|
||||
T: PartialOrd + Zero,
|
||||
{
|
||||
match query_value {
|
||||
Some(v) => Self::evaluate_with_query_value(range_or_op, v, value),
|
||||
None => !value.is_zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a given range given a non-optional query value and a value from
|
||||
/// the browser.
|
||||
fn evaluate_with_query_value<T>(range_or_op: Option<Self>, query_value: T, value: T) -> bool
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
let cmp = match value.partial_cmp(&query_value) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let range_or_op = match range_or_op {
|
||||
Some(r) => r,
|
||||
None => return cmp == Ordering::Equal,
|
||||
};
|
||||
|
||||
match range_or_op {
|
||||
RangeOrOperator::Range(range) => {
|
||||
cmp == Ordering::Equal ||
|
||||
match range {
|
||||
Range::Min => cmp == Ordering::Greater,
|
||||
Range::Max => cmp == Ordering::Less,
|
||||
}
|
||||
},
|
||||
RangeOrOperator::Operator(op) => match op {
|
||||
Operator::Equal => cmp == Ordering::Equal,
|
||||
Operator::GreaterThan => cmp == Ordering::Greater,
|
||||
Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
||||
Operator::LessThan => cmp == Ordering::Less,
|
||||
Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A feature expression contains a reference to the media feature, the value
|
||||
/// the media query contained, and the range to evaluate.
|
||||
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
|
||||
pub struct MediaFeatureExpression {
|
||||
feature_index: usize,
|
||||
value: Option<MediaExpressionValue>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
}
|
||||
|
||||
impl ToCss for MediaFeatureExpression {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str("(")?;
|
||||
|
||||
let feature = self.feature();
|
||||
|
||||
if feature
|
||||
.requirements
|
||||
.contains(ParsingRequirements::WEBKIT_PREFIX)
|
||||
{
|
||||
dest.write_str("-webkit-")?;
|
||||
}
|
||||
|
||||
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
|
||||
match range {
|
||||
Range::Min => dest.write_str("min-")?,
|
||||
Range::Max => dest.write_str("max-")?,
|
||||
}
|
||||
}
|
||||
|
||||
// NB: CssStringWriter not needed, feature names are under control.
|
||||
write!(dest, "{}", feature.name)?;
|
||||
|
||||
if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator {
|
||||
dest.write_char(' ')?;
|
||||
op.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
} else if self.value.is_some() {
|
||||
dest.write_str(": ")?;
|
||||
}
|
||||
|
||||
if let Some(ref val) = self.value {
|
||||
val.to_css(dest, self)?;
|
||||
}
|
||||
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes an operation or a colon, or returns an error.
|
||||
fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, ()> {
|
||||
let first_delim = {
|
||||
let next_token = match input.next() {
|
||||
Ok(t) => t,
|
||||
Err(..) => return Err(()),
|
||||
};
|
||||
|
||||
match *next_token {
|
||||
Token::Colon => return Ok(None),
|
||||
Token::Delim(oper) => oper,
|
||||
_ => return Err(()),
|
||||
}
|
||||
};
|
||||
let operator = match first_delim {
|
||||
'=' => return Ok(Some(Operator::Equal)),
|
||||
'>' => Operator::GreaterThan,
|
||||
'<' => Operator::LessThan,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
|
||||
//
|
||||
// No whitespace is allowed between the “<” or “>”
|
||||
// <delim-token>s and the following “=” <delim-token>, if it’s
|
||||
// present.
|
||||
//
|
||||
// TODO(emilio): Maybe we should ignore comments as well?
|
||||
// https://github.com/w3c/csswg-drafts/issues/6248
|
||||
let parsed_equal = input
|
||||
.try_parse(|i| {
|
||||
let t = i.next_including_whitespace().map_err(|_| ())?;
|
||||
if !matches!(t, Token::Delim('=')) {
|
||||
return Err(());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.is_ok();
|
||||
|
||||
if !parsed_equal {
|
||||
return Ok(Some(operator));
|
||||
}
|
||||
|
||||
Ok(Some(match operator {
|
||||
Operator::GreaterThan => Operator::GreaterThanEqual,
|
||||
Operator::LessThan => Operator::LessThanEqual,
|
||||
_ => unreachable!(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
|
||||
#[cfg(feature = "gecko")]
|
||||
{
|
||||
if *feature == atom!("forced-colors") {
|
||||
// forced-colors is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preference, which is defaulted
|
||||
// to 'true' as of bug 1659511.
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.forced-colors.enabled");
|
||||
}
|
||||
// prefers-contrast is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preference.
|
||||
if *feature == atom!("prefers-contrast") {
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.prefers-contrast.enabled");
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl MediaFeatureExpression {
|
||||
fn new(
|
||||
feature_index: usize,
|
||||
value: Option<MediaExpressionValue>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> Self {
|
||||
debug_assert!(feature_index < MEDIA_FEATURES.len());
|
||||
Self {
|
||||
feature_index,
|
||||
value,
|
||||
range_or_operator,
|
||||
}
|
||||
}
|
||||
|
||||
fn feature(&self) -> &'static MediaFeatureDescription {
|
||||
&MEDIA_FEATURES[self.feature_index]
|
||||
}
|
||||
|
||||
/// Parse a media expression of the form:
|
||||
///
|
||||
/// ```
|
||||
/// (media-feature: media-value)
|
||||
/// ```
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
input.parse_nested_block(|input| Self::parse_in_parenthesis_block(context, input))
|
||||
}
|
||||
|
||||
/// Parse a media feature expression where we've already consumed the
|
||||
/// parenthesis.
|
||||
pub fn parse_in_parenthesis_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let mut requirements = ParsingRequirements::empty();
|
||||
let location = input.current_source_location();
|
||||
let ident = input.expect_ident()?;
|
||||
|
||||
if context.in_ua_or_chrome_sheet() {
|
||||
requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY);
|
||||
}
|
||||
|
||||
let mut feature_name = &**ident;
|
||||
|
||||
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
|
||||
feature_name = &feature_name[8..];
|
||||
requirements.insert(ParsingRequirements::WEBKIT_PREFIX);
|
||||
}
|
||||
|
||||
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(Range::Min)
|
||||
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(Range::Max)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
||||
|
||||
let (feature_index, feature) = match MEDIA_FEATURES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, f)| f.name == atom)
|
||||
{
|
||||
Some((i, f)) => (i, f),
|
||||
None => {
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
if disabled_by_pref(&feature.name, context) ||
|
||||
!requirements.contains(feature.requirements) ||
|
||||
(range.is_some() && !feature.allows_ranges())
|
||||
{
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
let operator = input.try_parse(consume_operation_or_colon);
|
||||
let operator = match operator {
|
||||
Err(..) => {
|
||||
// If there's no colon, this is a media query of the
|
||||
// form '(<feature>)', that is, there's no value
|
||||
// specified.
|
||||
//
|
||||
// Gecko doesn't allow ranged expressions without a
|
||||
// value, so just reject them here too.
|
||||
if range.is_some() {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(Self::new(feature_index, None, None));
|
||||
},
|
||||
Ok(operator) => operator,
|
||||
};
|
||||
|
||||
let range_or_operator = match range {
|
||||
Some(range) => {
|
||||
if operator.is_some() {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
||||
);
|
||||
}
|
||||
Some(RangeOrOperator::Range(range))
|
||||
},
|
||||
None => match operator {
|
||||
Some(operator) => {
|
||||
if !feature.allows_ranges() {
|
||||
return Err(input
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
||||
}
|
||||
Some(RangeOrOperator::Operator(operator))
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
let value = MediaExpressionValue::parse(feature, context, input).map_err(|err| {
|
||||
err.location
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
||||
})?;
|
||||
|
||||
Ok(Self::new(feature_index, Some(value), range_or_operator))
|
||||
}
|
||||
|
||||
/// Returns whether this media query evaluates to true for the given device.
|
||||
pub fn matches(&self, context: &computed::Context) -> bool {
|
||||
let value = self.value.as_ref();
|
||||
|
||||
macro_rules! expect {
|
||||
($variant:ident) => {
|
||||
value.map(|value| match *value {
|
||||
MediaExpressionValue::$variant(ref v) => v,
|
||||
_ => unreachable!("Unexpected MediaExpressionValue"),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
match self.feature().evaluator {
|
||||
Evaluator::Length(eval) => {
|
||||
let computed = expect!(Length).map(|specified| {
|
||||
specified.to_computed_value(context)
|
||||
});
|
||||
let length = eval(context);
|
||||
RangeOrOperator::evaluate(self.range_or_operator, computed, length)
|
||||
},
|
||||
Evaluator::Integer(eval) => {
|
||||
let computed = expect!(Integer).cloned();
|
||||
let integer = eval(context);
|
||||
RangeOrOperator::evaluate(self.range_or_operator, computed, integer)
|
||||
},
|
||||
Evaluator::Float(eval) => {
|
||||
let computed = expect!(Float).cloned();
|
||||
let float = eval(context);
|
||||
RangeOrOperator::evaluate(self.range_or_operator, computed, float)
|
||||
}
|
||||
Evaluator::NumberRatio(eval) => {
|
||||
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
|
||||
// to convert it if necessary.
|
||||
// FIXME: we may need to update here once
|
||||
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
||||
let computed = match expect!(NumberRatio).cloned() {
|
||||
Some(ratio) => ratio.used_value(),
|
||||
None => return true,
|
||||
};
|
||||
let ratio = eval(context);
|
||||
RangeOrOperator::evaluate_with_query_value(self.range_or_operator, computed, ratio)
|
||||
},
|
||||
Evaluator::Resolution(eval) => {
|
||||
let computed = expect!(Resolution).map(|specified| {
|
||||
specified.to_computed_value(context).dppx()
|
||||
});
|
||||
let resolution = eval(context).dppx();
|
||||
RangeOrOperator::evaluate(self.range_or_operator, computed, resolution)
|
||||
},
|
||||
Evaluator::Enumerated { evaluator, .. } => {
|
||||
debug_assert!(self.range_or_operator.is_none(), "Ranges with keywords?");
|
||||
evaluator(context, expect!(Enumerated).cloned())
|
||||
},
|
||||
Evaluator::BoolInteger(eval) => {
|
||||
debug_assert!(self.range_or_operator.is_none(), "Ranges with bools?");
|
||||
let computed = expect!(BoolInteger).cloned();
|
||||
let boolean = eval(context);
|
||||
computed.map_or(boolean, |v| v == boolean)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value found or expected in a media expression.
|
||||
///
|
||||
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
||||
/// BoolInteger / NumberRatio case, as computed or as specified value?
|
||||
///
|
||||
/// If the first, this would need to store the relevant values.
|
||||
///
|
||||
/// See: https://github.com/w3c/csswg-drafts/issues/1968
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum MediaExpressionValue {
|
||||
/// A length.
|
||||
Length(Length),
|
||||
/// A (non-negative) integer.
|
||||
Integer(u32),
|
||||
/// A floating point value.
|
||||
Float(CSSFloat),
|
||||
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
||||
BoolInteger(bool),
|
||||
/// A single non-negative number or two non-negative numbers separated by '/',
|
||||
/// with optional whitespace on either side of the '/'.
|
||||
NumberRatio(Ratio),
|
||||
/// A resolution.
|
||||
Resolution(Resolution),
|
||||
/// An enumerated value, defined by the variant keyword table in the
|
||||
/// feature's `mData` member.
|
||||
Enumerated(KeywordDiscriminant),
|
||||
}
|
||||
|
||||
impl MediaExpressionValue {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
match *self {
|
||||
MediaExpressionValue::Length(ref l) => l.to_css(dest),
|
||||
MediaExpressionValue::Integer(v) => v.to_css(dest),
|
||||
MediaExpressionValue::Float(v) => v.to_css(dest),
|
||||
MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
||||
MediaExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
|
||||
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||
MediaExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
|
||||
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<'i, 't>(
|
||||
for_feature: &MediaFeatureDescription,
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<MediaExpressionValue, ParseError<'i>> {
|
||||
Ok(match for_feature.evaluator {
|
||||
Evaluator::Length(..) => {
|
||||
let length = Length::parse_non_negative(context, input)?;
|
||||
MediaExpressionValue::Length(length)
|
||||
},
|
||||
Evaluator::Integer(..) => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
MediaExpressionValue::Integer(integer.value() as u32)
|
||||
},
|
||||
Evaluator::BoolInteger(..) => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
let value = integer.value();
|
||||
if value > 1 {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
MediaExpressionValue::BoolInteger(value == 1)
|
||||
},
|
||||
Evaluator::Float(..) => {
|
||||
let number = Number::parse(context, input)?;
|
||||
MediaExpressionValue::Float(number.get())
|
||||
},
|
||||
Evaluator::NumberRatio(..) => {
|
||||
use crate::values::specified::Ratio as SpecifiedRatio;
|
||||
let ratio = SpecifiedRatio::parse(context, input)?;
|
||||
MediaExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
|
||||
},
|
||||
Evaluator::Resolution(..) => {
|
||||
MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
|
||||
},
|
||||
Evaluator::Enumerated { parser, .. } => {
|
||||
MediaExpressionValue::Enumerated(parser(context, input)?)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
//!
|
||||
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
|
||||
|
||||
use super::media_condition::MediaCondition;
|
||||
use crate::queries::QueryCondition;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::str::string_as_ascii_lowercase;
|
||||
use crate::values::CustomIdent;
|
||||
|
@ -66,7 +66,7 @@ pub struct MediaQuery {
|
|||
pub media_type: MediaQueryType,
|
||||
/// The condition that this media query contains. This cannot have `or`
|
||||
/// in the first level.
|
||||
pub condition: Option<MediaCondition>,
|
||||
pub condition: Option<QueryCondition>,
|
||||
}
|
||||
|
||||
impl ToCss for MediaQuery {
|
||||
|
@ -134,9 +134,9 @@ impl MediaQuery {
|
|||
.unwrap_or_default();
|
||||
|
||||
let condition = if explicit_media_type.is_none() {
|
||||
Some(MediaCondition::parse(context, input)?)
|
||||
Some(QueryCondition::parse(context, input)?)
|
||||
} else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
|
||||
Some(MediaCondition::parse_disallow_or(context, input)?)
|
||||
Some(QueryCondition::parse_disallow_or(context, input)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
|
@ -6,15 +6,9 @@
|
|||
//!
|
||||
//! [mq]: https://drafts.csswg.org/mediaqueries/
|
||||
|
||||
mod media_condition;
|
||||
mod media_list;
|
||||
mod media_query;
|
||||
#[macro_use]
|
||||
pub mod media_feature;
|
||||
pub mod media_feature_expression;
|
||||
|
||||
pub use self::media_condition::MediaCondition;
|
||||
pub use self::media_feature_expression::MediaFeatureExpression;
|
||||
pub use self::media_list::MediaList;
|
||||
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue