servo: Merge #3610 - Implement media queries parser and matching. Improves mobile first sites like bootstrap3 (from glennw:media-queries); r=SimonSapin

Source-Repo: https://github.com/servo/servo
Source-Revision: 3eb6b17137964fc12779eca9597fa77930440138
This commit is contained in:
Glenn Watson 2014-10-14 18:36:29 -06:00
parent 3443a24780
commit ab3dd48482
7 changed files with 686 additions and 64 deletions

View File

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

View File

@ -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() {

View File

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

View File

@ -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 struct MediaQuery {
media_type: MediaQueryType,
// TODO: Level 3 MQ expressions
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,
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}) }
}
let mut queries = vec!();
loop {
let mq = match 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
// 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))
}
},
_ => None
};
match iter.next() {
None => {
for mq in mq.into_iter() {
queries.push(mq);
"max-width" => {
let au = try!(parse_value_as_length(value));
Width(Max(au))
}
return MediaQueryList{ media_queries: queries }
},
Some(&Comma) => {
for mq in mq.into_iter() {
queries.push(mq);
}
},
// Ingnore this comma-separated part
_ => loop {
match iter.next() {
Some(&Comma) => break,
None => return MediaQueryList{ media_queries: queries },
_ => (),
}
},
_ => return Err(())
};
if iter.is_eof() {
Ok(expression)
} else {
Err(())
}
}
next = iter.next();
_ => Err(())
}
}
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 {
// Each expression should begin with and
match iter.next() {
Some(&Ident(ref value)) => {
match value.as_slice().to_ascii_lower().as_slice() {
"and" => {
let expression = try!(parse_media_query_expression(iter));
expressions.push(expression);
}
_ => return Err(()),
}
}
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) => {
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;
}
}
}
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);
}
}

View File

@ -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> {

View File

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

View File

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