Bug 1422225: Add code to parse media conditions. r?xidorn
Still unused.
MozReview-Commit-ID: IQfxObw9BV5
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -625,151 +625,160 @@ impl MediaFeatureExpression {
BasicParseErrorKind::UnexpectedToken(t) => {
StyleParseErrorKind::ExpectedIdentifier(t)
},
_ => StyleParseErrorKind::UnspecifiedError,
})
})?;
input.parse_nested_block(|input| {
- // FIXME: remove extra indented block when lifetimes are non-lexical
- let feature;
- let range;
+ Self::parse_in_parenthesis_block(context, input)
+ })
+ }
+
+ /// Parse a media range 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>> {
+ // FIXME: remove extra indented block when lifetimes are non-lexical
+ let feature;
+ let range;
+ {
+ let location = input.current_source_location();
+ let ident = input.expect_ident().map_err(|err| {
+ err.location.new_custom_error(match err.kind {
+ BasicParseErrorKind::UnexpectedToken(t) => {
+ StyleParseErrorKind::ExpectedIdentifier(t)
+ },
+ _ => StyleParseErrorKind::UnspecifiedError,
+ })
+ })?;
+
+ let mut flags = 0;
+
+ if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent
{
- let location = input.current_source_location();
- let ident = input.expect_ident().map_err(|err| {
- err.location.new_custom_error(match err.kind {
- BasicParseErrorKind::UnexpectedToken(t) => {
- StyleParseErrorKind::ExpectedIdentifier(t)
- },
- _ => StyleParseErrorKind::UnspecifiedError,
- })
- })?;
+ flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly;
+ }
+
+ let result = {
+ let mut feature_name = &**ident;
- let mut flags = 0;
-
- if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent
+ if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } &&
+ starts_with_ignore_ascii_case(feature_name, "-webkit-")
{
- flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly;
+ feature_name = &feature_name[8..];
+ flags |= structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix;
+ if unsafe {
+ structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit
+ } {
+ flags |= structs::nsMediaFeature_RequirementFlags_eWebkitDevicePixelRatioPrefEnabled;
+ }
}
- let result = {
- let mut feature_name = &**ident;
-
- if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } &&
- starts_with_ignore_ascii_case(feature_name, "-webkit-")
- {
- feature_name = &feature_name[8..];
- flags |= structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix;
- if unsafe {
- structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit
- } {
- flags |= structs::nsMediaFeature_RequirementFlags_eWebkitDevicePixelRatioPrefEnabled;
- }
- }
-
- 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));
- match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) {
- Some(f) => Ok((f, range)),
- None => Err(()),
- }
+ 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
};
- match result {
- Ok((f, r)) => {
- feature = f;
- range = r;
- },
- Err(()) => {
- return Err(location.new_custom_error(
- StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
- ))
- },
+ let atom = Atom::from(string_as_ascii_lowercase(feature_name));
+ match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) {
+ Some(f) => Ok((f, range)),
+ None => Err(()),
}
+ };
- if (feature.mReqFlags & !flags) != 0 {
+ match result {
+ Ok((f, r)) => {
+ feature = f;
+ range = r;
+ },
+ Err(()) => {
return Err(location.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
+ ))
+ },
+ }
+
+ if (feature.mReqFlags & !flags) != 0 {
+ return Err(location.new_custom_error(
+ StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
+ ));
+ }
+
+ if range.is_some() &&
+ feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed
+ {
+ return Err(location.new_custom_error(
+ StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
+ ));
+ }
+ }
+
+ let feature_allows_ranges =
+ feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed;
+
+ let operator = input.try(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
));
}
- if range.is_some() &&
- feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed
- {
- return Err(location.new_custom_error(
- StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
+ return Ok(Self::new(feature, None, None));
+ }
+ Ok(operator) => operator,
+ };
+
+ let range_or_operator = match range {
+ Some(range) => {
+ if operator.is_some() {
+ return Err(input.new_custom_error(
+ StyleParseErrorKind::MediaQueryExpectedFeatureValue
));
}
+ Some(RangeOrOperator::Range(range))
+ }
+ None => {
+ match operator {
+ Some(operator) => {
+ if !feature_allows_ranges {
+ return Err(input.new_custom_error(
+ StyleParseErrorKind::MediaQueryExpectedFeatureValue
+ ));
+ }
+ Some(RangeOrOperator::Operator(operator))
+ }
+ None => None,
+ }
}
-
- let feature_allows_ranges =
- feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed;
-
- let operator = input.try(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, None, None));
- }
- Ok(operator) => operator,
- };
+ };
- let range_or_operator = match range {
- Some(range) => {
- if operator.is_some() {
- return Err(input.new_custom_error(
- StyleParseErrorKind::MediaQueryExpectedFeatureValue
- ));
- }
- Some(RangeOrOperator::Range(range))
- }
- None => {
- match operator {
- Some(operator) => {
- if !feature_allows_ranges {
- return Err(input.new_custom_error(
- StyleParseErrorKind::MediaQueryExpectedFeatureValue
- ));
- }
- Some(RangeOrOperator::Operator(operator))
- }
- None => None,
- }
- }
- };
+ let value =
+ parse_feature_value(feature, feature.mValueType, context, input).map_err(|err| {
+ err.location
+ .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
+ })?;
- let value =
- parse_feature_value(feature, feature.mValueType, context, input).map_err(|err| {
- err.location
- .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
- })?;
-
- Ok(Self::new(feature, Some(value), range_or_operator))
- })
+ Ok(Self::new(feature, Some(value), range_or_operator))
}
/// Returns whether this media query evaluates to true for the given device.
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
let mut css_value = nsCSSValue::null();
unsafe {
(self.feature.mGetter.unwrap())(
device
new file mode 100644
--- /dev/null
+++ b/servo/components/style/media_queries/media_condition.rs
@@ -0,0 +1,109 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+//! A media query condition:
+//!
+//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
+
+use cssparser::{Parser, Token};
+use parser::ParserContext;
+use style_traits::ParseError;
+
+use super::MediaFeatureExpression;
+
+
+/// A binary `and` or `or` operator.
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Parse, ToCss)]
+#[allow(missing_docs)]
+pub enum Operator {
+ And,
+ Or,
+}
+
+/// Represents a media condition.
+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 MediaCondition {
+ /// Parse a single media condition.
+ pub fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> 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(Operator::parse) {
+ Ok(op) => op,
+ Err(..) => return Ok(first_condition),
+ };
+
+ 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(|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(expr) = input.try(|i| MediaFeatureExpression::parse_in_parenthesis_block(context, i)) {
+ return Ok(MediaCondition::Feature(expr));
+ }
+
+ let inner = Self::parse(context, input)?;
+ Ok(MediaCondition::InParens(Box::new(inner)))
+ })
+ }
+}
--- a/servo/components/style/media_queries/mod.rs
+++ b/servo/components/style/media_queries/mod.rs
@@ -1,18 +1,20 @@
/* 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 http://mozilla.org/MPL/2.0/. */
//! [Media queries][mq].
//!
//! [mq]: https://drafts.csswg.org/mediaqueries/
+mod media_condition;
mod media_list;
mod media_query;
+pub use self::media_condition::MediaCondition;
pub use self::media_list::MediaList;
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
#[cfg(feature = "servo")]
pub use servo::media_queries::{Device, MediaFeatureExpression};
#[cfg(feature = "gecko")]
pub use gecko::media_queries::{Device, MediaFeatureExpression};
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -178,44 +178,53 @@ impl MediaFeatureExpression {
/// Eventually this will become servo-only.
pub fn kind_for_testing(&self) -> &ExpressionKind {
&self.0
}
/// Parse a media expression of the form:
///
/// ```
- /// (media-feature: media-value)
+ /// media-feature: media-value
/// ```
///
- /// Only supports width and width ranges for now.
+ /// Only supports width ranges for now.
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| {
- let name = input.expect_ident_cloned()?;
- input.expect_colon()?;
- // TODO: Handle other media features
- Ok(MediaFeatureExpression(match_ignore_ascii_case! { &name,
- "min-width" => {
- ExpressionKind::Width(Range::Min(specified::Length::parse_non_negative(context, input)?))
- },
- "max-width" => {
- ExpressionKind::Width(Range::Max(specified::Length::parse_non_negative(context, input)?))
- },
- "width" => {
- ExpressionKind::Width(Range::Eq(specified::Length::parse_non_negative(context, input)?))
- },
- _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name)))
- }))
+ Self::parse_in_parenthesis_block(context, input)
})
}
+ /// Parse a media range 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 name = input.expect_ident_cloned()?;
+ input.expect_colon()?;
+ // TODO: Handle other media features
+ Ok(MediaFeatureExpression(match_ignore_ascii_case! { &name,
+ "min-width" => {
+ ExpressionKind::Width(Range::Min(specified::Length::parse_non_negative(context, input)?))
+ },
+ "max-width" => {
+ ExpressionKind::Width(Range::Max(specified::Length::parse_non_negative(context, input)?))
+ },
+ "width" => {
+ ExpressionKind::Width(Range::Eq(specified::Length::parse_non_negative(context, input)?))
+ },
+ _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name)))
+ }))
+ }
+
/// Evaluate this expression and return whether it matches the current
/// device.
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
let viewport_size = device.au_viewport_size();
let value = viewport_size.width;
match self.0 {
ExpressionKind::Width(ref range) => {
match range.to_computed_range(device, quirks_mode) {