diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2017-05-17 05:40:14 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-17 05:40:14 -0500 |
commit | d8b7013fcddff79a9c879077e1a564d83201359c (patch) | |
tree | 88a16d0317249297479192f769217f69a8b39895 | |
parent | a7b77306f985b79d3b8b4d9b2cb51fa38f45bd39 (diff) | |
parent | 522f8489d6fca474e6aad1d341ee84a99c130738 (diff) | |
download | servo-d8b7013fcddff79a9c879077e1a564d83201359c.tar.gz servo-d8b7013fcddff79a9c879077e1a564d83201359c.zip |
Auto merge of #16900 - emilio:pseudos, r=bholley
Bug 1364850: Move PseudoElement to be just another combinator in selectors. r=bholley
On top of https://github.com/servo/servo/pull/16890.
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16900)
<!-- Reviewable:end -->
-rw-r--r-- | components/script/dom/element.rs | 20 | ||||
-rw-r--r-- | components/script/dom/node.rs | 10 | ||||
-rw-r--r-- | components/script/layout_wrapper.rs | 16 | ||||
-rw-r--r-- | components/script_layout_interface/wrapper_traits.rs | 2 | ||||
-rw-r--r-- | components/selectors/gecko_like_types.rs | 3 | ||||
-rw-r--r-- | components/selectors/matching.rs | 152 | ||||
-rw-r--r-- | components/selectors/parser.rs | 400 | ||||
-rw-r--r-- | components/selectors/size_of_tests.rs | 13 | ||||
-rw-r--r-- | components/selectors/tree.rs | 13 | ||||
-rw-r--r-- | components/style/gecko/pseudo_element.rs | 14 | ||||
-rw-r--r-- | components/style/gecko/selector_parser.rs | 92 | ||||
-rw-r--r-- | components/style/gecko/wrapper.rs | 24 | ||||
-rw-r--r-- | components/style/matching.rs | 50 | ||||
-rw-r--r-- | components/style/restyle_hints.rs | 158 | ||||
-rw-r--r-- | components/style/servo/selector_parser.rs | 31 | ||||
-rw-r--r-- | components/style/stylist.rs | 177 | ||||
-rw-r--r-- | ports/geckolib/glue.rs | 1 | ||||
-rw-r--r-- | tests/unit/style/stylesheets.rs | 36 | ||||
-rw-r--r-- | tests/unit/stylo/size_of.rs | 4 |
19 files changed, 675 insertions, 541 deletions
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 011b87aabf1..6b844dd83a2 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -86,7 +86,7 @@ use net_traits::request::CorsSettings; use ref_filter_map::ref_filter_map; use script_layout_interface::message::ReflowQueryType; use script_thread::Runnable; -use selectors::matching::{ElementSelectorFlags, MatchingContext, matches_selector_list}; +use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, matches_selector_list}; use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS}; use selectors::parser::{AttrSelector, NamespaceConstraint}; use servo_atoms::Atom; @@ -104,7 +104,7 @@ use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBloc use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x}; use style::restyle_hints::RESTYLE_SELF; use style::rule_tree::CascadeLevel; -use style::selector_parser::{NonTSPseudoClass, RestyleDamage, SelectorImpl, SelectorParser}; +use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser}; use style::shared_lock::{SharedRwLock, Locked}; use style::sink::Push; use style::stylearc::Arc; @@ -2058,7 +2058,8 @@ impl ElementMethods for Element { match SelectorParser::parse_author_origin_no_namespace(&selectors) { Err(()) => Err(Error::Syntax), Ok(selectors) => { - Ok(matches_selector_list(&selectors.0, &Root::from_ref(self), None)) + let mut ctx = MatchingContext::new(MatchingMode::Normal, None); + Ok(matches_selector_list(&selectors.0, &Root::from_ref(self), &mut ctx)) } } } @@ -2076,8 +2077,8 @@ impl ElementMethods for Element { let root = self.upcast::<Node>(); for element in root.inclusive_ancestors() { if let Some(element) = Root::downcast::<Element>(element) { - if matches_selector_list(&selectors.0, &element, None) - { + let mut ctx = MatchingContext::new(MatchingMode::Normal, None); + if matches_selector_list(&selectors.0, &element, &mut ctx) { return Ok(Some(element)); } } @@ -2386,6 +2387,15 @@ impl<'a> ::selectors::Element for Root<Element> { self.upcast::<Node>().GetParentElement() } + fn match_pseudo_element(&self, + _pseudo: &PseudoElement, + _context: &mut MatchingContext) + -> bool + { + false + } + + fn first_child_element(&self) -> Option<Root<Element>> { self.node.child_elements().next() } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 8c8831801ef..1ad28974f3d 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -70,7 +70,7 @@ use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddr use script_layout_interface::message::Msg; use script_traits::DocumentActivity; use script_traits::UntrustedNodeAddress; -use selectors::matching::matches_selector_list; +use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode}; use selectors::parser::SelectorList; use servo_url::ServoUrl; use std::borrow::ToOwned; @@ -346,11 +346,14 @@ impl<'a> Iterator for QuerySelectorIterator { fn next(&mut self) -> Option<Root<Node>> { let selectors = &self.selectors.0; + // TODO(cgaebel): Is it worth it to build a bloom filter here // (instead of passing `None`)? Probably. + let mut ctx = MatchingContext::new(MatchingMode::Normal, None); + self.iterator.by_ref().filter_map(|node| { if let Some(element) = Root::downcast(node) { - if matches_selector_list(selectors, &element, None) { + if matches_selector_list(selectors, &element, &mut ctx) { return Some(Root::upcast(element)); } } @@ -717,8 +720,9 @@ impl Node { Err(()) => Err(Error::Syntax), // Step 3. Ok(selectors) => { + let mut ctx = MatchingContext::new(MatchingMode::Normal, None); Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| { - matches_selector_list(&selectors.0, element, None) + matches_selector_list(&selectors.0, element, &mut ctx) })) } } diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 7802d446114..4cf1789beef 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -652,6 +652,14 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { self.element.namespace() } + fn match_pseudo_element(&self, + _pseudo: &PseudoElement, + _context: &mut MatchingContext) + -> bool + { + false + } + fn match_non_ts_pseudo_class<F>(&self, pseudo_class: &NonTSPseudoClass, _: &mut MatchingContext, @@ -1150,6 +1158,14 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { self.element.get_namespace() } + fn match_pseudo_element(&self, + _pseudo: &PseudoElement, + _context: &mut MatchingContext) + -> bool + { + false + } + fn match_non_ts_pseudo_class<F>(&self, _: &NonTSPseudoClass, _: &mut MatchingContext, diff --git a/components/script_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index 4f2870aace0..2ba72231269 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -20,7 +20,6 @@ use style::context::SharedStyleContext; use style::data::ElementData; use style::dom::{LayoutIterator, NodeInfo, PresentationalHintsSynthesizer, TNode}; use style::dom::OpaqueNode; -use style::element_state::ElementState; use style::font_metrics::ServoMetricsProvider; use style::properties::{CascadeFlags, ServoComputedValues}; use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl}; @@ -435,7 +434,6 @@ pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + Debug + &context.guards, unsafe { &self.unsafe_get() }, &style_pseudo, - ElementState::empty(), data.styles().primary.values(), &ServoMetricsProvider); data.styles_mut().cached_pseudos diff --git a/components/selectors/gecko_like_types.rs b/components/selectors/gecko_like_types.rs index bbb78823c50..9ffffaf2aed 100644 --- a/components/selectors/gecko_like_types.rs +++ b/components/selectors/gecko_like_types.rs @@ -19,9 +19,6 @@ pub enum PseudoElement { B, } -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct PseudoElementSelector(PseudoElement, u64); - #[derive(Eq, PartialEq, Clone, Debug, Default)] pub struct Atom(usize); diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index c4e19f47fd3..9c60e6b78d7 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -69,28 +69,67 @@ impl ElementSelectorFlags { } } +/// What kind of selector matching mode we should use. +/// +/// There are two modes of selector matching. The difference is only noticeable +/// in presence of pseudo-elements. +#[derive(Debug, PartialEq)] +pub enum MatchingMode { + /// Don't ignore any pseudo-element selectors. + Normal, + + /// Ignores any stateless pseudo-element selectors in the rightmost sequence + /// of simple selectors. + /// + /// This is useful, for example, to match against ::before when you aren't a + /// pseudo-element yourself. + /// + /// For example, in presence of `::before:hover`, it would never match, but + /// `::before` would be ignored as in "matching". + /// + /// It's required for all the selectors you match using this mode to have a + /// pseudo-element. + ForStatelessPseudoElement, +} + + /// Data associated with the matching process for a element. This context is /// used across many selectors for an element, so it's not appropriate for /// transient data that applies to only a single selector. -#[derive(Default)] -pub struct MatchingContext { +pub struct MatchingContext<'a> { /// Output that records certains relations between elements noticed during /// matching (and also extended after matching). pub relations: StyleRelations, + /// The matching mode we should use when matching selectors. + pub matching_mode: MatchingMode, + /// The bloom filter used to fast-reject selectors. + pub bloom_filter: Option<&'a BloomFilter>, +} + +impl<'a> MatchingContext<'a> { + /// Constructs a new `MatchingContext`. + pub fn new(matching_mode: MatchingMode, + bloom_filter: Option<&'a BloomFilter>) + -> Self + { + Self { + relations: StyleRelations::empty(), + matching_mode: matching_mode, + bloom_filter: bloom_filter, + } + } } pub fn matches_selector_list<E>(selector_list: &[Selector<E::Impl>], element: &E, - parent_bf: Option<&BloomFilter>) + context: &mut MatchingContext) -> bool where E: Element { selector_list.iter().any(|selector| { - selector.pseudo_element.is_none() && matches_selector(&selector.inner, element, - parent_bf, - &mut MatchingContext::default(), + context, &mut |_, _| {}) }) } @@ -115,27 +154,6 @@ fn may_match<E>(sel: &SelectorInner<E::Impl>, true } -/// Determines whether the given element matches the given complex selector. -pub fn matches_selector<E, F>(selector: &SelectorInner<E::Impl>, - element: &E, - parent_bf: Option<&BloomFilter>, - context: &mut MatchingContext, - flags_setter: &mut F) - -> bool - where E: Element, - F: FnMut(&E, ElementSelectorFlags), -{ - // Use the bloom filter to fast-reject. - if let Some(filter) = parent_bf { - if !may_match::<E>(selector, filter) { - return false; - } - } - - // Match the selector. - matches_complex_selector(&selector.complex, element, context, flags_setter) -} - /// A result of selector matching, includes 3 failure types, /// /// NotMatchedAndRestartFromClosestLaterSibling @@ -186,16 +204,65 @@ enum SelectorMatchingResult { NotMatchedGlobally, } +/// Matches an inner selector. +pub fn matches_selector<E, F>(selector: &SelectorInner<E::Impl>, + element: &E, + context: &mut MatchingContext, + flags_setter: &mut F) + -> bool + where E: Element, + F: FnMut(&E, ElementSelectorFlags), +{ + // Use the bloom filter to fast-reject. + if let Some(filter) = context.bloom_filter { + if !may_match::<E>(&selector, filter) { + return false; + } + } + + matches_complex_selector(&selector.complex, element, context, flags_setter) +} + /// Matches a complex selector. -pub fn matches_complex_selector<E, F>(selector: &ComplexSelector<E::Impl>, +/// +/// Use `matches_selector` if you need to skip pseudos. +pub fn matches_complex_selector<E, F>(complex_selector: &ComplexSelector<E::Impl>, element: &E, context: &mut MatchingContext, flags_setter: &mut F) -> bool - where E: Element, - F: FnMut(&E, ElementSelectorFlags), + where E: Element, + F: FnMut(&E, ElementSelectorFlags), { - match matches_complex_selector_internal(selector.iter(), + let mut iter = complex_selector.iter(); + + if cfg!(debug_assertions) { + if context.matching_mode == MatchingMode::ForStatelessPseudoElement { + assert!(complex_selector.iter().any(|c| { + matches!(*c, Component::PseudoElement(..)) + })); + } + } + + if context.matching_mode == MatchingMode::ForStatelessPseudoElement { + match *iter.next().unwrap() { + // Stateful pseudo, just don't match. + Component::NonTSPseudoClass(..) => return false, + Component::PseudoElement(..) => { + // Pseudo, just eat the whole sequence. + let next = iter.next(); + debug_assert!(next.is_none(), + "Someone messed up pseudo-element parsing?"); + + if iter.next_sequence().is_none() { + return true; + } + } + _ => panic!("Used MatchingMode::ForStatelessPseudoElement in a non-pseudo selector"), + } + } + + match matches_complex_selector_internal(iter, element, context, flags_setter) { @@ -229,12 +296,19 @@ fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Im match combinator { None => SelectorMatchingResult::Matched, Some(c) => { - let (mut next_element, candidate_not_found) = if siblings { - (element.prev_sibling_element(), - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant) - } else { - (element.parent_element(), - SelectorMatchingResult::NotMatchedGlobally) + let (mut next_element, candidate_not_found) = match c { + Combinator::NextSibling | Combinator::LaterSibling => { + (element.prev_sibling_element(), + SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant) + } + Combinator::Child | Combinator::Descendant => { + (element.parent_element(), + SelectorMatchingResult::NotMatchedGlobally) + } + Combinator::PseudoElement => { + (element.pseudo_element_originating_element(), + SelectorMatchingResult::NotMatchedGlobally) + } }; loop { @@ -253,6 +327,7 @@ fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Im // Upgrade the failure status to // NotMatchedAndRestartFromClosestDescendant. + (_, Combinator::PseudoElement) | (_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, // Return the status directly. @@ -306,6 +381,9 @@ fn matches_simple_selector<E, F>( match *selector { Component::Combinator(_) => unreachable!(), + Component::PseudoElement(ref pseudo) => { + element.match_pseudo_element(pseudo, context) + } Component::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() diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 34ed4418b3d..01f1eafc07c 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -16,6 +16,22 @@ use std::slice; use tree::SELECTOR_WHITESPACE; use visitor::SelectorVisitor; +/// A trait that represents a pseudo-element. +pub trait PseudoElement : Sized + ToCss { + /// The `SelectorImpl` this pseudo-element is used for. + type Impl: SelectorImpl; + + /// Whether the pseudo-element supports a given state selector to the right + /// of it. + fn supports_pseudo_class( + &self, + _pseudo_class: &<Self::Impl as SelectorImpl>::NonTSPseudoClass) + -> bool + { + false + } +} + macro_rules! with_all_bounds { ( [ $( $InSelector: tt )* ] @@ -60,7 +76,7 @@ macro_rules! with_all_bounds { type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>; /// pseudo-elements - type PseudoElementSelector: $($CommonBounds)* + Sized + ToCss; + type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>; } } } @@ -97,8 +113,8 @@ pub trait Parser { Err(()) } - fn parse_pseudo_element(&self, _name: Cow<str>, _input: &mut CssParser) - -> Result<<Self::Impl as SelectorImpl>::PseudoElementSelector, ()> { + fn parse_pseudo_element(&self, _name: Cow<str>) + -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> { Err(()) } @@ -178,18 +194,57 @@ impl<Impl: SelectorImpl> SelectorInner<Impl> { #[derive(PartialEq, Eq, Clone)] pub struct Selector<Impl: SelectorImpl> { pub inner: SelectorInner<Impl>, - pub pseudo_element: Option<Impl::PseudoElementSelector>, - pub specificity: u32, + specificity_and_flags: u32, } +impl<Impl: SelectorImpl> ::std::borrow::Borrow<SelectorInner<Impl>> for Selector<Impl> { + fn borrow(&self) -> &SelectorInner<Impl> { + &self.inner + } +} + +const HAS_PSEUDO_BIT: u32 = 1 << 30; + impl<Impl: SelectorImpl> Selector<Impl> { + pub fn specificity(&self) -> u32 { + self.specificity_and_flags & !HAS_PSEUDO_BIT + } + + pub fn new_for_unit_testing(inner: SelectorInner<Impl>, specificity: u32) -> Self { + Self { + inner: inner, + specificity_and_flags: specificity, + } + } + + pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { + if !self.has_pseudo_element() { + return None + } + + for component in self.inner.complex.iter() { + if let Component::PseudoElement(ref pseudo) = *component { + return Some(pseudo) + } + } + + debug_assert!(false, "has_pseudo_element lied!"); + None + } + + pub fn has_pseudo_element(&self) -> bool { + (self.specificity_and_flags & HAS_PSEUDO_BIT) != 0 + } + /// Whether this selector (pseudo-element part excluded) matches every element. /// /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs pub fn is_universal(&self) -> bool { self.inner.complex.iter_raw().all(|c| matches!(*c, Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace + Component::ExplicitAnyNamespace | + Component::Combinator(Combinator::PseudoElement) | + Component::PseudoElement(..) )) } } @@ -361,7 +416,8 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> { type Item = &'a Component<Impl>; fn next(&mut self) -> Option<Self::Item> { - debug_assert!(self.next_combinator.is_none(), "Should call take_combinator!"); + debug_assert!(self.next_combinator.is_none(), + "You should call next_sequence!"); match self.iter.next() { None => None, Some(&Component::Combinator(c)) => { @@ -384,12 +440,12 @@ impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> { result } - /// Skips a sequence of simple selectors and all subsequent sequences until an - /// ancestor combinator is reached. + /// Skips a sequence of simple selectors and all subsequent sequences until + /// a non-pseudo-element ancestor combinator is reached. fn skip_until_ancestor(&mut self) { loop { - while let Some(_) = self.0.next() {} - if self.0.next_sequence().map_or(true, |x| x.is_ancestor()) { + while self.0.next().is_some() {} + if self.0.next_sequence().map_or(true, |x| matches!(x, Combinator::Child | Combinator::Descendant)) { break; } } @@ -407,7 +463,7 @@ impl<'a, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> { // See if there are more sequences. If so, skip any non-ancestor sequences. if let Some(combinator) = self.0.next_sequence() { - if !combinator.is_ancestor() { + if !matches!(combinator, Combinator::Child | Combinator::Descendant) { self.skip_until_ancestor(); } } @@ -422,12 +478,24 @@ pub enum Combinator { Descendant, // space NextSibling, // + LaterSibling, // ~ + /// A dummy combinator we use to the left of pseudo-elements. + /// + /// It serializes as the empty string, and acts effectively as a child + /// combinator. + PseudoElement, } impl Combinator { /// Returns true if this combinator is a child or descendant combinator. pub fn is_ancestor(&self) -> bool { - matches!(*self, Combinator::Child | Combinator::Descendant) + matches!(*self, Combinator::Child | + Combinator::Descendant | + Combinator::PseudoElement) + } + + /// Returns true if this combinator is a pseudo-element combinator. + pub fn is_pseudo_element(&self) -> bool { + matches!(*self, Combinator::PseudoElement) } /// Returns true if this combinator is a next- or later-sibling combinator. @@ -491,6 +559,7 @@ pub enum Component<Impl: SelectorImpl> { LastOfType, OnlyOfType, NonTSPseudoClass(Impl::NonTSPseudoClass), + PseudoElement(Impl::PseudoElement), // ... } @@ -582,7 +651,7 @@ impl<Impl: SelectorImpl> Debug for Selector<Impl> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Selector(")?; self.to_css(f)?; - write!(f, ", specificity = 0x{:x})", self.specificity) + write!(f, ", specificity = 0x{:x})", self.specificity()) } } @@ -621,11 +690,7 @@ impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> { impl<Impl: SelectorImpl> ToCss for Selector<Impl> { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - self.inner.complex.to_css(dest)?; - if let Some(ref pseudo) = self.pseudo_element { - pseudo.to_css(dest)?; - } - Ok(()) + self.inner.complex.to_css(dest) } } @@ -646,6 +711,7 @@ impl ToCss for Combinator { Combinator::Descendant => dest.write_str(" "), Combinator::NextSibling => dest.write_str(" + "), Combinator::LaterSibling => dest.write_str(" ~ "), + Combinator::PseudoElement => Ok(()), } } } @@ -657,6 +723,9 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> { Combinator(ref c) => { c.to_css(dest) } + PseudoElement(ref p) => { + p.to_css(dest) + } ID(ref s) => { dest.write_char('#')?; display_to_css_identifier(s, dest) @@ -841,25 +910,23 @@ impl From<Specificity> for u32 { } } -fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>, - pseudo_element: Option<&Impl::PseudoElementSelector>) - -> u32 - where Impl: SelectorImpl { - let mut specificity = complex_selector_specificity(complex_selector); - if pseudo_element.is_some() { - specificity.element_selectors += 1; - } - specificity.into() +fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>) -> u32 + where Impl: SelectorImpl +{ + complex_selector_specificity(complex_selector).into() } fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>) -> Specificity - where Impl: SelectorImpl { + where Impl: SelectorImpl +{ fn simple_selector_specificity<Impl>(simple_selector: &Component<Impl>, specificity: &mut Specificity) - where Impl: SelectorImpl { + where Impl: SelectorImpl + { match *simple_selector { Component::Combinator(..) => unreachable!(), + Component::PseudoElement(..) | Component::LocalName(..) => { specificity.element_selectors += 1 } @@ -928,12 +995,14 @@ fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>) fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector<Impl>, ()> where P: Parser<Impl=Impl>, Impl: SelectorImpl { - let (complex, pseudo_element) = - parse_complex_selector_and_pseudo_element(parser, input)?; + let (complex, has_pseudo_element) = parse_complex_selector(parser, input)?; + let mut specificity = specificity(&complex); + if has_pseudo_element { + specificity |= HAS_PSEUDO_BIT; + } Ok(Selector { - specificity: specificity(&complex, pseudo_element.as_ref()), + specificity_and_flags: specificity, inner: SelectorInner::new(complex), - pseudo_element: pseudo_element, }) } @@ -947,19 +1016,25 @@ fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector /// If we parse N > 8 entries, we save two reallocations. type ParseVec<Impl> = SmallVec<[Component<Impl>; 8]>; -fn parse_complex_selector_and_pseudo_element<P, Impl>( +/// Parses a complex selector, including any pseudo-element. +/// +/// For now, it always forces the pseudo-element to be at the end of the +/// selector, and the boolean represents whether the last thing parsed was a +/// pseudo-element. +fn parse_complex_selector<P, Impl>( parser: &P, input: &mut CssParser) - -> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElementSelector>), ()> + -> Result<(ComplexSelector<Impl>, bool), ()> where P: Parser<Impl=Impl>, Impl: SelectorImpl { let mut sequence = ParseVec::new(); - let mut pseudo_element; + let mut parsed_pseudo_element; 'outer_loop: loop { // Parse a sequence of simple selectors. - pseudo_element = parse_compound_selector(parser, input, &mut sequence, - /* inside_negation = */ false)?; - if pseudo_element.is_some() { + parsed_pseudo_element = + parse_compound_selector(parser, input, &mut sequence, + /* inside_negation = */ false)?; + if parsed_pseudo_element { break; } @@ -998,17 +1073,17 @@ fn parse_complex_selector_and_pseudo_element<P, Impl>( } let complex = ComplexSelector(ArcSlice::new(sequence.into_vec().into_boxed_slice())); - Ok((complex, pseudo_element)) + Ok((complex, parsed_pseudo_element)) } impl<Impl: SelectorImpl> ComplexSelector<Impl> { - /// Parse a complex selector. + /// Parse a complex selector, without any pseudo-element. pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()> where P: Parser<Impl=Impl> { - let (complex, pseudo_element) = - parse_complex_selector_and_pseudo_element(parser, input)?; - if pseudo_element.is_some() { + let (complex, has_pseudo_element) = + parse_complex_selector(parser, input)?; + if has_pseudo_element { return Err(()) } Ok(complex) @@ -1062,7 +1137,7 @@ fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser, sequence: &mu #[derive(Debug)] enum SimpleSelectorParseResult<Impl: SelectorImpl> { SimpleSelector(Component<Impl>), - PseudoElement(Impl::PseudoElementSelector), + PseudoElement(Impl::PseudoElement), } #[derive(Debug)] @@ -1295,13 +1370,15 @@ fn single_simple_selector<Impl: SelectorImpl>(v: &[Component<Impl>]) -> bool { /// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* /// | [ HASH | class | attrib | pseudo | negation ]+ /// -/// `Err(())` means invalid selector +/// `Err(())` means invalid selector. +/// +/// The boolean represent whether a pseudo-element has been parsed. fn parse_compound_selector<P, Impl>( parser: &P, input: &mut CssParser, mut sequence: &mut ParseVec<Impl>, inside_negation: bool) - -> Result<Option<Impl::PseudoElementSelector>, ()> + -> Result<bool, ()> where P: Parser<Impl=Impl>, Impl: SelectorImpl { // Consume any leading whitespace. @@ -1328,7 +1405,7 @@ fn parse_compound_selector<P, Impl>( empty = false; } - let mut pseudo_element = None; + let mut pseudo = false; loop { match parse_one_simple_selector(parser, input, inside_negation)? { None => break, @@ -1337,7 +1414,41 @@ fn parse_compound_selector<P, Impl>( empty = false } Some(SimpleSelectorParseResult::PseudoElement(p)) => { - pseudo_element = Some(p); + // Try to parse state to its right. + let mut state_selectors = ParseVec::new(); + + loop { + match input.next_including_whitespace() { + Ok(Token::Colon) => {}, + Ok(Token::WhiteSpace(_)) | Err(()) => break, + _ => return Err(()), + } + + // TODO(emilio): Functional pseudo-classes too? + // We don't need it for now. + let name = match input.next_including_whitespace() { + Ok(Token::Ident(name)) => name, + _ => return Err(()), + }; + + let pseudo_class = + P::parse_non_ts_pseudo_class(parser, name)?; + if !p.supports_pseudo_class(&pseudo_class) { + return Err(()); + } + state_selectors.push(Component::NonTSPseudoClass(pseudo_class)); + } + + if !sequence.is_empty() { + sequence.push(Component::Combinator(Combinator::PseudoElement)); + } + + sequence.push(Component::PseudoElement(p)); + for state_selector in state_selectors { + sequence.push(state_selector); + } + + pseudo = true; empty = false; break } @@ -1347,7 +1458,7 @@ fn parse_compound_selector<P, Impl>( // An empty selector is invalid. Err(()) } else { - Ok(pseudo_element) + Ok(pseudo) } } @@ -1423,7 +1534,7 @@ fn parse_one_simple_selector<P, Impl>(parser: &P, name.eq_ignore_ascii_case("after") || name.eq_ignore_ascii_case("first-line") || name.eq_ignore_ascii_case("first-letter") { - let pseudo_element = P::parse_pseudo_element(parser, name, input)?; + let pseudo_element = P::parse_pseudo_element(parser, name)?; Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element))) } else { let pseudo_class = parse_simple_pseudo_class(parser, name)?; @@ -1439,7 +1550,7 @@ fn parse_one_simple_selector<P, Impl>(parser: &P, Ok(Token::Colon) => { match input.next_including_whitespace() { Ok(Token::Ident(name)) => { - let pseudo = P::parse_pseudo_element(parser, name, input)?; + let pseudo = P::parse_pseudo_element(parser, name)?; Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo))) } _ => Err(()) @@ -1478,6 +1589,7 @@ fn parse_simple_pseudo_class<P, Impl>(parser: &P, name: Cow<str>) -> Result<Comp #[cfg(test)] pub mod tests { use cssparser::{Parser as CssParser, ToCss, serialize_identifier}; + use parser; use std::borrow::Cow; use std::collections::HashMap; use std::fmt; @@ -1486,6 +1598,7 @@ pub mod tests { #[derive(PartialEq, Clone, Debug, Eq)] pub enum PseudoClass { Hover, + Active, Lang(String), } @@ -1495,10 +1608,23 @@ pub mod tests { After, } + impl parser::PseudoElement for PseudoElement { + type Impl = DummySelectorImpl; + + fn supports_pseudo_class(&self, pc: &PseudoClass) -> bool { + match *pc { + PseudoClass::Hover => true, + PseudoClass::Active | + PseudoClass::Lang(..) => false, + } + } + } + impl ToCss for PseudoClass { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { PseudoClass::Hover => dest.write_str(":hover"), + PseudoClass::Active => dest.write_str(":active"), PseudoClass::Lang(ref lang) => { dest.write_str(":lang(")?; serialize_identifier(lang, dest)?; @@ -1543,7 +1669,7 @@ pub mod tests { type BorrowedLocalName = DummyAtom; type BorrowedNamespaceUrl = DummyAtom; type NonTSPseudoClass = PseudoClass; - type PseudoElementSelector = PseudoElement; + type PseudoElement = PseudoElement; } #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] @@ -1580,6 +1706,7 @@ pub mod tests { -> Result<PseudoClass, ()> { match_ignore_ascii_case! { &name, "hover" => Ok(PseudoClass::Hover), + "active" => Ok(PseudoClass::Active), _ => Err(()) } } @@ -1593,8 +1720,7 @@ pub mod tests { } } - fn parse_pseudo_element(&self, name: Cow<str>, _input: &mut CssParser) - -> Result<PseudoElement, ()> { + fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> { match_ignore_ascii_case! { &name, "before" => Ok(PseudoElement::Before), "after" => Ok(PseudoElement::After), @@ -1647,11 +1773,9 @@ pub mod tests { inner: SelectorInner::from_vec(vec!( Component::LocalName(LocalName { name: DummyAtom::from("EeÉ"), - lower_name: DummyAtom::from("eeÉ") - }), - )), - pseudo_element: None, - specificity: specificity(0, 0, 1), + lower_name: DummyAtom::from("eeÉ") })), + ), + specificity_and_flags: specificity(0, 0, 1), })))); assert_eq!(parse("|e"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( @@ -1661,8 +1785,7 @@ pub mod tests { lower_name: DummyAtom::from("e") }), )), - pseudo_element: None, - specificity: specificity(0, 0, 1), + specificity_and_flags: specificity(0, 0, 1), })))); // https://github.com/servo/servo/issues/16020 assert_eq!(parse("*|e"), Ok(SelectorList(vec!(Selector { @@ -1673,44 +1796,38 @@ pub mod tests { lower_name: DummyAtom::from("e") }), )), - pseudo_element: None, - specificity: specificity(0, 0, 1), + specificity_and_flags: specificity(0, 0, 1), })))); assert_eq!(parse("*"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( Component::ExplicitUniversalType, )), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse("|*"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( Component::ExplicitNoNamespace, Component::ExplicitUniversalType, )), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse("*|*"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( Component::ExplicitAnyNamespace, Component::ExplicitUniversalType, )), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec![ Component::Class(DummyAtom::from("foo")), Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())) ]), - pseudo_element: None, - specificity: specificity(0, 2, 0), + specificity_and_flags: specificity(0, 2, 0), })))); assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!(Component::ID(DummyAtom::from("bar")))), - pseudo_element: None, - specificity: specificity(1, 0, 0), + specificity_and_flags: specificity(1, 0, 0), })))); assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!(Component::LocalName(LocalName { @@ -1718,8 +1835,7 @@ pub mod tests { lower_name: DummyAtom::from("e") }), Component::Class(DummyAtom::from("foo")), Component::ID(DummyAtom::from("bar")))), - pseudo_element: None, - specificity: specificity(1, 1, 1), + specificity_and_flags: specificity(1, 1, 1), })))); assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( @@ -1731,8 +1847,7 @@ pub mod tests { Component::Combinator(Combinator::Descendant), Component::ID(DummyAtom::from("bar")), )), - pseudo_element: None, - specificity: specificity(1, 1, 1), + specificity_and_flags: specificity(1, 1, 1), })))); // Default namespace does not apply to attribute selectors // https://github.com/mozilla/servo/pull/1652 @@ -1746,8 +1861,7 @@ pub mod tests { prefix: None, url: "".into(), }) }))), - pseudo_element: None, - specificity: specificity(0, 1, 0), + specificity_and_flags: specificity(0, 1, 0), })))); assert_eq!(parse_ns("svg|circle", &parser), Err(())); parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); @@ -1760,8 +1874,7 @@ pub mod tests { lower_name: DummyAtom::from("circle"), }) ]), - pseudo_element: None, - specificity: specificity(0, 0, 1), + specificity_and_flags: specificity(0, 0, 1), }]))); assert_eq!(parse_ns("svg|*", &parser), Ok(SelectorList(vec![Selector { inner: SelectorInner::from_vec( @@ -1769,8 +1882,7 @@ pub mod tests { Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::ExplicitUniversalType, ]), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), }]))); // Default namespace does not apply to attribute selectors // https://github.com/mozilla/servo/pull/1652 @@ -1790,8 +1902,7 @@ pub mod tests { }), }), ]), - pseudo_element: None, - specificity: specificity(0, 1, 0), + specificity_and_flags: specificity(0, 1, 0), })))); // Default namespace does apply to type selectors assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector { @@ -1802,8 +1913,7 @@ pub mod tests { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e") }), )), - pseudo_element: None, - specificity: specificity(0, 0, 1), + specificity_and_flags: specificity(0, 0, 1), })))); assert_eq!(parse_ns("*", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec( @@ -1811,8 +1921,7 @@ pub mod tests { Component::DefaultNamespace(MATHML.into()), Component::ExplicitUniversalType, )), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse_ns("*|*", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec( @@ -1820,8 +1929,7 @@ pub mod tests { Component::ExplicitAnyNamespace, Component::ExplicitUniversalType, )), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); // Default namespace applies to universal and type selectors inside :not and :matches, // but not otherwise. @@ -1832,8 +1940,7 @@ pub mod tests { Component::Class(DummyAtom::from("cl")) ].into_boxed_slice()), )), - pseudo_element: None, - specificity: specificity(0, 1, 0), + specificity_and_flags: specificity(0, 1, 0), })))); assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( @@ -1843,8 +1950,7 @@ pub mod tests { Component::ExplicitUniversalType, ].into_boxed_slice()), )), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse_ns(":not(e)", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!( @@ -1857,8 +1963,7 @@ pub mod tests { }), ].into_boxed_slice()) )), - pseudo_element: None, - specificity: specificity(0, 0, 1), + specificity_and_flags: specificity(0, 0, 1), })))); assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector { inner: SelectorInner::from_vec( @@ -1872,15 +1977,42 @@ pub mod tests { }), }, DummyAtom::from("foo")) ]), - pseudo_element: None, - specificity: specificity(0, 1, 0), + specificity_and_flags: specificity(0, 1, 0), }]))); // https://github.com/mozilla/servo/issues/1723 assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec![]), - pseudo_element: Some(PseudoElement::Before), - specificity: specificity(0, 0, 1), + inner: SelectorInner::from_vec( + vec![ + Component::PseudoElement(PseudoElement::Before), + ] + ), + specificity_and_flags: specificity(0, 0, 1) | HAS_PSEUDO_BIT, })))); + assert_eq!(parse("::before:hover"), Ok(SelectorList(vec!(Selector { + inner: SelectorInner::from_vec( + vec![ + Component::PseudoElement(PseudoElement::Before), + Component::NonTSPseudoClass(PseudoClass::Hover), + ] + ), + specificity_and_flags: specificity(0, 1, 1) | HAS_PSEUDO_BIT, + })))); + assert_eq!(parse("::before:hover:hover"), Ok(SelectorList(vec!(Selector { + inner: SelectorInner::from_vec( + vec![ + Component::PseudoElement(PseudoElement::Before), + Component::NonTSPseudoClass(PseudoClass::Hover), + Component::NonTSPseudoClass(PseudoClass::Hover), + ] + ), + specificity_and_flags: specificity(0, 2, 1) | HAS_PSEUDO_BIT, + })))); + assert_eq!(parse("::before:hover:active"), Err(())); + assert_eq!(parse("::before:hover .foo"), Err(())); + assert_eq!(parse("::before .foo"), Err(())); + assert_eq!(parse("::before ~ bar"), Err(())); + assert_eq!(parse("::before:active"), Err(())); + // https://github.com/servo/servo/issues/15335 assert_eq!(parse(":: before"), Err(())); assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector { @@ -1890,9 +2022,10 @@ pub mod tests { name: DummyAtom::from("div"), lower_name: DummyAtom::from("div") }), Component::Combinator(Combinator::Descendant), + Component::Combinator(Combinator::PseudoElement), + Component::PseudoElement(PseudoElement::After), ]), - pseudo_element: Some(PseudoElement::After), - specificity: specificity(0, 0, 2), + specificity_and_flags: specificity(0, 0, 2) | HAS_PSEUDO_BIT, })))); assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector { inner: SelectorInner::from_vec( @@ -1901,8 +2034,7 @@ pub mod tests { Component::Combinator(Combinator::Child), Component::Class(DummyAtom::from("ok")), ]), - pseudo_element: None, - specificity: (1 << 20) + (1 << 10) + (0 << 0), + specificity_and_flags: (1 << 20) + (1 << 10) + (0 << 0), }]))); parser.default_ns = None; assert_eq!(parse(":not(#provel.old)"), Err(())); @@ -1914,8 +2046,7 @@ pub mod tests { Component::ID(DummyAtom::from("provel")), ].into_boxed_slice() ))), - pseudo_element: None, - specificity: specificity(1, 0, 0), + specificity_and_flags: specificity(1, 0, 0), })))); assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!(Component::Negation( @@ -1927,8 +2058,7 @@ pub mod tests { }), ].into_boxed_slice() ))), - pseudo_element: None, - specificity: specificity(0, 0, 1), + specificity_and_flags: specificity(0, 0, 1), })))); // https://github.com/servo/servo/issues/16017 assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector { @@ -1937,8 +2067,7 @@ pub mod tests { Component::ExplicitUniversalType, ].into_boxed_slice() ))), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse_ns(":not(|*)", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!(Component::Negation( @@ -1947,8 +2076,7 @@ pub mod tests { Component::ExplicitUniversalType, ].into_boxed_slice() ))), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse_ns(":not(*|*)", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!(Component::Negation( @@ -1957,8 +2085,7 @@ pub mod tests { Component::ExplicitUniversalType, ].into_boxed_slice() ))), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); assert_eq!(parse_ns(":not(svg|*)", &parser), Ok(SelectorList(vec!(Selector { inner: SelectorInner::from_vec(vec!(Component::Negation( @@ -1967,11 +2094,40 @@ pub mod tests { Component::ExplicitUniversalType, ].into_boxed_slice() ))), - pseudo_element: None, - specificity: specificity(0, 0, 0), + specificity_and_flags: specificity(0, 0, 0), })))); } + #[test] + fn test_pseudo_iter() { + let selector = &parse("q::before").unwrap().0[0]; + assert!(!selector.is_universal()); + let mut iter = selector.inner.complex.iter(); + assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); + assert_eq!(iter.next(), None); + let combinator = iter.next_sequence(); + assert_eq!(combinator, Some(Combinator::PseudoElement)); + assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_sequence(), None); + } + + #[test] + fn test_universal() { + let selector = &parse("*|*::before").unwrap().0[0]; + assert!(selector.is_universal()); + } + + #[test] + fn test_empty_pseudo_iter() { + let selector = &parse("::before").unwrap().0[0]; + assert!(selector.is_universal()); + let mut iter = selector.inner.complex.iter(); + assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_sequence(), None); + } + struct TestVisitor { seen: Vec<String>, } @@ -1992,5 +2148,9 @@ pub mod tests { let mut test_visitor = TestVisitor { seen: vec![], }; parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); assert!(test_visitor.seen.contains(&":hover".into())); + + let mut test_visitor = TestVisitor { seen: vec![], }; + parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); + assert!(test_visitor.seen.contains(&":hover".into())); } } diff --git a/components/selectors/size_of_tests.rs b/components/selectors/size_of_tests.rs index 1d06278abd5..c03a0c80db5 100644 --- a/components/selectors/size_of_tests.rs +++ b/components/selectors/size_of_tests.rs @@ -3,14 +3,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::ToCss; +use gecko_like_types; use gecko_like_types::*; +use parser; use parser::*; use precomputed_hash::PrecomputedHash; use std::fmt; use visitor::SelectorVisitor; -size_of_test!(size_of_selector, Selector<Impl>, 72); -size_of_test!(size_of_pseudo_element, PseudoElementSelector, 16); +size_of_test!(size_of_selector, Selector<Impl>, 48); +size_of_test!(size_of_pseudo_element, gecko_like_types::PseudoElement, 1); size_of_test!(size_of_selector_inner, SelectorInner<Impl>, 40); size_of_test!(size_of_complex_selector, ComplexSelector<Impl>, 24); @@ -18,6 +20,9 @@ size_of_test!(size_of_component, Component<Impl>, 64); size_of_test!(size_of_attr_selector, AttrSelector<Impl>, 48); size_of_test!(size_of_pseudo_class, PseudoClass, 24); +impl parser::PseudoElement for gecko_like_types::PseudoElement { + type Impl = Impl; +} // Boilerplate @@ -31,7 +36,7 @@ impl SelectorImpl for Impl { type BorrowedLocalName = Atom; type BorrowedNamespaceUrl = Atom; type NonTSPseudoClass = PseudoClass; - type PseudoElementSelector = PseudoElementSelector; + type PseudoElement = gecko_like_types::PseudoElement; } impl SelectorMethods for PseudoClass { @@ -45,7 +50,7 @@ impl ToCss for PseudoClass { fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { unimplemented!() } } -impl ToCss for PseudoElementSelector { +impl ToCss for gecko_like_types::PseudoElement { fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { unimplemented!() } } diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index 6514a786673..e5a4539e3cc 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -123,6 +123,14 @@ impl<T> MatchAttr for T where T: MatchAttrGeneric, T::Impl: SelectorImpl<AttrVal pub trait Element: MatchAttr + Sized { fn parent_element(&self) -> Option<Self>; + /// The parent of a given pseudo-element, after matching a pseudo-element + /// selector. + /// + /// This is guaranteed to be called in a pseudo-element. + fn pseudo_element_originating_element(&self) -> Option<Self> { + self.parent_element() + } + // Skips non-element nodes fn first_child_element(&self) -> Option<Self>; @@ -145,6 +153,11 @@ pub trait Element: MatchAttr + Sized { flags_setter: &mut F) -> bool where F: FnMut(&Self, ElementSelectorFlags); + fn match_pseudo_element(&self, + pe: &<Self::Impl as SelectorImpl>::PseudoElement, + context: &mut MatchingContext) + -> bool; + fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>; fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool; diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs index f01c182802e..b15e17e6f38 100644 --- a/components/style/gecko/pseudo_element.rs +++ b/components/style/gecko/pseudo_element.rs @@ -10,12 +10,24 @@ use cssparser::ToCss; use gecko_bindings::structs::{self, CSSPseudoElementType}; -use selector_parser::PseudoElementCascadeType; +use selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl}; use std::fmt; use string_cache::Atom; include!(concat!(env!("OUT_DIR"), "/gecko/pseudo_element_definition.rs")); +impl ::selectors::parser::PseudoElement for PseudoElement { + type Impl = SelectorImpl; + + fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool { + if !self.supports_user_action_state() { + return false; + } + + return pseudo_class.is_safe_user_action_state(); + } +} + impl PseudoElement { /// Returns the kind of cascade type that a given pseudo is going to use. /// diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 22abaf63b67..787ecd5b2cb 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -5,7 +5,6 @@ //! Gecko-specific bits for selector-parsing. use cssparser::{Parser, ToCss}; -use element_state::{IN_ACTIVE_STATE, IN_FOCUS_STATE, IN_HOVER_STATE}; use element_state::ElementState; use gecko_bindings::structs::CSSPseudoClassType; use selector_parser::{SelectorParser, PseudoElementCascadeType}; @@ -132,7 +131,7 @@ impl NonTSPseudoClass { /// https://drafts.csswg.org/selectors-4/#useraction-pseudos /// /// We intentionally skip the link-related ones. - fn is_safe_user_action_state(&self) -> bool { + pub fn is_safe_user_action_state(&self) -> bool { matches!(*self, NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus) @@ -195,58 +194,6 @@ impl NonTSPseudoClass { #[derive(Clone, Debug, PartialEq, Eq)] pub struct SelectorImpl; -/// Some subset of pseudo-elements in Gecko are sensitive to some state -/// selectors. -/// -/// We store the sensitive states in this struct in order to properly handle -/// these. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct PseudoElementSelector { - pseudo: PseudoElement, - state: ElementState, -} - -impl PseudoElementSelector { - /// Returns the pseudo-element this selector represents. - pub fn pseudo_element(&self) -> &PseudoElement { - &self.pseudo - } - - /// Returns the pseudo-element selector state. - pub fn state(&self) -> ElementState { - self.state - } -} - -impl ToCss for PseudoElementSelector { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where W: fmt::Write, - { - if cfg!(debug_assertions) { - let mut state = self.state; - state.remove(IN_HOVER_STATE | IN_ACTIVE_STATE | IN_FOCUS_STATE); - assert_eq!(state, ElementState::empty(), - "Unhandled pseudo-element state selector?"); - } - - self.pseudo.to_css(dest)?; - - if self.state.contains(IN_HOVER_STATE) { - dest.write_str(":hover")? - } - - if self.state.contains(IN_ACTIVE_STATE) { - dest.write_str(":active")? - } - - if self.state.contains(IN_FOCUS_STATE) { - dest.write_str(":focus")? - } - - Ok(()) - } -} - impl ::selectors::SelectorImpl for SelectorImpl { type AttrValue = Atom; type Identifier = Atom; @@ -257,7 +204,7 @@ impl ::selectors::SelectorImpl for SelectorImpl { type BorrowedNamespaceUrl = WeakNamespace; type BorrowedLocalName = WeakAtom; - type PseudoElementSelector = PseudoElementSelector; + type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; } @@ -319,38 +266,9 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { } } - fn parse_pseudo_element(&self, name: Cow<str>, input: &mut Parser) -> Result<PseudoElementSelector, ()> { - let pseudo = - match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) { - Some(pseudo) => pseudo, - None => return Err(()), - }; - - let state = if pseudo.supports_user_action_state() { - input.try(|input| { - let mut state = ElementState::empty(); - - while !input.is_exhausted() { - input.expect_colon()?; - let ident = input.expect_ident()?; - let pseudo_class = self.parse_non_ts_pseudo_class(ident)?; - - if !pseudo_class.is_safe_user_action_state() { - return Err(()) - } - state.insert(pseudo_class.state_flag()); - } - - Ok(state) - }).ok() - } else { - None - }; - - Ok(PseudoElementSelector { - pseudo: pseudo, - state: state.unwrap_or(ElementState::empty()), - }) + fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> { + PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) + .ok_or(()) } fn default_namespace(&self) -> Option<Namespace> { diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index c171404d3eb..cef8962df3c 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -64,7 +64,7 @@ use properties::style_structs::Font; use rule_tree::CascadeLevel as ServoCascadeLevel; use selector_parser::ElementExt; use selectors::Element; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; +use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::parser::{AttrSelector, NamespaceConstraint}; use shared_lock::Locked; use sink::Push; @@ -1055,6 +1055,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { parent_node.and_then(|n| n.as_element()) } + fn pseudo_element_originating_element(&self) -> Option<Self> { + debug_assert!(self.implemented_pseudo_element().is_some()); + self.closest_non_native_anonymous_ancestor() + } + fn first_child_element(&self) -> Option<Self> { let mut child = self.as_node().first_child(); while let Some(child_node) = child { @@ -1244,6 +1249,20 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { } } + fn match_pseudo_element(&self, + pseudo_element: &PseudoElement, + _context: &mut MatchingContext) + -> bool + { + // TODO(emilio): I believe we could assert we are a pseudo-element and + // match the proper pseudo-element, given how we rulehash the stuff + // based on the pseudo. + match self.implemented_pseudo_element() { + Some(ref pseudo) => pseudo == pseudo_element, + None => false, + } + } + fn get_id(&self) -> Option<Atom> { let ptr = unsafe { bindings::Gecko_AtomAttrValue(self.0, @@ -1378,8 +1397,9 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { impl<'le> ElementExt for GeckoElement<'le> { #[inline] fn is_link(&self) -> bool { + let mut context = MatchingContext::new(MatchingMode::Normal, None); self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink, - &mut MatchingContext::default(), + &mut context, &mut |_, _| {}) } diff --git a/components/style/matching.rs b/components/style/matching.rs index 24cb5c2abe7..4765e89004c 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -15,7 +15,6 @@ use cascade_info::CascadeInfo; use context::{CurrentElementInfo, SelectorFlagsMap, SharedStyleContext, StyleContext}; use data::{ComputedStyle, ElementData, ElementStyles, RestyleData}; use dom::{AnimationRules, SendElement, TElement, TNode}; -use element_state::ElementState; use font_metrics::FontMetricsProvider; use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade}; use properties::longhands::display::computed_value as display; @@ -24,7 +23,7 @@ use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL}; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode}; use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl}; use selectors::bloom::BloomFilter; -use selectors::matching::{ElementSelectorFlags, MatchingContext, StyleRelations}; +use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations}; use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS; use shared_lock::StylesheetGuards; use sink::ForgetfulSink; @@ -895,10 +894,10 @@ pub trait MatchMethods : TElement { sharing: StyleSharingBehavior) { // Perform selector matching for the primary style. - let mut primary_matching_context = MatchingContext::default(); + let mut relations = StyleRelations::empty(); let _rule_node_changed = self.match_primary(context, data, - &mut primary_matching_context); + &mut relations); // Cascade properties and compute primary values. self.cascade_primary(context, data); @@ -912,7 +911,7 @@ pub trait MatchMethods : TElement { // If we have any pseudo elements, indicate so in the primary StyleRelations. if !data.styles().pseudos.is_empty() { - primary_matching_context.relations |= AFFECTED_BY_PSEUDO_ELEMENTS; + relations |= AFFECTED_BY_PSEUDO_ELEMENTS; } // If the style is shareable, add it to the LRU cache. @@ -932,7 +931,7 @@ pub trait MatchMethods : TElement { .style_sharing_candidate_cache .insert_if_possible(self, data.styles().primary.values(), - primary_matching_context.relations, + relations, revalidation_match_results); } } @@ -952,7 +951,7 @@ pub trait MatchMethods : TElement { fn match_primary(&self, context: &mut StyleContext<Self>, data: &mut ElementData, - matching_context: &mut MatchingContext) + relations: &mut StyleRelations) -> bool { let implemented_pseudo = self.implemented_pseudo_element(); @@ -1004,35 +1003,27 @@ pub trait MatchMethods : TElement { let animation_rules = self.get_animation_rules(); let bloom = context.thread_local.bloom_filter.filter(); + let map = &mut context.thread_local.selector_flags; let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { self.apply_selector_flags(map, element, flags); }; - let selector_matching_target = match implemented_pseudo { - Some(..) => { - self.closest_non_native_anonymous_ancestor() - .expect("Pseudo-element without non-NAC parent?") - }, - None => *self, - }; - - let pseudo_and_state = match implemented_pseudo { - Some(ref pseudo) => Some((pseudo, self.get_state())), - None => None, - }; + let mut matching_context = + MatchingContext::new(MatchingMode::Normal, Some(bloom)); // Compute the primary rule node. - stylist.push_applicable_declarations(&selector_matching_target, - Some(bloom), + stylist.push_applicable_declarations(self, + implemented_pseudo.as_ref(), style_attribute, smil_override, animation_rules, - pseudo_and_state, &mut applicable_declarations, - matching_context, + &mut matching_context, &mut set_selector_flags); + *relations = matching_context.relations; + let primary_rule_node = compute_rule_node::<Self>(&stylist.rule_tree, &mut applicable_declarations, @@ -1041,8 +1032,8 @@ pub trait MatchMethods : TElement { return data.set_primary_rules(primary_rule_node); } - /// Runs selector matching to (re)compute eager pseudo-element rule nodes for this - /// element. + /// Runs selector matching to (re)compute eager pseudo-element rule nodes + /// for this element. /// /// Returns whether any of the pseudo rule nodes changed (including, but not /// limited to, cases where we match different pseudos altogether). @@ -1070,6 +1061,10 @@ pub trait MatchMethods : TElement { let rule_tree = &stylist.rule_tree; let bloom_filter = context.thread_local.bloom_filter.filter(); + let mut matching_context = + MatchingContext::new(MatchingMode::ForStatelessPseudoElement, + Some(bloom_filter)); + // Compute rule nodes for eagerly-cascaded pseudo-elements. let mut matches_different_pseudos = false; let mut rule_nodes_changed = false; @@ -1079,13 +1074,12 @@ pub trait MatchMethods : TElement { // NB: We handle animation rules for ::before and ::after when // traversing them. stylist.push_applicable_declarations(self, - Some(bloom_filter), + Some(&pseudo), None, None, AnimationRules(None, None), - Some((&pseudo, ElementState::empty())), &mut applicable_declarations, - &mut MatchingContext::default(), + &mut matching_context, &mut set_selector_flags); if !applicable_declarations.is_empty() { diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index d0b9cd665f8..23a3cc9f2c3 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -9,14 +9,13 @@ use Atom; use dom::TElement; use element_state::*; -use fnv::FnvHashMap; #[cfg(feature = "gecko")] use gecko_bindings::structs::nsRestyleHint; #[cfg(feature = "servo")] use heapsize::HeapSizeOf; use selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap}; use selectors::{Element, MatchAttr}; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; +use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::matching::matches_selector; use selectors::parser::{AttrSelector, Combinator, Component, Selector}; use selectors::parser::{SelectorInner, SelectorMethods}; @@ -406,6 +405,14 @@ impl<'a, E> Element for ElementWrapper<'a, E> } } + fn match_pseudo_element(&self, + pseudo_element: &PseudoElement, + context: &mut MatchingContext) + -> bool + { + self.element.match_pseudo_element(pseudo_element, context) + } + fn parent_element(&self) -> Option<Self> { self.element.parent_element() .map(|e| ElementWrapper::new(e, self.snapshot_map)) @@ -475,6 +482,11 @@ impl<'a, E> Element for ElementWrapper<'a, E> _ => self.element.each_class(callback) } } + + fn pseudo_element_originating_element(&self) -> Option<Self> { + self.element.closest_non_native_anonymous_ancestor() + .map(|e| ElementWrapper::new(e, self.snapshot_map)) + } } fn selector_to_state(sel: &Component<SelectorImpl>) -> ElementState { @@ -507,6 +519,9 @@ fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint { match combinator { None => RESTYLE_SELF, Some(c) => match c { + // NB: RESTYLE_SELF is needed to handle properly eager pseudos, + // otherwise we may leave a stale style on the parent. + Combinator::PseudoElement => RESTYLE_SELF | RESTYLE_DESCENDANTS, Combinator::Child => RESTYLE_DESCENDANTS, Combinator::Descendant => RESTYLE_DESCENDANTS, Combinator::NextSibling => RESTYLE_LATER_SIBLINGS, @@ -634,13 +649,6 @@ impl SelectorVisitor for SensitivitiesVisitor { #[derive(Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct DependencySet { - /// A map used for pseudo-element's dependencies. - /// - /// Note that pseudo-elements are somewhat special, because some of them in - /// Gecko track state, and also because they don't do selector-matching as - /// normal, but against their parent element. - pseudo_dependencies: FnvHashMap<PseudoElement, SelectorMap<PseudoElementDependency>>, - /// This is for all other normal element's selectors/selector parts. dependencies: SelectorMap<Dependency>, } @@ -668,34 +676,9 @@ impl DependencySet { index += 1; // Account for the simple selector. } - - let pseudo_selector_is_state_dependent = - sequence_start == 0 && - selector.pseudo_element.as_ref().map_or(false, |pseudo_selector| { - !pseudo_selector.state().is_empty() - }); - - if pseudo_selector_is_state_dependent { - let pseudo_selector = selector.pseudo_element.as_ref().unwrap(); - self.pseudo_dependencies - .entry(pseudo_selector.pseudo_element().clone()) - .or_insert_with(SelectorMap::new) - .insert(PseudoElementDependency { - selector: selector.clone(), - }); - } - // If we found a sensitivity, add an entry in the dependency set. if !visitor.sensitivities.is_empty() { - let mut hint = combinator_to_restyle_hint(combinator); - - if sequence_start == 0 && selector.pseudo_element.is_some() { - // FIXME(emilio): Be more granular about this. See the - // comment in `PseudoElementDependency` about how could this - // be modified in order to be more efficient and restyle - // less. - hint |= RESTYLE_DESCENDANTS; - } + let hint = combinator_to_restyle_hint(combinator); let dep_selector = if sequence_start == 0 { // Reuse the bloom hashes if this is the base selector. @@ -724,82 +707,22 @@ impl DependencySet { pub fn new() -> Self { DependencySet { dependencies: SelectorMap::new(), - pseudo_dependencies: FnvHashMap::default(), } } /// Return the total number of dependencies that this set contains. pub fn len(&self) -> usize { - self.dependencies.len() + - self.pseudo_dependencies.values().fold(0, |acc, val| acc + val.len()) + self.dependencies.len() } /// Clear this dependency set. pub fn clear(&mut self) { self.dependencies = SelectorMap::new(); - self.pseudo_dependencies.clear() } - fn compute_pseudo_hint<E>( - &self, - pseudo: &E, - pseudo_element: PseudoElement, - snapshots: &SnapshotMap) - -> RestyleHint - where E: TElement, - { - debug!("compute_pseudo_hint: {:?}, {:?}", pseudo, pseudo_element); - debug_assert!(pseudo.has_snapshot()); - - let map = match self.pseudo_dependencies.get(&pseudo_element) { - Some(map) => map, - None => return RestyleHint::empty(), - }; - - // Only pseudo-element's state is relevant. - let pseudo_state_changes = - ElementWrapper::new(*pseudo, snapshots).state_changes(); - - debug!("pseudo_state_changes: {:?}", pseudo_state_changes); - if pseudo_state_changes.is_empty() { - return RestyleHint::empty(); - } - - let selector_matching_target = - pseudo.closest_non_native_anonymous_ancestor().unwrap(); - - // Note that we rely on that, if the originating element changes, it'll - // post a restyle hint that would make us redo selector matching, so we - // don't need to care about that. - // - // If that ever changes, we'd need to share more code with - // `compute_element_hint`. - let mut hint = RestyleHint::empty(); - map.lookup(selector_matching_target, &mut |dep| { - // If the selector didn't match before, it either doesn't match now - // either (or it doesn't matter because our parent posted a restyle - // for us above). - if !matches_selector(&dep.selector.inner, &selector_matching_target, - None, &mut MatchingContext::default(), - &mut |_, _| {}) { - return true; - } - - let pseudo_selector = dep.selector.pseudo_element.as_ref().unwrap(); - debug_assert!(!pseudo_selector.state().is_empty()); - - if pseudo_selector.state().intersects(pseudo_state_changes) { - hint = RESTYLE_SELF; - return false; - } - - true - }); - - hint - } - - fn compute_element_hint<E>( + /// Compute a restyle hint given an element and a snapshot, per the rules + /// explained in the rest of the documentation. + pub fn compute_hint<E>( &self, el: &E, snapshots: &SnapshotMap) @@ -838,8 +761,18 @@ impl DependencySet { }); } + // FIXME(emilio): A bloom filter here would be neat. + let mut matching_context = + MatchingContext::new(MatchingMode::Normal, None); + + let lookup_element = if el.implemented_pseudo_element().is_some() { + el.closest_non_native_anonymous_ancestor().unwrap() + } else { + *el + }; + self.dependencies - .lookup_with_additional(*el, additional_id, &additional_classes, &mut |dep| { + .lookup_with_additional(lookup_element, additional_id, &additional_classes, &mut |dep| { trace!("scanning dependency: {:?}", dep); if !dep.sensitivities.sensitive_to(attrs_changed, state_changes) { @@ -856,12 +789,12 @@ impl DependencySet { // been set during original matching for any element that might // change its matching behavior here. let matched_then = - matches_selector(&dep.selector, &snapshot_el, None, - &mut MatchingContext::default(), + matches_selector(&dep.selector, &snapshot_el, + &mut matching_context, &mut |_, _| {}); let matches_now = - matches_selector(&dep.selector, el, None, - &mut MatchingContext::default(), + matches_selector(&dep.selector, el, + &mut matching_context, &mut |_, _| {}); if matched_then != matches_now { hint.insert(dep.hint); @@ -875,21 +808,4 @@ impl DependencySet { hint } - - - /// Compute a restyle hint given an element and a snapshot, per the rules - /// explained in the rest of the documentation. - pub fn compute_hint<E>(&self, - el: &E, - snapshots: &SnapshotMap) - -> RestyleHint - where E: TElement + Clone, - { - debug!("DependencySet::compute_hint({:?})", el); - if let Some(pseudo) = el.implemented_pseudo_element() { - return self.compute_pseudo_hint(el, pseudo, snapshots); - } - - self.compute_element_hint(el, snapshots) - } } diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index 6f9ce19f40e..6fd498ee9aa 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -15,7 +15,7 @@ use fnv::FnvHashMap; use restyle_hints::ElementSnapshot; use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser}; use selectors::{Element, MatchAttrGeneric}; -use selectors::matching::MatchingContext; +use selectors::matching::{MatchingContext, MatchingMode}; use selectors::parser::{AttrSelector, SelectorMethods}; use selectors::visitor::SelectorVisitor; use std::borrow::Cow; @@ -51,6 +51,14 @@ pub enum PseudoElement { ServoInlineAbsolute, } +impl ::selectors::parser::PseudoElement for PseudoElement { + type Impl = SelectorImpl; + + fn supports_pseudo_class(&self, _: &NonTSPseudoClass) -> bool { + false + } +} + impl ToCss for PseudoElement { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::PseudoElement::*; @@ -78,18 +86,6 @@ impl ToCss for PseudoElement { pub const EAGER_PSEUDO_COUNT: usize = 3; impl PseudoElement { - /// The pseudo-element, used for compatibility with Gecko's - /// `PseudoElementSelector`. - pub fn pseudo_element(&self) -> &Self { - self - } - - /// The pseudo-element selector's state, used for compatibility with Gecko's - /// `PseudoElementSelector`. - pub fn state(&self) -> ElementState { - ElementState::empty() - } - /// Gets the canonical index of this eagerly-cascaded pseudo-element. #[inline] pub fn eager_index(&self) -> usize { @@ -264,7 +260,7 @@ impl NonTSPseudoClass { pub struct SelectorImpl; impl ::selectors::SelectorImpl for SelectorImpl { - type PseudoElementSelector = PseudoElement; + type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; type AttrValue = String; @@ -323,9 +319,7 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { Ok(pseudo_class) } - fn parse_pseudo_element(&self, - name: Cow<str>, - _input: &mut CssParser) + fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> { use self::PseudoElement::*; let pseudo_element = match_ignore_ascii_case! { &name, @@ -579,8 +573,9 @@ impl MatchAttrGeneric for ServoElementSnapshot { impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E { fn is_link(&self) -> bool { + let mut context = MatchingContext::new(MatchingMode::Normal, None); self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink, - &mut MatchingContext::default(), + &mut context, &mut |_, _| {}) } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index acef07d054e..644552050b2 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -26,10 +26,9 @@ use properties::PropertyDeclarationBlock; use restyle_hints::{RestyleHint, DependencySet}; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap}; -use selectors::Element; use selectors::bloom::BloomFilter; use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS}; -use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext}; +use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; use selectors::parser::{AttrSelector, Combinator, Component, Selector, SelectorInner, SelectorIter}; use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector}; use selectors::visitor::SelectorVisitor; @@ -479,9 +478,9 @@ impl Stylist { rule: &Arc<Locked<StyleRule>>, stylesheet: &Stylesheet) { - let map = if let Some(ref pseudo_selector) = selector.pseudo_element { + let map = if let Some(pseudo) = selector.pseudo_element() { self.pseudos_map - .entry(pseudo_selector.pseudo_element().clone()) + .entry(pseudo.clone()) .or_insert_with(PerPseudoElementSelectorMap::new) .borrow_for_origin(&stylesheet.origin) } else { @@ -525,9 +524,6 @@ impl Stylist { #[inline] fn note_attribute_and_state_dependencies(&mut self, selector: &Selector<SelectorImpl>) { - if let Some(ref pseudo_selector) = selector.pseudo_element { - self.state_dependencies.insert(pseudo_selector.state()); - } selector.visit(&mut AttributeAndStateDependencyVisitor(self)); } @@ -635,14 +631,13 @@ impl Stylist { guards: &StylesheetGuards, element: &E, pseudo: &PseudoElement, - pseudo_state: ElementState, parent: &Arc<ComputedValues>, font_metrics: &FontMetricsProvider) -> Option<ComputedStyle> where E: TElement, { let rule_node = - match self.lazy_pseudo_rules(guards, element, pseudo, pseudo_state) { + match self.lazy_pseudo_rules(guards, element, pseudo) { Some(rule_node) => rule_node, None => return None }; @@ -673,8 +668,7 @@ impl Stylist { pub fn lazy_pseudo_rules<E>(&self, guards: &StylesheetGuards, element: &E, - pseudo: &PseudoElement, - pseudo_state: ElementState) + pseudo: &PseudoElement) -> Option<StrongRuleNode> where E: TElement { @@ -708,14 +702,15 @@ impl Stylist { }; let mut declarations = ApplicableDeclarationList::new(); + let mut matching_context = + MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None); self.push_applicable_declarations(element, - None, + Some(pseudo), None, None, AnimationRules(None, None), - Some((pseudo, pseudo_state)), &mut declarations, - &mut MatchingContext::default(), + &mut matching_context, &mut set_selector_flags); if declarations.is_empty() { return None @@ -839,16 +834,15 @@ impl Stylist { pub fn push_applicable_declarations<E, V, F>( &self, element: &E, - parent_bf: Option<&BloomFilter>, + pseudo_element: Option<&PseudoElement>, style_attribute: Option<&Arc<Locked<PropertyDeclarationBlock>>>, smil_override: Option<&Arc<Locked<PropertyDeclarationBlock>>>, animation_rules: AnimationRules, - pseudo_element: Option<(&PseudoElement, ElementState)>, applicable_declarations: &mut V, context: &mut MatchingContext, flags_setter: &mut F) where E: TElement, - V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>, + V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + ::std::fmt::Debug, F: FnMut(&E, ElementSelectorFlags), { debug_assert!(!self.is_device_dirty); @@ -857,20 +851,31 @@ impl Stylist { debug_assert!(cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"); - debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.0.is_precomputed())); + debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed())); let map = match pseudo_element { - Some((ref pseudo, _)) => self.pseudos_map.get(pseudo).unwrap(), + Some(pseudo) => self.pseudos_map.get(pseudo).unwrap(), None => &self.element_map, }; + let is_implemented_pseudo = + element.implemented_pseudo_element().is_some(); + + // NB: This causes use to rule has pseudo selectors based on the + // properties of the originating element (which is fine, given the + // find_first_from_right usage). + let rule_hash_target = if is_implemented_pseudo { + element.closest_non_native_anonymous_ancestor().unwrap() + } else { + *element + }; + debug!("Determining if style is shareable: pseudo: {}", pseudo_element.is_some()); // Step 1: Normal user-agent rules. map.user_agent.get_all_matching_rules(element, - pseudo_element, - parent_bf, + &rule_hash_target, applicable_declarations, context, flags_setter, @@ -893,19 +898,25 @@ impl Stylist { debug!("preshints: {:?}", context.relations); } - if element.matches_user_and_author_rules() { + // NB: the following condition, although it may look somewhat + // inaccurate, would be equivalent to something like: + // + // element.matches_user_and_author_rules() || + // (is_implemented_pseudo && + // rule_hash_target.matches_user_and_author_rules()) + // + // Which may be more what you would probably expect. + if rule_hash_target.matches_user_and_author_rules() { // Step 3: User and author normal rules. map.user.get_all_matching_rules(element, - pseudo_element, - parent_bf, + &rule_hash_target, applicable_declarations, context, flags_setter, CascadeLevel::UserNormal); debug!("user normal: {:?}", context.relations); map.author.get_all_matching_rules(element, - pseudo_element, - parent_bf, + &rule_hash_target, applicable_declarations, context, flags_setter, @@ -960,7 +971,6 @@ impl Stylist { ApplicableDeclarationBlock::from_declarations(anim, CascadeLevel::Transitions)); } debug!("transition: {:?}", context.relations); - debug!("push_applicable_declarations: shareable: {:?}", context.relations); } @@ -993,6 +1003,11 @@ impl Stylist { where E: TElement, F: FnMut(&E, ElementSelectorFlags), { + // NB: `MatchingMode` doesn't really matter, given we don't share style + // between pseudos. + let mut matching_context = + MatchingContext::new(MatchingMode::Normal, Some(bloom)); + // Note that, by the time we're revalidating, we're guaranteed that the // candidate and the entry have the same id, classes, and local name. // This means we're guaranteed to get the same rulehash buckets for all @@ -1002,8 +1017,7 @@ impl Stylist { self.selectors_for_cache_revalidation.lookup(*element, &mut |selector| { results.push(matches_selector(selector, element, - Some(bloom), - &mut MatchingContext::default(), + &mut matching_context, flags_setter)); true }); @@ -1182,6 +1196,7 @@ pub fn needs_revalidation(selector: &Selector<SelectorImpl>) -> bool { /// Map that contains the CSS rules for a specific PseudoElement /// (or lack of PseudoElement). #[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Debug)] struct PerPseudoElementSelectorMap { /// Rules from user agent stylesheets user_agent: SelectorMap<Rule>, @@ -1283,13 +1298,12 @@ impl SelectorMap<Rule> { /// Sort the Rules at the end to maintain cascading order. pub fn get_all_matching_rules<E, V, F>(&self, element: &E, - pseudo_element: Option<(&PseudoElement, ElementState)>, - parent_bf: Option<&BloomFilter>, + rule_hash_target: &E, matching_rules_list: &mut V, context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel) - where E: Element<Impl=SelectorImpl>, + where E: TElement, V: VecLike<ApplicableDeclarationBlock>, F: FnMut(&E, ElementSelectorFlags), { @@ -1299,10 +1313,8 @@ impl SelectorMap<Rule> { // 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) = element.get_id() { + if let Some(id) = rule_hash_target.get_id() { SelectorMap::get_matching_rules_from_hash(element, - pseudo_element, - parent_bf, &self.id_hash, &id, matching_rules_list, @@ -1311,10 +1323,8 @@ impl SelectorMap<Rule> { cascade_level) } - element.each_class(|class| { + rule_hash_target.each_class(|class| { SelectorMap::get_matching_rules_from_hash(element, - pseudo_element, - parent_bf, &self.class_hash, class, matching_rules_list, @@ -1324,18 +1334,14 @@ impl SelectorMap<Rule> { }); SelectorMap::get_matching_rules_from_hash(element, - pseudo_element, - parent_bf, &self.local_name_hash, - element.get_local_name(), + rule_hash_target.get_local_name(), matching_rules_list, context, flags_setter, cascade_level); SelectorMap::get_matching_rules(element, - pseudo_element, - parent_bf, &self.other, matching_rules_list, context, @@ -1372,15 +1378,13 @@ impl SelectorMap<Rule> { fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>( element: &E, - pseudo_element: Option<(&PseudoElement, ElementState)>, - parent_bf: Option<&BloomFilter>, hash: &FnvHashMap<Str, Vec<Rule>>, key: &BorrowedStr, matching_rules: &mut Vector, context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel) - where E: Element<Impl=SelectorImpl>, + where E: TElement, Str: Borrow<BorrowedStr> + Eq + Hash, BorrowedStr: Eq + Hash, Vector: VecLike<ApplicableDeclarationBlock>, @@ -1388,8 +1392,6 @@ impl SelectorMap<Rule> { { if let Some(rules) = hash.get(key) { SelectorMap::get_matching_rules(element, - pseudo_element, - parent_bf, rules, matching_rules, context, @@ -1400,42 +1402,18 @@ impl SelectorMap<Rule> { /// Adds rules in `rules` that match `element` to the `matching_rules` list. fn get_matching_rules<E, V, F>(element: &E, - pseudo_element: Option<(&PseudoElement, ElementState)>, - parent_bf: Option<&BloomFilter>, rules: &[Rule], matching_rules: &mut V, context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel) - where E: Element<Impl=SelectorImpl>, + where E: TElement, V: VecLike<ApplicableDeclarationBlock>, F: FnMut(&E, ElementSelectorFlags), { - for rule in rules.iter() { - debug_assert_eq!(rule.selector.pseudo_element.is_some(), - pseudo_element.is_some(), - "Testing pseudo-elements against the wrong map"); - - if let Some((pseudo, pseudo_state)) = pseudo_element { - let pseudo_selector = - rule.selector.pseudo_element.as_ref().unwrap(); - - debug_assert_eq!(pseudo_selector.pseudo_element(), pseudo, - "Testing pseudo-element against the wrong entry"); - - let state = pseudo_selector.state(); - - // NB: We only allow a subset of the flags here, so using - // contains for them is fine, (and it's necessary, to handle - // multiple state flags properly). - if !state.is_empty() && !pseudo_state.contains(state) { - continue; - } - } - + for rule in rules { if matches_selector(&rule.selector.inner, element, - parent_bf, context, flags_setter) { matching_rules.push( @@ -1593,45 +1571,70 @@ impl<T> SelectorMap<T> where T: Clone + Borrow<SelectorInner<SelectorImpl>> { } } +/// 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<F, R>(selector: &SelectorInner<SelectorImpl>, mut f: F) -> Option<R> + where F: FnMut(&Component<SelectorImpl>) -> Option<R>, +{ + 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<SelectorImpl>) -> Option<Atom> { - for ss in selector.complex.iter() { + 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 + None + }) } /// Retrieve the FIRST class name in the selector, or None otherwise. pub fn get_class_name(selector: &SelectorInner<SelectorImpl>) -> Option<Atom> { - for ss in selector.complex.iter() { + 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 + None + }) } /// Retrieve the name if it is a type selector, or None otherwise. pub fn get_local_name(selector: &SelectorInner<SelectorImpl>) -> Option<LocalNameSelector<SelectorImpl>> { - for ss in selector.complex.iter() { + 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 + None + }) } /// A rule, that wraps a style rule, but represents a single selector of the @@ -1661,7 +1664,7 @@ impl Borrow<SelectorInner<SelectorImpl>> for Rule { impl Rule { /// Returns the specificity of the rule. pub fn specificity(&self) -> u32 { - self.selector.specificity + self.selector.specificity() } fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock { diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index f7aca89a524..dfd563a3fb1 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -1068,7 +1068,6 @@ fn get_pseudo_style(guard: &SharedRwLockReadGuard, d.stylist.lazily_compute_pseudo_element_style(&guards, &element, &pseudo, - ElementState::empty(), base, &metrics) .map(|s| s.values().clone()) diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index 81d8a6fffd4..6d2a2d72bee 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -90,8 +90,8 @@ fn test_parse_stylesheet() { }))), CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { selectors: SelectorList(vec![ - Selector { - inner: SelectorInner::from_vec(vec![ + Selector::new_for_unit_testing( + SelectorInner::from_vec(vec![ Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::LocalName(LocalName { name: local_name!("input"), @@ -106,9 +106,8 @@ fn test_parse_stylesheet() { }), }, "hidden".to_owned(), CaseSensitivity::CaseInsensitive) ]), - pseudo_element: None, - specificity: (0 << 20) + (1 << 10) + (1 << 0), - }, + (0 << 20) + (1 << 10) + (1 << 0) + ), ]), block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ (PropertyDeclaration::Display(longhands::display::SpecifiedValue::none), @@ -124,28 +123,26 @@ fn test_parse_stylesheet() { }))), CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { selectors: SelectorList(vec![ - Selector { - inner: SelectorInner::from_vec(vec![ + Selector::new_for_unit_testing( + SelectorInner::from_vec(vec![ Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::LocalName(LocalName { name: local_name!("html"), lower_name: local_name!("html"), }), ]), - pseudo_element: None, - specificity: (0 << 20) + (0 << 10) + (1 << 0), - }, - Selector { - inner: SelectorInner::from_vec(vec![ + (0 << 20) + (0 << 10) + (1 << 0) + ), + Selector::new_for_unit_testing( + SelectorInner::from_vec(vec![ Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::LocalName(LocalName { name: local_name!("body"), lower_name: local_name!("body"), }), ]), - pseudo_element: None, - specificity: (0 << 20) + (0 << 10) + (1 << 0), - }, + (0 << 20) + (0 << 10) + (1 << 0) + ), ]), block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ (PropertyDeclaration::Display(longhands::display::SpecifiedValue::block), @@ -158,17 +155,16 @@ fn test_parse_stylesheet() { }))), CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { selectors: SelectorList(vec![ - Selector { - inner: SelectorInner::from_vec(vec![ + Selector::new_for_unit_testing( + SelectorInner::from_vec(vec![ Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::ID(Atom::from("d1")), Component::Combinator(Combinator::Child), Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::Class(Atom::from("ok")), ]), - pseudo_element: None, - specificity: (1 << 20) + (1 << 10) + (0 << 0), - }, + (1 << 20) + (1 << 10) + (0 << 0) + ), ]), block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ (PropertyDeclaration::BackgroundColor( diff --git a/tests/unit/stylo/size_of.rs b/tests/unit/stylo/size_of.rs index 4ae541c8de0..f8770a8842e 100644 --- a/tests/unit/stylo/size_of.rs +++ b/tests/unit/stylo/size_of.rs @@ -12,8 +12,8 @@ fn size_of_selectors_dummy_types() { assert_eq!(size_of::<dummies::PseudoClass>(), size_of::<real::NonTSPseudoClass>()); assert_eq!(align_of::<dummies::PseudoClass>(), align_of::<real::NonTSPseudoClass>()); - assert_eq!(size_of::<dummies::PseudoElementSelector>(), size_of::<real::PseudoElementSelector>()); - assert_eq!(align_of::<dummies::PseudoElementSelector>(), align_of::<real::PseudoElementSelector>()); + assert_eq!(size_of::<dummies::PseudoElement>(), size_of::<real::PseudoElement>()); + assert_eq!(align_of::<dummies::PseudoElement>(), align_of::<real::PseudoElement>()); assert_eq!(size_of::<dummies::Atom>(), size_of::<style::Atom>()); assert_eq!(align_of::<dummies::Atom>(), align_of::<style::Atom>()); |