style: Isolate the soon-to-be style-backend-specific from the media_query module.

This commit is contained in:
Emilio Cobos Álvarez 2017-01-03 21:09:57 +01:00
parent 7788f43c7e
commit c0cf847043
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
9 changed files with 389 additions and 318 deletions

View file

@ -4,13 +4,18 @@
//! Gecko-specific style-system bits. //! Gecko-specific style-system bits.
pub mod conversions;
pub mod data; pub mod data;
// TODO(emilio): Implement Gecko media query parsing and evaluation using
// nsMediaFeatures.
#[path = "../servo/media_queries.rs"]
pub mod media_queries;
pub mod restyle_damage; pub mod restyle_damage;
pub mod selector_parser;
pub mod snapshot; pub mod snapshot;
pub mod snapshot_helpers; pub mod snapshot_helpers;
pub mod traversal; pub mod traversal;
pub mod wrapper;
pub mod conversions;
pub mod selector_parser;
pub mod values; pub mod values;
pub mod wrapper;

View file

@ -110,7 +110,6 @@ pub mod keyframes;
#[allow(missing_docs)] // TODO. #[allow(missing_docs)] // TODO.
pub mod logical_geometry; pub mod logical_geometry;
pub mod matching; pub mod matching;
#[allow(missing_docs)]
pub mod media_queries; pub mod media_queries;
pub mod owning_handle; pub mod owning_handle;
pub mod parallel; pub mod parallel;

View file

