Bug 1364412: Allow pseudo-element selectors to store state. r=bholley

MozReview-Commit-ID: CzAwg2uxqO2
Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
Emilio Cobos Álvarez 2017-05-12 15:51:46 +02:00
parent 5bd6b92494
commit 10560ae043
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
4 changed files with 117 additions and 22 deletions

View file

@ -61,7 +61,7 @@ macro_rules! with_all_bounds {
type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>; type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>;
/// pseudo-elements /// pseudo-elements
type PseudoElement: $($CommonBounds)* + Sized + ToCss; type PseudoElementSelector: $($CommonBounds)* + Sized + ToCss;
} }
} }
} }
@ -98,8 +98,8 @@ pub trait Parser {
Err(()) Err(())
} }
fn parse_pseudo_element(&self, _name: Cow<str>) fn parse_pseudo_element(&self, _name: Cow<str>, _input: &mut CssParser)
-> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> { -> Result<<Self::Impl as SelectorImpl>::PseudoElementSelector, ()> {
Err(()) Err(())
} }
@ -179,7 +179,7 @@ impl<Impl: SelectorImpl> SelectorInner<Impl> {
#[derive(PartialEq, Eq, Hash, Clone)] #[derive(PartialEq, Eq, Hash, Clone)]
pub struct Selector<Impl: SelectorImpl> { pub struct Selector<Impl: SelectorImpl> {
pub inner: SelectorInner<Impl>, pub inner: SelectorInner<Impl>,
pub pseudo_element: Option<Impl::PseudoElement>, pub pseudo_element: Option<Impl::PseudoElementSelector>,
pub specificity: u32, pub specificity: u32,
} }
@ -816,7 +816,7 @@ impl From<Specificity> for u32 {
} }
fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>, fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>,
pseudo_element: Option<&Impl::PseudoElement>) pseudo_element: Option<&Impl::PseudoElementSelector>)
-> u32 -> u32
where Impl: SelectorImpl { where Impl: SelectorImpl {
let mut specificity = complex_selector_specificity(complex_selector); let mut specificity = complex_selector_specificity(complex_selector);
@ -916,7 +916,7 @@ type ParseVec<Impl> = SmallVec<[Component<Impl>; 8]>;
fn parse_complex_selector_and_pseudo_element<P, Impl>( fn parse_complex_selector_and_pseudo_element<P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser) input: &mut CssParser)
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElement>), ()> -> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElementSelector>), ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl where P: Parser<Impl=Impl>, Impl: SelectorImpl
{ {
let mut sequence = ParseVec::new(); let mut sequence = ParseVec::new();
@ -1014,7 +1014,7 @@ fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser, sequence: &mu
#[derive(Debug)] #[derive(Debug)]
enum SimpleSelectorParseResult<Impl: SelectorImpl> { enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>), SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElement), PseudoElement(Impl::PseudoElementSelector),
} }
/// * `Err(())`: Invalid selector, abort /// * `Err(())`: Invalid selector, abort
@ -1210,7 +1210,7 @@ fn parse_compound_selector<P, Impl>(
input: &mut CssParser, input: &mut CssParser,
mut sequence: &mut ParseVec<Impl>, mut sequence: &mut ParseVec<Impl>,
inside_negation: bool) inside_negation: bool)
-> Result<Option<Impl::PseudoElement>, ()> -> Result<Option<Impl::PseudoElementSelector>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl where P: Parser<Impl=Impl>, Impl: SelectorImpl
{ {
// Consume any leading whitespace. // Consume any leading whitespace.
@ -1335,7 +1335,7 @@ fn parse_one_simple_selector<P, Impl>(parser: &P,
name.eq_ignore_ascii_case("after") || name.eq_ignore_ascii_case("after") ||
name.eq_ignore_ascii_case("first-line") || name.eq_ignore_ascii_case("first-line") ||
name.eq_ignore_ascii_case("first-letter") { 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))) Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element)))
} else { } else {
let pseudo_class = parse_simple_pseudo_class(parser, name)?; let pseudo_class = parse_simple_pseudo_class(parser, name)?;
@ -1351,7 +1351,7 @@ fn parse_one_simple_selector<P, Impl>(parser: &P,
Ok(Token::Colon) => { Ok(Token::Colon) => {
match input.next_including_whitespace() { match input.next_including_whitespace() {
Ok(Token::Ident(name)) => { 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))) Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo)))
} }
_ => Err(()) _ => Err(())
@ -1455,7 +1455,7 @@ pub mod tests {
type BorrowedLocalName = DummyAtom; type BorrowedLocalName = DummyAtom;
type BorrowedNamespaceUrl = DummyAtom; type BorrowedNamespaceUrl = DummyAtom;
type NonTSPseudoClass = PseudoClass; type NonTSPseudoClass = PseudoClass;
type PseudoElement = PseudoElement; type PseudoElementSelector = PseudoElement;
} }
#[derive(Default, Debug, Hash, Clone, PartialEq, Eq)] #[derive(Default, Debug, Hash, Clone, PartialEq, Eq)]
@ -1505,7 +1505,7 @@ pub mod tests {
} }
} }
fn parse_pseudo_element(&self, name: Cow<str>) fn parse_pseudo_element(&self, name: Cow<str>, input: &mut CssParser)
-> Result<PseudoElement, ()> { -> Result<PseudoElement, ()> {
match_ignore_ascii_case! { &name, match_ignore_ascii_case! { &name,
"before" => Ok(PseudoElement::Before), "before" => Ok(PseudoElement::Before),

View file

@ -5,6 +5,7 @@
//! Gecko-specific bits for selector-parsing. //! Gecko-specific bits for selector-parsing.
use cssparser::{Parser, ToCss}; use cssparser::{Parser, ToCss};
use element_state::{IN_ACTIVE_STATE, IN_FOCUS_STATE, IN_HOVER_STATE};
use element_state::ElementState; use element_state::ElementState;
use gecko_bindings::structs::CSSPseudoClassType; use gecko_bindings::structs::CSSPseudoClassType;
use gecko_bindings::structs::nsIAtom; use gecko_bindings::structs::nsIAtom;
@ -325,6 +326,15 @@ impl NonTSPseudoClass {
apply_non_ts_list!(pseudo_class_check_internal) 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. /// Get the state flag associated with a pseudo-class, if any.
pub fn state_flag(&self) -> ElementState { pub fn state_flag(&self) -> ElementState {
macro_rules! flag { macro_rules! flag {
@ -375,6 +385,53 @@ impl NonTSPseudoClass {
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SelectorImpl; 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<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 { impl ::selectors::SelectorImpl for SelectorImpl {
type AttrValue = Atom; type AttrValue = Atom;
type Identifier = Atom; type Identifier = Atom;
@ -385,7 +442,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type BorrowedNamespaceUrl = WeakNamespace; type BorrowedNamespaceUrl = WeakNamespace;
type BorrowedLocalName = WeakAtom; type BorrowedLocalName = WeakAtom;
type PseudoElement = PseudoElement; type PseudoElementSelector = PseudoElementSelector;
type NonTSPseudoClass = NonTSPseudoClass; type NonTSPseudoClass = NonTSPseudoClass;
} }
@ -447,11 +504,34 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
} }
} }
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> { fn parse_pseudo_element(&self, name: Cow<str>, input: &mut Parser) -> Result<PseudoElementSelector, ()> {
match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) { let pseudo =
Some(pseudo) => Ok(pseudo), match PseudoElement::from_slice(&name, self.in_user_agent_stylesheet()) {
None => Err(()), 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<Namespace> { fn default_namespace(&self) -> Option<Namespace> {

View file

@ -78,6 +78,18 @@ impl ToCss for PseudoElement {
pub const EAGER_PSEUDO_COUNT: usize = 3; pub const EAGER_PSEUDO_COUNT: usize = 3;
impl PseudoElement { 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. /// Gets the canonical index of this eagerly-cascaded pseudo-element.
#[inline] #[inline]
pub fn eager_index(&self) -> usize { pub fn eager_index(&self) -> usize {
@ -252,7 +264,7 @@ impl NonTSPseudoClass {
pub struct SelectorImpl; pub struct SelectorImpl;
impl ::selectors::SelectorImpl for SelectorImpl { impl ::selectors::SelectorImpl for SelectorImpl {
type PseudoElement = PseudoElement; type PseudoElementSelector = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass; type NonTSPseudoClass = NonTSPseudoClass;
type AttrValue = String; type AttrValue = String;
@ -311,7 +323,10 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
Ok(pseudo_class) Ok(pseudo_class)
} }
fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> { fn parse_pseudo_element(&self,
name: Cow<str>,
_input: &mut CssParser)
-> Result<PseudoElement, ()> {
use self::PseudoElement::*; use self::PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { &name, let pseudo_element = match_ignore_ascii_case! { &name,
"before" => Before, "before" => Before,

View file

@ -449,9 +449,9 @@ impl Stylist {
rule: &Arc<Locked<StyleRule>>, rule: &Arc<Locked<StyleRule>>,
stylesheet: &Stylesheet) 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 self.pseudos_map
.entry(pseudo.clone()) .entry(pseudo_selector.pseudo_element().clone())
.or_insert_with(PerPseudoElementSelectorMap::new) .or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin) .borrow_for_origin(&stylesheet.origin)
} else { } else {