From 5758c31df776b25470cf79c34346b9e1107b35a6 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 10 Aug 2013 15:22:38 +0100 Subject: [PATCH] Add @media and media type parsing (no Media Queries yet.) --- media_queries.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++ servo-style.rc | 1 + stylesheets.rs | 33 ++++++++----- 3 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 media_queries.rs diff --git a/media_queries.rs b/media_queries.rs new file mode 100644 index 00000000000..5729ec6c4b4 --- /dev/null +++ b/media_queries.rs @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::ascii::to_ascii_lower; +use cssparser::*; +use errors::{ErrorLoggerIterator, log_css_error}; +use stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule}; +use namespaces::NamespaceMap; + + +pub struct MediaRule { + media_queries: MediaQueryList, + rules: ~[CSSRule], +} + + +pub struct MediaQueryList { + // "not all" is omitted from the list. + // An empty list never matches. + media_queries: ~[MediaQuery] +} + +// For now, this is a "Level 2 MQ", ie. a media type. +struct MediaQuery { + media_type: MediaQueryType, + // TODO: Level 3 MQ expressions +} + + +enum MediaQueryType { + All, // Always true + MediaType(MediaType), +} + +#[deriving(Eq)] +pub enum MediaType { + Screen, + Print, +} + +pub struct Device { + media_type: MediaType, + // TODO: Level 3 MQ data: viewport size, etc. +} + + +pub fn parse_media_rule(rule: AtRule, parent_rules: &mut ~[CSSRule], + namespaces: &NamespaceMap) { + let media_queries = parse_media_query_list(rule.prelude); + let block = match rule.block { + Some(block) => block, + None => { + log_css_error(rule.location, "Invalid @media rule"); + return + } + }; + let mut rules = ~[]; + for rule in ErrorLoggerIterator(parse_rule_list(block.consume_iter())) { + match rule { + QualifiedRule(rule) => parse_style_rule(rule, &mut rules, namespaces), + AtRule(rule) => parse_nested_at_rule( + to_ascii_lower(rule.name), rule, &mut rules, namespaces), + } + } + parent_rules.push(CSSMediaRule(MediaRule { + media_queries: media_queries, + rules: rules, + })) +} + + +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: ~[MediaQuery{media_type: All}] } + } + let mut queries = ~[]; + loop { + let mq = match next { + Some(&Ident(ref value)) => { + let media_type: &str = to_ascii_lower(value.as_slice()); + match media_type { + "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 => { + mq.map_move(|mq| queries.push(mq)); + return MediaQueryList{ media_queries: queries } + }, + Some(&Comma) => { + mq.map_move(|mq| queries.push(mq)); + }, + // Ingnore this comma-separated part + _ => loop { + match iter.next() { + Some(&Comma) => break, + None => return MediaQueryList{ media_queries: queries }, + _ => (), + } + }, + } + next = iter.next(); + } +} + + +impl MediaQueryList { + pub fn evaluate(&self, device: &Device) -> bool { + do self.media_queries.iter().any |mq| { + match mq.media_type { + MediaType(media_type) => media_type == device.media_type, + All => true, + } + // TODO: match Level 3 expressions + } + } +} diff --git a/servo-style.rc b/servo-style.rc index 8c229f35c8c..8425b53c8df 100644 --- a/servo-style.rc +++ b/servo-style.rc @@ -13,3 +13,4 @@ pub mod errors; pub mod selectors; pub mod properties; pub mod namespaces; +pub mod media_queries; diff --git a/stylesheets.rs b/stylesheets.rs index 0ed968c3b02..0f7f275ef01 100644 --- a/stylesheets.rs +++ b/stylesheets.rs @@ -9,6 +9,7 @@ use selectors; use properties; use errors::{ErrorLoggerIterator, log_css_error}; use namespaces::{NamespaceMap, parse_namespace_rule}; +use media_queries::{MediaRule, parse_media_rule}; pub struct Stylesheet { @@ -19,7 +20,7 @@ pub struct Stylesheet { pub enum CSSRule { CSSStyleRule(StyleRule), -// CSSMediaRule(MediaRule), + CSSMediaRule(MediaRule), } @@ -42,9 +43,13 @@ fn parse_stylesheet(css: &str) -> Stylesheet { for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) { let next_state; // Unitialized to force each branch to set it. match rule { + QualifiedRule(rule) => { + next_state = STATE_BODY; + parse_style_rule(rule, &mut rules, &namespaces) + }, AtRule(rule) => { - let name: &str = to_ascii_lower(rule.name); - match name { + let lower_name: &str = to_ascii_lower(rule.name); + match lower_name { "charset" => { if state > STATE_CHARSET { log_css_error(rule.location, "@charset must be the first rule") @@ -76,14 +81,10 @@ fn parse_stylesheet(css: &str) -> Stylesheet { }, _ => { next_state = STATE_BODY; - log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", name)) + parse_nested_at_rule(lower_name, rule, &mut rules, &namespaces) }, } }, - QualifiedRule(rule) => { - next_state = STATE_BODY; - parse_style_rule(rule, &mut rules, &namespaces) - }, } state = next_state; } @@ -91,14 +92,24 @@ fn parse_stylesheet(css: &str) -> Stylesheet { } -fn parse_style_rule(rule: QualifiedRule, rule_list: &mut ~[CSSRule], - namespaces: &NamespaceMap) { +pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut ~[CSSRule], + namespaces: &NamespaceMap) { let QualifiedRule{location: location, prelude: prelude, block: block} = rule; match selectors::parse_selector_list(prelude, namespaces) { - Some(selectors) => rule_list.push(CSSStyleRule(StyleRule{ + Some(selectors) => parent_rules.push(CSSStyleRule(StyleRule{ selectors: selectors, declarations: properties::parse_property_declaration_list(block) })), None => log_css_error(location, "Unsupported CSS selector."), } } + + +// lower_name is passed explicitly to avoid computing it twice. +pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule, + parent_rules: &mut ~[CSSRule], namespaces: &NamespaceMap) { + match lower_name { + "media" => parse_media_rule(rule, parent_rules, namespaces), + _ => log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", lower_name)) + } +}