diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index b95e938d3a2..88c94a2870f 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -5,20 +5,16 @@ //! Data needed to style a Gecko document. use Atom; -use animation::Animation; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use dom::OpaqueNode; +use fnv::FnvHashMap; use gecko::rules::{CounterStyleRule, FontFaceRule}; use gecko_bindings::bindings::RawServoStyleSet; use gecko_bindings::structs::RawGeckoPresContextOwned; use gecko_bindings::structs::nsIDocument; use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use media_queries::Device; -use parking_lot::RwLock; use properties::ComputedValues; use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard}; -use std::collections::HashMap; -use std::sync::mpsc::{Receiver, Sender, channel}; use stylearc::Arc; use stylesheet_set::StylesheetSet; use stylesheets::Origin; @@ -33,24 +29,11 @@ pub struct PerDocumentStyleDataImpl { /// List of stylesheets, mirrored from Gecko. pub stylesheets: StylesheetSet, - // FIXME(bholley): Hook these up to something. - /// Unused. Will go away when we actually implement transitions and - /// animations properly. - pub new_animations_sender: Sender, - /// Unused. Will go away when we actually implement transitions and - /// animations properly. - pub new_animations_receiver: Receiver, - /// Unused. Will go away when we actually implement transitions and - /// animations properly. - pub running_animations: Arc>>>, - /// Unused. Will go away when we actually implement transitions and - /// animations properly. - pub expired_animations: Arc>>>, - /// List of effective font face rules. pub font_faces: Vec<(Arc>, Origin)>, + /// Map for effective counter style rules. - pub counter_styles: HashMap>>, + pub counter_styles: FnvHashMap>>, } /// The data itself is an `AtomicRefCell`, which guarantees the proper semantics @@ -65,17 +48,11 @@ impl PerDocumentStyleData { (*(*device.pres_context).mDocument.raw::()).mCompatMode }; - let (new_anims_sender, new_anims_receiver) = channel(); - PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { stylist: Stylist::new(device, quirks_mode.into()), stylesheets: StylesheetSet::new(), - new_animations_sender: new_anims_sender, - new_animations_receiver: new_anims_receiver, - running_animations: Arc::new(RwLock::new(HashMap::new())), - expired_animations: Arc::new(RwLock::new(HashMap::new())), font_faces: vec![], - counter_styles: HashMap::new(), + counter_styles: FnvHashMap::default(), })) } diff --git a/components/style/lib.rs b/components/style/lib.rs index fd796bc2f2d..247daf6222d 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -120,6 +120,7 @@ pub mod parser; pub mod restyle_hints; pub mod rule_tree; pub mod scoped_tls; +pub mod selector_map; pub mod selector_parser; pub mod shared_lock; pub mod sharing; diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 06188329261..2d5d9aed27c 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -16,6 +16,7 @@ use element_state::*; use gecko_bindings::structs::nsRestyleHint; #[cfg(feature = "servo")] use heapsize::HeapSizeOf; +use selector_map::{SelectorMap, SelectorMapEntry}; use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue}; use selectors::Element; use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; @@ -24,11 +25,9 @@ use selectors::matching::matches_selector; use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorMethods}; use selectors::visitor::SelectorVisitor; use smallvec::SmallVec; -use std::borrow::Borrow; use std::cell::Cell; use std::clone::Clone; use std::cmp; -use stylist::SelectorMap; /// When the ElementState of an element (like IN_HOVER_STATE) changes, /// certain pseudo-classes (like :hover) may require us to restyle that @@ -759,44 +758,12 @@ pub struct Dependency { pub sensitivities: Sensitivities, } -impl Borrow> for Dependency { - fn borrow(&self) -> &SelectorInner { +impl SelectorMapEntry for Dependency { + fn selector(&self) -> &SelectorInner { &self.selector } } -/// A similar version of the above, but for pseudo-elements, which only care -/// about the full selector, and need it in order to properly track -/// pseudo-element selector state. -/// -/// NOTE(emilio): We could add a `hint` and `sensitivities` field to the -/// `PseudoElementDependency` and stop posting `RESTYLE_DESCENDANTS`s hints if -/// we visited all the pseudo-elements of an element unconditionally as part of -/// the traversal. -/// -/// That would allow us to stop posting `RESTYLE_DESCENDANTS` hints for dumb -/// selectors, and storing pseudo dependencies in the element dependency map. -/// -/// That would allow us to avoid restyling the element itself when a selector -/// has only changed a pseudo-element's style, too. -/// -/// There's no good way to do that right now though, and I think for the -/// foreseeable future we may just want to optimize that `RESTYLE_DESCENDANTS` -/// to become a `RESTYLE_PSEUDO_ELEMENTS` or something like that, in order to at -/// least not restyle the whole subtree. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -struct PseudoElementDependency { - #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")] - selector: Selector, -} - -impl Borrow> for PseudoElementDependency { - fn borrow(&self) -> &SelectorInner { - &self.selector.inner - } -} - /// The following visitor visits all the simple selectors for a given complex /// selector, taking care of :not and :any combinators, collecting whether any /// of them is sensitive to attribute or state changes. diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs new file mode 100644 index 00000000000..497baf2f914 --- /dev/null +++ b/components/style/selector_map.rs @@ -0,0 +1,455 @@ +/* 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/. */ + +//! A data structure to efficiently index structs containing selectors by local +//! name, ids and hash. + +use {Atom, LocalName}; +use dom::TElement; +use fnv::FnvHashMap; +use pdqsort::sort_by; +use rule_tree::CascadeLevel; +use selector_parser::SelectorImpl; +use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags}; +use selectors::parser::{Component, Combinator, SelectorInner}; +use selectors::parser::LocalName as LocalNameSelector; +use smallvec::VecLike; +use std::borrow::Borrow; +use std::collections::HashMap; +use std::hash::Hash; +use stylist::{ApplicableDeclarationBlock, Rule}; + +/// A trait to abstract over a given selector map entry. +pub trait SelectorMapEntry : Sized + Clone { + /// Get the selector we should use to index in the selector map. + fn selector(&self) -> &SelectorInner; +} + +impl SelectorMapEntry for SelectorInner { + fn selector(&self) -> &SelectorInner { + self + } +} + +/// Map element data to selector-providing objects for which the last simple +/// selector starts with them. +/// +/// e.g., +/// "p > img" would go into the set of selectors corresponding to the +/// element "img" +/// "a .foo .bar.baz" would go into the set of selectors corresponding to +/// the class "bar" +/// +/// Because we match selectors right-to-left (i.e., moving up the tree +/// from an element), we need to compare the last simple selector in the +/// selector with the element. +/// +/// So, if an element has ID "id1" and classes "foo" and "bar", then all +/// the rules it matches will have their last simple selector starting +/// either with "#id1" or with ".foo" or with ".bar". +/// +/// Hence, the union of the rules keyed on each of element's classes, ID, +/// element name, etc. will contain the Selectors that actually match that +/// element. +/// +/// TODO: Tune the initial capacity of the HashMap +#[derive(Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct SelectorMap { + /// A hash from an ID to rules which contain that ID selector. + pub id_hash: FnvHashMap>, + /// A hash from a class name to rules which contain that class selector. + pub class_hash: FnvHashMap>, + /// A hash from local name to rules which contain that local name selector. + pub local_name_hash: FnvHashMap>, + /// Rules that don't have ID, class, or element selectors. + pub other: Vec, + /// The number of entries in this map. + pub count: usize, +} + +#[inline] +fn sort_by_key K, K: Ord>(v: &mut [T], f: F) { + sort_by(v, |a, b| f(a).cmp(&f(b))) +} + +impl SelectorMap { + /// Trivially constructs an empty `SelectorMap`. + pub fn new() -> Self { + SelectorMap { + id_hash: HashMap::default(), + class_hash: HashMap::default(), + local_name_hash: HashMap::default(), + other: Vec::new(), + count: 0, + } + } + + /// Returns whether there are any entries in the map. + pub fn is_empty(&self) -> bool { + self.count == 0 + } + + /// Returns the number of entries. + pub fn len(&self) -> usize { + self.count + } +} + +impl SelectorMap { + /// Append to `rule_list` all Rules in `self` that match element. + /// + /// Extract matching rules as per element's ID, classes, tag name, etc.. + /// Sort the Rules at the end to maintain cascading order. + pub fn get_all_matching_rules(&self, + element: &E, + rule_hash_target: &E, + matching_rules_list: &mut V, + context: &mut MatchingContext, + flags_setter: &mut F, + cascade_level: CascadeLevel) + where E: TElement, + V: VecLike, + F: FnMut(&E, ElementSelectorFlags), + { + if self.is_empty() { + return + } + + // At the end, we're going to sort the rules that we added, so remember where we began. + let init_len = matching_rules_list.len(); + if let Some(id) = rule_hash_target.get_id() { + SelectorMap::get_matching_rules_from_hash(element, + &self.id_hash, + &id, + matching_rules_list, + context, + flags_setter, + cascade_level) + } + + rule_hash_target.each_class(|class| { + SelectorMap::get_matching_rules_from_hash(element, + &self.class_hash, + class, + matching_rules_list, + context, + flags_setter, + cascade_level); + }); + + SelectorMap::get_matching_rules_from_hash(element, + &self.local_name_hash, + rule_hash_target.get_local_name(), + matching_rules_list, + context, + flags_setter, + cascade_level); + + SelectorMap::get_matching_rules(element, + &self.other, + matching_rules_list, + context, + flags_setter, + cascade_level); + + // Sort only the rules we just added. + sort_by_key(&mut matching_rules_list[init_len..], + |block| (block.specificity, block.source_order)); + } + + /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in + /// `self` sorted by specificity and source order. + pub fn get_universal_rules(&self, + cascade_level: CascadeLevel) + -> Vec { + debug_assert!(!cascade_level.is_important()); + if self.is_empty() { + return vec![]; + } + + let mut rules_list = vec![]; + for rule in self.other.iter() { + if rule.selector.is_universal() { + rules_list.push(rule.to_applicable_declaration_block(cascade_level)) + } + } + + sort_by_key(&mut rules_list, + |block| (block.specificity, block.source_order)); + + rules_list + } + + fn get_matching_rules_from_hash( + element: &E, + hash: &FnvHashMap>, + key: &BorrowedStr, + matching_rules: &mut Vector, + context: &mut MatchingContext, + flags_setter: &mut F, + cascade_level: CascadeLevel) + where E: TElement, + Str: Borrow + Eq + Hash, + BorrowedStr: Eq + Hash, + Vector: VecLike, + F: FnMut(&E, ElementSelectorFlags), + { + if let Some(rules) = hash.get(key) { + SelectorMap::get_matching_rules(element, + rules, + matching_rules, + context, + flags_setter, + cascade_level) + } + } + + /// Adds rules in `rules` that match `element` to the `matching_rules` list. + fn get_matching_rules(element: &E, + rules: &[Rule], + matching_rules: &mut V, + context: &mut MatchingContext, + flags_setter: &mut F, + cascade_level: CascadeLevel) + where E: TElement, + V: VecLike, + F: FnMut(&E, ElementSelectorFlags), + { + for rule in rules { + if matches_selector(&rule.selector.inner, + element, + context, + flags_setter) { + matching_rules.push( + rule.to_applicable_declaration_block(cascade_level)); + } + } + } +} + +impl SelectorMap { + /// Inserts into the correct hash, trying id, class, and localname. + pub fn insert(&mut self, entry: T) { + self.count += 1; + + if let Some(id_name) = get_id_name(entry.selector()) { + find_push(&mut self.id_hash, id_name, entry); + return; + } + + if let Some(class_name) = get_class_name(entry.selector()) { + find_push(&mut self.class_hash, class_name, entry); + return; + } + + if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.selector()) { + // If the local name in the selector isn't lowercase, insert it into + // the rule hash twice. This means that, during lookup, we can always + // find the rules based on the local name of the element, regardless + // of whether it's an html element in an html document (in which case + // we match against lower_name) or not (in which case we match against + // name). + // + // In the case of a non-html-element-in-html-document with a + // lowercase localname and a non-lowercase selector, the rulehash + // lookup may produce superfluous selectors, but the subsequent + // selector matching work will filter them out. + if name != lower_name { + find_push(&mut self.local_name_hash, lower_name, entry.clone()); + } + find_push(&mut self.local_name_hash, name, entry); + + return; + } + + self.other.push(entry); + } + + /// Looks up entries by id, class, local name, and other (in order). + /// + /// Each entry is passed to the callback, which returns true to continue + /// iterating entries, or false to terminate the lookup. + /// + /// Returns false if the callback ever returns false. + /// + /// FIXME(bholley) This overlaps with SelectorMap::get_all_matching_rules, + /// but that function is extremely hot and I'd rather not rearrange it. + #[inline] + pub fn lookup(&self, element: E, f: &mut F) -> bool + where E: TElement, + F: FnMut(&T) -> bool + { + // Id. + if let Some(id) = element.get_id() { + if let Some(v) = self.id_hash.get(&id) { + for entry in v.iter() { + if !f(&entry) { + return false; + } + } + } + } + + // Class. + let mut done = false; + element.each_class(|class| { + if !done { + if let Some(v) = self.class_hash.get(class) { + for entry in v.iter() { + if !f(&entry) { + done = true; + return; + } + } + } + } + }); + if done { + return false; + } + + // Local name. + if let Some(v) = self.local_name_hash.get(element.get_local_name()) { + for entry in v.iter() { + if !f(&entry) { + return false; + } + } + } + + // Other. + for entry in self.other.iter() { + if !f(&entry) { + return false; + } + } + + true + } + + /// Performs a normal lookup, and also looks up entries for the passed-in + /// id and classes. + /// + /// Each entry is passed to the callback, which returns true to continue + /// iterating entries, or false to terminate the lookup. + /// + /// Returns false if the callback ever returns false. + #[inline] + pub fn lookup_with_additional(&self, + element: E, + additional_id: Option, + additional_classes: &[Atom], + f: &mut F) + -> bool + where E: TElement, + F: FnMut(&T) -> bool + { + // Do the normal lookup. + if !self.lookup(element, f) { + return false; + } + + // Check the additional id. + if let Some(id) = additional_id { + if let Some(v) = self.id_hash.get(&id) { + for entry in v.iter() { + if !f(&entry) { + return false; + } + } + } + } + + // Check the additional classes. + for class in additional_classes { + if let Some(v) = self.class_hash.get(class) { + for entry in v.iter() { + if !f(&entry) { + return false; + } + } + } + } + + true + } +} + +/// Searches the selector from right to left, beginning to the left of the +/// ::pseudo-element (if any), and ending at the first combinator. +/// +/// The first non-None value returned from |f| is returned. +/// +/// Effectively, pseudo-elements are ignored, given only state pseudo-classes +/// may appear before them. +fn find_from_right(selector: &SelectorInner, + mut f: F) + -> Option + where F: FnMut(&Component) -> Option, +{ + let mut iter = selector.complex.iter(); + for ss in &mut iter { + if let Some(r) = f(ss) { + return Some(r) + } + } + + if iter.next_sequence() == Some(Combinator::PseudoElement) { + for ss in &mut iter { + if let Some(r) = f(ss) { + return Some(r) + } + } + } + + None +} + +/// Retrieve the first ID name in the selector, or None otherwise. +pub fn get_id_name(selector: &SelectorInner) + -> Option { + find_from_right(selector, |ss| { + // TODO(pradeep): Implement case-sensitivity based on the + // document type and quirks mode. + if let Component::ID(ref id) = *ss { + return Some(id.clone()); + } + None + }) +} + +/// Retrieve the FIRST class name in the selector, or None otherwise. +pub fn get_class_name(selector: &SelectorInner) + -> Option { + find_from_right(selector, |ss| { + // TODO(pradeep): Implement case-sensitivity based on the + // document type and quirks mode. + if let Component::Class(ref class) = *ss { + return Some(class.clone()); + } + None + }) +} + +/// Retrieve the name if it is a type selector, or None otherwise. +pub fn get_local_name(selector: &SelectorInner) + -> Option> { + find_from_right(selector, |ss| { + if let Component::LocalName(ref n) = *ss { + return Some(LocalNameSelector { + name: n.name.clone(), + lower_name: n.lower_name.clone(), + }) + } + None + }) +} + +#[inline] +fn find_push(map: &mut FnvHashMap>, + key: Str, + value: V) { + map.entry(key).or_insert_with(Vec::new).push(value) +} diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 3189defd7b1..7fb64c8acf5 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -16,27 +16,23 @@ use font_metrics::FontMetricsProvider; use gecko_bindings::structs::nsIAtom; use keyframes::KeyframesAnimation; use media_queries::Device; -use pdqsort::sort_by; use properties::{self, CascadeFlags, ComputedValues}; #[cfg(feature = "servo")] use properties::INHERIT_ALL; use properties::PropertyDeclarationBlock; use restyle_hints::{HintComputationContext, DependencySet, RestyleHint}; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; +use selector_map::{SelectorMap, SelectorMapEntry}; use selector_parser::{SelectorImpl, PseudoElement}; use selectors::attr::NamespaceConstraint; use selectors::bloom::BloomFilter; use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS}; use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; -use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter}; -use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector}; +use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter, SelectorMethods}; use selectors::visitor::SelectorVisitor; use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use sink::Push; use smallvec::{SmallVec, VecLike}; -use std::borrow::Borrow; -use std::collections::HashMap; -use std::hash::Hash; #[cfg(feature = "servo")] use std::marker::PhantomData; use style_traits::viewport::ViewportConstraints; @@ -174,7 +170,7 @@ pub struct ExtraStyleData<'a> { /// A list of effective font-face rules and their origin. pub font_faces: &'a mut Vec<(Arc>, Origin)>, /// A map of effective counter-style rules. - pub counter_styles: &'a mut HashMap>>, + pub counter_styles: &'a mut FnvHashMap>>, } #[cfg(feature = "gecko")] @@ -1237,417 +1233,6 @@ impl PerPseudoElementSelectorMap { } } -/// Map element data to selector-providing objects for which the last simple -/// selector starts with them. -/// -/// e.g., -/// "p > img" would go into the set of selectors corresponding to the -/// element "img" -/// "a .foo .bar.baz" would go into the set of selectors corresponding to -/// the class "bar" -/// -/// Because we match selectors right-to-left (i.e., moving up the tree -/// from an element), we need to compare the last simple selector in the -/// selector with the element. -/// -/// So, if an element has ID "id1" and classes "foo" and "bar", then all -/// the rules it matches will have their last simple selector starting -/// either with "#id1" or with ".foo" or with ".bar". -/// -/// Hence, the union of the rules keyed on each of element's classes, ID, -/// element name, etc. will contain the Selectors that actually match that -/// element. -/// -/// TODO: Tune the initial capacity of the HashMap -#[derive(Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct SelectorMap>> { - /// A hash from an ID to rules which contain that ID selector. - pub id_hash: FnvHashMap>, - /// A hash from a class name to rules which contain that class selector. - pub class_hash: FnvHashMap>, - /// A hash from local name to rules which contain that local name selector. - pub local_name_hash: FnvHashMap>, - /// Rules that don't have ID, class, or element selectors. - pub other: Vec, - /// The number of entries in this map. - pub count: usize, -} - -#[inline] -fn sort_by_key K, K: Ord>(v: &mut [T], f: F) { - sort_by(v, |a, b| f(a).cmp(&f(b))) -} - -impl SelectorMap where T: Clone + Borrow> { - /// Trivially constructs an empty `SelectorMap`. - pub fn new() -> Self { - SelectorMap { - id_hash: HashMap::default(), - class_hash: HashMap::default(), - local_name_hash: HashMap::default(), - other: Vec::new(), - count: 0, - } - } - - /// Returns whether there are any entries in the map. - pub fn is_empty(&self) -> bool { - self.count == 0 - } - - /// Returns the number of entries. - pub fn len(&self) -> usize { - self.count - } -} - -impl SelectorMap { - /// Append to `rule_list` all Rules in `self` that match element. - /// - /// Extract matching rules as per element's ID, classes, tag name, etc.. - /// Sort the Rules at the end to maintain cascading order. - pub fn get_all_matching_rules(&self, - element: &E, - rule_hash_target: &E, - matching_rules_list: &mut V, - context: &mut MatchingContext, - flags_setter: &mut F, - cascade_level: CascadeLevel) - where E: TElement, - V: VecLike, - F: FnMut(&E, ElementSelectorFlags), - { - if self.is_empty() { - return - } - - // At the end, we're going to sort the rules that we added, so remember where we began. - let init_len = matching_rules_list.len(); - if let Some(id) = rule_hash_target.get_id() { - SelectorMap::get_matching_rules_from_hash(element, - &self.id_hash, - &id, - matching_rules_list, - context, - flags_setter, - cascade_level) - } - - rule_hash_target.each_class(|class| { - SelectorMap::get_matching_rules_from_hash(element, - &self.class_hash, - class, - matching_rules_list, - context, - flags_setter, - cascade_level); - }); - - SelectorMap::get_matching_rules_from_hash(element, - &self.local_name_hash, - rule_hash_target.get_local_name(), - matching_rules_list, - context, - flags_setter, - cascade_level); - - SelectorMap::get_matching_rules(element, - &self.other, - matching_rules_list, - context, - flags_setter, - cascade_level); - - // Sort only the rules we just added. - sort_by_key(&mut matching_rules_list[init_len..], - |block| (block.specificity, block.source_order)); - } - - /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in - /// `self` sorted by specificity and source order. - pub fn get_universal_rules(&self, - cascade_level: CascadeLevel) - -> Vec { - debug_assert!(!cascade_level.is_important()); - if self.is_empty() { - return vec![]; - } - - let mut rules_list = vec![]; - for rule in self.other.iter() { - if rule.selector.is_universal() { - rules_list.push(rule.to_applicable_declaration_block(cascade_level)) - } - } - - sort_by_key(&mut rules_list, - |block| (block.specificity, block.source_order)); - - rules_list - } - - fn get_matching_rules_from_hash( - element: &E, - hash: &FnvHashMap>, - key: &BorrowedStr, - matching_rules: &mut Vector, - context: &mut MatchingContext, - flags_setter: &mut F, - cascade_level: CascadeLevel) - where E: TElement, - Str: Borrow + Eq + Hash, - BorrowedStr: Eq + Hash, - Vector: VecLike, - F: FnMut(&E, ElementSelectorFlags), - { - if let Some(rules) = hash.get(key) { - SelectorMap::get_matching_rules(element, - rules, - matching_rules, - context, - flags_setter, - cascade_level) - } - } - - /// Adds rules in `rules` that match `element` to the `matching_rules` list. - fn get_matching_rules(element: &E, - rules: &[Rule], - matching_rules: &mut V, - context: &mut MatchingContext, - flags_setter: &mut F, - cascade_level: CascadeLevel) - where E: TElement, - V: VecLike, - F: FnMut(&E, ElementSelectorFlags), - { - for rule in rules { - if matches_selector(&rule.selector.inner, - element, - context, - flags_setter) { - matching_rules.push( - rule.to_applicable_declaration_block(cascade_level)); - } - } - } -} - -impl SelectorMap where T: Clone + Borrow> { - /// Inserts into the correct hash, trying id, class, and localname. - pub fn insert(&mut self, entry: T) { - self.count += 1; - - if let Some(id_name) = get_id_name(entry.borrow()) { - find_push(&mut self.id_hash, id_name, entry); - return; - } - - if let Some(class_name) = get_class_name(entry.borrow()) { - find_push(&mut self.class_hash, class_name, entry); - return; - } - - if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.borrow()) { - // If the local name in the selector isn't lowercase, insert it into - // the rule hash twice. This means that, during lookup, we can always - // find the rules based on the local name of the element, regardless - // of whether it's an html element in an html document (in which case - // we match against lower_name) or not (in which case we match against - // name). - // - // In the case of a non-html-element-in-html-document with a - // lowercase localname and a non-lowercase selector, the rulehash - // lookup may produce superfluous selectors, but the subsequent - // selector matching work will filter them out. - if name != lower_name { - find_push(&mut self.local_name_hash, lower_name, entry.clone()); - } - find_push(&mut self.local_name_hash, name, entry); - - return; - } - - self.other.push(entry); - } - - /// Looks up entries by id, class, local name, and other (in order). - /// - /// Each entry is passed to the callback, which returns true to continue - /// iterating entries, or false to terminate the lookup. - /// - /// Returns false if the callback ever returns false. - /// - /// FIXME(bholley) This overlaps with SelectorMap::get_all_matching_rules, - /// but that function is extremely hot and I'd rather not rearrange it. - #[inline] - pub fn lookup(&self, element: E, f: &mut F) -> bool - where E: TElement, - F: FnMut(&T) -> bool - { - // Id. - if let Some(id) = element.get_id() { - if let Some(v) = self.id_hash.get(&id) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - } - - // Class. - let mut done = false; - element.each_class(|class| { - if !done { - if let Some(v) = self.class_hash.get(class) { - for entry in v.iter() { - if !f(&entry) { - done = true; - return; - } - } - } - } - }); - if done { - return false; - } - - // Local name. - if let Some(v) = self.local_name_hash.get(element.get_local_name()) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - - // Other. - for entry in self.other.iter() { - if !f(&entry) { - return false; - } - } - - true - } - - /// Performs a normal lookup, and also looks up entries for the passed-in - /// id and classes. - /// - /// Each entry is passed to the callback, which returns true to continue - /// iterating entries, or false to terminate the lookup. - /// - /// Returns false if the callback ever returns false. - #[inline] - pub fn lookup_with_additional(&self, - element: E, - additional_id: Option, - additional_classes: &[Atom], - f: &mut F) - -> bool - where E: TElement, - F: FnMut(&T) -> bool - { - // Do the normal lookup. - if !self.lookup(element, f) { - return false; - } - - // Check the additional id. - if let Some(id) = additional_id { - if let Some(v) = self.id_hash.get(&id) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - } - - // Check the additional classes. - for class in additional_classes { - if let Some(v) = self.class_hash.get(class) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - } - - true - } -} - -/// Searches the selector from right to left, beginning to the left of the -/// ::pseudo-element (if any), and ending at the first combinator. -/// -/// The first non-None value returned from |f| is returned. -/// -/// Effectively, pseudo-elements are ignored, given only state pseudo-classes -/// may appear before them. -fn find_from_right(selector: &SelectorInner, mut f: F) -> Option - where F: FnMut(&Component) -> Option, -{ - let mut iter = selector.complex.iter(); - for ss in &mut iter { - if let Some(r) = f(ss) { - return Some(r) - } - } - - if iter.next_sequence() == Some(Combinator::PseudoElement) { - for ss in &mut iter { - if let Some(r) = f(ss) { - return Some(r) - } - } - } - - None -} - -/// Retrieve the first ID name in the selector, or None otherwise. -pub fn get_id_name(selector: &SelectorInner) -> Option { - find_from_right(selector, |ss| { - // TODO(pradeep): Implement case-sensitivity based on the - // document type and quirks mode. - if let Component::ID(ref id) = *ss { - return Some(id.clone()); - } - None - }) -} - -/// Retrieve the FIRST class name in the selector, or None otherwise. -pub fn get_class_name(selector: &SelectorInner) -> Option { - find_from_right(selector, |ss| { - // TODO(pradeep): Implement case-sensitivity based on the - // document type and quirks mode. - if let Component::Class(ref class) = *ss { - return Some(class.clone()); - } - None - }) -} - -/// Retrieve the name if it is a type selector, or None otherwise. -pub fn get_local_name(selector: &SelectorInner) - -> Option> { - find_from_right(selector, |ss| { - if let Component::LocalName(ref n) = *ss { - return Some(LocalNameSelector { - name: n.name.clone(), - lower_name: n.lower_name.clone(), - }) - } - None - }) -} - /// A rule, that wraps a style rule, but represents a single selector of the /// rule. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -1666,8 +1251,8 @@ pub struct Rule { pub source_order: usize, } -impl Borrow> for Rule { - fn borrow(&self) -> &SelectorInner { +impl SelectorMapEntry for Rule { + fn selector(&self) -> &SelectorInner { &self.selector.inner } } @@ -1678,7 +1263,11 @@ impl Rule { self.selector.specificity() } - fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock { + /// Turns this rule into an `ApplicableDeclarationBlock` for the given + /// cascade level. + pub fn to_applicable_declaration_block(&self, + level: CascadeLevel) + -> ApplicableDeclarationBlock { ApplicableDeclarationBlock { source: StyleSource::Style(self.style_rule.clone()), level: level, @@ -1735,10 +1324,3 @@ impl ApplicableDeclarationBlock { } } } - -#[inline] -fn find_push(map: &mut FnvHashMap>, - key: Str, - value: V) { - map.entry(key).or_insert_with(Vec::new).push(value) -} diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 6820253b2be..3d307d23057 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -13,12 +13,12 @@ use style::media_queries::{Device, MediaType}; use style::properties::{PropertyDeclarationBlock, PropertyDeclaration}; use style::properties::{longhands, Importance}; use style::rule_tree::CascadeLevel; +use style::selector_map::{self, SelectorMap}; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::shared_lock::SharedRwLock; use style::stylearc::Arc; use style::stylesheets::StyleRule; -use style::stylist; -use style::stylist::{Rule, SelectorMap, Stylist}; +use style::stylist::{Stylist, Rule}; use style::stylist::needs_revalidation; use style::thread_state; @@ -175,22 +175,22 @@ fn test_rule_ordering_same_specificity() { #[test] fn test_get_id_name() { let (rules_list, _) = get_mock_rules(&[".intro", "#top"]); - assert_eq!(stylist::get_id_name(&rules_list[0][0].selector.inner), None); - assert_eq!(stylist::get_id_name(&rules_list[1][0].selector.inner), Some(Atom::from("top"))); + assert_eq!(selector_map::get_id_name(&rules_list[0][0].selector.inner), None); + assert_eq!(selector_map::get_id_name(&rules_list[1][0].selector.inner), Some(Atom::from("top"))); } #[test] fn test_get_class_name() { let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); - assert_eq!(stylist::get_class_name(&rules_list[0][0].selector.inner), Some(Atom::from("foo"))); - assert_eq!(stylist::get_class_name(&rules_list[1][0].selector.inner), None); + assert_eq!(selector_map::get_class_name(&rules_list[0][0].selector.inner), Some(Atom::from("foo"))); + assert_eq!(selector_map::get_class_name(&rules_list[1][0].selector.inner), None); } #[test] fn test_get_local_name() { let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]); let check = |i: usize, names: Option<(&str, &str)>| { - assert!(stylist::get_local_name(&rules_list[i][0].selector.inner) + assert!(selector_map::get_local_name(&rules_list[i][0].selector.inner) == names.map(|(name, lower_name)| LocalNameSelector { name: LocalName::from(name), lower_name: LocalName::from(lower_name) }))