style: Evaluate MediaConditions, and glue it all together.

Bug: 1422225
Reviewed-by: xidorn
MozReview-Commit-ID: 3MThE2FvfDf
This commit is contained in:
Emilio Cobos Álvarez 2018-06-16 00:59:04 -07:00
parent d2a1895752
commit 3a92fd1cfc
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
4 changed files with 96 additions and 58 deletions

View file

@ -7,11 +7,12 @@
//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition //! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
use cssparser::{Parser, Token}; use cssparser::{Parser, Token};
use context::QuirksMode;
use parser::ParserContext; use parser::ParserContext;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, ToCss}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use super::MediaFeatureExpression; use super::{Device, MediaFeatureExpression};
/// A binary `and` or `or` operator. /// A binary `and` or `or` operator.
@ -22,7 +23,15 @@ pub enum Operator {
Or, Or,
} }
/// Whether to allow an `or` condition or not during parsing.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Parse, ToCss)]
enum AllowOr {
Yes,
No,
}
/// Represents a media condition. /// Represents a media condition.
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub enum MediaCondition { pub enum MediaCondition {
/// A simple media feature expression, implicitly parenthesized. /// A simple media feature expression, implicitly parenthesized.
Feature(MediaFeatureExpression), Feature(MediaFeatureExpression),
@ -72,6 +81,24 @@ impl MediaCondition {
pub fn parse<'i, 't>( pub fn parse<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, 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>> { ) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location(); let location = input.current_source_location();
@ -96,6 +123,10 @@ impl MediaCondition {
Err(..) => return Ok(first_condition), 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![]; let mut conditions = vec![];
conditions.push(first_condition); conditions.push(first_condition);
conditions.push(Self::parse_in_parens(context, input)?); conditions.push(Self::parse_in_parens(context, input)?);
@ -140,4 +171,24 @@ impl MediaCondition {
Ok(MediaCondition::InParens(Box::new(inner))) Ok(MediaCondition::InParens(Box::new(inner)))
}) })
} }
/// Whether this condition matches the device and quirks mode.
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
match *self {
MediaCondition::Feature(ref f) => f.matches(device, quirks_mode),
MediaCondition::InParens(ref c) => c.matches(device, quirks_mode),
MediaCondition::Not(ref c) => !c.matches(device, quirks_mode),
MediaCondition::Operation(ref conditions, op) => {
let mut iter = conditions.iter();
match op {
Operator::And => {
iter.all(|c| c.matches(device, quirks_mode))
}
Operator::Or => {
iter.any(|c| c.matches(device, quirks_mode))
}
}
}
}
}
} }

View file

