/* 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/. */ //! Style sheets and their CSS rules. use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, decode_stylesheet_bytes}; use cssparser::{AtRuleType, RuleListParser, Token}; use encoding::EncodingRef; use error_reporting::ParseErrorReporter; use font_face::{FontFaceRule, parse_font_face_block}; use keyframes::{Keyframe, parse_keyframe_list}; use media_queries::{Device, MediaQueryList, parse_media_query_list}; use parser::{ParserContext, ParserContextExtraData, log_css_error}; use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; use selector_impl::TheSelectorImpl; use selectors::parser::{Selector, parse_selector_list}; use smallvec::SmallVec; use std::cell::Cell; use std::iter::Iterator; use std::slice; use std::sync::Arc; use string_cache::{Atom, Namespace}; use url::Url; use viewport::ViewportRule; /// Each style rule has an origin, which determines where it enters the cascade. /// /// http://dev.w3.org/csswg/css-cascade/#cascading-origins #[derive(Clone, PartialEq, Eq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum Origin { /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua UserAgent, /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-author Author, /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-user User, } #[derive(Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Stylesheet { /// List of rules in the order they were found (important for /// cascading order) pub rules: Vec, /// List of media associated with the Stylesheet, if any. pub media: Option, pub origin: Origin, pub dirty_on_viewport_size_change: bool, } /// This structure holds the user-agent and user stylesheets. pub struct UserAgentStylesheets { pub user_or_user_agent_stylesheets: Vec, pub quirks_mode_stylesheet: Stylesheet, } #[derive(Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum CSSRule { // No Charset here, CSSCharsetRule has been removed from CSSOM // https://drafts.csswg.org/cssom/#changes-from-5-december-2013 Namespace(Arc), Style(Arc), Media(Arc), FontFace(Arc), Viewport(Arc), Keyframes(Arc), } #[derive(Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct NamespaceRule { /// `None` for the default Namespace pub prefix: Option, pub url: Namespace, } #[derive(Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct KeyframesRule { pub name: Atom, pub keyframes: Vec>, } #[derive(Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct MediaRule { pub media_queries: Arc, pub rules: Vec, } impl MediaRule { #[inline] pub fn evaluate(&self, device: &Device) -> bool { self.media_queries.evaluate(device) } } #[derive(Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct StyleRule { pub selectors: Vec>, pub declarations: Arc, } impl Stylesheet { pub fn from_bytes_iter>>( input: I, base_url: Url, protocol_encoding_label: Option<&str>, environment_encoding: Option, origin: Origin, error_reporter: Box, extra_data: ParserContextExtraData) -> Stylesheet { let mut bytes = vec![]; // TODO: incremental decoding and tokenization/parsing for chunk in input { bytes.extend_from_slice(&chunk) } Stylesheet::from_bytes(&bytes, base_url, protocol_encoding_label, environment_encoding, origin, error_reporter, extra_data) } pub fn from_bytes(bytes: &[u8], base_url: Url, protocol_encoding_label: Option<&str>, environment_encoding: Option, origin: Origin, error_reporter: Box, extra_data: ParserContextExtraData) -> Stylesheet { // TODO: bytes.as_slice could be bytes.container_as_bytes() let (string, _) = decode_stylesheet_bytes( bytes, protocol_encoding_label, environment_encoding); Stylesheet::from_str(&string, base_url, origin, error_reporter, extra_data) } pub fn from_str(css: &str, base_url: Url, origin: Origin, error_reporter: Box, extra_data: ParserContextExtraData) -> Stylesheet { let rule_parser = TopLevelRuleParser { context: ParserContext::new_with_extra_data(origin, &base_url, error_reporter.clone(), extra_data), state: Cell::new(State::Start), }; let mut input = Parser::new(css); input.look_for_viewport_percentages(); let mut rules = vec![]; { let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser); while let Some(result) = iter.next() { match result { Ok(rule) => rules.push(rule), Err(range) => { let pos = range.start; let message = format!("Invalid rule: '{}'", iter.input.slice(range)); let context = ParserContext::new(origin, &base_url, error_reporter.clone()); log_css_error(iter.input, pos, &*message, &context); } } } } Stylesheet { origin: origin, rules: rules, media: None, dirty_on_viewport_size_change: input.seen_viewport_percentages(), } } /// Set the MediaQueryList associated with the style-sheet. pub fn set_media(&mut self, media: Option) { self.media = media; } /// Returns whether the style-sheet applies for the current device depending /// on the associated MediaQueryList. /// /// Always true if no associated MediaQueryList exists. pub fn is_effective_for_device(&self, device: &Device) -> bool { self.media.as_ref().map_or(true, |ref media| media.evaluate(device)) } /// Return an iterator over all the rules within the style-sheet. #[inline] pub fn rules(&self) -> Rules { Rules::new(self.rules.iter(), None) } /// Return an iterator over the effective rules within the style-sheet, as /// according to the supplied `Device`. /// /// If a condition does not hold, its associated conditional group rule and /// nested rules will be skipped. Use `rules` if all rules need to be /// examined. #[inline] pub fn effective_rules<'a>(&'a self, device: &'a Device) -> Rules<'a> { Rules::new(self.rules.iter(), Some(device)) } } /// `CSSRule` iterator. /// /// The iteration order is pre-order. Specifically, this implies that a /// conditional group rule will come before its nested rules. pub struct Rules<'a> { // 2 because normal case is likely to be just one level of nesting (@media) stack: SmallVec<[slice::Iter<'a, CSSRule>; 2]>, device: Option<&'a Device> } impl<'a> Rules<'a> { fn new(iter: slice::Iter<'a, CSSRule>, device: Option<&'a Device>) -> Rules<'a> { let mut stack: SmallVec<[slice::Iter<'a, CSSRule>; 2]> = SmallVec::new(); stack.push(iter); Rules { stack: stack, device: device } } } impl<'a> Iterator for Rules<'a> { type Item = &'a CSSRule; fn next(&mut self) -> Option<&'a CSSRule> { while !self.stack.is_empty() { let top = self.stack.len() - 1; while let Some(rule) = self.stack[top].next() { // handle conditional group rules if let &CSSRule::Media(ref rule) = rule { if let Some(device) = self.device { if rule.evaluate(device) { self.stack.push(rule.rules.iter()); } else { continue } } else { self.stack.push(rule.rules.iter()); } } return Some(rule) } self.stack.pop(); } None } #[inline] fn size_hint(&self) -> (usize, Option) { // TODO: track total number of rules in style-sheet for upper bound? (0, None) } } pub mod rule_filter { //! Specific `CSSRule` variant iterators. use std::marker::PhantomData; use super::{CSSRule, KeyframesRule, MediaRule, StyleRule}; use super::super::font_face::FontFaceRule; use super::super::viewport::ViewportRule; macro_rules! rule_filter { ($variant:ident -> $value:ty) => { /// An iterator that only yields rules that are of the synonymous `CSSRule` variant. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct $variant<'a, I> { iter: I, _lifetime: PhantomData<&'a ()> } impl<'a, I> $variant<'a, I> where I: Iterator { #[inline] pub fn new(iter: I) -> $variant<'a, I> { $variant { iter: iter, _lifetime: PhantomData } } } impl<'a, I> Iterator for $variant<'a, I> where I: Iterator { type Item = &'a $value; fn next(&mut self) -> Option<&'a $value> { while let Some(rule) = self.iter.next() { match *rule { CSSRule::$variant(ref value) => return Some(value), _ => continue } } None } #[inline] fn size_hint(&self) -> (usize, Option) { (0, self.iter.size_hint().1) } } } } rule_filter!(Media -> MediaRule); rule_filter!(Style -> StyleRule); rule_filter!(FontFace -> FontFaceRule); rule_filter!(Viewport -> ViewportRule); rule_filter!(Keyframes -> KeyframesRule); } /// Extension methods for `CSSRule` iterators. pub trait CSSRuleIteratorExt<'a>: Iterator + Sized { /// Yield only @font-face rules. fn font_face(self) -> rule_filter::FontFace<'a, Self>; /// Yield only @media rules. fn media(self) -> rule_filter::Media<'a, Self>; /// Yield only style rules. fn style(self) -> rule_filter::Style<'a, Self>; /// Yield only @viewport rules. fn viewport(self) -> rule_filter::Viewport<'a, Self>; /// Yield only @keyframes rules. fn keyframes(self) -> rule_filter::Keyframes<'a, Self>; } impl<'a, I> CSSRuleIteratorExt<'a> for I where I: Iterator { #[inline] fn font_face(self) -> rule_filter::FontFace<'a, I> { rule_filter::FontFace::new(self) } #[inline] fn media(self) -> rule_filter::Media<'a, I> { rule_filter::Media::new(self) } #[inline] fn style(self) -> rule_filter::Style<'a, I> { rule_filter::Style::new(self) } #[inline] fn viewport(self) -> rule_filter::Viewport<'a, I> { rule_filter::Viewport::new(self) } #[inline] fn keyframes(self) -> rule_filter::Keyframes<'a, I> { rule_filter::Keyframes::new(self) } } fn parse_nested_rules(context: &ParserContext, input: &mut Parser) -> Vec { let mut iter = RuleListParser::new_for_nested_rule(input, NestedRuleParser { context: context }); let mut rules = Vec::new(); while let Some(result) = iter.next() { match result { Ok(rule) => rules.push(rule), Err(range) => { let pos = range.start; let message = format!("Unsupported rule: '{}'", iter.input.slice(range)); log_css_error(iter.input, pos, &*message, &context); } } } rules } struct TopLevelRuleParser<'a> { context: ParserContext<'a>, state: Cell, } #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] enum State { Start = 1, Imports = 2, Namespaces = 3, Body = 4, } enum AtRulePrelude { /// A @font-face rule prelude. FontFace, /// A @media rule prelude, with its media queries. Media(Arc), /// A @viewport rule prelude. Viewport, /// A @keyframes rule, with its animation name. Keyframes(Atom), } impl<'a> AtRuleParser for TopLevelRuleParser<'a> { type Prelude = AtRulePrelude; type AtRule = CSSRule; fn parse_prelude(&mut self, name: &str, input: &mut Parser) -> Result, ()> { match_ignore_ascii_case! { name, "import" => { if self.state.get() <= State::Imports { self.state.set(State::Imports); // TODO: support @import return Err(()) // "@import is not supported yet" } else { return Err(()) // "@import must be before any rule but @charset" } }, "namespace" => { if self.state.get() <= State::Namespaces { self.state.set(State::Namespaces); let prefix_result = input.try(|input| input.expect_ident()); let url = Namespace(Atom::from(try!(input.expect_url_or_string()))); let opt_prefix = if let Ok(prefix) = prefix_result { let prefix: Atom = prefix.into(); self.context.selector_context.namespace_prefixes.insert( prefix.clone(), url.clone()); Some(prefix) } else { self.context.selector_context.default_namespace = Some(url.clone()); None }; return Ok(AtRuleType::WithoutBlock(CSSRule::Namespace(Arc::new(NamespaceRule { prefix: opt_prefix, url: url, })))) } else { return Err(()) // "@namespace must be before any rule but @charset and @import" } }, // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet // anything left is invalid. "charset" => return Err(()), // (insert appropriate error message) _ => {} } self.state.set(State::Body); AtRuleParser::parse_prelude(&mut NestedRuleParser { context: &self.context }, name, input) } #[inline] fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result { AtRuleParser::parse_block(&mut NestedRuleParser { context: &self.context }, prelude, input) } } impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> { type Prelude = Vec>; type QualifiedRule = CSSRule; #[inline] fn parse_prelude(&mut self, input: &mut Parser) -> Result>, ()> { self.state.set(State::Body); QualifiedRuleParser::parse_prelude(&mut NestedRuleParser { context: &self.context }, input) } #[inline] fn parse_block(&mut self, prelude: Vec>, input: &mut Parser) -> Result { QualifiedRuleParser::parse_block(&mut NestedRuleParser { context: &self.context }, prelude, input) } } struct NestedRuleParser<'a, 'b: 'a> { context: &'a ParserContext<'b>, } impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { type Prelude = AtRulePrelude; type AtRule = CSSRule; fn parse_prelude(&mut self, name: &str, input: &mut Parser) -> Result, ()> { match_ignore_ascii_case! { name, "media" => { let media_queries = parse_media_query_list(input); Ok(AtRuleType::WithBlock(AtRulePrelude::Media(Arc::new(media_queries)))) }, "font-face" => { Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace)) }, "viewport" => { if ::util::prefs::PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false) { Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport)) } else { Err(()) } }, "keyframes" => { let name = match input.next() { Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value), Ok(Token::QuotedString(value)) => Atom::from(&*value), _ => return Err(()) }; Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name)))) }, _ => Err(()) } } fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result { match prelude { AtRulePrelude::FontFace => { Ok(CSSRule::FontFace(Arc::new(try!(parse_font_face_block(self.context, input))))) } AtRulePrelude::Media(media_queries) => { Ok(CSSRule::Media(Arc::new(MediaRule { media_queries: media_queries, rules: parse_nested_rules(self.context, input), }))) } AtRulePrelude::Viewport => { Ok(CSSRule::Viewport(Arc::new(try!(ViewportRule::parse(input, self.context))))) } AtRulePrelude::Keyframes(name) => { Ok(CSSRule::Keyframes(Arc::new(KeyframesRule { name: name, keyframes: parse_keyframe_list(&self.context, input), }))) } } } } impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> { type Prelude = Vec>; type QualifiedRule = CSSRule; fn parse_prelude(&mut self, input: &mut Parser) -> Result>, ()> { parse_selector_list(&self.context.selector_context, input) } fn parse_block(&mut self, prelude: Vec>, input: &mut Parser) -> Result { Ok(CSSRule::Style(Arc::new(StyleRule { selectors: prelude, declarations: Arc::new(parse_property_declaration_list(self.context, input)) }))) } }