diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index a488ae461e91..636f0646ce57 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -51,9 +51,8 @@ pub use ::fnv::FnvHashMap; /// This structure holds all the selectors and device characteristics /// for a given document. The selectors are converted into `Rule`s -/// (defined in rust-selectors), and introduced in a `SelectorMap` -/// depending on the pseudo-element (see `PerPseudoElementSelectorMap`), -/// and stylesheet origin (see the fields of `PerPseudoElementSelectorMap`). +/// (defined in rust-selectors), and sorted into `SelectorMap`s keyed +/// off stylesheet origin and pseudo-element (see `CascadeData`). /// /// This structure is effectively created once per pipeline, in the /// LayoutThread corresponding to that pipeline. @@ -90,17 +89,14 @@ pub struct Stylist { /// had clear() called on it with no following rebuild()). is_cleared: bool, - /// The current selector maps, after evaluating media - /// rules against the current device. - element_map: PerPseudoElementSelectorMap, + /// Selector maps for all of the style sheets in the stylist, after + /// evalutaing media rules against the current device, split out per + /// cascade level. + cascade_data: CascadeData, /// The rule tree, that stores the results of selector matching. rule_tree: RuleTree, - /// The selector maps corresponding to a given pseudo-element - /// (depending on the implementation) - pseudos_map: FnvHashMap, - /// A map with all the animations indexed by name. animations: FnvHashMap, @@ -234,7 +230,7 @@ impl Stylist { /// be reset in clear(). #[inline] pub fn new(device: Device, quirks_mode: QuirksMode) -> Self { - let mut stylist = Stylist { + Stylist { viewport_constraints: None, device: device, is_device_dirty: true, @@ -242,8 +238,7 @@ impl Stylist { quirks_mode: quirks_mode, effective_media_query_results: EffectiveMediaQueryResults::new(), - element_map: PerPseudoElementSelectorMap::new(), - pseudos_map: Default::default(), + cascade_data: CascadeData::new(), animations: Default::default(), precomputed_pseudo_element_decls: Default::default(), rules_source_order: 0, @@ -257,15 +252,9 @@ impl Stylist { num_selectors: 0, num_declarations: 0, num_rebuilds: 0, - }; - - SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { - stylist.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new()); - }); + } // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8. - - stylist } /// Returns the number of selectors. @@ -317,8 +306,7 @@ impl Stylist { // preserve current device self.is_device_dirty = true; // preserve current quirks_mode value - self.element_map = PerPseudoElementSelectorMap::new(); - self.pseudos_map = Default::default(); + self.cascade_data.clear(); self.animations.clear(); // Or set to Default::default()? self.precomputed_pseudo_element_decls = Default::default(); self.rules_source_order = 0; @@ -393,10 +381,6 @@ impl Stylist { self.device.account_for_viewport_rule(constraints); } - SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { - self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new()); - }); - extra_data.clear(); if let Some(ua_stylesheets) = ua_stylesheets { @@ -420,8 +404,8 @@ impl Stylist { } SelectorImpl::each_precomputed_pseudo_element(|pseudo| { - if let Some(map) = self.pseudos_map.remove(&pseudo) { - let declarations = map.user_agent.get_universal_rules(CascadeLevel::UANormal); + if let Some(map) = self.cascade_data.user_agent.pseudos_map.remove(&pseudo) { + let declarations = map.get_universal_rules(CascadeLevel::UANormal); self.precomputed_pseudo_element_decls.insert(pseudo, declarations); } }); @@ -482,24 +466,18 @@ impl Stylist { for selector in &style_rule.selectors.0 { self.num_selectors += 1; - let map = if let Some(pseudo) = selector.pseudo_element() { - self.pseudos_map - .entry(pseudo.canonical()) - .or_insert_with(PerPseudoElementSelectorMap::new) - .borrow_for_origin(&origin) - } else { - self.element_map.borrow_for_origin(&origin) - }; - let hashes = AncestorHashes::new(&selector, self.quirks_mode); - map.insert( - Rule::new(selector.clone(), - hashes.clone(), - locked.clone(), - self.rules_source_order), - self.quirks_mode); + self.cascade_data + .borrow_mut_for_origin(&origin) + .borrow_mut_for_pseudo_or_insert(selector.pseudo_element()) + .insert( + Rule::new(selector.clone(), + hashes.clone(), + locked.clone(), + self.rules_source_order), + self.quirks_mode); self.invalidation_map.note_selector(selector, self.quirks_mode); let mut visitor = StylistSelectorVisitor { @@ -857,7 +835,8 @@ impl Stylist { { let pseudo = pseudo.canonical(); debug_assert!(pseudo.is_lazy()); - if self.pseudos_map.get(&pseudo).is_none() { + + if !self.cascade_data.has_rules_for_pseudo(&pseudo) { return CascadeInputs::default() } @@ -1117,17 +1096,6 @@ impl Stylist { self.quirks_mode = quirks_mode; } - /// Returns the correspond PerPseudoElementSelectorMap given PseudoElement. - fn get_map(&self, - pseudo_element: Option<&PseudoElement>) -> Option<&PerPseudoElementSelectorMap> - { - match pseudo_element { - Some(pseudo) => self.pseudos_map.get(pseudo), - None => Some(&self.element_map), - } - } - - /// Returns the applicable CSS declarations for the given element by /// treating us as an XBL stylesheet-only stylist. pub fn push_applicable_declarations_as_xbl_only_stylist(&self, @@ -1141,21 +1109,19 @@ impl Stylist { MatchingContext::new(MatchingMode::Normal, None, self.quirks_mode); let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {}; - let map = match self.get_map(pseudo_element) { - Some(map) => map, - None => return, - }; let rule_hash_target = element.rule_hash_target(); // nsXBLPrototypeResources::LoadResources() loads Chrome XBL style // sheets under eAuthorSheetFeatures level. - map.author.get_all_matching_rules(element, - &rule_hash_target, - applicable_declarations, - &mut matching_context, - self.quirks_mode, - &mut dummy_flag_setter, - CascadeLevel::XBL); + if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { + map.get_all_matching_rules(element, + &rule_hash_target, + applicable_declarations, + &mut matching_context, + self.quirks_mode, + &mut dummy_flag_setter, + CascadeLevel::XBL); + } } /// Returns the applicable CSS declarations for the given element. @@ -1187,10 +1153,6 @@ impl Stylist { "Style attributes do not apply to pseudo-elements"); debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed())); - let map = match self.get_map(pseudo_element) { - Some(map) => map, - None => return, - }; let rule_hash_target = element.rule_hash_target(); debug!("Determining if style is shareable: pseudo: {}", @@ -1199,13 +1161,15 @@ impl Stylist { let only_default_rules = rule_inclusion == RuleInclusion::DefaultOnly; // Step 1: Normal user-agent rules. - map.user_agent.get_all_matching_rules(element, - &rule_hash_target, - applicable_declarations, - context, - self.quirks_mode, - flags_setter, - CascadeLevel::UANormal); + if let Some(map) = self.cascade_data.user_agent.borrow_for_pseudo(pseudo_element) { + map.get_all_matching_rules(element, + &rule_hash_target, + applicable_declarations, + context, + self.quirks_mode, + flags_setter, + CascadeLevel::UANormal); + } if pseudo_element.is_none() && !only_default_rules { // Step 2: Presentational hints. @@ -1233,13 +1197,15 @@ impl Stylist { // Which may be more what you would probably expect. if rule_hash_target.matches_user_and_author_rules() { // Step 3a: User normal rules. - map.user.get_all_matching_rules(element, - &rule_hash_target, - applicable_declarations, - context, - self.quirks_mode, - flags_setter, - CascadeLevel::UserNormal); + if let Some(map) = self.cascade_data.user.borrow_for_pseudo(pseudo_element) { + map.get_all_matching_rules(element, + &rule_hash_target, + applicable_declarations, + context, + self.quirks_mode, + flags_setter, + CascadeLevel::UserNormal); + } } else { debug!("skipping user rules"); } @@ -1254,13 +1220,15 @@ impl Stylist { // See nsStyleSet::FileRules(). if !cut_off_inheritance { // Step 3c: Author normal rules. - map.author.get_all_matching_rules(element, - &rule_hash_target, - applicable_declarations, - context, - self.quirks_mode, - flags_setter, - CascadeLevel::AuthorNormal); + if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { + map.get_all_matching_rules(element, + &rule_hash_target, + applicable_declarations, + context, + self.quirks_mode, + flags_setter, + CascadeLevel::AuthorNormal); + } } else { debug!("skipping author normal rules due to cut off inheritance"); } @@ -1592,37 +1560,132 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> { } } -/// Map that contains the CSS rules for a specific PseudoElement -/// (or lack of PseudoElement). +/// Data resulting from performing the CSS cascade. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[derive(Debug)] -struct PerPseudoElementSelectorMap { +struct CascadeData { /// Rules from user agent stylesheets - user_agent: SelectorMap, + user_agent: PerOriginCascadeData, /// Rules from author stylesheets - author: SelectorMap, + author: PerOriginCascadeData, /// Rules from user stylesheets - user: SelectorMap, + user: PerOriginCascadeData, } -impl PerPseudoElementSelectorMap { - #[inline] +impl CascadeData { fn new() -> Self { - PerPseudoElementSelectorMap { - user_agent: SelectorMap::new(), - author: SelectorMap::new(), - user: SelectorMap::new(), + CascadeData { + user_agent: PerOriginCascadeData::new(), + author: PerOriginCascadeData::new(), + user: PerOriginCascadeData::new(), } } #[inline] - fn borrow_for_origin(&mut self, origin: &Origin) -> &mut SelectorMap { + fn borrow_mut_for_origin(&mut self, origin: &Origin) -> &mut PerOriginCascadeData { match *origin { Origin::UserAgent => &mut self.user_agent, Origin::Author => &mut self.author, Origin::User => &mut self.user, } } + + fn clear(&mut self) { + self.user_agent.clear(); + self.author.clear(); + self.user.clear(); + } + + fn has_rules_for_pseudo(&self, pseudo: &PseudoElement) -> bool { + self.iter_origins().any(|d| d.has_rules_for_pseudo(pseudo)) + } + + fn iter_origins(&self) -> CascadeDataIter { + CascadeDataIter { + cascade_data: &self, + cur: 0, + } + } +} + +struct CascadeDataIter<'a> { + cascade_data: &'a CascadeData, + cur: usize, +} + +impl<'a> Iterator for CascadeDataIter<'a> { + type Item = &'a PerOriginCascadeData; + + fn next(&mut self) -> Option<&'a PerOriginCascadeData> { + let result = match self.cur { + 0 => &self.cascade_data.user_agent, + 1 => &self.cascade_data.author, + 2 => &self.cascade_data.user, + _ => return None, + }; + self.cur += 1; + Some(result) + } +} + +/// Data resulting from performing the CSS cascade that is specific to a given +/// origin. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Debug)] +struct PerOriginCascadeData { + /// Rules from stylesheets at this `CascadeData`'s origin. + element_map: SelectorMap, + + /// Rules from stylesheets at this `CascadeData`'s origin that correspond + /// to a given pseudo-element. + pseudos_map: FnvHashMap>, +} + +impl PerOriginCascadeData { + fn new() -> Self { + let mut data = PerOriginCascadeData { + element_map: SelectorMap::new(), + pseudos_map: Default::default(), + }; + SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { + data.pseudos_map.insert(pseudo, SelectorMap::new()); + }); + data + } + + #[inline] + fn borrow_for_pseudo(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap> { + match pseudo { + Some(pseudo) => self.pseudos_map.get(&pseudo.canonical()), + None => Some(&self.element_map), + } + } + + #[inline] + fn borrow_mut_for_pseudo_or_insert(&mut self, pseudo: Option<&PseudoElement>) -> &mut SelectorMap { + match pseudo { + Some(pseudo) => { + self.pseudos_map + .entry(pseudo.canonical()) + .or_insert_with(SelectorMap::new) + } + None => &mut self.element_map, + } + } + + fn clear(&mut self) { + self.element_map = SelectorMap::new(); + self.pseudos_map = Default::default(); + SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { + self.pseudos_map.insert(pseudo, SelectorMap::new()); + }); + } + + fn has_rules_for_pseudo(&self, pseudo: &PseudoElement) -> bool { + // FIXME(emilio): We should probably make the pseudos map be an + // enumerated array. + self.pseudos_map.contains_key(pseudo) + } } /// A rule, that wraps a style rule, but represents a single selector of the