aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEmilio Cobos Álvarez <emilio@crisal.io>2019-04-16 13:16:56 +0000
committerEmilio Cobos Álvarez <emilio@crisal.io>2019-05-07 12:55:23 +0200
commit09d497db3d05773d7cf1b33b7bb261bab1b9413e (patch)
tree69a550481b88b13a229b6fa45a71fc4eef9fbaad
parent498a163cdfbdcb260fb0149cb5103fe1cd6f3e34 (diff)
downloadservo-09d497db3d05773d7cf1b33b7bb261bab1b9413e.tar.gz
servo-09d497db3d05773d7cf1b33b7bb261bab1b9413e.zip
style: Refactor the selector parser to make implementing ::part() easier.
::slotted() is already weird in the sense that it supports a pseudo-element afterwards (so ::slotted(*)::before is valid for example). ::part() is weirder because you are supposed to allow stuff like ::part(foo):hover, ::part(foo):hover::before, etc. In order to avoid making the already-complex parse_compound_selector more complex, shuffle stuff so that we pass the progress of our current compound selector around, and is the parsing code for each selector which decides whether it's ok to parse at the given point. Differential Revision: https://phabricator.services.mozilla.com/D27158
-rw-r--r--components/selectors/parser.rs349
-rw-r--r--components/style/gecko/pseudo_element.rs12
-rw-r--r--components/style/gecko/selector_parser.rs19
3 files changed, 189 insertions, 191 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs
index 0515eaca193..afb7280508d 100644
--- a/components/selectors/parser.rs
+++ b/components/selectors/parser.rs
@@ -30,17 +30,10 @@ pub trait PseudoElement: Sized + ToCss {
/// 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
- }
+ fn accepts_state_pseudo_classes(&self) -> bool { false }
/// Whether this pseudo-element is valid after a ::slotted(..) pseudo.
- fn valid_after_slotted(&self) -> bool {
- false
- }
+ fn valid_after_slotted(&self) -> bool { false }
}
/// A trait that represents a pseudo-class.
@@ -50,6 +43,11 @@ pub trait NonTSPseudoClass: Sized + ToCss {
/// Whether this pseudo-class is :active or :hover.
fn is_active_or_hover(&self) -> bool;
+
+ /// Whether this pseudo-class belongs to:
+ ///
+ /// https://drafts.csswg.org/selectors-4/#useraction-pseudos
+ fn is_user_action_state(&self) -> bool;
}
/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a
@@ -64,6 +62,55 @@ fn to_ascii_lowercase(s: &str) -> Cow<str> {
}
}
+bitflags! {
+ /// Flags that indicate at which point of parsing a selector are we.
+ struct SelectorParsingState: u8 {
+ /// Whether we're inside a negation. If we're inside a negation, we're
+ /// not allowed to add another negation or such, for example.
+ const INSIDE_NEGATION = 1 << 0;
+ /// Whether we've parsed an ::slotted() pseudo-element already.
+ ///
+ /// If so, then we can only parse a subset of pseudo-elements, and
+ /// whatever comes after them if so.
+ const AFTER_SLOTTED = 1 << 1;
+ /// Whether we've parsed a pseudo-element (as in, an
+ /// `Impl::PseudoElement` thus not accounting for `::slotted`) already.
+ ///
+ /// If so, then other pseudo-elements and most other selectors are
+ /// disallowed.
+ const AFTER_PSEUDO_ELEMENT = 1 << 2;
+ /// Whether we've parsed a non-stateful pseudo-element (again, as-in
+ /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are
+ /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
+ /// as well.
+ const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 3;
+ /// Whether we are after any of the pseudo-like things.
+ const AFTER_PSEUDO = Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
+ }
+}
+
+impl SelectorParsingState {
+ #[inline]
+ fn allows_functional_pseudo_classes(self) -> bool {
+ !self.intersects(SelectorParsingState::AFTER_PSEUDO)
+ }
+
+ #[inline]
+ fn allows_slotted(self) -> bool {
+ !self.intersects(SelectorParsingState::AFTER_PSEUDO)
+ }
+
+ #[inline]
+ fn allows_non_functional_pseudo_classes(self) -> bool {
+ !self.intersects(SelectorParsingState::AFTER_SLOTTED | SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
+ }
+
+ #[inline]
+ fn allows_tree_structural_pseudo_classes(self) -> bool {
+ !self.intersects(SelectorParsingState::AFTER_PSEUDO)
+ }
+}
+
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
#[derive(Clone, Debug, PartialEq)]
@@ -76,6 +123,7 @@ pub enum SelectorParseErrorKind<'i> {
NonCompoundSelector,
NonPseudoElementAfterSlotted,
InvalidPseudoElementAfterSlotted,
+ InvalidState,
UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedColon(Token<'i>),
PseudoElementExpectedIdent(Token<'i>),
@@ -1369,9 +1417,9 @@ where
'outer_loop: loop {
// Parse a sequence of simple selectors.
match parse_compound_selector(parser, input, &mut builder)? {
- Some((has_pseudo, slot)) => {
- has_pseudo_element = has_pseudo;
- slotted = slot;
+ Some(state) => {
+ has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
+ slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
},
None => {
return Err(input.new_custom_error(if builder.has_combinators() {
@@ -1848,7 +1896,7 @@ where
Err(e) => return Err(e.into()),
};
if !is_type_sel {
- match parse_one_simple_selector(parser, input, /* inside_negation = */ true)? {
+ match parse_one_simple_selector(parser, input, SelectorParsingState::INSIDE_NEGATION)? {
Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
sequence.push(s);
},
@@ -1875,14 +1923,11 @@ where
///
/// `Err(())` means invalid selector.
/// `Ok(None)` is an empty selector
-///
-/// The booleans represent whether a pseudo-element has been parsed, and whether
-/// ::slotted() has been parsed, respectively.
fn parse_compound_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
builder: &mut SelectorBuilder<Impl>,
-) -> Result<Option<(bool, bool)>, ParseError<'i, P::Error>>
+) -> Result<Option<SelectorParsingState>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
@@ -1901,122 +1946,44 @@ where
empty = false;
}
- let mut pseudo = false;
- let mut slot = false;
+ let mut state = SelectorParsingState::empty();
loop {
let parse_result =
- match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
+ match parse_one_simple_selector(parser, input, state)? {
None => break,
Some(result) => result,
};
empty = false;
- let slotted_selector;
- let pseudo_element;
-
match parse_result {
SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s);
- continue;
- },
- SimpleSelectorParseResult::PseudoElement(p) => {
- slotted_selector = None;
- pseudo_element = Some(p);
},
SimpleSelectorParseResult::SlottedPseudo(selector) => {
- slotted_selector = Some(selector);
- let maybe_pseudo =
- parse_one_simple_selector(parser, input, /* inside_negation = */ false)?;
-
- pseudo_element = match maybe_pseudo {
- None => None,
- Some(SimpleSelectorParseResult::PseudoElement(pseudo)) => {
- if !pseudo.valid_after_slotted() {
- return Err(input.new_custom_error(
- SelectorParseErrorKind::InvalidPseudoElementAfterSlotted,
- ));
- }
- Some(pseudo)
- },
- Some(SimpleSelectorParseResult::SimpleSelector(..)) |
- Some(SimpleSelectorParseResult::SlottedPseudo(..)) => {
- return Err(input.new_custom_error(
- SelectorParseErrorKind::NonPseudoElementAfterSlotted,
- ));
- },
- };
+ state.insert(SelectorParsingState::AFTER_SLOTTED);
+ if !builder.is_empty() {
+ builder.push_combinator(Combinator::SlotAssignment);
+ }
+ builder.push_simple_selector(Component::Slotted(selector));
},
- }
-
- debug_assert!(slotted_selector.is_some() || pseudo_element.is_some());
- // Try to parse state to the right of the pseudo-element.
- //
- // There are only 3 allowable state selectors that can go on
- // pseudo-elements as of right now.
- let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new();
- if let Some(ref p) = pseudo_element {
- loop {
- let location = input.current_source_location();
- match input.next_including_whitespace() {
- Ok(&Token::Colon) => {},
- Ok(&Token::WhiteSpace(_)) | Err(_) => break,
- Ok(t) => {
- let e = SelectorParseErrorKind::PseudoElementExpectedColon(t.clone());
- return Err(location.new_custom_error(e));
- },
+ SimpleSelectorParseResult::PseudoElement(p) => {
+ state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
+ if !p.accepts_state_pseudo_classes() {
+ state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
}
-
- let location = input.current_source_location();
- // TODO(emilio): Functional pseudo-classes too?
- // We don't need it for now.
- let name = match input.next_including_whitespace()? {
- &Token::Ident(ref name) => name.clone(),
- t => {
- return Err(location.new_custom_error(
- SelectorParseErrorKind::NoIdentForPseudo(t.clone()),
- ));
- },
- };
-
- let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name.clone())?;
- if !p.supports_pseudo_class(&pseudo_class) {
- return Err(input.new_custom_error(
- SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
- ));
+ if !builder.is_empty() {
+ builder.push_combinator(Combinator::PseudoElement);
}
- state_selectors.push(Component::NonTSPseudoClass(pseudo_class));
- }
- }
-
- if let Some(slotted) = slotted_selector {
- slot = true;
- if !builder.is_empty() {
- builder.push_combinator(Combinator::SlotAssignment);
- }
- builder.push_simple_selector(Component::Slotted(slotted));
- }
-
- if let Some(p) = pseudo_element {
- pseudo = true;
- if !builder.is_empty() {
- builder.push_combinator(Combinator::PseudoElement);
- }
-
- builder.push_simple_selector(Component::PseudoElement(p));
-
- for state_selector in state_selectors.drain() {
- builder.push_simple_selector(state_selector);
- }
+ builder.push_simple_selector(Component::PseudoElement(p));
+ },
}
-
- break;
}
if empty {
// An empty selector is invalid.
Ok(None)
} else {
- Ok(Some((pseudo, slot)))
+ Ok(Some(state))
}
}
@@ -2024,12 +1991,18 @@ fn parse_functional_pseudo_class<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
name: CowRcStr<'i>,
- inside_negation: bool,
+ state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
+ if !state.allows_functional_pseudo_classes() {
+ return Err(input.new_custom_error(
+ SelectorParseErrorKind::InvalidState
+ ));
+ }
+ debug_assert!(state.allows_tree_structural_pseudo_classes());
match_ignore_ascii_case! { &name,
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
@@ -2037,11 +2010,12 @@ where
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
"not" => {
- if inside_negation {
+ if state.intersects(SelectorParsingState::INSIDE_NEGATION) {
return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent("not".into())
));
}
+ debug_assert!(state.is_empty());
return parse_negation(parser, input)
},
_ => {}
@@ -2080,37 +2054,52 @@ pub fn is_css2_pseudo_element(name: &str) -> bool {
fn parse_one_simple_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
- inside_negation: bool,
+ state: SelectorParsingState,
) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
let start = input.state();
- // FIXME: remove clone() when lifetimes are non-lexical
- match input.next_including_whitespace().map(|t| t.clone()) {
- Ok(Token::IDHash(id)) => {
+ let token = match input.next_including_whitespace().map(|t| t.clone()) {
+ Ok(t) => t,
+ Err(..) => {
+ input.reset(&start);
+ return Ok(None);
+ }
+ };
+
+ Ok(Some(match token {
+ Token::IDHash(id) => {
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
let id = Component::ID(id.as_ref().into());
- Ok(Some(SimpleSelectorParseResult::SimpleSelector(id)))
+ SimpleSelectorParseResult::SimpleSelector(id)
},
- Ok(Token::Delim('.')) => {
+ Token::Delim('.') => {
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
let location = input.current_source_location();
- match *input.next_including_whitespace()? {
- Token::Ident(ref class) => {
- let class = Component::Class(class.as_ref().into());
- Ok(Some(SimpleSelectorParseResult::SimpleSelector(class)))
- },
+ let class = match *input.next_including_whitespace()? {
+ Token::Ident(ref class) => class,
ref t => {
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
- Err(location.new_custom_error(e))
+ return Err(location.new_custom_error(e))
},
- }
+ };
+ let class = Component::Class(class.as_ref().into());
+ SimpleSelectorParseResult::SimpleSelector(class)
},
- Ok(Token::SquareBracketBlock) => {
+ Token::SquareBracketBlock => {
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
- Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr)))
+ SimpleSelectorParseResult::SimpleSelector(attr)
},
- Ok(Token::Colon) => {
+ Token::Colon => {
let location = input.current_source_location();
let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() {
Token::Colon => (false, input.next_including_whitespace()?.clone()),
@@ -2127,69 +2116,83 @@ where
let is_pseudo_element =
!is_single_colon || P::pseudo_element_allows_single_colon(&name);
if is_pseudo_element {
- let parse_result = if is_functional {
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
+ let pseudo_element = if is_functional {
if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
+ if !state.allows_slotted() {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
let selector = input.parse_nested_block(|input| {
parse_inner_compound_selector(parser, input)
})?;
- SimpleSelectorParseResult::SlottedPseudo(selector)
- } else {
- let selector = input.parse_nested_block(|input| {
- P::parse_functional_pseudo_element(parser, name, input)
- })?;
- SimpleSelectorParseResult::PseudoElement(selector)
+ return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));
}
+ input.parse_nested_block(|input| {
+ P::parse_functional_pseudo_element(parser, name, input)
+ })?
} else {
- SimpleSelectorParseResult::PseudoElement(P::parse_pseudo_element(
- parser, location, name,
- )?)
+ P::parse_pseudo_element(parser, location, name)?
};
- Ok(Some(parse_result))
+
+ if state.intersects(SelectorParsingState::AFTER_SLOTTED) && !pseudo_element.valid_after_slotted() {
+ return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
+ SimpleSelectorParseResult::PseudoElement(pseudo_element)
} else {
let pseudo_class = if is_functional {
input.parse_nested_block(|input| {
- parse_functional_pseudo_class(parser, input, name, inside_negation)
+ parse_functional_pseudo_class(parser, input, name, state)
})?
} else {
- parse_simple_pseudo_class(parser, location, name)?
+ parse_simple_pseudo_class(parser, location, name, state)?
};
- Ok(Some(SimpleSelectorParseResult::SimpleSelector(
- pseudo_class,
- )))
+ SimpleSelectorParseResult::SimpleSelector(pseudo_class)
}
},
_ => {
input.reset(&start);
- Ok(None)
+ return Ok(None)
},
- }
+ }))
}
fn parse_simple_pseudo_class<'i, P, Impl>(
parser: &P,
location: SourceLocation,
name: CowRcStr<'i>,
+ state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
- (match_ignore_ascii_case! { &name,
- "first-child" => Ok(Component::FirstChild),
- "last-child" => Ok(Component::LastChild),
- "only-child" => Ok(Component::OnlyChild),
- "root" => Ok(Component::Root),
- "empty" => Ok(Component::Empty),
- "scope" => Ok(Component::Scope),
- "host" if P::parse_host(parser) => Ok(Component::Host(None)),
- "first-of-type" => Ok(Component::FirstOfType),
- "last-of-type" => Ok(Component::LastOfType),
- "only-of-type" => Ok(Component::OnlyOfType),
- _ => Err(())
- })
- .or_else(|()| {
- P::parse_non_ts_pseudo_class(parser, location, name).map(Component::NonTSPseudoClass)
- })
+ if !state.allows_non_functional_pseudo_classes() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
+
+ if state.allows_tree_structural_pseudo_classes() {
+ match_ignore_ascii_case! { &name,
+ "first-child" => return Ok(Component::FirstChild),
+ "last-child" => return Ok(Component::LastChild),
+ "only-child" => return Ok(Component::OnlyChild),
+ "root" => return Ok(Component::Root),
+ "empty" => return Ok(Component::Empty),
+ "scope" => return Ok(Component::Scope),
+ "host" if P::parse_host(parser) => return Ok(Component::Host(None)),
+ "first-of-type" => return Ok(Component::FirstOfType),
+ "last-of-type" => return Ok(Component::LastOfType),
+ "only-of-type" => return Ok(Component::OnlyOfType),
+ _ => {},
+ }
+ }
+
+ let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?;
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) && !pseudo_class.is_user_action_state() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
+ }
+ Ok(Component::NonTSPseudoClass(pseudo_class))
}
// NB: pub module in order to access the DummyParser
@@ -2218,16 +2221,9 @@ pub mod tests {
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,
- }
- }
+ fn accepts_state_pseudo_classes(&self) -> bool { true }
- fn valid_after_slotted(&self) -> bool {
- true
- }
+ fn valid_after_slotted(&self) -> bool { true }
}
impl parser::NonTSPseudoClass for PseudoClass {
@@ -2237,6 +2233,11 @@ pub mod tests {
fn is_active_or_hover(&self) -> bool {
matches!(*self, PseudoClass::Active | PseudoClass::Hover)
}
+
+ #[inline]
+ fn is_user_action_state(&self) -> bool {
+ self.is_active_or_hover()
+ }
}
impl ToCss for PseudoClass {
@@ -2789,11 +2790,11 @@ pub mod tests {
specificity(0, 2, 1) | HAS_PSEUDO_BIT,
)]))
);
- assert!(parse("::before:hover:active").is_err());
+ assert!(parse("::before:hover:lang(foo)").is_err());
assert!(parse("::before:hover .foo").is_err());
assert!(parse("::before .foo").is_err());
assert!(parse("::before ~ bar").is_err());
- assert!(parse("::before:active").is_err());
+ assert!(parse("::before:active").is_ok());
// https://github.com/servo/servo/issues/15335
assert!(parse(":: before").is_err());
diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs
index 479c12b9cde..7538b785858 100644
--- a/components/style/gecko/pseudo_element.rs
+++ b/components/style/gecko/pseudo_element.rs
@@ -11,7 +11,7 @@
use crate::gecko_bindings::structs::{self, PseudoStyleType};
use crate::properties::longhands::display::computed_value::T as Display;
use crate::properties::{ComputedValues, PropertyFlags};
-use crate::selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
+use crate::selector_parser::{PseudoElementCascadeType, SelectorImpl};
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::string_cache::Atom;
use crate::values::serialize_atom_identifier;
@@ -30,6 +30,7 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
// ::slotted() should support all tree-abiding pseudo-elements, see
// https://drafts.csswg.org/css-scoping/#slotted-pseudo
// https://drafts.csswg.org/css-pseudo-4/#treelike
+ #[inline]
fn valid_after_slotted(&self) -> bool {
matches!(
*self,
@@ -40,12 +41,9 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
)
}
- 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();
+ #[inline]
+ fn accepts_state_pseudo_classes(&self) -> bool {
+ self.supports_user_action_state()
}
}
diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs
index 41de05c33b9..36558b9f5db 100644
--- a/components/style/gecko/selector_parser.rs
+++ b/components/style/gecko/selector_parser.rs
@@ -184,16 +184,6 @@ impl NonTSPseudoClass {
}
}
- /// <https://drafts.csswg.org/selectors-4/#useraction-pseudos>
- ///
- /// We intentionally skip the link-related ones.
- pub 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 {
@@ -279,6 +269,15 @@ impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
fn is_active_or_hover(&self) -> bool {
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
}
+
+ /// We intentionally skip the link-related ones.
+ #[inline]
+ fn is_user_action_state(&self) -> bool {
+ matches!(
+ *self,
+ NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus
+ )
+ }
}
/// The dummy struct we use to implement our selector parsing.