diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 606fd6f479d..7c0f53ab0a6 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -61,7 +61,7 @@ macro_rules! with_all_bounds { type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods; /// pseudo-elements - type PseudoElement: $($CommonBounds)* + Sized + ToCss; + type PseudoElementSelector: $($CommonBounds)* + Sized + ToCss; } } } @@ -98,8 +98,8 @@ pub trait Parser { Err(()) } - fn parse_pseudo_element(&self, _name: Cow) - -> Result<::PseudoElement, ()> { + fn parse_pseudo_element(&self, _name: Cow, _input: &mut CssParser) + -> Result<::PseudoElementSelector, ()> { Err(()) } @@ -179,7 +179,7 @@ impl SelectorInner { #[derive(PartialEq, Eq, Hash, Clone)] pub struct Selector { pub inner: SelectorInner, - pub pseudo_element: Option, + pub pseudo_element: Option, pub specificity: u32, } @@ -816,7 +816,7 @@ impl From for u32 { } fn specificity(complex_selector: &ComplexSelector, - pseudo_element: Option<&Impl::PseudoElement>) + pseudo_element: Option<&Impl::PseudoElementSelector>) -> u32 where Impl: SelectorImpl { let mut specificity = complex_selector_specificity(complex_selector); @@ -916,7 +916,7 @@ type ParseVec = SmallVec<[Component; 8]>; fn parse_complex_selector_and_pseudo_element( parser: &P, input: &mut CssParser) - -> Result<(ComplexSelector, Option), ()> + -> Result<(ComplexSelector, Option), ()> where P: Parser, Impl: SelectorImpl { let mut sequence = ParseVec::new(); @@ -1014,7 +1014,7 @@ fn parse_type_selector(parser: &P, input: &mut CssParser, sequence: &mu #[derive(Debug)] enum SimpleSelectorParseResult { SimpleSelector(Component), - PseudoElement(Impl::PseudoElement), + PseudoElement(Impl::PseudoElementSelector), } /// * `Err(())`: Invalid selector, abort @@ -1210,7 +1210,7 @@ fn parse_compound_selector( input: &mut CssParser, mut sequence: &mut ParseVec, inside_negation: bool) - -> Result, ()> + -> Result, ()> where P: Parser, Impl: SelectorImpl { // Consume any leading whitespace. @@ -1335,7 +1335,7 @@ fn parse_one_simple_selector(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)?; + let pseudo_element = P::parse_pseudo_element(parser, name, input)?; Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element))) } else { let pseudo_class = parse_simple_pseudo_class(parser, name)?; @@ -1351,7 +1351,7 @@ fn parse_one_simple_selector(parser: &P, Ok(Token::Colon) => { match input.next_including_whitespace() { Ok(Token::Ident(name)) => { - let pseudo = P::parse_pseudo_element(parser, name)?; + let pseudo = P::parse_pseudo_element(parser, name, input)?; Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo))) } _ => Err(()) @@ -1455,7 +1455,7 @@ pub mod tests { type BorrowedLocalName = DummyAtom; type BorrowedNamespaceUrl = DummyAtom; type NonTSPseudoClass = PseudoClass; - type PseudoElement = PseudoElement; + type PseudoElementSelector = PseudoElement; } #[derive(Default, Debug, Hash, Clone, PartialEq, Eq)] @@ -1505,7 +1505,7 @@ pub mod tests { } } - fn parse_pseudo_element(&self, name: Cow) + fn parse_pseudo_element(&self, name: Cow, input: &mut CssParser) -> Result { match_ignore_ascii_case! { &name, "before" => Ok(PseudoElement::Before), diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 6badfd145b6..ca35d1dc35e 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -5,6 +5,7 @@ //! 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 gecko_bindings::structs::nsIAtom; @@ -325,6 +326,15 @@ impl NonTSPseudoClass { apply_non_ts_list!(pseudo_class_check_internal) } + /// https://drafts.csswg.org/selectors-4/#useraction-pseudos + /// + /// We intentionally skip the link-related ones. + fn is_safe_user_action_state(&self) -> bool { + matches!(*self, NonTSPseudoClass::Hover | + NonTSPseudoClass::Active | + NonTSPseudoClass::Focus) + } + /// Get the state flag associated with a pseudo-class, if any. pub fn state_flag(&self) -> ElementState { macro_rules! flag { @@ -375,6 +385,53 @@ impl NonTSPseudoClass { #[derive(Clone, Debug, PartialEq, Eq, Hash)] 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 + } +} + +impl ToCss for PseudoElementSelector { + fn to_css(&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; @@ -385,7 +442,7 @@ impl ::selectors::SelectorImpl for SelectorImpl { type BorrowedNamespaceUrl = WeakNamespace; type BorrowedLocalName = WeakAtom; - type PseudoElement = PseudoElement; + type PseudoElementSelector = PseudoElementSelector; type NonTSPseudoClass = NonTSPseudoClass; } @@ -447,11 +504,34 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { } } - fn parse_pseudo_element(&self, name: Cow) -> Result { - match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) { - Some(pseudo) => Ok(pseudo), - None => Err(()), - } + fn parse_pseudo_element(&self, name: Cow, input: &mut Parser) -> Result { + let pseudo = + match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) { + Some(pseudo) => pseudo, + None => return Err(()), + }; + + let 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(PseudoElementSelector { + pseudo: pseudo, + state: state.unwrap_or(ElementState::empty()), + }) } fn default_namespace(&self) -> Option { diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index d81e14235a2..e801738866c 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -78,6 +78,18 @@ 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 { @@ -252,7 +264,7 @@ impl NonTSPseudoClass { pub struct SelectorImpl; impl ::selectors::SelectorImpl for SelectorImpl { - type PseudoElement = PseudoElement; + type PseudoElementSelector = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; type AttrValue = String; @@ -311,7 +323,10 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { Ok(pseudo_class) } - fn parse_pseudo_element(&self, name: Cow) -> Result { + fn parse_pseudo_element(&self, + name: Cow, + _input: &mut CssParser) + -> Result { use self::PseudoElement::*; let pseudo_element = match_ignore_ascii_case! { &name, "before" => Before, diff --git a/components/style/stylist.rs b/components/style/stylist.rs index b929777bd2f..0cfb9e9a04d 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -449,9 +449,9 @@ impl Stylist { rule: &Arc>, stylesheet: &Stylesheet) { - let map = if let Some(ref pseudo) = selector.pseudo_element { + let map = if let Some(ref pseudo_selector) = selector.pseudo_element { self.pseudos_map - .entry(pseudo.clone()) + .entry(pseudo_selector.pseudo_element().clone()) .or_insert_with(PerPseudoElementSelectorMap::new) .borrow_for_origin(&stylesheet.origin) } else {