@ -7,22 +7,22 @@
//! [mq]: https://drafts.csswg.org/mediaqueries/ //! [mq]: https://drafts.csswg.org/mediaqueries/
use Atom; use Atom;
use app_units::Au;
use cssparser::{Delimiter, Parser, Token}; use cssparser::{Delimiter, Parser, Token};
use euclid::size::{Size2D, TypedSize2D};
use properties::ComputedValues;
use serialize_comma_separated_list; use serialize_comma_separated_list;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::fmt; use std::fmt;
#[cfg(feature = "gecko")] use style_traits::ToCss;
use std::sync::Arc;
use style_traits::{ToCss, ViewportPx};
use values::computed::{self, ToComputedValue};
use values::specified;
#[cfg(feature = "servo")]
pub use servo::media_queries::{Device, Expression};
#[cfg(feature = "gecko")]
pub use gecko::media_queries::{Device, Expression};
/// A type that encapsulates a media query list.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct MediaList { pub struct MediaList {
/// The list of media queries.
pub media_queries: Vec<MediaQuery> pub media_queries: Vec<MediaQuery>
} }
@ -40,59 +40,15 @@ impl Default for MediaList {
} }
} }
#[derive(PartialEq, Eq, Copy, Clone, Debug)] /// https://drafts.csswg.org/mediaqueries/#mq-prefix
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Range<T> {
Min(T),
Max(T),
Eq(T),
}
impl Range<specified::Length> {
fn to_computed_range(&self, viewport_size: Size2D<Au>, default_values: &ComputedValues) -> Range<Au> {
// http://dev.w3.org/csswg/mediaqueries3/#units
// em units are relative to the initial font-size.
let context = computed::Context {
is_root_element: false,
viewport_size: viewport_size,
inherited_style: default_values,
// This cloning business is kind of dumb.... It's because Context
// insists on having an actual ComputedValues inside itself.
style: default_values.clone(),
font_metrics_provider: None
};
match *self {
Range::Min(ref width) => Range::Min(width.to_computed_value(&context)),
Range::Max(ref width) => Range::Max(width.to_computed_value(&context)),
Range::Eq(ref width) => Range::Eq(width.to_computed_value(&context))
}
}
}
impl<T: Ord> Range<T> {
fn evaluate(&self, value: T) -> bool {
match *self {
Range::Min(ref width) => { value >= *width },
Range::Max(ref width) => { value <= *width },
Range::Eq(ref width) => { value == *width },
}
}
}
/// http://dev.w3.org/csswg/mediaqueries-3/#media1
#[derive(PartialEq, Copy, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Expression {
/// http://dev.w3.org/csswg/mediaqueries-3/#width
Width(Range<specified::Length>),
}
/// http://dev.w3.org/csswg/mediaqueries-3/#media0
#[derive(PartialEq, Eq, Copy, Clone, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Qualifier { pub enum Qualifier {
/// Hide a media query from legacy UAs:
/// https://drafts.csswg.org/mediaqueries/#mq-only
Only, Only,
/// Negate a media query:
/// https://drafts.csswg.org/mediaqueries/#mq-not
Not, Not,
} }
@ -107,11 +63,17 @@ impl ToCss for Qualifier {
} }
} }
/// A [media query][mq].
///
/// [mq]: https://drafts.csswg.org/mediaqueries/
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct MediaQuery { pub struct MediaQuery {
/// The qualifier for this query.
pub qualifier: Option<Qualifier>, pub qualifier: Option<Qualifier>,
/// The media type for this query, that can be known, unknown, or "all".
pub media_type: MediaQueryType, pub media_type: MediaQueryType,
/// The set of expressions that this media query contains.
pub expressions: Vec<Expression>, pub expressions: Vec<Expression>,
} }
@ -122,7 +84,9 @@ impl MediaQuery {
Self::new(Some(Qualifier::Not), MediaQueryType::All, vec![]) Self::new(Some(Qualifier::Not), MediaQueryType::All, vec![])
} }
pub fn new(qualifier: Option<Qualifier>, media_type: MediaQueryType, /// Trivially constructs a new media query.
pub fn new(qualifier: Option<Qualifier>,
media_type: MediaQueryType,
expressions: Vec<Expression>) -> MediaQuery { expressions: Vec<Expression>) -> MediaQuery {
MediaQuery { MediaQuery {
qualifier: qualifier, qualifier: qualifier,
@ -134,7 +98,7 @@ impl MediaQuery {
impl ToCss for MediaQuery { impl ToCss for MediaQuery {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write where W: fmt::Write,
{ {
if let Some(qual) = self.qualifier { if let Some(qual) = self.qualifier {
try!(qual.to_css(dest)); try!(qual.to_css(dest));
@ -165,19 +129,11 @@ impl ToCss for MediaQuery {
try!(write!(dest, " and ")); try!(write!(dest, " and "));
} }
for (i, &e) in self.expressions.iter().enumerate() { try!(self.expressions[0].to_css(dest));
try!(write!(dest, "("));
let (mm, l) = match e { for expr in self.expressions.iter().skip(1) {
Expression::Width(Range::Min(ref l)) => ("min-", l),
Expression::Width(Range::Max(ref l)) => ("max-", l),
Expression::Width(Range::Eq(ref l)) => ("", l),
};
try!(write!(dest, "{}width: ", mm));
try!(l.to_css(dest));
try!(write!(dest, ")"));
if i != self.expressions.len() - 1 {
try!(write!(dest, " and ")); try!(write!(dest, " and "));
} try!(expr.to_css(dest));
} }
Ok(()) Ok(())
} }
@ -187,8 +143,11 @@ impl ToCss for MediaQuery {
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum MediaQueryType { pub enum MediaQueryType {
All, // Always true /// A media type that matches every device.
All,
/// A known media type, that we parse and understand.
Known(MediaType), Known(MediaType),
/// An unknown media type.
Unknown(Atom), Unknown(Atom),
} }
@ -204,19 +163,22 @@ impl MediaQueryType {
} }
} }
fn matches(&self, other: &MediaType) -> bool { fn matches(&self, other: MediaType) -> bool {
match *self { match *self {
MediaQueryType::All => true, MediaQueryType::All => true,
MediaQueryType::Known(ref known_type) => known_type == other, MediaQueryType::Known(ref known_type) => *known_type == other,
MediaQueryType::Unknown(..) => false, MediaQueryType::Unknown(..) => false,
} }
} }
} }
/// https://drafts.csswg.org/mediaqueries/#media-types
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum MediaType { pub enum MediaType {
/// The "screen" media type.
Screen, Screen,
/// The "print" media type.
Print, Print,
} }
@ -229,77 +191,10 @@ impl MediaType {
}) })
} }
} }
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Device {
pub media_type: MediaType,
pub viewport_size: TypedSize2D<f32, ViewportPx>,
#[cfg(feature = "gecko")]
pub default_values: Arc<ComputedValues>,
}
impl Device {
#[cfg(feature = "servo")]
pub fn new(media_type: MediaType, viewport_size: TypedSize2D<f32, ViewportPx>) -> Device {
Device {
media_type: media_type,
viewport_size: viewport_size,
}
}
#[cfg(feature = "servo")]
pub fn default_values(&self) -> &ComputedValues {
ComputedValues::initial_values()
}
#[cfg(feature = "gecko")]
pub fn new(media_type: MediaType, viewport_size: TypedSize2D<f32, ViewportPx>,
default_values: &Arc<ComputedValues>) -> Device {
Device {
media_type: media_type,
viewport_size: viewport_size,
default_values: default_values.clone(),
}
}
#[cfg(feature = "gecko")]
pub fn default_values(&self) -> &ComputedValues {
&*self.default_values
}
#[inline]
pub fn au_viewport_size(&self) -> Size2D<Au> {
Size2D::new(Au::from_f32_px(self.viewport_size.width),
Au::from_f32_px(self.viewport_size.height))
}
}
impl Expression {
fn parse(input: &mut Parser) -> Result<Expression, ()> {
try!(input.expect_parenthesis_block());
input.parse_nested_block(|input| {
let name = try!(input.expect_ident());
try!(input.expect_colon());
// TODO: Handle other media features
match_ignore_ascii_case! { name,
"min-width" => {
Ok(Expression::Width(Range::Min(try!(specified::Length::parse_non_negative(input)))))
},
"max-width" => {
Ok(Expression::Width(Range::Max(try!(specified::Length::parse_non_negative(input)))))
},
"width" => {
Ok(Expression::Width(Range::Eq(try!(specified::Length::parse_non_negative(input)))))
},
_ => Err(())
}
})
}
}
impl MediaQuery { impl MediaQuery {
/// Parse a media query given css input.
///
/// Returns an error if any of the expressions is unknown.
pub fn parse(input: &mut Parser) -> Result<MediaQuery, ()> { pub fn parse(input: &mut Parser) -> Result<MediaQuery, ()> {
let mut expressions = vec![]; let mut expressions = vec![];
@ -336,43 +231,61 @@ impl MediaQuery {
} }
} }
/// Parse a media query list from CSS.
///
/// Always returns a media query list. If any invalid media query is found, the
/// media query list is only filled with the equivalent of "not all", see:
///
/// https://drafts.csswg.org/mediaqueries/#error-handling
pub fn parse_media_query_list(input: &mut Parser) -> MediaList { pub fn parse_media_query_list(input: &mut Parser) -> MediaList {
if input.is_exhausted() { if input.is_exhausted() {
return Default::default() return Default::default()
} }
let mut media_queries = vec![]; let mut media_queries = vec![];
let mut found_invalid = false;
loop { loop {
media_queries.push( match input.parse_until_before(Delimiter::Comma, MediaQuery::parse) {
input.parse_until_before(Delimiter::Comma, MediaQuery::parse).ok() Ok(mq) => if !found_invalid {
.unwrap_or_else(MediaQuery::never_matching)); media_queries.push(mq);
},
Err(..) => if !found_invalid {
media_queries.clear();
media_queries.push(MediaQuery::never_matching());
// Consume the rest of the input as if they were valid
// expressions (they might be, they might not), but ignore the
// result, this allows correctly parsing invalid media queries.
found_invalid = true;
},
}
match input.next() { match input.next() {
Ok(Token::Comma) => {}, Ok(Token::Comma) => {},
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
Err(()) => break, Err(()) => break,
} }
} }
debug_assert!(!found_invalid || media_queries.len() == 1);
MediaList { MediaList {
media_queries: media_queries, media_queries: media_queries,
} }
} }
impl MediaList { impl MediaList {
/// Evaluate a whole `MediaList` against `Device`.
pub fn evaluate(&self, device: &Device) -> bool { pub fn evaluate(&self, device: &Device) -> bool {
let viewport_size = device.au_viewport_size();
// 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 (OR condition)
// 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 all conditions match (AND condition)
let query_match = media_match && mq.expressions.iter().all(|expression| { let query_match =
match *expression { media_match &&
Expression::Width(ref value) => mq.expressions.iter()
value.to_computed_range(viewport_size, device.default_values()).evaluate(viewport_size.width), .all(|expression| expression.matches(&device));
}
});
// Apply the logical NOT qualifier to the result // Apply the logical NOT qualifier to the result
match mq.qualifier { match mq.qualifier {
@ -382,6 +295,7 @@ impl MediaList {
}) })
} }
/// Whether this `MediaList` contains no media queries.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.media_queries.is_empty() self.media_queries.is_empty()
} }

