use std::borrow::Borrow;
use bloom::BloomFilter;
use parser::{CaseSensitivity, Combinator, ComplexSelector, LocalName};
use parser::{SimpleSelector, Selector, SelectorImpl};
use tree::Element;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MatchingReason {
ForStyling,
Other,
}
impl MatchingReason {
#[inline]
fn for_styling(&self) -> bool {
*self == MatchingReason::ForStyling
}
}
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
bitflags! {
pub flags StyleRelations: u16 {
const AFFECTED_BY_SIBLINGS = 1 << 0,
const AFFECTED_BY_CHILD_INDEX = 1 << 1,
const AFFECTED_BY_STATE = 1 << 2,
const AFFECTED_BY_ID_SELECTOR = 1 << 3,
const AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR = 1 << 4,
const AFFECTED_BY_EMPTY = 1 << 5,
const AFFECTED_BY_STYLE_ATTRIBUTE = 1 << 6,
const AFFECTED_BY_PRESENTATIONAL_HINTS = 1 << 7,
const AFFECTED_BY_PSEUDO_ELEMENTS = 1 << 8,
}
}
bitflags! {
pub flags ElementFlags: u8 {
const HAS_SLOW_SELECTOR = 1 << 0,
const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1,
const HAS_EDGE_CHILD_SELECTOR = 1 << 2,
const HAS_EMPTY_SELECTOR = 1 << 3,
}
}
pub fn matches<E>(selector_list: &[Selector<E::Impl>],
element: &E,
parent_bf: Option<&BloomFilter>,
reason: MatchingReason)
-> bool
where E: Element
{
selector_list.iter().any(|selector| {
selector.pseudo_element.is_none() &&
matches_complex_selector(&*selector.complex_selector, element, parent_bf, &mut StyleRelations::empty(), reason)
})
}
pub fn matches_complex_selector<E>(selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> bool
where E: Element
{
match matches_complex_selector_internal(selector, element, parent_bf, relations, reason) {
SelectorMatchingResult::Matched => {
match selector.next {
Some((_, Combinator::NextSibling)) |
Some((_, Combinator::LaterSibling)) => *relations |= AFFECTED_BY_SIBLINGS,
_ => {}
}
true
}
_ => false
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
enum SelectorMatchingResult {
Matched,
NotMatchedAndRestartFromClosestLaterSibling,
NotMatchedAndRestartFromClosestDescendant,
NotMatchedGlobally,
}
fn can_fast_reject<E>(mut selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> Option<SelectorMatchingResult>
where E: Element
{
if !selector.compound_selector.iter().all(|simple_selector| {
matches_simple_selector(simple_selector, element, parent_bf, relations, reason) }) {
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.compound_selector.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.url) {
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);
}
},
_ => {},
}
}
}
None
}
fn matches_complex_selector_internal<E>(selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> SelectorMatchingResult
where E: Element
{
if let Some(result) = can_fast_reject(selector, element, parent_bf, relations, reason) {
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_complex_selector_internal(&**next_selector,
&element,
parent_bf,
relations,
reason);
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()
};
}
}
}
}
#[inline]
fn matches_simple_selector<E>(
selector: &SimpleSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> bool
where E: Element
{
macro_rules! relation_if {
($ex:expr, $flag:ident) => {
if $ex {
*relations |= $flag;
true
} else {
false
}
}
}
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.borrow()
}
SimpleSelector::Namespace(ref namespace) => {
element.get_namespace() == namespace.url.borrow()
}
SimpleSelector::ID(ref id) => {
relation_if!(element.get_id().map_or(false, |attr| attr == *id),
AFFECTED_BY_ID_SELECTOR)
}
SimpleSelector::Class(ref class) => {
element.has_class(class)
}
SimpleSelector::AttrExists(ref attr) => {
let matches = element.match_attr_has(attr);
if matches && !E::Impl::attr_exists_selector_is_shareable(attr) {
*relations |= AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR;
}
matches
}
SimpleSelector::AttrEqual(ref attr, ref value, case_sensitivity) => {
let matches = match case_sensitivity {
CaseSensitivity::CaseSensitive => element.match_attr_equals(attr, value),
CaseSensitivity::CaseInsensitive => element.match_attr_equals_ignore_ascii_case(attr, value),
};
if matches && !E::Impl::attr_equals_selector_is_shareable(attr, value) {
*relations |= AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR;
}
matches
}
SimpleSelector::AttrIncludes(ref attr, ref value) => {
relation_if!(element.match_attr_includes(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrDashMatch(ref attr, ref value) => {
relation_if!(element.match_attr_dash(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrPrefixMatch(ref attr, ref value) => {
relation_if!(element.match_attr_prefix(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrSubstringMatch(ref attr, ref value) => {
relation_if!(element.match_attr_substring(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrSuffixMatch(ref attr, ref value) => {
relation_if!(element.match_attr_suffix(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::NonTSPseudoClass(ref pc) => {
relation_if!(element.match_non_ts_pseudo_class(pc.clone()),
AFFECTED_BY_STATE)
}
SimpleSelector::FirstChild => {
relation_if!(matches_first_child(element, reason), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::LastChild => {
relation_if!(matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::OnlyChild => {
relation_if!(matches_first_child(element, reason) && matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::Root => {
element.is_root()
}
SimpleSelector::Empty => {
if reason.for_styling() {
element.insert_flags(HAS_EMPTY_SELECTOR);
}
relation_if!(element.is_empty(), AFFECTED_BY_EMPTY)
}
SimpleSelector::NthChild(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, false, false, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthLastChild(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, false, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthOfType(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, true, false, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthLastOfType(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, true, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::FirstOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::LastOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::OnlyOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason) &&
matches_generic_nth_child(element, 0, 1, true, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::Negation(ref negated) => {
!negated.iter().all(|s| {
matches_complex_selector(s, element, parent_bf, relations, reason)
})
}
}
}
#[inline]
fn matches_generic_nth_child<E>(element: &E,
a: i32,
b: i32,
is_of_type: bool,
is_from_end: bool,
reason: MatchingReason) -> bool
where E: Element
{
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(if is_from_end {
HAS_SLOW_SELECTOR
} else {
HAS_SLOW_SELECTOR_LATER_SIBLINGS
});
}
}
let mut index = 1;
let mut next_sibling = if is_from_end {
element.next_sibling_element()
} else {
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 = if is_from_end {
sibling.next_sibling_element()
} else {
sibling.prev_sibling_element()
};
}
if a == 0 {
b == index
} else {
(index - b) / a >= 0 &&
(index - b) % a == 0
}
}
#[inline]
fn matches_first_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
element.prev_sibling_element().is_none()
}
#[inline]
fn matches_last_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
element.next_sibling_element().is_none()
}