use std::ascii::AsciiExt;
use std::cmp::Ordering;
use std::sync::Arc;
use bloom::BloomFilter;
use smallvec::VecLike;
use quickersort::sort_by;
use string_cache::Atom;
use parser::{CaseSensitivity, Combinator, CompoundSelector, LocalName};
use parser::{SimpleSelector, Selector, SelectorImpl};
use tree::Element;
use hash_map::{self, HashMap};
pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
#[cfg_attr(feature = "heap_size", derive(HeapSizeOf))]
pub struct SelectorMap<T, Impl: SelectorImpl> {
id_hash: HashMap<Atom, Vec<Rule<T, Impl>>>,
class_hash: HashMap<Atom, Vec<Rule<T, Impl>>>,
local_name_hash: HashMap<Atom, Vec<Rule<T, Impl>>>,
lower_local_name_hash: HashMap<Atom, Vec<Rule<T, Impl>>>,
universal_rules: Vec<Rule<T, Impl>>,
empty: bool,
}
impl<T, Impl: SelectorImpl> SelectorMap<T, Impl> {
pub fn new() -> SelectorMap<T, Impl> {
SelectorMap {
id_hash: hash_map::new(),
class_hash: hash_map::new(),
local_name_hash: hash_map::new(),
lower_local_name_hash: hash_map::new(),
universal_rules: vec!(),
empty: true,
}
}
pub fn get_all_matching_rules<E, V>(&self,
element: &E,
parent_bf: Option<&BloomFilter>,
matching_rules_list: &mut V,
shareable: &mut bool)
where E: Element<Impl=Impl>,
V: VecLike<DeclarationBlock<T>> {
if self.empty {
return
}
let init_len = matching_rules_list.len();
if let Some(id) = element.get_id() {
SelectorMap::get_matching_rules_from_hash(element,
parent_bf,
&self.id_hash,
&id,
matching_rules_list,
shareable)
}
element.each_class(|class| {
SelectorMap::get_matching_rules_from_hash(element,
parent_bf,
&self.class_hash,
class,
matching_rules_list,
shareable);
});
let local_name_hash = if element.is_html_element_in_html_document() {
&self.lower_local_name_hash
} else {
&self.local_name_hash
};
SelectorMap::get_matching_rules_from_hash(element,
parent_bf,
local_name_hash,
element.get_local_name(),
matching_rules_list,
shareable);
SelectorMap::get_matching_rules(element,
parent_bf,
&self.universal_rules,
matching_rules_list,
shareable);
sort_by(&mut matching_rules_list[init_len..], &compare);
fn compare<T>(a: &DeclarationBlock<T>, b: &DeclarationBlock<T>) -> Ordering {
(a.specificity, a.source_order).cmp(&(b.specificity, b.source_order))
}
}
fn get_matching_rules_from_hash<E, V>(element: &E,
parent_bf: Option<&BloomFilter>,
hash: &HashMap<Atom, Vec<Rule<T, Impl>>>,
key: &Atom,
matching_rules: &mut V,
shareable: &mut bool)
where E: Element<Impl=Impl>,
V: VecLike<DeclarationBlock<T>> {
match hash.get(key) {
Some(rules) => {
SelectorMap::get_matching_rules(element,
parent_bf,
rules,
matching_rules,
shareable)
}
None => {}
}
}
fn get_matching_rules<E, V>(element: &E,
parent_bf: Option<&BloomFilter>,
rules: &[Rule<T, Impl>],
matching_rules: &mut V,
shareable: &mut bool)
where E: Element<Impl=Impl>,
V: VecLike<DeclarationBlock<T>> {
for rule in rules.iter() {
if matches_compound_selector(&*rule.selector, element, parent_bf, shareable) {
matching_rules.push(rule.declarations.clone());
}
}
}
pub fn insert(&mut self, rule: Rule<T, Impl>) {
self.empty = false;
if let Some(id_name) = SelectorMap::get_id_name(&rule) {
find_push(&mut self.id_hash, id_name, rule);
return;
}
if let Some(class_name) = SelectorMap::get_class_name(&rule) {
find_push(&mut self.class_hash, class_name, rule);
return;
}
if let Some(LocalName { name, lower_name }) = SelectorMap::get_local_name(&rule) {
find_push(&mut self.local_name_hash, name, rule.clone());
find_push(&mut self.lower_local_name_hash, lower_name, rule);
return;
}
self.universal_rules.push(rule);
}
fn get_id_name(rule: &Rule<T, Impl>) -> Option<Atom> {
let simple_selector_sequence = &rule.selector.simple_selectors;
for ss in simple_selector_sequence.iter() {
match *ss {
SimpleSelector::ID(ref id) => return Some(id.clone()),
_ => {}
}
}
return None
}
fn get_class_name(rule: &Rule<T, Impl>) -> Option<Atom> {
let simple_selector_sequence = &rule.selector.simple_selectors;
for ss in simple_selector_sequence.iter() {
match *ss {
SimpleSelector::Class(ref class) => return Some(class.clone()),
_ => {}
}
}
return None
}
fn get_local_name(rule: &Rule<T, Impl>) -> 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
}
}
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
#[cfg_attr(feature = "heap_size", derive(HeapSizeOf))]
pub struct Rule<T, Impl: SelectorImpl> {
pub selector: Arc<CompoundSelector<Impl>>,
pub declarations: DeclarationBlock<T>,
}
#[cfg_attr(feature = "heap_size", derive(HeapSizeOf))]
#[derive(Debug)]
pub struct DeclarationBlock<T> {
pub declarations: Arc<T>,
pub source_order: usize,
pub specificity: u32,
}
impl<T> Clone for DeclarationBlock<T> {
fn clone(&self) -> DeclarationBlock<T> {
DeclarationBlock {
declarations: self.declarations.clone(),
source_order: self.source_order,
specificity: self.specificity,
}
}
}
impl<T, Impl: SelectorImpl> Clone for Rule<T, Impl> {
fn clone(&self) -> Rule<T, Impl> {
Rule {
selector: self.selector.clone(),
declarations: self.declarations.clone(),
}
}
}
impl<T> DeclarationBlock<T> {
#[inline]
pub fn from_declarations(declarations: Arc<T>) -> DeclarationBlock<T> {
DeclarationBlock {
declarations: declarations,
source_order: 0,
specificity: 0,
}
}
}
bitflags! {
#[doc = "Flags set on elements during the matching process."]
flags ElementFlags: u8 {
#[doc = "When a child is added or removed from this element, all the children must be"]
#[doc = "restyled, because they may match :nth-last-child, :last-of-type,"]
#[doc = ":nth-last-of-type, or :only-of-type."]
const HAS_SLOW_SELECTOR = 0x01,
#[doc = "When a child is added or removed from this element, any later children must be"]
#[doc = "restyled, because they may match :nth-child, :first-of-type, or :nth-of-type."]
const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 0x02,
#[doc = "When a child is added or removed from this element, the first and last children"]
#[doc = "must be restyled, because they may match :first-child, :last-child, or"]
#[doc = ":only-child."]
const HAS_EDGE_CHILD_SELECTOR = 0x03,
}
}
pub fn matches<E>(selector_list: &[Selector<E::Impl>],
element: &E,
parent_bf: Option<&BloomFilter>)
-> bool
where E: Element {
selector_list.iter().any(|selector| {
selector.pseudo_element.is_none() &&
matches_compound_selector(&*selector.compound_selectors, element, parent_bf, &mut false)
})
}
pub fn matches_compound_selector<E>(selector: &CompoundSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
shareable: &mut bool)
-> bool
where E: Element {
match matches_compound_selector_internal(selector, element, parent_bf, shareable) {
SelectorMatchingResult::Matched => true,
_ => false
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
enum SelectorMatchingResult {
Matched,
NotMatchedAndRestartFromClosestLaterSibling,
NotMatchedAndRestartFromClosestDescendant,
NotMatchedGlobally,
}
fn can_fast_reject<E>(mut selector: &CompoundSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
shareable: &mut bool)
-> Option<SelectorMatchingResult>
where E: Element {
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,
};
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);
}
},
_ => {},
}
}
}
return None;
}
fn matches_compound_selector_internal<E>(selector: &CompoundSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
shareable: &mut bool)
-> SelectorMatchingResult
where E: Element {
if let Some(result) = can_fast_reject(selector, element, parent_bf, shareable) {
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 next_element = if siblings {
element.prev_sibling_element()
} else {
element.parent_element()
};
loop {
let element = match next_element {
None => return candidate_not_found,
Some(next_element) => next_element,
};
let result = matches_compound_selector_internal(&**next_selector,
&element,
parent_bf,
shareable);
match (result, combinator) {
(SelectorMatchingResult::Matched, _) => return result,
(SelectorMatchingResult::NotMatchedGlobally, _) => return result,
(_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,
(_, Combinator::NextSibling) => return result,
(SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => return result,
_ => {},
}
next_element = if siblings {
element.prev_sibling_element()
} else {
element.parent_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, Clone)]
pub enum CommonStyleAffectingAttributeMode {
IsPresent(CommonStyleAffectingAttributes),
IsEqual(&'static str, 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),
}
]
}
pub fn rare_style_affecting_attributes() -> [Atom; 3] {
[ atom!("bgcolor"), atom!("border"), atom!("colspan") ]
}
#[inline]
pub fn matches_simple_selector<E>(selector: &SimpleSelector<E::Impl>,
element: &E,
shareable: &mut bool)
-> bool
where E: Element {
match *selector {
SimpleSelector::LocalName(LocalName { ref name, ref lower_name }) => {
let name = if element.is_html_element_in_html_document() { lower_name } else { name };
element.get_local_name() == name
}
SimpleSelector::Namespace(ref namespace) => {
element.get_namespace() == namespace
}
SimpleSelector::ID(ref id) => {
*shareable = false;
element.get_id().map_or(false, |attr| {
attr == *id
})
}
SimpleSelector::Class(ref class) => {
element.has_class(class)
}
SimpleSelector::AttrExists(ref attr) => {
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,
})
}) {
*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::NonTSPseudoClass(ref pc) => {
*shareable = false;
element.match_non_ts_pseudo_class(pc.clone())
}
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;
element.is_root()
}
SimpleSelector::Empty => {
*shareable = false;
element.is_empty()
}
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::Negation(ref negated) => {
*shareable = false;
!negated.iter().all(|s| matches_simple_selector(s, element, shareable))
}
}
}
#[inline]
fn matches_generic_nth_child<E>(element: &E,
a: i32,
b: i32,
is_of_type: bool,
is_from_end: bool)
-> bool
where E: Element {
let mut index = 1;
let mut next_sibling = match is_from_end {
true => element.next_sibling_element(),
false => element.prev_sibling_element(),
};
loop {
let sibling = match next_sibling {
None => break,
Some(next_sibling) => next_sibling
};
if is_of_type {
if element.get_local_name() == sibling.get_local_name() &&
element.get_namespace() == sibling.get_namespace() {
index += 1;
}
} else {
index += 1;
}
next_sibling = match is_from_end {
true => sibling.next_sibling_element(),
false => sibling.prev_sibling_element(),
};
}
let result = if a == 0 {
b == index
} else {
(index - b) / a >= 0 &&
(index - b) % a == 0
};
if result {
if let Some(parent) = element.parent_element() {
parent.insert_flags(if is_from_end {
HAS_SLOW_SELECTOR
} else {
HAS_SLOW_SELECTOR_LATER_SIBLINGS
});
}
}
result
}
#[inline]
fn matches_first_child<E>(element: &E) -> bool where E: Element {
let result = element.prev_sibling_element().is_none();
if result {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
result
}
#[inline]
fn matches_last_child<E>(element: &E) -> bool where E: Element {
let result = element.next_sibling_element().is_none();
if result {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
result
}
fn find_push<T, Impl: SelectorImpl>(map: &mut HashMap<Atom, Vec<Rule<T, Impl>>>,
key: Atom,
value: Rule<T, Impl>) {
if let Some(vec) = map.get_mut(&key) {
vec.push(value);
return
}
map.insert(key, vec![value]);
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::{DeclarationBlock, Rule, SelectorMap};
use parser::LocalName;
use parser::tests::DummySelectorImpl;
use string_cache::Atom;
use cssparser::Parser;
use parser::ParserContext;
fn get_mock_rules(css_selectors: &[&str]) -> Vec<Vec<Rule<(), DummySelectorImpl>>> {
use parser::parse_selector_list;
css_selectors.iter().enumerate().map(|(i, selectors)| {
let context = ParserContext::new();
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(()),
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) < ((b.specificity, b.source_order)),
"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("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: usize, names: Option<(&str, &str)>| {
assert!(SelectorMap::get_local_name(&rules_list[i][0])
== names.map(|(name, lower_name)| LocalName {
name: Atom::from(name),
lower_name: Atom::from(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("intro")).unwrap()[0].declarations.source_order);
assert!(selector_map.class_hash.get(&Atom::from("foo")).is_none());
}
}