View file

@ -0,0 +1,224 @@
/* 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/. */
//! Servo's media-query device and expression representation.
use app_units::Au;
use cssparser::Parser;
use euclid::{Size2D, TypedSize2D};
use media_queries::MediaType;
use properties::ComputedValues;
use std::fmt;
#[cfg(feature = "gecko")]
use std::sync::Arc;
use style_traits::{ToCss, ViewportPx};
use style_traits::viewport::ViewportConstraints;
use values::computed::{self, ToComputedValue};
use values::specified;
/// A device is a structure that represents the current media a given document
/// is displayed in.
///
/// This is the struct against which media queries are evaluated.
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Device {
/// The current media type used by de device.
media_type: MediaType,
/// The current viewport size, in viewport pixels.
viewport_size: TypedSize2D<f32, ViewportPx>,
/// A set of default computed values for this document.
///
/// This allows handling zoom correctly, among other things. Gecko-only for
/// now, see #14773.
#[cfg(feature = "gecko")]
default_values: Arc<ComputedValues>,
}
impl Device {
/// Trivially construct a new `Device`.
#[cfg(feature = "servo")]
pub fn new(media_type: MediaType,
viewport_size: TypedSize2D<f32, ViewportPx>)
-> Device {
Device {
media_type: media_type,
viewport_size: viewport_size,
}
}
/// Trivially construct a new `Device`.
#[cfg(feature = "gecko")]
pub fn new(media_type:
MediaType, viewport_size: TypedSize2D<f32, ViewportPx>,
default_values: &Arc<ComputedValues>) -> Device {
Device {
media_type: media_type,
viewport_size: viewport_size,
default_values: default_values.clone(),
}
}
/// Return the default computed values for this device.
#[cfg(feature = "servo")]
pub fn default_values(&self) -> &ComputedValues {
ComputedValues::initial_values()
}
/// Return the default computed values for this device.
#[cfg(feature = "gecko")]
pub fn default_values(&self) -> &ComputedValues {
&*self.default_values
}
/// Returns the viewport size of the current device in app units, needed,
/// among other things, to resolve viewport units.
#[inline]
pub fn au_viewport_size(&self) -> Size2D<Au> {
Size2D::new(Au::from_f32_px(self.viewport_size.width),
Au::from_f32_px(self.viewport_size.height))
}
/// Returns the viewport size in pixels.
#[inline]
pub fn px_viewport_size(&self) -> TypedSize2D<f32, ViewportPx> {
self.viewport_size
}
/// Take into account a viewport rule taken from the stylesheets.
pub fn account_for_viewport_rule(&mut self, constraints: &ViewportConstraints) {
self.viewport_size = constraints.size;
}
/// Return the media type of the current device.
pub fn media_type(&self) -> MediaType {
self.media_type.clone()
}
}
/// A expression kind servo understands and parses.
///
/// Only `pub` for unit testing, please don't use it directly!
#[derive(PartialEq, Copy, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum ExpressionKind {
/// http://dev.w3.org/csswg/mediaqueries-3/#width
Width(Range<specified::Length>),
}
/// A single expression a per:
///
/// http://dev.w3.org/csswg/mediaqueries-3/#media1
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Expression(ExpressionKind);
impl Expression {
/// The kind of expression we're, just for unit testing.
///
/// 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)
/// ```
///
/// Only supports width and width ranges for now.
pub fn parse(input: &mut Parser) -> Result<Self, ()> {
try!(input.expect_parenthesis_block());
input.parse_nested_block(|input| {
let name = try!(input.expect_ident());
try!(input.expect_colon());
// TODO: Handle other media features
Ok(Expression(match_ignore_ascii_case! { name,
"min-width" => {
ExpressionKind::Width(Range::Min(try!(specified::Length::parse_non_negative(input))))
},
"max-width" => {
ExpressionKind::Width(Range::Max(try!(specified::Length::parse_non_negative(input))))
},
"width" => {
ExpressionKind::Width(Range::Eq(try!(specified::Length::parse_non_negative(input))))
},
_ => return Err(())
}))
})
}
/// Evaluate this expression and return whether it matches the current
/// device.
pub fn matches(&self, device: &Device) -> 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(viewport_size, device.default_values()) {
Range::Min(ref width) => { value >= *width },
Range::Max(ref width) => { value <= *width },
Range::Eq(ref width) => { value == *width },
}
}
}
}
}
impl ToCss for Expression {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
try!(write!(dest, "("));
let (mm, l) = match self.0 {
ExpressionKind::Width(Range::Min(ref l)) => ("min-", l),
ExpressionKind::Width(Range::Max(ref l)) => ("max-", l),
ExpressionKind::Width(Range::Eq(ref l)) => ("", l),
};
try!(write!(dest, "{}width: ", mm));
try!(l.to_css(dest));
write!(dest, ")")
}
}
/// An enumeration that represents a ranged value.
///
/// Only public for testing, implementation details of `Expression` may change
/// for Stylo.
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Range<T> {
/// At least the inner value.
Min(T),
/// At most the inner value.
Max(T),
/// Exactly the inner value.
Eq(T),
}
impl Range<specified::Length> {
fn to_computed_range(&self,
viewport_size: Size2D<Au>,
default_values: &ComputedValues)
-> Range<Au> {
// http://dev.w3.org/csswg/mediaqueries3/#units
// em units are relative to the initial font-size.
let context = computed::Context {
is_root_element: false,
viewport_size: viewport_size,
inherited_style: default_values,
// This cloning business is kind of dumb.... It's because Context
// insists on having an actual ComputedValues inside itself.
style: default_values.clone(),
font_metrics_provider: None
};
match *self {
Range::Min(ref width) => Range::Min(width.to_computed_value(&context)),
Range::Max(ref width) => Range::Max(width.to_computed_value(&context)),
Range::Eq(ref width) => Range::Eq(width.to_computed_value(&context))
}
}
}

