aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/selector_matching.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/style/selector_matching.rs')
-rw-r--r--components/style/selector_matching.rs985
1 files changed, 14 insertions, 971 deletions
diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs
index bf780ac6f0a..3bd03be1eae 100644
--- a/components/style/selector_matching.rs
+++ b/components/style/selector_matching.rs
@@ -2,258 +2,25 @@
* 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::AsciiExt;
-use std::cmp::Ordering;
-use std::collections::HashMap;
-use std::sync::Arc;
-
use url::Url;
-use util::bloom::BloomFilter;
+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 util::smallvec::VecLike;
-use util::sort;
-use string_cache::Atom;
use legacy::PresentationalHintSynthesis;
use media_queries::Device;
-use node::{TElement, TElementAttributes, TNode};
+use node::TElementAttributes;
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
-use selectors::{CaseSensitivity, Combinator, CompoundSelector, LocalName};
-use selectors::{PseudoElement, SimpleSelector, Selector};
use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules, Origin};
-/// The definition of whitespace per CSS Selectors Level 3 § 4.
-pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
-
-/// Map node attributes to Rules whose last simple selector starts with them.
-///
-/// e.g.,
-/// "p > img" would go into the set of Rules corresponding to the
-/// element "img"
-/// "a .foo .bar.baz" would go into the set of Rules corresponding to
-/// the class "bar"
-///
-/// Because we match Rules right-to-left (i.e., moving up the tree
-/// from a node), we need to compare the last simple selector in the
-/// Rule with the node.
-///
-/// So, if a node 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 node's classes, ID,
-/// element name, etc. will contain the Rules that actually match that
-/// node.
-struct SelectorMap {
- // TODO: Tune the initial capacity of the HashMap
- id_hash: HashMap<Atom, Vec<Rule>>,
- class_hash: HashMap<Atom, Vec<Rule>>,
- local_name_hash: HashMap<Atom, Vec<Rule>>,
- /// Same as local_name_hash, but keys are lower-cased.
- /// For HTML elements in HTML documents.
- lower_local_name_hash: HashMap<Atom, Vec<Rule>>,
- // For Rules that don't have ID, class, or element selectors.
- universal_rules: Vec<Rule>,
- /// Whether this hash is empty.
- empty: bool,
-}
-
-impl SelectorMap {
- fn new() -> SelectorMap {
- SelectorMap {
- id_hash: HashMap::new(),
- class_hash: HashMap::new(),
- local_name_hash: HashMap::new(),
- lower_local_name_hash: HashMap::new(),
- universal_rules: vec!(),
- empty: true,
- }
- }
-
- /// Append to `rule_list` all Rules in `self` that match node.
- ///
- /// Extract matching rules as per node's ID, classes, tag name, etc..
- /// Sort the Rules at the end to maintain cascading order.
- fn get_all_matching_rules<'a,E,N,V>(&self,
- node: &N,
- parent_bf: &Option<Box<BloomFilter>>,
- matching_rules_list: &mut V,
- shareable: &mut bool)
- where E: TElement<'a> + TElementAttributes,
- N: TNode<'a,E>,
- V: VecLike<DeclarationBlock> {
- if self.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.vec_len();
- let element = node.as_element();
- match element.get_id() {
- Some(id) => {
- SelectorMap::get_matching_rules_from_hash(node,
- parent_bf,
- &self.id_hash,
- &id,
- matching_rules_list,
- shareable)
- }
- None => {}
- }
-
- element.each_class(|class| {
- SelectorMap::get_matching_rules_from_hash(node,
- parent_bf,
- &self.class_hash,
- class,
- matching_rules_list,
- shareable);
- });
+pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
- let local_name_hash = if node.is_html_element_in_html_document() {
- &self.lower_local_name_hash
- } else {
- &self.local_name_hash
- };
- SelectorMap::get_matching_rules_from_hash(node,
- parent_bf,
- local_name_hash,
- element.get_local_name(),
- matching_rules_list,
- shareable);
-
- SelectorMap::get_matching_rules(node,
- parent_bf,
- &self.universal_rules,
- matching_rules_list,
- shareable);
-
- // Sort only the rules we just added.
- sort::quicksort_by(matching_rules_list.vec_slice_from_mut(init_len), compare);
-
- fn compare(a: &DeclarationBlock, b: &DeclarationBlock) -> Ordering {
- (a.specificity, a.source_order).cmp(&(b.specificity, b.source_order))
- }
- }
-
- fn get_matching_rules_from_hash<'a,E,N,V>(node: &N,
- parent_bf: &Option<Box<BloomFilter>>,
- hash: &HashMap<Atom, Vec<Rule>>,
- key: &Atom,
- matching_rules: &mut V,
- shareable: &mut bool)
- where E: TElement<'a> + TElementAttributes,
- N: TNode<'a,E>,
- V: VecLike<DeclarationBlock> {
- match hash.get(key) {
- Some(rules) => {
- SelectorMap::get_matching_rules(node,
- parent_bf,
- rules,
- matching_rules,
- shareable)
- }
- None => {}
- }
- }
-
- /// Adds rules in `rules` that match `node` to the `matching_rules` list.
- fn get_matching_rules<'a,E,N,V>(node: &N,
- parent_bf: &Option<Box<BloomFilter>>,
- rules: &[Rule],
- matching_rules: &mut V,
- shareable: &mut bool)
- where E: TElement<'a> + TElementAttributes,
- N: TNode<'a,E>,
- V: VecLike<DeclarationBlock> {
- for rule in rules.iter() {
- if matches_compound_selector(&*rule.selector, node, parent_bf, shareable) {
- matching_rules.vec_push(rule.declarations.clone());
- }
- }
- }
-
- /// Insert rule into the correct hash.
- /// Order in which to try: id_hash, class_hash, local_name_hash, universal_rules.
- fn insert(&mut self, rule: Rule) {
- self.empty = false;
-
- match SelectorMap::get_id_name(&rule) {
- Some(id_name) => {
- find_push(&mut self.id_hash, id_name, rule);
- return;
- }
- None => {}
- }
- match SelectorMap::get_class_name(&rule) {
- Some(class_name) => {
- find_push(&mut self.class_hash, class_name, rule);
- return;
- }
- None => {}
- }
-
- match SelectorMap::get_local_name(&rule) {
- Some(LocalName { name, lower_name }) => {
- find_push(&mut self.local_name_hash, name, rule.clone());
- find_push(&mut self.lower_local_name_hash, lower_name, rule);
- return;
- }
- None => {}
- }
-
- self.universal_rules.push(rule);
- }
-
- /// Retrieve the first ID name in Rule, or None otherwise.
- fn get_id_name(rule: &Rule) -> Option<Atom> {
- let simple_selector_sequence = &rule.selector.simple_selectors;
- for ss in simple_selector_sequence.iter() {
- match *ss {
- // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
- // mode.
- SimpleSelector::ID(ref id) => return Some(id.clone()),
- _ => {}
- }
- }
- return None
- }
-
- /// Retrieve the FIRST class name in Rule, or None otherwise.
- fn get_class_name(rule: &Rule) -> Option<Atom> {
- let simple_selector_sequence = &rule.selector.simple_selectors;
- for ss in simple_selector_sequence.iter() {
- match *ss {
- // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
- // mode.
- SimpleSelector::Class(ref class) => return Some(class.clone()),
- _ => {}
- }
- }
- return None
- }
-
- /// Retrieve the name if it is a type selector, or None otherwise.
- fn get_local_name(rule: &Rule) -> Option<LocalName> {
- let simple_selector_sequence = &rule.selector.simple_selectors;
- for ss in simple_selector_sequence.iter() {
- match *ss {
- SimpleSelector::LocalName(ref name) => {
- return Some(name.clone())
- }
- _ => {}
- }
- }
- return None
- }
-}
-
-// The bloom filter for descendant CSS selectors will have a <1% false
-// positive rate until it has this many selectors in it, then it will
-// rapidly increase.
-pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: uint = 4096;
pub struct Stylist {
// List of stylesheets (including all media rules)
@@ -448,8 +215,8 @@ impl Stylist {
// Step 3: Normal style attributes.
style_attribute.map(|sa| {
shareable = false;
- applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.normal
- .clone()))
+ applicable_declarations.vec_push(
+ GenericDeclarationBlock::from_declarations(sa.normal.clone()))
});
// Step 4: Author-supplied `!important` rules.
@@ -461,8 +228,8 @@ impl Stylist {
// Step 5: `!important` style attributes.
style_attribute.map(|sa| {
shareable = false;
- applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.important
- .clone()))
+ applicable_declarations.vec_push(
+ GenericDeclarationBlock::from_declarations(sa.important.clone()))
});
// Step 6: User and UA `!important` rules.
@@ -480,8 +247,8 @@ impl Stylist {
}
struct PerOriginSelectorMap {
- normal: SelectorMap,
- important: SelectorMap,
+ normal: SelectorMap<Vec<PropertyDeclaration>>,
+ important: SelectorMap<Vec<PropertyDeclaration>>,
}
impl PerOriginSelectorMap {
@@ -510,727 +277,3 @@ impl PerPseudoElementSelectorMap {
}
}
}
-
-#[derive(Clone)]
-struct Rule {
- // This is an Arc because Rule will essentially be cloned for every node
- // that it matches. Selector contains an owned vector (through
- // CompoundSelector) and we want to avoid the allocation.
- selector: Arc<CompoundSelector>,
- declarations: DeclarationBlock,
-}
-
-/// A property declaration together with its precedence among rules of equal specificity so that
-/// we can sort them.
-#[derive(Clone, Debug)]
-pub struct DeclarationBlock {
- pub declarations: Arc<Vec<PropertyDeclaration>>,
- source_order: uint,
- specificity: u32,
-}
-
-impl DeclarationBlock {
- #[inline]
- pub fn from_declarations(declarations: Arc<Vec<PropertyDeclaration>>) -> DeclarationBlock {
- DeclarationBlock {
- declarations: declarations,
- source_order: 0,
- specificity: 0,
- }
- }
-
- /// A convenience function to create a declaration block from a single declaration. This is
- /// primarily used in `synthesize_rules_for_legacy_attributes`.
- #[inline]
- pub fn from_declaration(rule: PropertyDeclaration) -> DeclarationBlock {
- DeclarationBlock::from_declarations(Arc::new(vec![rule]))
- }
-}
-
-pub fn matches<'a,E,N>(selector_list: &Vec<Selector>,
- element: &N,
- parent_bf: &Option<Box<BloomFilter>>)
- -> bool
- where E: TElement<'a>, N: TNode<'a,E> {
- selector_list.iter().any(|selector| {
- selector.pseudo_element.is_none() &&
- matches_compound_selector(&*selector.compound_selectors, element, parent_bf, &mut false)
- })
-}
-
-/// Determines whether the given element matches the given single or compound selector.
-///
-/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
-/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
-/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
-/// `main/css/matching.rs`.)
-fn matches_compound_selector<'a,E,N>(selector: &CompoundSelector,
- element: &N,
- parent_bf: &Option<Box<BloomFilter>>,
- shareable: &mut bool)
- -> bool
- where E: TElement<'a>, N: TNode<'a,E> {
- match matches_compound_selector_internal(selector, element, parent_bf, shareable) {
- SelectorMatchingResult::Matched => true,
- _ => false
- }
-}
-
-/// A result of selector matching, includes 3 failure types,
-///
-/// NotMatchedAndRestartFromClosestLaterSibling
-/// NotMatchedAndRestartFromClosestDescendant
-/// NotMatchedGlobally
-///
-/// When NotMatchedGlobally appears, stop selector matching completely since
-/// the succeeding selectors never matches.
-/// It is raised when
-/// Child combinator cannot find the candidate element.
-/// Descendant combinator cannot find the candidate element.
-///
-/// When NotMatchedAndRestartFromClosestDescendant appears, the selector
-/// matching does backtracking and restarts from the closest Descendant
-/// combinator.
-/// It is raised when
-/// NextSibling combinator cannot find the candidate element.
-/// LaterSibling combinator cannot find the candidate element.
-/// Child combinator doesn't match on the found element.
-///
-/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector
-/// matching does backtracking and restarts from the closest LaterSibling
-/// combinator.
-/// It is raised when
-/// NextSibling combinator doesn't match on the found element.
-///
-/// For example, when the selector "d1 d2 a" is provided and we cannot *find*
-/// an appropriate ancestor node for "d1", this selector matching raises
-/// NotMatchedGlobally since even if "d2" is moved to more upper node, the
-/// candidates for "d1" becomes less than before and d1 .
-///
-/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is
-/// provided and we cannot *find* an appropriate brother node for b1,
-/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.
-/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1".
-///
-/// The additional example is child and sibling. When the selector
-/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on
-/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling.
-/// However since the selector "c1" raises
-/// NotMatchedAndRestartFromClosestDescendant. So the selector
-/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1".
-#[derive(PartialEq, Eq, Copy)]
-enum SelectorMatchingResult {
- Matched,
- NotMatchedAndRestartFromClosestLaterSibling,
- NotMatchedAndRestartFromClosestDescendant,
- NotMatchedGlobally,
-}
-
-/// Quickly figures out whether or not the compound selector is worth doing more
-/// work on. If the simple selectors don't match, or there's a child selector
-/// that does not appear in the bloom parent bloom filter, we can exit early.
-fn can_fast_reject<'a,E,N>(mut selector: &CompoundSelector,
- element: &N,
- parent_bf: &Option<Box<BloomFilter>>,
- shareable: &mut bool)
- -> Option<SelectorMatchingResult>
- where E: TElement<'a>, N: TNode<'a,E> {
- if !selector.simple_selectors.iter().all(|simple_selector| {
- matches_simple_selector(simple_selector, element, shareable) }) {
- return Some(SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling);
- }
-
- let bf: &BloomFilter = match *parent_bf {
- None => return None,
- Some(ref bf) => &**bf,
- };
-
- // See if the bloom filter can exclude any of the descendant selectors, and
- // reject if we can.
- loop {
- match selector.next {
- None => break,
- Some((ref cs, Combinator::Descendant)) => selector = &**cs,
- Some((ref cs, _)) => {
- selector = &**cs;
- continue;
- }
- };
-
- for ss in selector.simple_selectors.iter() {
- match *ss {
- SimpleSelector::LocalName(LocalName { ref name, ref lower_name }) => {
- if !bf.might_contain(name)
- && !bf.might_contain(lower_name) {
- return Some(SelectorMatchingResult::NotMatchedGlobally);
- }
- },
- SimpleSelector::Namespace(ref namespace) => {
- if !bf.might_contain(namespace) {
- return Some(SelectorMatchingResult::NotMatchedGlobally);
- }
- },
- SimpleSelector::ID(ref id) => {
- if !bf.might_contain(id) {
- return Some(SelectorMatchingResult::NotMatchedGlobally);
- }
- },
- SimpleSelector::Class(ref class) => {
- if !bf.might_contain(class) {
- return Some(SelectorMatchingResult::NotMatchedGlobally);
- }
- },
- _ => {},
- }
- }
-
- }
-
- // Can't fast reject.
- return None;
-}
-
-fn matches_compound_selector_internal<'a,E,N>(selector: &CompoundSelector,
- element: &N,
- parent_bf: &Option<Box<BloomFilter>>,
- shareable: &mut bool)
- -> SelectorMatchingResult
- where E: TElement<'a>, N: TNode<'a,E> {
- match can_fast_reject(selector, element, parent_bf, shareable) {
- None => {},
- Some(result) => return result,
- };
-
- match selector.next {
- None => SelectorMatchingResult::Matched,
- Some((ref next_selector, combinator)) => {
- let (siblings, candidate_not_found) = match combinator {
- Combinator::Child => (false, SelectorMatchingResult::NotMatchedGlobally),
- Combinator::Descendant => (false, SelectorMatchingResult::NotMatchedGlobally),
- Combinator::NextSibling => (true, SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant),
- Combinator::LaterSibling => (true, SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant),
- };
- let mut node = (*element).clone();
- loop {
- let next_node = if siblings {
- node.prev_sibling()
- } else {
- node.parent_node()
- };
- match next_node {
- None => return candidate_not_found,
- Some(next_node) => node = next_node,
- }
- if node.is_element() {
- let result = matches_compound_selector_internal(&**next_selector,
- &node,
- parent_bf,
- shareable);
- match (result, combinator) {
- // Return the status immediately.
- (SelectorMatchingResult::Matched, _) => return result,
- (SelectorMatchingResult::NotMatchedGlobally, _) => return result,
-
- // Upgrade the failure status to
- // NotMatchedAndRestartFromClosestDescendant.
- (_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,
-
- // Return the status directly.
- (_, Combinator::NextSibling) => return result,
-
- // If the failure status is NotMatchedAndRestartFromClosestDescendant
- // and combinator is Combinator::LaterSibling, give up this Combinator::LaterSibling matching
- // and restart from the closest descendant combinator.
- (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => return result,
-
- // The Combinator::Descendant combinator and the status is
- // NotMatchedAndRestartFromClosestLaterSibling or
- // NotMatchedAndRestartFromClosestDescendant,
- // or the Combinator::LaterSibling combinator and the status is
- // NotMatchedAndRestartFromClosestDescendant
- // can continue to matching on the next candidate element.
- _ => {},
- }
- }
- }
- }
- }
-}
-
-bitflags! {
- flags CommonStyleAffectingAttributes: u8 {
- const HIDDEN_ATTRIBUTE = 0x01,
- const NO_WRAP_ATTRIBUTE = 0x02,
- const ALIGN_LEFT_ATTRIBUTE = 0x04,
- const ALIGN_CENTER_ATTRIBUTE = 0x08,
- const ALIGN_RIGHT_ATTRIBUTE = 0x10,
- }
-}
-
-pub struct CommonStyleAffectingAttributeInfo {
- pub atom: Atom,
- pub mode: CommonStyleAffectingAttributeMode,
-}
-
-#[derive(Copy)]
-pub enum CommonStyleAffectingAttributeMode {
- IsPresent(CommonStyleAffectingAttributes),
- IsEqual(&'static str, CommonStyleAffectingAttributes),
-}
-
-// NB: This must match the order in `layout::css::matching::CommonStyleAffectingAttributes`.
-#[inline]
-pub fn common_style_affecting_attributes() -> [CommonStyleAffectingAttributeInfo; 5] {
- [
- CommonStyleAffectingAttributeInfo {
- atom: atom!("hidden"),
- mode: CommonStyleAffectingAttributeMode::IsPresent(HIDDEN_ATTRIBUTE),
- },
- CommonStyleAffectingAttributeInfo {
- atom: atom!("nowrap"),
- mode: CommonStyleAffectingAttributeMode::IsPresent(NO_WRAP_ATTRIBUTE),
- },
- CommonStyleAffectingAttributeInfo {
- atom: atom!("align"),
- mode: CommonStyleAffectingAttributeMode::IsEqual("left", ALIGN_LEFT_ATTRIBUTE),
- },
- CommonStyleAffectingAttributeInfo {
- atom: atom!("align"),
- mode: CommonStyleAffectingAttributeMode::IsEqual("center", ALIGN_CENTER_ATTRIBUTE),
- },
- CommonStyleAffectingAttributeInfo {
- atom: atom!("align"),
- mode: CommonStyleAffectingAttributeMode::IsEqual("right", ALIGN_RIGHT_ATTRIBUTE),
- }
- ]
-}
-
-/// Attributes that, if present, disable style sharing. All legacy HTML attributes must be in
-/// either this list or `common_style_affecting_attributes`. See the comment in
-/// `synthesize_presentational_hints_for_legacy_attributes`.
-pub fn rare_style_affecting_attributes() -> [Atom; 3] {
- [ atom!("bgcolor"), atom!("border"), atom!("colspan") ]
-}
-
-/// Determines whether the given element matches the given single selector.
-///
-/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
-/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
-/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
-/// `main/css/matching.rs`.)
-#[inline]
-pub fn matches_simple_selector<'a,E,N>(selector: &SimpleSelector,
- element: &N,
- shareable: &mut bool)
- -> bool
- where E: TElement<'a>, N: TNode<'a,E> {
- match *selector {
- SimpleSelector::LocalName(LocalName { ref name, ref lower_name }) => {
- let name = if element.is_html_element_in_html_document() { lower_name } else { name };
- let element = element.as_element();
- element.get_local_name() == name
- }
-
- SimpleSelector::Namespace(ref namespace) => {
- let element = element.as_element();
- element.get_namespace() == namespace
- }
- // TODO: case-sensitivity depends on the document type and quirks mode
- SimpleSelector::ID(ref id) => {
- *shareable = false;
- let element = element.as_element();
- element.get_id().map_or(false, |attr| {
- attr == *id
- })
- }
- SimpleSelector::Class(ref class) => {
- let element = element.as_element();
- element.has_class(class)
- }
-
- SimpleSelector::AttrExists(ref attr) => {
- // NB(pcwalton): If you update this, remember to update the corresponding list in
- // `can_share_style_with()` as well.
- if common_style_affecting_attributes().iter().all(|common_attr_info| {
- !(common_attr_info.atom == attr.name && match common_attr_info.mode {
- CommonStyleAffectingAttributeMode::IsPresent(_) => true,
- CommonStyleAffectingAttributeMode::IsEqual(..) => false,
- })
- }) {
- *shareable = false;
- }
- element.match_attr(attr, |_| true)
- }
- SimpleSelector::AttrEqual(ref attr, ref value, case_sensitivity) => {
- if *value != "DIR" &&
- common_style_affecting_attributes().iter().all(|common_attr_info| {
- !(common_attr_info.atom == attr.name && match common_attr_info.mode {
- CommonStyleAffectingAttributeMode::IsEqual(target_value, _) => *value == target_value,
- CommonStyleAffectingAttributeMode::IsPresent(_) => false,
- })
- }) {
- // FIXME(pcwalton): Remove once we start actually supporting RTL text. This is in
- // here because the UA style otherwise disables all style sharing completely.
- *shareable = false
- }
- element.match_attr(attr, |attr_value| {
- match case_sensitivity {
- CaseSensitivity::CaseSensitive => attr_value == *value,
- CaseSensitivity::CaseInsensitive => attr_value.eq_ignore_ascii_case(value),
- }
- })
- }
- SimpleSelector::AttrIncludes(ref attr, ref value) => {
- *shareable = false;
- element.match_attr(attr, |attr_value| {
- attr_value.split(SELECTOR_WHITESPACE).any(|v| v == *value)
- })
- }
- SimpleSelector::AttrDashMatch(ref attr, ref value, ref dashing_value) => {
- *shareable = false;
- element.match_attr(attr, |attr_value| {
- attr_value == *value ||
- attr_value.starts_with(dashing_value)
- })
- }
- SimpleSelector::AttrPrefixMatch(ref attr, ref value) => {
- *shareable = false;
- element.match_attr(attr, |attr_value| {
- attr_value.starts_with(value)
- })
- }
- SimpleSelector::AttrSubstringMatch(ref attr, ref value) => {
- *shareable = false;
- element.match_attr(attr, |attr_value| {
- attr_value.contains(value)
- })
- }
- SimpleSelector::AttrSuffixMatch(ref attr, ref value) => {
- *shareable = false;
- element.match_attr(attr, |attr_value| {
- attr_value.ends_with(value)
- })
- }
-
- SimpleSelector::AnyLink => {
- *shareable = false;
- let element = element.as_element();
- element.get_link().is_some()
- }
- SimpleSelector::Link => {
- let elem = element.as_element();
- match elem.get_link() {
- Some(url) => !url_is_visited(url),
- None => false,
- }
- }
- SimpleSelector::Visited => {
- // NB(pcwalton): When we actually start supporting visited links, remember to update
- // `can_share_style_with`.
- let elem = element.as_element();
- match elem.get_link() {
- Some(url) => url_is_visited(url),
- None => false,
- }
- }
-
- SimpleSelector::Hover => {
- *shareable = false;
- let elem = element.as_element();
- elem.get_hover_state()
- },
- // http://www.whatwg.org/html/#selector-disabled
- SimpleSelector::Disabled => {
- *shareable = false;
- let elem = element.as_element();
- elem.get_disabled_state()
- },
- // http://www.whatwg.org/html/#selector-enabled
- SimpleSelector::Enabled => {
- *shareable = false;
- let elem = element.as_element();
- elem.get_enabled_state()
- },
- // https://html.spec.whatwg.org/multipage/scripting.html#selector-checked
- SimpleSelector::Checked => {
- *shareable = false;
- let elem = element.as_element();
- elem.get_checked_state()
- }
- // https://html.spec.whatwg.org/multipage/scripting.html#selector-indeterminate
- SimpleSelector::Indeterminate => {
- *shareable = false;
- let elem = element.as_element();
- elem.get_indeterminate_state()
- }
- SimpleSelector::FirstChild => {
- *shareable = false;
- matches_first_child(element)
- }
- SimpleSelector::LastChild => {
- *shareable = false;
- matches_last_child(element)
- }
- SimpleSelector::OnlyChild => {
- *shareable = false;
- matches_first_child(element) && matches_last_child(element)
- }
-
- SimpleSelector::Root => {
- *shareable = false;
- matches_root(element)
- }
-
- SimpleSelector::NthChild(a, b) => {
- *shareable = false;
- matches_generic_nth_child(element, a, b, false, false)
- }
- SimpleSelector::NthLastChild(a, b) => {
- *shareable = false;
- matches_generic_nth_child(element, a, b, false, true)
- }
- SimpleSelector::NthOfType(a, b) => {
- *shareable = false;
- matches_generic_nth_child(element, a, b, true, false)
- }
- SimpleSelector::NthLastOfType(a, b) => {
- *shareable = false;
- matches_generic_nth_child(element, a, b, true, true)
- }
-
- SimpleSelector::FirstOfType => {
- *shareable = false;
- matches_generic_nth_child(element, 0, 1, true, false)
- }
- SimpleSelector::LastOfType => {
- *shareable = false;
- matches_generic_nth_child(element, 0, 1, true, true)
- }
- SimpleSelector::OnlyOfType => {
- *shareable = false;
- matches_generic_nth_child(element, 0, 1, true, false) &&
- matches_generic_nth_child(element, 0, 1, true, true)
- }
-
- SimpleSelector::ServoNonzeroBorder => {
- *shareable = false;
- let elem = element.as_element();
- elem.has_nonzero_border()
- }
-
- SimpleSelector::Negation(ref negated) => {
- *shareable = false;
- !negated.iter().all(|s| matches_simple_selector(s, element, shareable))
- },
- }
-}
-
-#[inline]
-fn url_is_visited(_url: &str) -> bool {
- // FIXME: implement this.
- // This function will probably need to take a "session"
- // or something containing browsing history as an additional parameter.
- // NB(pcwalton): When you implement this, remember to update `can_share_style_with`!
- false
-}
-
-#[inline]
-fn matches_generic_nth_child<'a,E,N>(element: &N,
- a: i32,
- b: i32,
- is_of_type: bool,
- is_from_end: bool)
- -> bool
- where E: TElement<'a>, N: TNode<'a,E> {
- let mut node = element.clone();
- // fail if we can't find a parent or if the node is the root element
- // of the document (Cf. Selectors Level 3)
- match node.parent_node() {
- Some(parent) => if parent.is_document() {
- return false;
- },
- None => return false
- };
-
- let mut index = 1;
- loop {
- if is_from_end {
- match node.next_sibling() {
- None => break,
- Some(next_sibling) => node = next_sibling
- }
- } else {
- match node.prev_sibling() {
- None => break,
- Some(prev_sibling) => node = prev_sibling
- }
- }
-
- if node.is_element() {
- if is_of_type {
- let element = element.as_element();
- let node = node.as_element();
- if element.get_local_name() == node.get_local_name() &&
- element.get_namespace() == node.get_namespace() {
- index += 1;
- }
- } else {
- index += 1;
- }
- }
- }
-
- if a == 0 {
- b == index
- } else {
- (index - b) / a >= 0 &&
- (index - b) % a == 0
- }
-}
-
-#[inline]
-fn matches_root<'a,E,N>(element: &N) -> bool where E: TElement<'a>, N: TNode<'a,E> {
- match element.parent_node() {
- Some(parent) => parent.is_document(),
- None => false
- }
-}
-
-#[inline]
-fn matches_first_child<'a,E,N>(element: &N) -> bool where E: TElement<'a>, N: TNode<'a,E> {
- let mut node = element.clone();
- loop {
- match node.prev_sibling() {
- Some(prev_sibling) => {
- node = prev_sibling;
- if node.is_element() {
- return false
- }
- },
- None => match node.parent_node() {
- // Selectors level 3 says :first-child does not match the
- // root of the document; Warning, level 4 says, for the time
- // being, the contrary...
- Some(parent) => return !parent.is_document(),
- None => return false
- }
- }
- }
-}
-
-#[inline]
-fn matches_last_child<'a,E,N>(element: &N) -> bool where E: TElement<'a>, N: TNode<'a,E> {
- let mut node = element.clone();
- loop {
- match node.next_sibling() {
- Some(next_sibling) => {
- node = next_sibling;
- if node.is_element() {
- return false
- }
- },
- None => match node.parent_node() {
- // Selectors level 3 says :last-child does not match the
- // root of the document; Warning, level 4 says, for the time
- // being, the contrary...
- Some(parent) => return !parent.is_document(),
- None => return false
- }
- }
- }
-}
-
-fn find_push(map: &mut HashMap<Atom, Vec<Rule>>, key: Atom, value: Rule) {
- match map.get_mut(&key) {
- Some(vec) => {
- vec.push(value);
- return
- }
- None => {}
- }
- map.insert(key, vec![value]);
-}
-
-#[cfg(test)]
-mod tests {
- use std::cmp::Ordering;
- use std::sync::Arc;
- use super::{DeclarationBlock, Rule, SelectorMap};
- use selectors::LocalName;
- use string_cache::Atom;
- use cssparser::Parser;
- use parser::ParserContext;
- use url::Url;
-
- /// Helper method to get some Rules from selector strings.
- /// Each sublist of the result contains the Rules for one StyleRule.
- fn get_mock_rules(css_selectors: &[&str]) -> Vec<Vec<Rule>> {
- use selectors::parse_selector_list;
- use stylesheets::Origin;
-
- css_selectors.iter().enumerate().map(|(i, selectors)| {
- let url = Url::parse("about:blank").unwrap();
- let context = ParserContext::new(Origin::Author, &url);
- parse_selector_list(&context, &mut Parser::new(*selectors))
- .unwrap().into_iter().map(|s| {
- Rule {
- selector: s.compound_selectors.clone(),
- declarations: DeclarationBlock {
- specificity: s.specificity,
- declarations: Arc::new(vec!()),
- source_order: i,
- }
- }
- }).collect()
- }).collect()
- }
-
- #[test]
- fn test_rule_ordering_same_specificity(){
- let rules_list = get_mock_rules(&["a.intro", "img.sidebar"]);
- let a = &rules_list[0][0].declarations;
- let b = &rules_list[1][0].declarations;
- assert!((a.specificity, a.source_order).cmp(&(b.specificity, b.source_order)) == Ordering::Less,
- "The rule that comes later should win.");
- }
-
- #[test]
- fn test_get_id_name(){
- let rules_list = get_mock_rules(&[".intro", "#top"]);
- assert_eq!(SelectorMap::get_id_name(&rules_list[0][0]), None);
- assert_eq!(SelectorMap::get_id_name(&rules_list[1][0]), Some(atom!("top")));
- }
-
- #[test]
- fn test_get_class_name(){
- let rules_list = get_mock_rules(&[".intro.foo", "#top"]);
- assert_eq!(SelectorMap::get_class_name(&rules_list[0][0]), Some(Atom::from_slice("intro")));
- assert_eq!(SelectorMap::get_class_name(&rules_list[1][0]), None);
- }
-
- #[test]
- fn test_get_local_name(){
- let rules_list = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]);
- let check = |&:i: uint, names: Option<(&str, &str)>| {
- assert!(SelectorMap::get_local_name(&rules_list[i][0])
- == names.map(|(name, lower_name)| LocalName {
- name: Atom::from_slice(name),
- lower_name: Atom::from_slice(lower_name) }))
- };
- check(0, Some(("img", "img")));
- check(1, None);
- check(2, Some(("IMG", "img")));
- check(3, Some(("ImG", "img")));
- }
-
- #[test]
- fn test_insert(){
- let rules_list = get_mock_rules(&[".intro.foo", "#top"]);
- let mut selector_map = SelectorMap::new();
- selector_map.insert(rules_list[1][0].clone());
- assert_eq!(1, selector_map.id_hash.get(&atom!("top")).unwrap()[0].declarations.source_order);
- selector_map.insert(rules_list[0][0].clone());
- assert_eq!(0, selector_map.class_hash.get(&Atom::from_slice("intro")).unwrap()[0].declarations.source_order);
- assert!(selector_map.class_hash.get(&Atom::from_slice("foo")).is_none());
- }
-}