From c0cf8470434477a0064694d94d027d5aee2576f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 3 Jan 2017 21:09:57 +0100 Subject: [PATCH] style: Isolate the soon-to-be style-backend-specific from the media_query module. --- components/style/gecko/mod.rs | 13 +- components/style/lib.rs | 1 - components/style/media_queries.rs | 228 ++++++++---------------- components/style/servo/media_queries.rs | 224 +++++++++++++++++++++++ components/style/servo/mod.rs | 1 + components/style/stylist.rs | 23 +-- components/style/viewport.rs | 27 +-- tests/unit/style/media_queries.rs | 150 +++++----------- tests/unit/style/viewport.rs | 40 ++--- 9 files changed, 389 insertions(+), 318 deletions(-) create mode 100644 components/style/servo/media_queries.rs diff --git a/components/style/gecko/mod.rs b/components/style/gecko/mod.rs index 0e49405d159..6166a64bea3 100644 --- a/components/style/gecko/mod.rs +++ b/components/style/gecko/mod.rs @@ -4,13 +4,18 @@ //! Gecko-specific style-system bits. +pub mod conversions; 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 selector_parser; pub mod snapshot; pub mod snapshot_helpers; pub mod traversal; -pub mod wrapper; - -pub mod conversions; -pub mod selector_parser; pub mod values; +pub mod wrapper; diff --git a/components/style/lib.rs b/components/style/lib.rs index 910d2cb1cf4..b7d5ace36cb 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -110,7 +110,6 @@ pub mod keyframes; #[allow(missing_docs)] // TODO. pub mod logical_geometry; pub mod matching; -#[allow(missing_docs)] pub mod media_queries; pub mod owning_handle; pub mod parallel; diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index 98c621ad89a..daac8bed876 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -7,22 +7,22 @@ //! [mq]: https://drafts.csswg.org/mediaqueries/ use Atom; -use app_units::Au; use cssparser::{Delimiter, Parser, Token}; -use euclid::size::{Size2D, TypedSize2D}; -use properties::ComputedValues; use serialize_comma_separated_list; use std::ascii::AsciiExt; use std::fmt; -#[cfg(feature = "gecko")] -use std::sync::Arc; -use style_traits::{ToCss, ViewportPx}; -use values::computed::{self, ToComputedValue}; -use values::specified; +use style_traits::ToCss; +#[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)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct MediaList { + /// The list of media queries. pub media_queries: Vec } @@ -40,59 +40,15 @@ impl Default for MediaList { } } -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum Range { - Min(T), - Max(T), - Eq(T), -} - -impl Range { - fn to_computed_range(&self, viewport_size: Size2D, default_values: &ComputedValues) -> Range { - // 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 Range { - 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), -} - -/// http://dev.w3.org/csswg/mediaqueries-3/#media0 +/// https://drafts.csswg.org/mediaqueries/#mq-prefix #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum Qualifier { + /// Hide a media query from legacy UAs: + /// https://drafts.csswg.org/mediaqueries/#mq-only Only, + /// Negate a media query: + /// https://drafts.csswg.org/mediaqueries/#mq-not Not, } @@ -107,11 +63,17 @@ impl ToCss for Qualifier { } } +/// A [media query][mq]. +/// +/// [mq]: https://drafts.csswg.org/mediaqueries/ #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct MediaQuery { + /// The qualifier for this query. pub qualifier: Option, + /// The media type for this query, that can be known, unknown, or "all". pub media_type: MediaQueryType, + /// The set of expressions that this media query contains. pub expressions: Vec, } @@ -122,7 +84,9 @@ impl MediaQuery { Self::new(Some(Qualifier::Not), MediaQueryType::All, vec![]) } - pub fn new(qualifier: Option, media_type: MediaQueryType, + /// Trivially constructs a new media query. + pub fn new(qualifier: Option, + media_type: MediaQueryType, expressions: Vec) -> MediaQuery { MediaQuery { qualifier: qualifier, @@ -134,7 +98,7 @@ impl MediaQuery { impl ToCss for MediaQuery { fn to_css(&self, dest: &mut W) -> fmt::Result - where W: fmt::Write + where W: fmt::Write, { if let Some(qual) = self.qualifier { try!(qual.to_css(dest)); @@ -165,19 +129,11 @@ impl ToCss for MediaQuery { try!(write!(dest, " and ")); } - for (i, &e) in self.expressions.iter().enumerate() { - try!(write!(dest, "(")); - let (mm, l) = match e { - 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!(self.expressions[0].to_css(dest)); + + for expr in self.expressions.iter().skip(1) { + try!(write!(dest, " and ")); + try!(expr.to_css(dest)); } Ok(()) } @@ -187,8 +143,11 @@ impl ToCss for MediaQuery { #[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] 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), + /// An unknown media type. Unknown(Atom), } @@ -204,19 +163,22 @@ impl MediaQueryType { } } - fn matches(&self, other: &MediaType) -> bool { + fn matches(&self, other: MediaType) -> bool { match *self { MediaQueryType::All => true, - MediaQueryType::Known(ref known_type) => known_type == other, + MediaQueryType::Known(ref known_type) => *known_type == other, MediaQueryType::Unknown(..) => false, } } } +/// https://drafts.csswg.org/mediaqueries/#media-types #[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum MediaType { + /// The "screen" media type. Screen, + /// The "print" media type. 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, - #[cfg(feature = "gecko")] - pub default_values: Arc, -} - -impl Device { - #[cfg(feature = "servo")] - pub fn new(media_type: MediaType, viewport_size: TypedSize2D) -> 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, - default_values: &Arc) -> 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 { - 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 { - 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 { + /// Parse a media query given css input. + /// + /// Returns an error if any of the expressions is unknown. pub fn parse(input: &mut Parser) -> Result { 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 { if input.is_exhausted() { return Default::default() } let mut media_queries = vec![]; + let mut found_invalid = false; loop { - media_queries.push( - input.parse_until_before(Delimiter::Comma, MediaQuery::parse).ok() - .unwrap_or_else(MediaQuery::never_matching)); + match input.parse_until_before(Delimiter::Comma, MediaQuery::parse) { + Ok(mq) => if !found_invalid { + 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() { Ok(Token::Comma) => {}, Ok(_) => unreachable!(), Err(()) => break, } } + + debug_assert!(!found_invalid || media_queries.len() == 1); + MediaList { media_queries: media_queries, } } impl MediaList { + /// Evaluate a whole `MediaList` against `Device`. 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) // https://drafts.csswg.org/mediaqueries-4/#mq-list 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) - let query_match = media_match && mq.expressions.iter().all(|expression| { - match *expression { - Expression::Width(ref value) => - value.to_computed_range(viewport_size, device.default_values()).evaluate(viewport_size.width), - } - }); + let query_match = + media_match && + mq.expressions.iter() + .all(|expression| expression.matches(&device)); // Apply the logical NOT qualifier to the result match mq.qualifier { @@ -382,6 +295,7 @@ impl MediaList { }) } + /// Whether this `MediaList` contains no media queries. pub fn is_empty(&self) -> bool { self.media_queries.is_empty() } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs new file mode 100644 index 00000000000..6dbd78d1587 --- /dev/null +++ b/components/style/servo/media_queries.rs @@ -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, + /// 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, +} + +impl Device { + /// Trivially construct a new `Device`. + #[cfg(feature = "servo")] + pub fn new(media_type: MediaType, + viewport_size: TypedSize2D) + -> 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, + default_values: &Arc) -> 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 { + 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 { + 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), +} + +/// 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 { + 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(&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 { + /// At least the inner value. + Min(T), + /// At most the inner value. + Max(T), + /// Exactly the inner value. + Eq(T), +} + +impl Range { + fn to_computed_range(&self, + viewport_size: Size2D, + default_values: &ComputedValues) + -> Range { + // 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)) + } + } +} diff --git a/components/style/servo/mod.rs b/components/style/servo/mod.rs index 29025b432a7..ad741616eeb 100644 --- a/components/style/servo/mod.rs +++ b/components/style/servo/mod.rs @@ -6,5 +6,6 @@ //! //! These get compiled out on a Gecko build. +pub mod media_queries; pub mod restyle_damage; pub mod selector_parser; diff --git a/components/style/stylist.rs b/components/style/stylist.rs index c67cf6f737d..9c3608ef0c0 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -12,8 +12,6 @@ use dom::{PresentationalHintsSynthetizer, TElement}; use error_reporting::StdoutErrorReporter; use keyframes::KeyframesAnimation; use media_queries::Device; -#[cfg(feature = "servo")] -use media_queries::MediaType; use parking_lot::RwLock; use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL, Importance}; use properties::{PropertyDeclaration, PropertyDeclarationBlock}; @@ -36,7 +34,6 @@ use std::slice; use std::sync::Arc; use style_traits::viewport::ViewportConstraints; use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets}; -#[cfg(feature = "servo")] use viewport::{self, MaybeNew, ViewportRule}; pub use ::fnv::FnvHashMap; @@ -389,20 +386,24 @@ impl Stylist { /// This means that we may need to rebuild style data even if the /// stylesheets haven't changed. /// - /// Viewport_Constraints::maybe_new is servo-only (see the comment above it - /// explaining why), so we need to be servo-only too, since we call it. - #[cfg(feature = "servo")] + /// Also, the device that arrives here may need to take the viewport rules + /// into account. + /// + /// 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]) { let cascaded_rule = ViewportRule { 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 { - // FIXME(emilio): creating a device here works, but is not really - // appropriate. I should get rid of this while doing the stylo media - // query work. - device = Device::new(MediaType::Screen, constraints.size); + device.account_for_viewport_rule(constraints); } fn mq_eval_changed(rules: &[CssRule], before: &Device, after: &Device) -> bool { diff --git a/components/style/viewport.rs b/components/style/viewport.rs index 38ff5a6942d..912bf881b53 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -9,28 +9,21 @@ #![deny(missing_docs)] -#[cfg(feature = "servo")] use app_units::Au; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important}; use cssparser::ToCss as ParserToCss; -#[cfg(feature = "servo")] use euclid::scale_factor::ScaleFactor; -#[cfg(feature = "servo")] -use euclid::size::Size2D; use euclid::size::TypedSize2D; use media_queries::Device; use parser::{ParserContext, log_css_error}; -#[cfg(feature = "servo")] -use properties::ComputedValues; use std::ascii::AsciiExt; use std::borrow::Cow; use std::fmt; use std::iter::Enumerate; use std::str::Chars; -use style_traits::{ToCss, ViewportPx}; +use style_traits::ToCss; use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom}; use stylesheets::{Stylesheet, Origin}; -#[cfg(feature = "servo")] use values::computed::{Context, ToComputedValue}; use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength}; @@ -606,18 +599,13 @@ impl Cascade { pub trait MaybeNew { /// Create a ViewportConstraints from a viewport size and a `@viewport` /// rule. - fn maybe_new(initial_viewport: TypedSize2D, + fn maybe_new(device: &Device, rule: &ViewportRule) -> Option; } -/// 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 { - fn maybe_new(initial_viewport: TypedSize2D, + fn maybe_new(device: &Device, rule: &ViewportRule) -> Option { @@ -695,15 +683,14 @@ impl MaybeNew for ViewportConstraints { // // Note: DEVICE-ADAPT ยง 5. states that relative length values are // resolved against initial values - let initial_viewport = Size2D::new(Au::from_f32_px(initial_viewport.width), - Au::from_f32_px(initial_viewport.height)); - + let initial_viewport = device.au_viewport_size(); + // TODO(emilio): Stop cloning `ComputedValues` around! let context = Context { is_root_element: false, viewport_size: initial_viewport, - inherited_style: ComputedValues::initial_values(), - style: ComputedValues::initial_values().clone(), + inherited_style: device.default_values(), + style: device.default_values().clone(), font_metrics_provider: None, // TODO: Should have! }; diff --git a/tests/unit/style/media_queries.rs b/tests/unit/style/media_queries.rs index e393fd552b7..9c156de96fa 100644 --- a/tests/unit/style/media_queries.rs +++ b/tests/unit/style/media_queries.rs @@ -11,6 +11,7 @@ use style::Atom; use style::error_reporting::ParseErrorReporter; use style::media_queries::*; use style::parser::ParserContextExtraData; +use style::servo::media_queries::*; use style::stylesheets::{Stylesheet, Origin, CssRule}; use style::values::specified; use style_traits::ToCss; @@ -25,8 +26,11 @@ impl ParseErrorReporter for CSSErrorReporterTest { } } -fn test_media_rule(css: &str, callback: F) where F: Fn(&MediaList, &str) { +fn test_media_rule(css: &str, callback: F) + where F: Fn(&MediaList, &str), +{ let url = ServoUrl::parse("http://localhost").unwrap(); + let css_str = css.to_owned(); let stylesheet = Stylesheet::from_str( css, url, Origin::Author, Default::default(), None, Box::new(CSSErrorReporterTest), @@ -36,10 +40,12 @@ fn test_media_rule(css: &str, callback: F) where F: Fn(&MediaList, &str) { rule_count += 1; callback(mq, css); }); - assert!(rule_count > 0); + assert!(rule_count > 0, css_str); } -fn media_queries(rules: &[CssRule], f: &mut F) where F: FnMut(&MediaList) { +fn media_queries(rules: &[CssRule], f: &mut F) + where F: FnMut(&MediaList), +{ for rule in rules { rule.with_nested_rules_and_mq(|rules, mq| { if let Some(mq) = mq { @@ -200,8 +206,8 @@ fn test_mq_default_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), _ => panic!("wrong expression type"), } }); @@ -212,8 +218,8 @@ fn test_mq_default_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), _ => panic!("wrong expression type"), } }); @@ -227,8 +233,8 @@ fn test_mq_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), _ => panic!("wrong expression type"), } }); @@ -239,8 +245,8 @@ fn test_mq_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), _ => panic!("wrong expression type"), } }); @@ -251,8 +257,8 @@ fn test_mq_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Print), css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Eq(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Eq(w)) => assert!(w == specified::Length::Absolute(Au::from_px(43))), _ => panic!("wrong expression type"), } }); @@ -263,8 +269,8 @@ fn test_mq_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::Unknown(Atom::from("fridge")), css.to_owned()); assert!(q.expressions.len() == 1, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(52))), _ => panic!("wrong expression type"), } }); @@ -288,12 +294,12 @@ fn test_mq_multiple_expressions() { assert!(q.qualifier == None, css.to_owned()); assert!(q.media_type == MediaQueryType::All, css.to_owned()); assert!(q.expressions.len() == 2, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), _ => panic!("wrong expression type"), } - match q.expressions[1] { - Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))), + match *q.expressions[1].kind_for_testing() { + ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))), _ => panic!("wrong expression type"), } }); @@ -304,12 +310,12 @@ fn test_mq_multiple_expressions() { assert!(q.qualifier == Some(Qualifier::Not), css.to_owned()); assert!(q.media_type == MediaQueryType::Known(MediaType::Screen), css.to_owned()); assert!(q.expressions.len() == 2, css.to_owned()); - match q.expressions[0] { - Expression::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), + match *q.expressions[0].kind_for_testing() { + ExpressionKind::Width(Range::Min(w)) => assert!(w == specified::Length::Absolute(Au::from_px(100))), _ => panic!("wrong expression type"), } - match q.expressions[1] { - Expression::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))), + match *q.expressions[1].kind_for_testing() { + ExpressionKind::Width(Range::Max(w)) => assert!(w == specified::Length::Absolute(Au::from_px(200))), _ => panic!("wrong expression type"), } }); @@ -317,89 +323,31 @@ fn test_mq_multiple_expressions() { #[test] 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()); 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 screen and (height: 200px) { }", |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 (min-width: 30em foo bar) {}", |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 {}", |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()); - }); + for rule in &[ + "@media (min-width: 100blah) and (max-width: 200px) { }", + "@media screen and (height: 200px) { }", + "@media (min-width: 30em foo bar) {}", + "@media not {}", + "@media not (min-width: 300px) {}", + "@media , {}", + "@media screen 4px, print {}", + "@media screen, {}", + ] { + test_media_rule(rule, check_malformed_expr); + } } #[test] fn test_matching_simple() { - let device = Device { - media_type: MediaType::Screen, - viewport_size: TypedSize2D::new(200.0, 100.0), - }; + let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0)); media_query_test(&device, "@media not all { a { color: red; } }", 0); media_query_test(&device, "@media not screen { a { color: red; } }", 0); @@ -415,10 +363,7 @@ fn test_matching_simple() { #[test] fn test_matching_width() { - let device = Device { - media_type: MediaType::Screen, - viewport_size: TypedSize2D::new(200.0, 100.0), - }; + let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0)); media_query_test(&device, "@media { a { color: red; } }", 1); @@ -459,10 +404,7 @@ fn test_matching_width() { #[test] fn test_matching_invalid() { - let device = Device { - media_type: MediaType::Screen, - viewport_size: TypedSize2D::new(200.0, 100.0), - }; + let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0)); media_query_test(&device, "@media fridge { a { color: red; } }", 0); media_query_test(&device, "@media screen and (height: 100px) { a { color: red; } }", 0); diff --git a/tests/unit/style/viewport.rs b/tests/unit/style/viewport.rs index 026269421f7..dce0deafca8 100644 --- a/tests/unit/style/viewport.rs +++ b/tests/unit/style/viewport.rs @@ -288,11 +288,10 @@ fn constrain_viewport() { } let initial_viewport = TypedSize2D::new(800., 600.); - assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("")), - None); + let device = Device::new(MediaType::Screen, initial_viewport); + assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("")), None); - let initial_viewport = TypedSize2D::new(800., 600.); - assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 320px auto")), + assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")), Some(ViewportConstraints { size: initial_viewport, @@ -304,21 +303,7 @@ fn constrain_viewport() { orientation: Orientation::Auto })); - let initial_viewport = TypedSize2D::new(200., 150.); - 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")), + assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 320px auto")), Some(ViewportConstraints { size: initial_viewport, @@ -330,8 +315,7 @@ fn constrain_viewport() { orientation: Orientation::Auto })); - let initial_viewport = TypedSize2D::new(800., 600.); - assert_eq!(ViewportConstraints::maybe_new(initial_viewport, from_css!("width: 800px; height: 600px;\ + assert_eq!(ViewportConstraints::maybe_new(&device, from_css!("width: 800px; height: 600px;\ zoom: 1;\ user-zoom: zoom;\ orientation: auto;")), @@ -342,6 +326,20 @@ fn constrain_viewport() { min_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, orientation: Orientation::Auto }));