View file

@ -6,5 +6,6 @@
//! //!
//! These get compiled out on a Gecko build. //! These get compiled out on a Gecko build.
pub mod media_queries;
pub mod restyle_damage; pub mod restyle_damage;
pub mod selector_parser; pub mod selector_parser;

View file

@ -12,8 +12,6 @@ use dom::{PresentationalHintsSynthetizer, TElement};
use error_reporting::StdoutErrorReporter; use error_reporting::StdoutErrorReporter;
use keyframes::KeyframesAnimation; use keyframes::KeyframesAnimation;
use media_queries::Device; use media_queries::Device;
#[cfg(feature = "servo")]
use media_queries::MediaType;
use parking_lot::RwLock; use parking_lot::RwLock;
use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL, Importance}; use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL, Importance};
use properties::{PropertyDeclaration, PropertyDeclarationBlock}; use properties::{PropertyDeclaration, PropertyDeclarationBlock};
@ -36,7 +34,6 @@ use std::slice;
use std::sync::Arc; use std::sync::Arc;
use style_traits::viewport::ViewportConstraints; use style_traits::viewport::ViewportConstraints;
use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets}; use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets};
#[cfg(feature = "servo")]
use viewport::{self, MaybeNew, ViewportRule}; use viewport::{self, MaybeNew, ViewportRule};
pub use ::fnv::FnvHashMap; pub use ::fnv::FnvHashMap;
@ -389,20 +386,24 @@ impl Stylist {
/// This means that we may need to rebuild style data even if the /// This means that we may need to rebuild style data even if the
/// stylesheets haven't changed. /// stylesheets haven't changed.
/// ///
/// Viewport_Constraints::maybe_new is servo-only (see the comment above it /// Also, the device that arrives here may need to take the viewport rules
/// explaining why), so we need to be servo-only too, since we call it. /// into account.
#[cfg(feature = "servo")] ///
/// TODO(emilio): Probably should be unified with `update`, right now I
/// don't think we take into account dynamic updates to viewport rules.
///
/// Probably worth to make the stylist own a single `Device`, and have a
/// `update_device` function?
pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet>]) { pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet>]) {
let cascaded_rule = ViewportRule { let cascaded_rule = ViewportRule {
declarations: viewport::Cascade::from_stylesheets(stylesheets, &device).finish(), declarations: viewport::Cascade::from_stylesheets(stylesheets, &device).finish(),
}; };
self.viewport_constraints = ViewportConstraints::maybe_new(device.viewport_size, &cascaded_rule); self.viewport_constraints =
ViewportConstraints::maybe_new(&device, &cascaded_rule);
if let Some(ref constraints) = self.viewport_constraints { if let Some(ref constraints) = self.viewport_constraints {
// FIXME(emilio): creating a device here works, but is not really device.account_for_viewport_rule(constraints);
// appropriate. I should get rid of this while doing the stylo media
// query work.
device = Device::new(MediaType::Screen, constraints.size);
} }
fn mq_eval_changed(rules: &[CssRule], before: &Device, after: &Device) -> bool { fn mq_eval_changed(rules: &[CssRule], before: &Device, after: &Device) -> bool {

View file

@ -9,28 +9,21 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#[cfg(feature = "servo")]
use app_units::Au; use app_units::Au;
use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important}; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
use cssparser::ToCss as ParserToCss; use cssparser::ToCss as ParserToCss;
#[cfg(feature = "servo")]
use euclid::scale_factor::ScaleFactor; use euclid::scale_factor::ScaleFactor;
#[cfg(feature = "servo")]
use euclid::size::Size2D;
use euclid::size::TypedSize2D; use euclid::size::TypedSize2D;
use media_queries::Device; use media_queries::Device;
use parser::{ParserContext, log_css_error}; use parser::{ParserContext, log_css_error};
#[cfg(feature = "servo")]
use properties::ComputedValues;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::iter::Enumerate; use std::iter::Enumerate;
use std::str::Chars; use std::str::Chars;
use style_traits::{ToCss, ViewportPx}; use style_traits::ToCss;
use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom}; use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
use stylesheets::{Stylesheet, Origin}; use stylesheets::{Stylesheet, Origin};
#[cfg(feature = "servo")]
use values::computed::{Context, ToComputedValue}; use values::computed::{Context, ToComputedValue};
use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength}; use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength};
@ -606,18 +599,13 @@ impl Cascade {
pub trait MaybeNew { pub trait MaybeNew {
/// Create a ViewportConstraints from a viewport size and a `@viewport` /// Create a ViewportConstraints from a viewport size and a `@viewport`
/// rule. /// rule.
fn maybe_new(initial_viewport: TypedSize2D<f32, ViewportPx>, fn maybe_new(device: &Device,
rule: &ViewportRule) rule: &ViewportRule)
-> Option<ViewportConstraints>; -> Option<ViewportConstraints>;
} }
/// MaybeNew for ViewportConstraints uses ComputedValues::initial_values which
/// is servo-only (not present in gecko). Once it has been changed to properly
/// use per-document initial computed values, or not use the initial computed
/// values at all, it can go back to being compiled unconditionally.
#[cfg(feature = "servo")]
impl MaybeNew for ViewportConstraints { impl MaybeNew for ViewportConstraints {
fn maybe_new(initial_viewport: TypedSize2D<f32, ViewportPx>, fn maybe_new(device: &Device,
rule: &ViewportRule) rule: &ViewportRule)
-> Option<ViewportConstraints> -> Option<ViewportConstraints>
{ {
@ -695,15 +683,14 @@ impl MaybeNew for ViewportConstraints {
// //
// Note: DEVICE-ADAPT § 5. states that relative length values are // Note: DEVICE-ADAPT § 5. states that relative length values are
// resolved against initial values // resolved against initial values
let initial_viewport = Size2D::new(Au::from_f32_px(initial_viewport.width), let initial_viewport = device.au_viewport_size();
Au::from_f32_px(initial_viewport.height));
// TODO(emilio): Stop cloning `ComputedValues` around!
let context = Context { let context = Context {
is_root_element: false, is_root_element: false,
viewport_size: initial_viewport, viewport_size: initial_viewport,
inherited_style: ComputedValues::initial_values(), inherited_style: device.default_values(),
style: ComputedValues::initial_values().clone(), style: device.default_values().clone(),
font_metrics_provider: None, // TODO: Should have! font_metrics_provider: None, // TODO: Should have!
}; };

View file

@ -11,6 +11,7 @@ use style::Atom;
use style::error_reporting::ParseErrorReporter; use style::error_reporting::ParseErrorReporter;
use style::media_queries::*; use style::media_queries::*;
use style::parser::ParserContextExtraData; use style::parser::ParserContextExtraData;
use style::servo::media_queries::*;
use style::stylesheets::{Stylesheet, Origin, CssRule}; use style::stylesheets::{Stylesheet, Origin, CssRule};
use style::values::specified; use style::values::specified;
use style_traits::ToCss; use style_traits::ToCss;
@ -25,8 +26,11 @@ impl ParseErrorReporter for CSSErrorReporterTest {
} }
} }
fn test_media_rule<F>(css: &str, callback: F) where F: Fn(&MediaList, &str) { fn test_media_rule<F>(css: &str, callback: F)
where F: Fn(&MediaList, &str),
{
let url = ServoUrl::parse("http://localhost").unwrap(); let url = ServoUrl::parse("http://localhost").unwrap();
let css_str = css.to_owned();
let stylesheet = Stylesheet::from_str( let stylesheet = Stylesheet::from_str(
css, url, Origin::Author, Default::default(), css, url, Origin::Author, Default::default(),
None, Box::new(CSSErrorReporterTest), None, Box::new(CSSErrorReporterTest),
@ -36,10 +40,12 @@ fn test_media_rule<F>(css: &str, callback: F) where F: Fn(&MediaList, &str) {
rule_count += 1; rule_count += 1;
callback(mq, css); callback(mq, css);
}); });
assert!(rule_count > 0); assert!(rule_count > 0, css_str);
} }
fn media_queries<F>(rules: &[CssRule], f: &mut F) where F: FnMut(&MediaList) { fn media_queries<F>(rules: &[CssRule], f: &mut F)
where F: FnMut(&MediaList),
{
for rule in rules { for rule in rules {
rule.with_nested_rules_and_mq(|rules, mq| { rule.with_nested_rules_and_mq(|rules, mq| {
if let Some(mq) = mq { if let Some(mq) = mq {
@ -200,8 +206,8 @@ fn test_mq_default_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -212,8 +218,8 @@ fn test_mq_default_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -227,8 +233,8 @@ fn test_mq_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -239,8 +245,8 @@ fn test_mq_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -251,8 +257,8 @@ fn test_mq_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Eq(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), ExpressionKind::Width(Range::Eq(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -263,8 +269,8 @@ fn test_mq_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Unknown(Atom::from("fridge")), css.to_owned()); assert!(q.media_type == MediaQueryType::Unknown(Atom::from("fridge")), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))), ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -288,12 +294,12 @@ fn test_mq_multiple_expressions() {
assert!(q.qualifier == None, css.to_owned()); assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 2, css.to_owned()); assert!(q.expressions.len() == 2, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
match q.expressions[1] { match *q.expressions[1].kind_for_testing() {
Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))), ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -304,12 +310,12 @@ fn test_mq_multiple_expressions() {
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
assert!(q.expressions.len() == 2, css.to_owned()); assert!(q.expressions.len() == 2, css.to_owned());
match q.expressions[0] { match *q.expressions[0].kind_for_testing() {
Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
match q.expressions[1] { match *q.expressions[1].kind_for_testing() {
Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))), ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))),
_ => panic!("wrong expression type"), _ => panic!("wrong expression type"),
} }
}); });
@ -317,89 +323,31 @@ fn test_mq_multiple_expressions() {
#[test] #[test]
fn test_mq_malformed_expressions() { fn test_mq_malformed_expressions() {
test_media_rule("@media (min-width: 100blah) and (max-width: 200px) { }", |list, css| { fn check_malformed_expr(list: &MediaList, css: &str) {
assert!(list.media_queries.len() == 1, css.to_owned()); assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0]; let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned());
}); }
test_media_rule("@media screen and (height: 200px) { }", |list, css| { for rule in &[
assert!(list.media_queries.len() == 1, css.to_owned()); "@media (min-width: 100blah) and (max-width: 200px) { }",
let q = &list.media_queries[0]; "@media screen and (height: 200px) { }",
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); "@media (min-width: 30em foo bar) {}",
assert!(q.media_type == MediaQueryType::All, css.to_owned()); "@media not {}",
assert!(q.expressions.len() == 0, css.to_owned()); "@media not (min-width: 300px) {}",
}); "@media , {}",
"@media screen 4px, print {}",
test_media_rule("@media (min-width: 30em foo bar) {}", |list, css| { "@media screen, {}",
assert!(list.media_queries.len() == 1, css.to_owned()); ] {
let q = &list.media_queries[0]; test_media_rule(rule, check_malformed_expr);
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); }
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media not {}", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media not (min-width: 300px) {}", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media , {}", |list, css| {
assert!(list.media_queries.len() == 2, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
let q = &list.media_queries[1];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media screen 4px, print {}", |list, css| {
assert!(list.media_queries.len() == 2, css.to_owned());
let q0 = &list.media_queries[0];
assert!(q0.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q0.media_type == MediaQueryType::All, css.to_owned());
assert!(q0.expressions.len() == 0, css.to_owned());
let q1 = &list.media_queries[1];
assert!(q1.qualifier == None, css.to_owned());
assert!(q1.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned());
assert!(q1.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media screen, {}", |list, css| {
assert!(list.media_queries.len() == 2, css.to_owned());
let q0 = &list.media_queries[0];
assert!(q0.qualifier == None, css.to_owned());
assert!(q0.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned());
assert!(q0.expressions.len() == 0, css.to_owned());
let q1 = &list.media_queries[1];
assert!(q1.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q1.media_type == MediaQueryType::All, css.to_owned());
assert!(q1.expressions.len() == 0, css.to_owned());
});
} }
#[test] #[test]
fn test_matching_simple() { fn test_matching_simple() {
let device = Device { let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0));
media_type: MediaType::Screen,
viewport_size: TypedSize2D::new(200.0, 100.0),
};
media_query_test(&device, "@media not all { a { color: red; } }", 0); media_query_test(&device, "@media not all { a { color: red; } }", 0);
media_query_test(&device, "@media not screen { a { color: red; } }", 0); media_query_test(&device, "@media not screen { a { color: red; } }", 0);
@ -415,10 +363,7 @@ fn test_matching_simple() {
#[test] #[test]
fn test_matching_width() { fn test_matching_width() {
let device = Device { let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0));
media_type: MediaType::Screen,
viewport_size: TypedSize2D::new(200.0, 100.0),
};
media_query_test(&device, "@media { a { color: red; } }", 1); media_query_test(&device, "@media { a { color: red; } }", 1);
@ -459,10 +404,7 @@ fn test_matching_width() {
#[test] #[test]
fn test_matching_invalid() { fn test_matching_invalid() {
let device = Device { let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0));
media_type: MediaType::Screen,
viewport_size: TypedSize2D::new(200.0, 100.0),
};
media_query_test(&device, "@media fridge { a { color: red; } }", 0); media_query_test(&device, "@media fridge { a { color: red; } }", 0);
media_query_test(&device, "@media screen and (height: 100px) { a { color: red; } }", 0); media_query_test(&device, "@media screen and (height: 100px) { a { color: red; } }", 0);

View file

@ -288,11 +288,10 @@ fn constrain_viewport() {
} }
let initial_viewport = TypedSize2D::new(800., 600.); let initial_viewport = TypedSize2D::new(800., 600.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("")), let device = Device::new(MediaType::Screen, initial_viewport);
None); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("")), None);
let initial_viewport = TypedSize2D::new(800., 600.); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")),
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
Some(ViewportConstraints { Some(ViewportConstraints {
size: initial_viewport, size: initial_viewport,
@ -304,21 +303,7 @@ fn constrain_viewport() {
orientation: Orientation::Auto orientation: Orientation::Auto
})); }));
let initial_viewport = TypedSize2D::new(200., 150.); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")),
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
Some(ViewportConstraints {
size: TypedSize2D::new(320., 240.),
initial_zoom: ScaleFactor::new(1.),
min_zoom: None,
max_zoom: None,
user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto
}));
let initial_viewport = TypedSize2D::new(800., 600.);
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")),
Some(ViewportConstraints { Some(ViewportConstraints {
size: initial_viewport, size: initial_viewport,
@ -330,8 +315,7 @@ fn constrain_viewport() {
orientation: Orientation::Auto orientation: Orientation::Auto
})); }));
let initial_viewport = TypedSize2D::new(800., 600.); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 800px; height: 600px;\
assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 800px; height: 600px;\
zoom: 1;\ zoom: 1;\
user-zoom: zoom;\ user-zoom: zoom;\
orientation: auto;")), orientation: auto;")),
@ -342,6 +326,20 @@ fn constrain_viewport() {
min_zoom: None, min_zoom: None,
max_zoom: None, max_zoom: None,
user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto
}));
let initial_viewport = TypedSize2D::new(200., 150.);
let device = Device::new(MediaType::Screen, initial_viewport);
assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")),
Some(ViewportConstraints {
size: TypedSize2D::new(320., 240.),
initial_zoom: ScaleFactor::new(1.),
min_zoom: None,
max_zoom: None,
user_zoom: UserZoom::Zoom, user_zoom: UserZoom::Zoom,
orientation: Orientation::Auto orientation: Orientation::Auto
})); }));