diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index 84bfecca058..fd8ccc6e52d 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -12,6 +12,7 @@ pub mod bloom; pub mod matching; pub mod parser; mod tree; +pub mod visitor; pub use parser::{SelectorImpl, Parser, SelectorList}; pub use tree::Element; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 628ec971f5c..c14c34386db 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use bloom::BloomFilter; use parser::{CaseSensitivity, Combinator, ComplexSelector, LocalName}; -use parser::{SimpleSelector, Selector, SelectorImpl}; +use parser::{SimpleSelector, Selector}; use precomputed_hash::PrecomputedHash; use std::borrow::Borrow; use tree::Element; @@ -35,9 +35,8 @@ bitflags! { /// Whether this element is affected by an ID selector. const AFFECTED_BY_ID_SELECTOR = 1 << 3, - /// Whether this element is affected by a non-common style-affecting - /// attribute. - const AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR = 1 << 4, + /// Whether this element matches attribute selectors. + const AFFECTED_BY_ATTRIBUTE_SELECTOR = 1 << 4, /// Whether this element matches the :empty pseudo class. const AFFECTED_BY_EMPTY = 1 << 5, @@ -364,48 +363,38 @@ fn matches_simple_selector( AFFECTED_BY_ID_SELECTOR) } SimpleSelector::Class(ref class) => { - element.has_class(class) + relation_if!(element.has_class(class), + AFFECTED_BY_ATTRIBUTE_SELECTOR) } 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 + relation_if!(element.match_attr_has(attr), + AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrEqual(ref attr, ref value, case_sensitivity) => { - let matches = match case_sensitivity { + relation_if!(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 + }, AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrIncludes(ref attr, ref value) => { relation_if!(element.match_attr_includes(attr, value), - AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR) + AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrDashMatch(ref attr, ref value) => { relation_if!(element.match_attr_dash(attr, value), - AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR) + AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrPrefixMatch(ref attr, ref value) => { relation_if!(element.match_attr_prefix(attr, value), - AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR) + AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrSubstringMatch(ref attr, ref value) => { relation_if!(element.match_attr_substring(attr, value), - AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR) + AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrSuffixMatch(ref attr, ref value) => { relation_if!(element.match_attr_suffix(attr, value), - AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR) + AFFECTED_BY_ATTRIBUTE_SELECTOR) } SimpleSelector::AttrIncludesNeverMatch(..) | SimpleSelector::AttrPrefixNeverMatch(..) | diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index d3987900301..7331bb1b44c 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -12,6 +12,7 @@ use std::hash::Hash; use std::ops::Add; use std::sync::Arc; use tree::SELECTOR_WHITESPACE; +use visitor::SelectorVisitor; macro_rules! with_all_bounds { ( @@ -50,26 +51,10 @@ macro_rules! with_all_bounds { /// non tree-structural pseudo-classes /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) - type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods; + type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods; /// pseudo-elements type PseudoElement: $($CommonBounds)* + Sized + ToCss; - - /// Declares if the following "attribute exists" selector is considered - /// "common" enough to be shareable. If that's not the case, when matching - /// over an element, the relation - /// AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE would be set. - fn attr_exists_selector_is_shareable(_attr_selector: &AttrSelector) -> bool { - false - } - - /// Declares if the following "equals" attribute selector is considered - /// "common" enough to be shareable. - fn attr_equals_selector_is_shareable(_attr_selector: &AttrSelector, - _value: &Self::AttrValue) - -> bool { - false - } } } } @@ -143,93 +128,91 @@ pub struct Selector { pub specificity: u32, } -fn affects_sibling(simple_selector: &SimpleSelector) -> bool { - match *simple_selector { - SimpleSelector::Negation(ref negated) => { - negated.iter().any(|ref selector| selector.affects_siblings()) - } - - SimpleSelector::FirstChild | - SimpleSelector::LastChild | - SimpleSelector::OnlyChild | - SimpleSelector::NthChild(..) | - SimpleSelector::NthLastChild(..) | - SimpleSelector::NthOfType(..) | - SimpleSelector::NthLastOfType(..) | - SimpleSelector::FirstOfType | - SimpleSelector::LastOfType | - SimpleSelector::OnlyOfType => true, - - SimpleSelector::NonTSPseudoClass(ref pseudo_class) => pseudo_class.affects_siblings(), - - _ => false, - } -} - -fn matches_non_common_style_affecting_attribute(simple_selector: &SimpleSelector) -> bool { - match *simple_selector { - SimpleSelector::Negation(ref negated) => { - negated.iter().any(|ref selector| selector.matches_non_common_style_affecting_attribute()) - } - SimpleSelector::AttrEqual(ref attr, ref val, _) => { - !Impl::attr_equals_selector_is_shareable(attr, val) - } - SimpleSelector::AttrExists(ref attr) => { - !Impl::attr_exists_selector_is_shareable(attr) - } - SimpleSelector::AttrIncludes(..) | - SimpleSelector::AttrDashMatch(..) | - SimpleSelector::AttrPrefixMatch(..) | - SimpleSelector::AttrSuffixMatch(..) | - SimpleSelector::AttrSubstringMatch(..) => true, - - SimpleSelector::NonTSPseudoClass(ref pseudo_class) => - pseudo_class.matches_non_common_style_affecting_attribute(), - - // This deliberately includes Attr*NeverMatch - // which never match regardless of element attributes. - _ => false, - } -} - pub trait SelectorMethods { - fn affects_siblings(&self) -> bool; - fn matches_non_common_style_affecting_attribute(&self) -> bool; + type Impl: SelectorImpl; + + fn visit(&self, visitor: &mut V) -> bool + where V: SelectorVisitor; } impl SelectorMethods for Selector { - /// Whether this selector, if matching on a set of siblings, could affect - /// other sibling's style. - fn affects_siblings(&self) -> bool { - self.complex_selector.affects_siblings() - } + type Impl = Impl; - fn matches_non_common_style_affecting_attribute(&self) -> bool { - self.complex_selector.matches_non_common_style_affecting_attribute() + fn visit(&self, visitor: &mut V) -> bool + where V: SelectorVisitor, + { + self.complex_selector.visit(visitor) } } impl SelectorMethods for ComplexSelector { - /// Whether this complex selector, if matching on a set of siblings, - /// could affect other sibling's style. - fn affects_siblings(&self) -> bool { - match self.next { - Some((_, Combinator::NextSibling)) | - Some((_, Combinator::LaterSibling)) => return true, - _ => {}, + type Impl = Impl; + + fn visit(&self, visitor: &mut V) -> bool + where V: SelectorVisitor, + { + let mut current = self; + loop { + if !visitor.visit_complex_selector(current) { + return false; + } + + for selector in ¤t.compound_selector { + if !selector.visit(visitor) { + return false; + } + } + + match current.next { + Some((ref next, _)) => current = next, + None => break, + } } - match self.compound_selector.last() { - Some(ref selector) => affects_sibling(selector), - None => false, - } + true } +} - fn matches_non_common_style_affecting_attribute(&self) -> bool { - match self.compound_selector.last() { - Some(ref selector) => matches_non_common_style_affecting_attribute(selector), - None => false, +impl SelectorMethods for SimpleSelector { + type Impl = Impl; + + fn visit(&self, visitor: &mut V) -> bool + where V: SelectorVisitor, + { + use self::SimpleSelector::*; + + if !visitor.visit_simple_selector(self) { + return false; } + + match *self { + Negation(ref negated) => { + for selector in negated { + if !selector.visit(visitor) { + return false; + } + } + } + AttrExists(ref selector) | + AttrEqual(ref selector, _, _) | + AttrIncludes(ref selector, _) | + AttrDashMatch(ref selector, _) | + AttrPrefixMatch(ref selector, _) | + AttrSubstringMatch(ref selector, _) | + AttrSuffixMatch(ref selector, _) => { + if !visitor.visit_attribute_selector(selector) { + return false; + } + } + NonTSPseudoClass(ref pseudo_class) => { + if !pseudo_class.visit(visitor) { + return false; + } + }, + _ => {} + } + + true } } @@ -239,6 +222,30 @@ pub struct ComplexSelector { pub next: Option<(Arc>, Combinator)>, // c.next is left of c } +impl ComplexSelector { + /// Visits this selectors and all the ones to the left of it, until a + /// visitor method returns `false`. + pub fn visit(&self, visitor: &mut V) -> bool + where V: SelectorVisitor, + { + let mut current = self; + loop { + for selector in ¤t.compound_selector { + if !selector.visit(visitor) { + return false; + } + } + + match current.next { + Some((ref next, _)) => current = next, + None => break, + } + } + + true + } +} + #[derive(Eq, PartialEq, Clone, Copy, Debug, Hash)] pub enum Combinator { Child, // > @@ -1023,9 +1030,8 @@ fn parse_functional_pseudo_class(parser: &P, "not" => { if inside_negation { return Err(()) - } else { - return parse_negation(parser, input) } + return parse_negation(parser, input) }, _ => {} } @@ -1127,7 +1133,8 @@ fn parse_simple_pseudo_class(parser: &P, name: Cow) -> Result Ok(SimpleSelector::OnlyOfType), _ => Err(()) }).or_else(|()| { - P::parse_non_ts_pseudo_class(parser, name).map(|pc| SimpleSelector::NonTSPseudoClass(pc)) + P::parse_non_ts_pseudo_class(parser, name) + .map(SimpleSelector::NonTSPseudoClass) }) } @@ -1176,8 +1183,10 @@ pub mod tests { } impl SelectorMethods for PseudoClass { - fn affects_siblings(&self) -> bool { false } - fn matches_non_common_style_affecting_attribute(&self) -> bool { false } + type Impl = DummySelectorImpl; + + fn visit(&self, visitor: &mut V) -> bool + where V: SelectorVisitor { true } } #[derive(PartialEq, Debug)] diff --git a/components/selectors/visitor.rs b/components/selectors/visitor.rs new file mode 100644 index 00000000000..d72528a34d7 --- /dev/null +++ b/components/selectors/visitor.rs @@ -0,0 +1,35 @@ +/* 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/. */ + +//! Visitor traits for selectors. + +#![deny(missing_docs)] + +use parser::{AttrSelector, ComplexSelector, SelectorImpl, SimpleSelector}; + +/// A trait to visit selector properties. +/// +/// All the `visit_foo` methods return a boolean indicating whether the +/// traversal should continue or not. +pub trait SelectorVisitor { + /// The selector implementation this visitor wants to visit. + type Impl: SelectorImpl; + + /// Visit an attribute selector that may match (there are other selectors + /// that may never match, like those containing whitespace or the empty + /// string). + fn visit_attribute_selector(&mut self, _: &AttrSelector) -> bool { + true + } + + /// Visits a complex selector. + fn visit_complex_selector(&mut self, _: &ComplexSelector) -> bool { + true + } + + /// Visits a simple selector. + fn visit_simple_selector(&mut self, _: &SimpleSelector) -> bool { + true + } +}