mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 08:45:46 +00:00
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:
parent
3443a24780
commit
ab3dd48482
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user