aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/layout_task.rs13
-rw-r--r--components/style/font_face.rs8
-rw-r--r--components/style/lib.rs1
-rw-r--r--components/style/media_queries.rs670
-rw-r--r--components/style/parsing_utils.rs19
-rw-r--r--components/style/selector_matching.rs10
-rw-r--r--components/style/stylesheets.rs17
7 files changed, 680 insertions, 58 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs
index 2ff6d921490..68ab0b863da 100644
--- a/components/layout/layout_task.rs
+++ b/components/layout/layout_task.rs
@@ -64,6 +64,7 @@ use std::comm::{channel, Sender, Receiver, Select};
use std::mem;
use std::ptr;
use style::{AuthorOrigin, Stylesheet, Stylist, TNode, iter_font_face_rules};
+use style::{Device, Screen};
use sync::{Arc, Mutex, MutexGuard};
use url::Url;
@@ -143,6 +144,10 @@ pub struct LayoutTask {
///
/// All the other elements of this struct are read-only.
pub rw_data: Arc<Mutex<LayoutTaskData>>,
+
+ /// The media queries device state.
+ /// TODO: Handle updating this when window size changes etc.
+ pub device: Device,
}
struct LayoutImageResponder {
@@ -252,6 +257,7 @@ impl LayoutTask {
-> LayoutTask {
let local_image_cache = Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone())));
let screen_size = Size2D(Au(0), Au(0));
+ let device = Device::new(Screen, opts.initial_window_size.as_f32());
let parallel_traversal = if opts.layout_threads != 1 {
Some(WorkQueue::new("LayoutWorker", opts.layout_threads, ptr::null()))
} else {
@@ -272,12 +278,13 @@ impl LayoutTask {
font_cache_task: font_cache_task,
opts: opts.clone(),
first_reflow: Cell::new(true),
+ device: device,
rw_data: Arc::new(Mutex::new(
LayoutTaskData {
local_image_cache: local_image_cache,
screen_size: screen_size,
display_list: None,
- stylist: box Stylist::new(),
+ stylist: box Stylist::new(&device),
parallel_traversal: parallel_traversal,
dirty: Rect::zero(),
generation: 0,
@@ -469,11 +476,11 @@ impl LayoutTask {
fn handle_add_stylesheet<'a>(&'a self, sheet: Stylesheet, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
// Find all font-face rules and notify the font cache of them.
// GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)
- iter_font_face_rules(&sheet, |family, src| {
+ iter_font_face_rules(&sheet, &self.device, |family, src| {
self.font_cache_task.add_web_font(family.to_string(), (*src).clone());
});
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
- rw_data.stylist.add_stylesheet(sheet, AuthorOrigin);
+ rw_data.stylist.add_stylesheet(sheet, AuthorOrigin, &self.device);
rw_data.stylesheet_dirty = true;
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
}
diff --git a/components/style/font_face.rs b/components/style/font_face.rs
index 62a65db96a5..b2d20f52675 100644
--- a/components/style/font_face.rs
+++ b/components/style/font_face.rs
@@ -10,17 +10,17 @@ use parsing_utils::{BufferedIter, ParserIter, parse_slice_comma_separated};
use properties::longhands::font_family::parse_one_family;
use properties::computed_values::font_family::FamilyName;
use stylesheets::{CSSRule, CSSFontFaceRule, CSSStyleRule, CSSMediaRule};
-use media_queries::{Device, Screen};
+use media_queries::Device;
use url::{Url, UrlParser};
-pub fn iter_font_face_rules_inner(rules: &[CSSRule], callback: |family: &str, source: &Source|) {
- let device = &Device { media_type: Screen }; // TODO, use Print when printing
+pub fn iter_font_face_rules_inner(rules: &[CSSRule], device: &Device,
+ callback: |family: &str, source: &Source|) {
for rule in rules.iter() {
match *rule {
CSSStyleRule(_) => {},
CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) {
- iter_font_face_rules_inner(rule.rules.as_slice(), |f, s| callback(f, s))
+ iter_font_face_rules_inner(rule.rules.as_slice(), device, |f, s| callback(f, s))
},
CSSFontFaceRule(ref rule) => {
for source in rule.sources.iter() {
diff --git a/components/style/lib.rs b/components/style/lib.rs
index fc3fd43a94d..8b516151025 100644
--- a/components/style/lib.rs
+++ b/components/style/lib.rs
@@ -35,6 +35,7 @@ extern crate "util" as servo_util;
// Public API
+pub use media_queries::{Device, Screen};
pub use stylesheets::{Stylesheet, iter_font_face_rules};
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
pub use selector_matching::{DeclarationBlock, matches,matches_simple_selector};
diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs
index af12b4fe638..d5e9dad9f12 100644
--- a/components/style/media_queries.rs
+++ b/components/style/media_queries.rs
@@ -7,30 +7,68 @@ use cssparser::parse_rule_list;
use cssparser::ast::*;
use errors::{ErrorLoggerIterator, log_css_error};
+use geom::size::TypedSize2D;
use stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule};
use namespaces::NamespaceMap;
+use parsing_utils::{BufferedIter, ParserIter};
+use properties::common_types::*;
+use properties::longhands;
+use servo_util::geometry::ScreenPx;
use url::Url;
-
pub struct MediaRule {
pub media_queries: MediaQueryList,
pub rules: Vec<CSSRule>,
}
-
pub struct MediaQueryList {
- // "not all" is omitted from the list.
- // An empty list never matches.
media_queries: Vec<MediaQuery>
}
-// For now, this is a "Level 2 MQ", ie. a media type.
+pub enum Range<T> {
+ Min(T),
+ Max(T),
+ Eq(T),
+}
+
+impl<T: Ord> Range<T> {
+ fn evaluate(&self, value: T) -> bool {
+ match *self {
+ Min(ref width) => { value >= *width },
+ Max(ref width) => { value <= *width },
+ Eq(ref width) => { value == *width },
+ }
+ }
+}
+
+pub enum Expression {
+ Width(Range<Au>),
+}
+
+#[deriving(PartialEq)]
+pub enum Qualifier {
+ Only,
+ Not,
+}
+
pub struct MediaQuery {
+ qualifier: Option<Qualifier>,
media_type: MediaQueryType,
- // TODO: Level 3 MQ expressions
+ expressions: Vec<Expression>,
}
+impl MediaQuery {
+ pub fn new(qualifier: Option<Qualifier>, media_type: MediaQueryType,
+ expressions: Vec<Expression>) -> MediaQuery {
+ MediaQuery {
+ qualifier: qualifier,
+ media_type: media_type,
+ expressions: expressions,
+ }
+ }
+}
+#[deriving(PartialEq)]
pub enum MediaQueryType {
All, // Always true
MediaType_(MediaType),
@@ -40,13 +78,22 @@ pub enum MediaQueryType {
pub enum MediaType {
Screen,
Print,
+ Unknown,
}
pub struct Device {
pub media_type: MediaType,
- // TODO: Level 3 MQ data: viewport size, etc.
+ pub viewport_size: TypedSize2D<ScreenPx, f32>,
}
+impl Device {
+ pub fn new(media_type: MediaType, viewport_size: TypedSize2D<ScreenPx, f32>) -> Device {
+ Device {
+ media_type: media_type,
+ viewport_size: viewport_size,
+ }
+ }
+}
pub fn parse_media_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>,
namespaces: &NamespaceMap, base_url: &Url) {
@@ -72,60 +119,597 @@ pub fn parse_media_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>,
}))
}
+fn parse_value_as_length(value: &ComponentValue) -> Result<Au, ()> {
+ let length = try!(specified::Length::parse_non_negative(value));
-pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
- let iter = &mut input.skip_whitespace();
- let mut next = iter.next();
- if next.is_none() {
- return MediaQueryList{ media_queries: vec!(MediaQuery{media_type: All}) }
+ // http://dev.w3.org/csswg/mediaqueries3/ - Section 6
+ // em units are relative to the initial font-size.
+ let initial_font_size = longhands::font_size::get_initial_value();
+ Ok(computed::compute_Au_with_font_size(length, initial_font_size))
+}
+
+fn parse_media_query_expression(iter: ParserIter) -> Result<Expression, ()> {
+ // Expect a parenthesis block with the condition
+ match iter.next() {
+ Some(&ParenthesisBlock(ref block)) => {
+ let iter = &mut BufferedIter::new(block.as_slice().skip_whitespace());
+
+ // Parse the variable (e.g. min-width)
+ let variable = match iter.next() {
+ Some(&Ident(ref value)) => value,
+ _ => return Err(())
+ };
+
+ // Ensure a colon follows
+ match iter.next() {
+ Some(&Colon) => {},
+ _ => return Err(())
+ }
+
+ // Retrieve the value
+ let value = try!(iter.next_as_result());
+
+ // TODO: Handle other media query types
+ let expression = match variable.as_slice().to_ascii_lower().as_slice() {
+ "min-width" => {
+ let au = try!(parse_value_as_length(value));
+ Width(Min(au))
+ }
+ "max-width" => {
+ let au = try!(parse_value_as_length(value));
+ Width(Max(au))
+ }
+ _ => return Err(())
+ };
+
+ if iter.is_eof() {
+ Ok(expression)
+ } else {
+ Err(())
+ }
+ }
+ _ => Err(())
}
- let mut queries = vec!();
+}
+
+fn parse_media_query(iter: ParserIter) -> Result<MediaQuery, ()> {
+ let mut expressions = vec!();
+
+ // Check for optional 'only' or 'not'
+ let qualifier = match iter.next() {
+ Some(&Ident(ref value)) if value.as_slice().to_ascii_lower().as_slice() == "only" => Some(Only),
+ Some(&Ident(ref value)) if value.as_slice().to_ascii_lower().as_slice() == "not" => Some(Not),
+ Some(component_value) => {
+ iter.push_back(component_value);
+ None
+ }
+ None => return Err(()), // Empty queries are invalid
+ };
+
+ // Check for media type
+ let media_type = match iter.next() {
+ Some(&Ident(ref value)) => {
+ match value.as_slice().to_ascii_lower().as_slice() {
+ "screen" => MediaType_(Screen),
+ "print" => MediaType_(Print),
+ "all" => All,
+ _ => MediaType_(Unknown), // Unknown media types never match
+ }
+ }
+ Some(component_value) => {
+ // Media type is only optional if qualifier is not specified.
+ if qualifier.is_some() {
+ return Err(());
+ }
+ iter.push_back(component_value);
+
+ // If no qualifier and media type present, an expression should exist here
+ let expression = try!(parse_media_query_expression(iter));
+ expressions.push(expression);
+
+ All
+ }
+ None => return Err(()),
+ };
+
+ // Parse any subsequent expressions
loop {
- let mq = match next {
+ // Each expression should begin with and
+ match iter.next() {
Some(&Ident(ref value)) => {
match value.as_slice().to_ascii_lower().as_slice() {
- "screen" => Some(MediaQuery{ media_type: MediaType_(Screen) }),
- "print" => Some(MediaQuery{ media_type: MediaType_(Print) }),
- "all" => Some(MediaQuery{ media_type: All }),
- _ => None
- }
- },
- _ => None
- };
- match iter.next() {
- None => {
- for mq in mq.into_iter() {
- queries.push(mq);
- }
- return MediaQueryList{ media_queries: queries }
- },
- Some(&Comma) => {
- for mq in mq.into_iter() {
- queries.push(mq);
+ "and" => {
+ let expression = try!(parse_media_query_expression(iter));
+ expressions.push(expression);
+ }
+ _ => return Err(()),
}
- },
- // Ingnore this comma-separated part
- _ => loop {
+ }
+ Some(component_value) => {
+ iter.push_back(component_value);
+ break;
+ }
+ None => break,
+ }
+ }
+
+ Ok(MediaQuery::new(qualifier, media_type, expressions))
+}
+
+pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
+ let iter = &mut BufferedIter::new(input.skip_whitespace());
+ let mut media_queries = vec!();
+
+ if iter.is_eof() {
+ media_queries.push(MediaQuery::new(None, All, vec!()));
+ } else {
+ loop {
+ // Attempt to parse a media query.
+ let media_query_result = parse_media_query(iter);
+
+ // Skip until next query or end
+ let mut trailing_tokens = false;
+ let mut more_queries = false;
+ loop {
match iter.next() {
- Some(&Comma) => break,
- None => return MediaQueryList{ media_queries: queries },
- _ => (),
+ Some(&Comma) => {
+ more_queries = true;
+ break;
+ }
+ Some(_) => trailing_tokens = true,
+ None => break,
}
- },
+ }
+
+ // Add the media query if it was valid and no trailing tokens were found.
+ // Otherwse, create a 'not all' media query, that will never match.
+ let media_query = match (media_query_result, trailing_tokens) {
+ (Ok(media_query), false) => media_query,
+ _ => MediaQuery::new(Some(Not), All, vec!()),
+ };
+ media_queries.push(media_query);
+
+ if !more_queries {
+ break;
+ }
}
- next = iter.next();
}
-}
+ MediaQueryList { media_queries: media_queries }
+}
impl MediaQueryList {
pub fn evaluate(&self, device: &Device) -> bool {
+ // Check if any queries match (OR condition)
self.media_queries.iter().any(|mq| {
- match mq.media_type {
+ // Check if media matches. Unknown media never matches.
+ let media_match = match mq.media_type {
+ MediaType_(Unknown) => false,
MediaType_(media_type) => media_type == device.media_type,
All => true,
+ };
+
+ // Check if all conditions match (AND condition)
+ let query_match = media_match && mq.expressions.iter().all(|expression| {
+ match expression {
+ &Width(value) => value.evaluate(
+ Au::from_frac_px(device.viewport_size.to_untyped().width as f64)),
+ }
+ });
+
+ // Apply the logical NOT qualifier to the result
+ match mq.qualifier {
+ Some(Not) => !query_match,
+ _ => query_match,
}
- // TODO: match Level 3 expressions
})
}
}
+
+#[cfg(test)]
+mod tests {
+ use geom::size::TypedSize2D;
+ use properties::common_types::*;
+ use stylesheets::{iter_stylesheet_media_rules, iter_stylesheet_style_rules, Stylesheet};
+ use super::*;
+ use url::Url;
+
+ fn test_media_rule(css: &str, callback: |&MediaQueryList, &str|) {
+ let url = Url::parse("http://localhost").unwrap();
+ let stylesheet = Stylesheet::from_str(css, url);
+ let mut rule_count: int = 0;
+ iter_stylesheet_media_rules(&stylesheet, |rule| {
+ rule_count += 1;
+ callback(&rule.media_queries, css);
+ });
+ assert!(rule_count > 0);
+ }
+
+ fn media_query_test(device: &Device, css: &str, expected_rule_count: int) {
+ let url = Url::parse("http://localhost").unwrap();
+ let ss = Stylesheet::from_str(css, url);
+ let mut rule_count: int = 0;
+ iter_stylesheet_style_rules(&ss, device, |_| rule_count += 1);
+ assert!(rule_count == expected_rule_count, css.to_string());
+ }
+
+ #[test]
+ fn test_mq_empty() {
+ test_media_rule("@media { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_mq_screen() {
+ test_media_rule("@media screen { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == MediaType_(Screen), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media only screen { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Only), css.to_string());
+ assert!(q.media_type == MediaType_(Screen), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media not screen { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == MediaType_(Screen), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_mq_print() {
+ test_media_rule("@media print { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == MediaType_(Print), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media only print { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Only), css.to_string());
+ assert!(q.media_type == MediaType_(Print), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media not print { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == MediaType_(Print), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_mq_unknown() {
+ test_media_rule("@media fridge { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == MediaType_(Unknown), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media only glass { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Only), css.to_string());
+ assert!(q.media_type == MediaType_(Unknown), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media not wood { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == MediaType_(Unknown), css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_mq_all() {
+ test_media_rule("@media all { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media only all { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Only), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media not all { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_mq_or() {
+ test_media_rule("@media screen, print { }", |list, css| {
+ assert!(list.media_queries.len() == 2, css.to_string());
+ let q0 = &list.media_queries[0];
+ assert!(q0.qualifier == None, css.to_string());
+ assert!(q0.media_type == MediaType_(Screen), css.to_string());
+ assert!(q0.expressions.len() == 0, css.to_string());
+
+ let q1 = &list.media_queries[1];
+ assert!(q1.qualifier == None, css.to_string());
+ assert!(q1.media_type == MediaType_(Print), css.to_string());
+ assert!(q1.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_mq_default_expressions() {
+ test_media_rule("@media (min-width: 100px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 1, css.to_string());
+ match q.expressions[0] {
+ Width(Min(w)) => assert!(w == Au::from_px(100)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+
+ test_media_rule("@media (max-width: 43px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 1, css.to_string());
+ match q.expressions[0] {
+ Width(Max(w)) => assert!(w == Au::from_px(43)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+ }
+
+ #[test]
+ fn test_mq_expressions() {
+ test_media_rule("@media screen and (min-width: 100px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == MediaType_(Screen), css.to_string());
+ assert!(q.expressions.len() == 1, css.to_string());
+ match q.expressions[0] {
+ Width(Min(w)) => assert!(w == Au::from_px(100)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+
+ test_media_rule("@media print and (max-width: 43px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == MediaType_(Print), css.to_string());
+ assert!(q.expressions.len() == 1, css.to_string());
+ match q.expressions[0] {
+ Width(Max(w)) => assert!(w == Au::from_px(43)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+
+ test_media_rule("@media fridge and (max-width: 52px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == MediaType_(Unknown), css.to_string());
+ assert!(q.expressions.len() == 1, css.to_string());
+ match q.expressions[0] {
+ Width(Max(w)) => assert!(w == Au::from_px(52)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+ }
+
+ #[test]
+ fn test_mq_multiple_expressions() {
+ test_media_rule("@media (min-width: 100px) and (max-width: 200px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == None, css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 2, css.to_string());
+ match q.expressions[0] {
+ Width(Min(w)) => assert!(w == Au::from_px(100)),
+ _ => fail!("wrong expression type"),
+ }
+ match q.expressions[1] {
+ Width(Max(w)) => assert!(w == Au::from_px(200)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+
+ test_media_rule("@media not screen and (min-width: 100px) and (max-width: 200px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == MediaType_(Screen), css.to_string());
+ assert!(q.expressions.len() == 2, css.to_string());
+ match q.expressions[0] {
+ Width(Min(w)) => assert!(w == Au::from_px(100)),
+ _ => fail!("wrong expression type"),
+ }
+ match q.expressions[1] {
+ Width(Max(w)) => assert!(w == Au::from_px(200)),
+ _ => fail!("wrong expression type"),
+ }
+ });
+ }
+
+ #[test]
+ fn test_mq_malformed_expressions() {
+ test_media_rule("@media (min-width: 100blah) and (max-width: 200px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media screen and (height: 200px) { }", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media (min-width: 30em foo bar) {}", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media not {}", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media not (min-width: 300px) {}", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media , {}", |list, css| {
+ assert!(list.media_queries.len() == 1, css.to_string());
+ let q = &list.media_queries[0];
+ assert!(q.qualifier == Some(Not), css.to_string());
+ assert!(q.media_type == All, css.to_string());
+ assert!(q.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media screen 4px, print {}", |list, css| {
+ assert!(list.media_queries.len() == 2, css.to_string());
+ let q0 = &list.media_queries[0];
+ assert!(q0.qualifier == Some(Not), css.to_string());
+ assert!(q0.media_type == All, css.to_string());
+ assert!(q0.expressions.len() == 0, css.to_string());
+ let q1 = &list.media_queries[1];
+ assert!(q1.qualifier == None, css.to_string());
+ assert!(q1.media_type == MediaType_(Print), css.to_string());
+ assert!(q1.expressions.len() == 0, css.to_string());
+ });
+
+ test_media_rule("@media screen, {}", |list, css| {
+ assert!(list.media_queries.len() == 2, css.to_string());
+ let q0 = &list.media_queries[0];
+ assert!(q0.qualifier == None, css.to_string());
+ assert!(q0.media_type == MediaType_(Screen), css.to_string());
+ assert!(q0.expressions.len() == 0, css.to_string());
+ let q1 = &list.media_queries[1];
+ assert!(q1.qualifier == Some(Not), css.to_string());
+ assert!(q1.media_type == All, css.to_string());
+ assert!(q1.expressions.len() == 0, css.to_string());
+ });
+ }
+
+ #[test]
+ fn test_matching_simple() {
+ let device = Device {
+ media_type: Screen,
+ viewport_size: TypedSize2D(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);
+ media_query_test(&device, "@media not print { a { color: red; } }", 1);
+
+ media_query_test(&device, "@media unknown { a { color: red; } }", 0);
+ media_query_test(&device, "@media not unknown { a { color: red; } }", 1);
+
+ media_query_test(&device, "@media { a { color: red; } }", 1);
+ media_query_test(&device, "@media screen { a { color: red; } }", 1);
+ media_query_test(&device, "@media print { a { color: red; } }", 0);
+ }
+
+ #[test]
+ fn test_matching_width() {
+ let device = Device {
+ media_type: Screen,
+ viewport_size: TypedSize2D(200.0, 100.0),
+ };
+
+ media_query_test(&device, "@media { a { color: red; } }", 1);
+
+ media_query_test(&device, "@media (min-width: 50px) { a { color: red; } }", 1);
+ media_query_test(&device, "@media (min-width: 150px) { a { color: red; } }", 1);
+ media_query_test(&device, "@media (min-width: 300px) { a { color: red; } }", 0);
+
+ media_query_test(&device, "@media screen and (min-width: 50px) { a { color: red; } }", 1);
+ media_query_test(&device, "@media screen and (min-width: 150px) { a { color: red; } }", 1);
+ media_query_test(&device, "@media screen and (min-width: 300px) { a { color: red; } }", 0);
+
+ media_query_test(&device, "@media not screen and (min-width: 50px) { a { color: red; } }", 0);
+ media_query_test(&device, "@media not screen and (min-width: 150px) { a { color: red; } }", 0);
+ media_query_test(&device, "@media not screen and (min-width: 300px) { a { color: red; } }", 1);
+
+ media_query_test(&device, "@media (max-width: 50px) { a { color: red; } }", 0);
+ media_query_test(&device, "@media (max-width: 150px) { a { color: red; } }", 0);
+ media_query_test(&device, "@media (max-width: 300px) { a { color: red; } }", 1);
+
+ media_query_test(&device, "@media screen and (min-width: 50px) and (max-width: 100px) { a { color: red; } }", 0);
+ media_query_test(&device, "@media screen and (min-width: 250px) and (max-width: 300px) { a { color: red; } }", 0);
+ media_query_test(&device, "@media screen and (min-width: 50px) and (max-width: 250px) { a { color: red; } }", 1);
+
+ media_query_test(&device, "@media not screen and (min-width: 50px) and (max-width: 100px) { a { color: red; } }", 1);
+ media_query_test(&device, "@media not screen and (min-width: 250px) and (max-width: 300px) { a { color: red; } }", 1);
+ media_query_test(&device, "@media not screen and (min-width: 50px) and (max-width: 250px) { a { color: red; } }", 0);
+
+ media_query_test(&device, "@media not screen and (min-width: 3.1em) and (max-width: 6em) { a { color: red; } }", 1);
+ media_query_test(&device, "@media not screen and (min-width: 16em) and (max-width: 19.75em) { a { color: red; } }", 1);
+ media_query_test(&device, "@media not screen and (min-width: 3em) and (max-width: 250px) { a { color: red; } }", 0);
+ }
+
+ #[test]
+ fn test_matching_invalid() {
+ let device = Device {
+ media_type: Screen,
+ viewport_size: TypedSize2D(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);
+ media_query_test(&device, "@media not print and (width: 100) { a { color: red; } }", 0);
+ }
+}
diff --git a/components/style/parsing_utils.rs b/components/style/parsing_utils.rs
index 1fd1034e117..c2f6cc04850 100644
--- a/components/style/parsing_utils.rs
+++ b/components/style/parsing_utils.rs
@@ -42,6 +42,25 @@ impl<E, I: Iterator<E>> BufferedIter<E, I> {
assert!(self.buffer.is_none());
self.buffer = Some(value);
}
+
+ #[inline]
+ pub fn is_eof(&mut self) -> bool {
+ match self.next() {
+ Some(value) => {
+ self.push_back(value);
+ false
+ }
+ None => true
+ }
+ }
+
+ #[inline]
+ pub fn next_as_result(&mut self) -> Result<E, ()> {
+ match self.next() {
+ Some(value) => Ok(value),
+ None => Err(()),
+ }
+ }
}
impl<E, I: Iterator<E>> Iterator<E> for BufferedIter<E, I> {
diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs
index 2c5bc005b26..5ebef7c5e3d 100644
--- a/components/style/selector_matching.rs
+++ b/components/style/selector_matching.rs
@@ -19,7 +19,7 @@ use servo_util::str::{AutoLpa, LengthLpa, PercentageLpa};
use string_cache::Atom;
use legacy::{SizeIntegerAttribute, WidthLengthAttribute};
-use media_queries::{Device, Screen};
+use media_queries::Device;
use node::{TElement, TElementAttributes, TNode};
use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration};
use properties::{specified};
@@ -272,7 +272,7 @@ pub struct Stylist {
impl Stylist {
#[inline]
- pub fn new() -> Stylist {
+ pub fn new(device: &Device) -> Stylist {
let mut stylist = Stylist {
element_map: PerPseudoElementSelectorMap::new(),
before_map: PerPseudoElementSelectorMap::new(),
@@ -289,12 +289,13 @@ impl Stylist {
Url::parse(format!("chrome:///{}", filename).as_slice()).unwrap(),
None,
None);
- stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin);
+ stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin, device);
}
stylist
}
- pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin) {
+ pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin,
+ device: &Device) {
let (mut element_map, mut before_map, mut after_map) = match origin {
UserAgentOrigin => (
&mut self.element_map.user_agent,
@@ -338,7 +339,6 @@ impl Stylist {
};
);
- let device = &Device { media_type: Screen }; // TODO, use Print when printing
iter_stylesheet_style_rules(&stylesheet, device, |style_rule| {
append!(style_rule, normal);
append!(style_rule, important);
diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs
index cad9ea3b88d..db1f13a8240 100644
--- a/components/style/stylesheets.rs
+++ b/components/style/stylesheets.rs
@@ -14,7 +14,7 @@ use selectors;
use properties;
use errors::{ErrorLoggerIterator, log_css_error};
use namespaces::{NamespaceMap, parse_namespace_rule};
-use media_queries::{MediaRule, parse_media_rule};
+use media_queries::{Device, MediaRule, parse_media_rule};
use media_queries;
use font_face::{FontFaceRule, Source, parse_font_face_rule, iter_font_face_rules_inner};
@@ -165,6 +165,16 @@ pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
}
}
+#[cfg(test)]
+pub fn iter_stylesheet_media_rules(stylesheet: &Stylesheet, callback: |&MediaRule|) {
+ for rule in stylesheet.rules.iter() {
+ match *rule {
+ CSSMediaRule(ref rule) => callback(rule),
+ _ => {}
+ }
+ }
+}
+
#[inline]
pub fn iter_stylesheet_style_rules(stylesheet: &Stylesheet, device: &media_queries::Device,
callback: |&StyleRule|) {
@@ -173,6 +183,7 @@ pub fn iter_stylesheet_style_rules(stylesheet: &Stylesheet, device: &media_queri
#[inline]
-pub fn iter_font_face_rules(stylesheet: &Stylesheet, callback: |family: &str, source: &Source|) {
- iter_font_face_rules_inner(stylesheet.rules.as_slice(), callback)
+pub fn iter_font_face_rules(stylesheet: &Stylesheet, device: &Device,
+ callback: |family: &str, source: &Source|) {
+ iter_font_face_rules_inner(stylesheet.rules.as_slice(), device, callback)
}