Bug 1422225: Add code to parse media conditions. r=xidorn

Still unused.

MozReview-Commit-ID: IQfxObw9BV5
This commit is contained in:
Emilio Cobos Álvarez 2018-06-15 23:51:07 -07:00
parent ca0fd99400
commit 9b7d6f0fc9
4 changed files with 267 additions and 138 deletions

View File

@ -651,141 +651,150 @@ impl MediaFeatureExpression {
})?;
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,
})
})?;
let mut flags = 0;
if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent
{
flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly;
}
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(()),
}
};
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()),
));
}
flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly;
}
let feature_allows_ranges =
feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed;
let result = {
let mut feature_name = &**ident;
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 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;
}
return Ok(Self::new(feature, None, None));
}
Ok(operator) => operator,
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_or_operator = match range {
Some(range) => {
if operator.is_some() {
return Err(input.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureValue
));
}
Some(RangeOrOperator::Range(range))
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
));
}
None => {
match operator {
Some(operator) => {
if !feature_allows_ranges {
return Err(input.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureValue
));
}
Some(RangeOrOperator::Operator(operator))
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
));
}
None => None,
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.

View File

@ -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)))
})
}
}

View File

@ -6,9 +6,11 @@
//!
//! [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};

View File

@ -183,34 +183,43 @@ impl MediaFeatureExpression {
/// 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 {