@ -73,16 +73,14 @@ impl MediaList {
/// Evaluate a whole `MediaList` against `Device`. /// Evaluate a whole `MediaList` against `Device`.
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool { pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
// Check if it is an empty media query list or any queries match (OR condition) // Check if it is an empty media query list or any queries match.
// https://drafts.csswg.org/mediaqueries-4/#mq-list // https://drafts.csswg.org/mediaqueries-4/#mq-list
self.media_queries.is_empty() || self.media_queries.iter().any(|mq| { self.media_queries.is_empty() || self.media_queries.iter().any(|mq| {
let media_match = mq.media_type.matches(device.media_type()); let media_match = mq.media_type.matches(device.media_type());
// Check if all conditions match (AND condition) // Check if the media condition match.
let query_match = media_match && let query_match = media_match &&
mq.expressions mq.condition.as_ref().map_or(true, |c| c.matches(device, quirks_mode));
.iter()
.all(|expression| expression.matches(&device, quirks_mode));
// Apply the logical NOT qualifier to the result // Apply the logical NOT qualifier to the result
match mq.qualifier { match mq.qualifier {

View file

@ -12,10 +12,11 @@ use parser::ParserContext;
use selectors::parser::SelectorParseErrorKind; use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use str::string_as_ascii_lowercase; use str::string_as_ascii_lowercase;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use style_traits::{CssWriter, ParseError, ToCss};
use super::MediaFeatureExpression; use super::media_condition::MediaCondition;
use values::CustomIdent; use values::CustomIdent;
/// <https://drafts.csswg.org/mediaqueries/#mq-prefix> /// <https://drafts.csswg.org/mediaqueries/#mq-prefix>
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)] #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
pub enum Qualifier { pub enum Qualifier {
@ -65,8 +66,9 @@ pub struct MediaQuery {
pub qualifier: Option<Qualifier>, pub qualifier: Option<Qualifier>,
/// The media type for this query, that can be known, unknown, or "all". /// The media type for this query, that can be known, unknown, or "all".
pub media_type: MediaQueryType, pub media_type: MediaQueryType,
/// The set of range expressions that this media query contains. /// The condition that this media query contains. This cannot have `or`
pub expressions: Vec<MediaFeatureExpression>, /// in the first level.
pub condition: Option<MediaCondition>,
} }
impl ToCss for MediaQuery { impl ToCss for MediaQuery {
@ -86,28 +88,23 @@ impl ToCss for MediaQuery {
// //
// Otherwise, we'd serialize media queries like "(min-width: // Otherwise, we'd serialize media queries like "(min-width:
// 40px)" in "all (min-width: 40px)", which is unexpected. // 40px)" in "all (min-width: 40px)", which is unexpected.
if self.qualifier.is_some() || self.expressions.is_empty() { if self.qualifier.is_some() || self.condition.is_none() {
dest.write_str("all")?; dest.write_str("all")?;
} }
}, },
MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?,
} }
if self.expressions.is_empty() { let condition = match self.condition {
return Ok(()); Some(ref c) => c,
} None => return Ok(()),
};
if self.media_type != MediaQueryType::All || self.qualifier.is_some() { if self.media_type != MediaQueryType::All || self.qualifier.is_some() {
dest.write_str(" and ")?; dest.write_str(" and ")?;
} }
self.expressions[0].to_css(dest)?; condition.to_css(dest)
for expr in self.expressions.iter().skip(1) {
dest.write_str(" and ")?;
expr.to_css(dest)?;
}
Ok(())
} }
} }
@ -118,7 +115,7 @@ impl MediaQuery {
Self { Self {
qualifier: Some(Qualifier::Not), qualifier: Some(Qualifier::Not),
media_type: MediaQueryType::All, media_type: MediaQueryType::All,
expressions: vec![], condition: None,
} }
} }
@ -129,40 +126,34 @@ impl MediaQuery {
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<MediaQuery, ParseError<'i>> { ) -> Result<MediaQuery, ParseError<'i>> {
let mut expressions = vec![]; if let Ok(condition) = input.try(|i| MediaCondition::parse(context, i)) {
return Ok(Self {
qualifier: None,
media_type: MediaQueryType::All,
condition: Some(condition),
})
}
let qualifier = input.try(Qualifier::parse).ok(); let qualifier = input.try(Qualifier::parse).ok();
let media_type = match input.try(|i| i.expect_ident_cloned()) { let media_type = {
Ok(ident) => MediaQueryType::parse(&*ident).map_err(|()| { let location = input.current_source_location();
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) let ident = input.expect_ident()?;
})?, match MediaQueryType::parse(&ident) {
Err(_) => { Ok(t) => t,
// Media type is only optional if qualifier is not specified. Err(..) => return Err(location.new_custom_error(
if qualifier.is_some() { SelectorParseErrorKind::UnexpectedIdent(ident.clone())
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); ))
} }
// Without a media type, require at least one expression.
expressions.push(MediaFeatureExpression::parse(context, input)?);
MediaQueryType::All
},
}; };
// Parse any subsequent expressions let condition =
loop { if input.try(|i| i.expect_ident_matching("and")).is_ok() {
if input Some(MediaCondition::parse_disallow_or(context, input)?)
.try(|input| input.expect_ident_matching("and")) } else {
.is_err() None
{ };
return Ok(MediaQuery {
qualifier, Ok(Self { qualifier, media_type, condition })
media_type,
expressions,
});
}
expressions.push(MediaFeatureExpression::parse(context, input)?)
}
} }
} }

View file

@ -8,7 +8,7 @@ use app_units::Au;
use cssparser::{Delimiter, Parser, Token}; use cssparser::{Delimiter, Parser, Token};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
use media_queries::{Device, MediaFeatureExpression}; use media_queries::{Device, MediaCondition};
use parser::{Parse, ParserContext}; use parser::{Parse, ParserContext};
use selectors::context::QuirksMode; use selectors::context::QuirksMode;
use style_traits::ParseError; use style_traits::ParseError;
@ -19,9 +19,7 @@ use values::specified::{Length, NoCalcLength, ViewportPercentageLength};
/// ///
/// https://html.spec.whatwg.org/multipage/#source-size /// https://html.spec.whatwg.org/multipage/#source-size
pub struct SourceSize { pub struct SourceSize {
// FIXME(emilio): This should be a `MediaCondition`, and support `and` and condition: MediaCondition,
// `or`.
condition: MediaFeatureExpression,
value: Length, value: Length,
} }
@ -30,7 +28,7 @@ impl Parse for SourceSize {
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let condition = MediaFeatureExpression::parse(context, input)?; let condition = MediaCondition::parse(context, input)?;
let value = Length::parse_non_negative(context, input)?; let value = Length::parse_non_negative(context, input)?;
Ok(Self { condition, value }) Ok(Self { condition, value })