/* 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 url::Url; use selectors::bloom::BloomFilter; use selectors::matching::{SelectorMap, Rule}; use selectors::matching::DeclarationBlock as GenericDeclarationBlock; use selectors::parser::PseudoElement; use selectors::smallvec::VecLike; use selectors::tree::{TNode, TElement}; use util::resource_files::read_resource_file; use legacy::PresentationalHintSynthesis; use media_queries::Device; use node::TElementAttributes; use properties::{PropertyDeclaration, PropertyDeclarationBlock}; use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules, Origin}; pub type DeclarationBlock = GenericDeclarationBlock>; pub struct Stylist { // List of stylesheets (including all media rules) stylesheets: Vec, // Device that the stylist is currently evaluating against. pub device: Device, // If true, a stylesheet has been added or the device has // changed, and the stylist needs to be updated. is_dirty: bool, // The current selector maps, after evaluating media // rules against the current device. element_map: PerPseudoElementSelectorMap, before_map: PerPseudoElementSelectorMap, after_map: PerPseudoElementSelectorMap, rules_source_order: uint, } impl Stylist { #[inline] pub fn new(device: Device) -> Stylist { let mut stylist = Stylist { stylesheets: vec!(), device: device, is_dirty: true, element_map: PerPseudoElementSelectorMap::new(), before_map: PerPseudoElementSelectorMap::new(), after_map: PerPseudoElementSelectorMap::new(), rules_source_order: 0u, }; // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8. // FIXME: presentational-hints.css should be at author origin with zero specificity. // (Does it make a difference?) for &filename in ["user-agent.css", "servo.css", "presentational-hints.css"].iter() { let ua_stylesheet = Stylesheet::from_bytes( &read_resource_file(&[filename]).unwrap(), Url::parse(&format!("chrome:///{:?}", filename)).unwrap(), None, None, Origin::UserAgent); stylist.add_stylesheet(ua_stylesheet); } stylist } pub fn update(&mut self) -> bool { if self.is_dirty { self.element_map = PerPseudoElementSelectorMap::new(); self.before_map = PerPseudoElementSelectorMap::new(); self.after_map = PerPseudoElementSelectorMap::new(); self.rules_source_order = 0; for stylesheet in self.stylesheets.iter() { let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin { Origin::UserAgent => ( &mut self.element_map.user_agent, &mut self.before_map.user_agent, &mut self.after_map.user_agent, ), Origin::Author => ( &mut self.element_map.author, &mut self.before_map.author, &mut self.after_map.author, ), Origin::User => ( &mut self.element_map.user, &mut self.before_map.user, &mut self.after_map.user, ), }; let mut rules_source_order = self.rules_source_order; // Take apart the StyleRule into individual Rules and insert // them into the SelectorMap of that priority. macro_rules! append( ($style_rule: ident, $priority: ident) => { if $style_rule.declarations.$priority.len() > 0 { for selector in $style_rule.selectors.iter() { let map = match selector.pseudo_element { None => &mut element_map, Some(PseudoElement::Before) => &mut before_map, Some(PseudoElement::After) => &mut after_map, }; map.$priority.insert(Rule { selector: selector.compound_selectors.clone(), declarations: DeclarationBlock { specificity: selector.specificity, declarations: $style_rule.declarations.$priority.clone(), source_order: rules_source_order, }, }); } } }; ); iter_stylesheet_style_rules(stylesheet, &self.device, |style_rule| { append!(style_rule, normal); append!(style_rule, important); rules_source_order += 1; }); self.rules_source_order = rules_source_order; } self.is_dirty = false; return true; } false } pub fn set_device(&mut self, device: Device) { let is_dirty = self.is_dirty || self.stylesheets.iter().any(|stylesheet| { let mut stylesheet_dirty = false; iter_stylesheet_media_rules(stylesheet, |rule| { stylesheet_dirty |= rule.media_queries.evaluate(&self.device) != rule.media_queries.evaluate(&device); }); stylesheet_dirty }); self.device = device; self.is_dirty |= is_dirty; } pub fn add_quirks_mode_stylesheet(&mut self) { self.add_stylesheet(Stylesheet::from_bytes( &read_resource_file(&["quirks-mode.css"]).unwrap(), Url::parse("chrome:///quirks-mode.css").unwrap(), None, None, Origin::UserAgent)) } pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) { self.stylesheets.push(stylesheet); self.is_dirty = true; } /// Returns the applicable CSS declarations for the given element. This corresponds to /// `ElementRuleCollector` in WebKit. /// /// The returned boolean indicates whether the style is *shareable*; that is, whether the /// matched selectors are simple enough to allow the matching logic to be reduced to the logic /// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`. pub fn push_applicable_declarations<'a,E,N,V>( &self, element: &N, parent_bf: &Option>, style_attribute: Option<&PropertyDeclarationBlock>, pseudo_element: Option, applicable_declarations: &mut V) -> bool where E: TElement<'a> + TElementAttributes, N: TNode<'a,E>, V: VecLike { assert!(!self.is_dirty); assert!(element.is_element()); assert!(style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"); let map = match pseudo_element { None => &self.element_map, Some(PseudoElement::Before) => &self.before_map, Some(PseudoElement::After) => &self.after_map, }; let mut shareable = true; // Step 1: Normal user-agent rules. map.user_agent.normal.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); // Step 2: Presentational hints. self.synthesize_presentational_hints_for_legacy_attributes(element, applicable_declarations, &mut shareable); // Step 3: User and author normal rules. map.user.normal.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); map.author.normal.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); // Step 4: Normal style attributes. style_attribute.map(|sa| { shareable = false; applicable_declarations.vec_push( GenericDeclarationBlock::from_declarations(sa.normal.clone())) }); // Step 5: Author-supplied `!important` rules. map.author.important.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); // Step 6: `!important` style attributes. style_attribute.map(|sa| { shareable = false; applicable_declarations.vec_push( GenericDeclarationBlock::from_declarations(sa.important.clone())) }); // Step 7: User and UA `!important` rules. map.user.important.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); map.user_agent.important.get_all_matching_rules(element, parent_bf, applicable_declarations, &mut shareable); shareable } } struct PerOriginSelectorMap { normal: SelectorMap>, important: SelectorMap>, } impl PerOriginSelectorMap { #[inline] fn new() -> PerOriginSelectorMap { PerOriginSelectorMap { normal: SelectorMap::new(), important: SelectorMap::new(), } } } struct PerPseudoElementSelectorMap { user_agent: PerOriginSelectorMap, author: PerOriginSelectorMap, user: PerOriginSelectorMap, } impl PerPseudoElementSelectorMap { #[inline] fn new() -> PerPseudoElementSelectorMap { PerPseudoElementSelectorMap { user_agent: PerOriginSelectorMap::new(), author: PerOriginSelectorMap::new(), user: PerOriginSelectorMap::new(), } } }