aboutsummaryrefslogtreecommitdiffstats
path: root/components/selectors/parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r--components/selectors/parser.rs446
1 files changed, 259 insertions, 187 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs
index 0515eaca193..a924bbc17d0 100644
--- a/components/selectors/parser.rs
+++ b/components/selectors/parser.rs
@@ -30,10 +30,7 @@ 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 {
+ fn accepts_state_pseudo_classes(&self) -> bool {
false
}
@@ -50,6 +47,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 +66,72 @@ 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 a ::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 ::part() pseudo-element already.
+ ///
+ /// If so, then we can only parse a subset of pseudo-elements, and
+ /// whatever comes after them if so.
+ const AFTER_PART = 1 << 2;
+ /// Whether we've parsed a pseudo-element (as in, an
+ /// `Impl::PseudoElement` thus not accounting for `::slotted` or
+ /// `::part`) already.
+ ///
+ /// If so, then other pseudo-elements and most other selectors are
+ /// disallowed.
+ const AFTER_PSEUDO_ELEMENT = 1 << 3;
+ /// 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 << 4;
+ /// Whether we are after any of the pseudo-like things.
+ const AFTER_PSEUDO = Self::AFTER_PART.bits | 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)
+ }
+
+ // TODO(emilio): Should we allow other ::part()s after ::part()?
+ //
+ // See https://github.com/w3c/csswg-drafts/issues/3841
+ #[inline]
+ fn allows_part(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 +144,7 @@ pub enum SelectorParseErrorKind<'i> {
NonCompoundSelector,
NonPseudoElementAfterSlotted,
InvalidPseudoElementAfterSlotted,
+ InvalidState,
UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedColon(Token<'i>),
PseudoElementExpectedIdent(Token<'i>),
@@ -108,6 +177,7 @@ macro_rules! with_all_bounds {
type AttrValue: $($InSelector)*;
type Identifier: $($InSelector)*;
type ClassName: $($InSelector)*;
+ type PartName: $($InSelector)*;
type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;
type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
type NamespacePrefix: $($InSelector)* + Default;
@@ -143,17 +213,16 @@ pub trait Parser<'i> {
type Impl: SelectorImpl;
type Error: 'i + From<SelectorParseErrorKind<'i>>;
- /// Whether the name is a pseudo-element that can be specified with
- /// the single colon syntax in addition to the double-colon syntax.
- fn pseudo_element_allows_single_colon(name: &str) -> bool {
- is_css2_pseudo_element(name)
- }
-
/// Whether to parse the `::slotted()` pseudo-element.
fn parse_slotted(&self) -> bool {
false
}
+ /// Whether to parse the `::part()` pseudo-element.
+ fn parse_part(&self) -> bool {
+ false
+ }
+
/// Whether to parse the `:host` pseudo-class.
fn parse_host(&self) -> bool {
false
@@ -694,7 +763,8 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
/// combinators to the left.
#[inline]
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
- self.all(|component| matches!(*component, Component::Host(..))) &&
+ self.selector_length() > 0 &&
+ self.all(|component| matches!(*component, Component::Host(..))) &&
self.next_sequence().is_none()
}
@@ -798,6 +868,9 @@ pub enum Combinator {
/// Another combinator used for ::slotted(), which represent the jump from
/// a node to its assigned slot.
SlotAssignment,
+ /// Another combinator used for `::part()`, which represents the jump from
+ /// the part to the containing shadow host.
+ Part,
}
impl Combinator {
@@ -891,8 +964,7 @@ pub enum Component<Impl: SelectorImpl> {
LastOfType,
OnlyOfType,
NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass),
- /// The ::slotted() pseudo-element (which isn't actually a pseudo-element,
- /// and probably should be a pseudo-class):
+ /// The ::slotted() pseudo-element:
///
/// https://drafts.csswg.org/css-scoping/#slotted-pseudo
///
@@ -904,6 +976,9 @@ pub enum Component<Impl: SelectorImpl> {
///
/// See https://github.com/w3c/csswg-drafts/issues/2158
Slotted(Selector<Impl>),
+ /// The `::part` pseudo-element.
+ /// https://drafts.csswg.org/css-shadow-parts/#part
+ Part(#[shmem(field_bound)] Impl::PartName),
/// The `:host` pseudo-class:
///
/// https://drafts.csswg.org/css-scoping/#host-selector
@@ -1153,8 +1228,7 @@ impl ToCss for Combinator {
Combinator::Descendant => dest.write_str(" "),
Combinator::NextSibling => dest.write_str(" + "),
Combinator::LaterSibling => dest.write_str(" ~ "),
- Combinator::PseudoElement => Ok(()),
- Combinator::SlotAssignment => Ok(()),
+ Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),
}
}
}
@@ -1193,6 +1267,11 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
selector.to_css(dest)?;
dest.write_char(')')
},
+ Part(ref part_name) => {
+ dest.write_str("::part(")?;
+ display_to_css_identifier(part_name, dest)?;
+ dest.write_char(')')
+ },
PseudoElement(ref p) => p.to_css(dest),
ID(ref s) => {
dest.write_char('#')?;
@@ -1364,15 +1443,12 @@ where
{
let mut builder = SelectorBuilder::default();
- let mut has_pseudo_element;
- let mut slotted;
+ let mut has_pseudo_element = false;
+ let mut slotted = false;
'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;
- },
+ let state = match parse_compound_selector(parser, input, &mut builder)? {
+ Some(state) => state,
None => {
return Err(input.new_custom_error(if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
@@ -1382,7 +1458,11 @@ where
},
};
- if has_pseudo_element || slotted {
+ if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
+ has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
+ slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
+ let part = state.intersects(SelectorParsingState::AFTER_PART);
+ debug_assert!(has_pseudo_element || slotted || part);
break;
}
@@ -1420,6 +1500,8 @@ where
builder.push_combinator(combinator);
}
+ // TODO(emilio): We'll have to flag part() somehow as well, but we need more
+ // bits!
Ok(Selector(builder.build(has_pseudo_element, slotted)))
}
@@ -1510,6 +1592,7 @@ enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElement),
SlottedPseudo(Selector<Impl>),
+ PartPseudo(Impl::PartName),
}
#[derive(Debug)]
@@ -1848,7 +1931,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);
},
@@ -1856,6 +1939,7 @@ where
return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
},
Some(SimpleSelectorParseResult::PseudoElement(_)) |
+ Some(SimpleSelectorParseResult::PartPseudo(_)) |
Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
return Err(input.new_custom_error(e));
@@ -1875,14 +1959,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 +1982,48 @@ 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)? {
- None => break,
- Some(result) => result,
- };
+ let parse_result = 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::PartPseudo(part_name) => {
+ state.insert(SelectorParsingState::AFTER_PART);
+ builder.push_combinator(Combinator::Part);
+ builder.push_simple_selector(Component::Part(part_name));
},
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 +2031,16 @@ 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 +2048,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)
},
_ => {}
@@ -2064,7 +2076,7 @@ where
/// Returns whether the name corresponds to a CSS2 pseudo-element that
/// can be specified with the single colon syntax (in addition to the
/// double-colon syntax, which can be used for all pseudo-elements).
-pub fn is_css2_pseudo_element(name: &str) -> bool {
+fn is_css2_pseudo_element(name: &str) -> bool {
// ** Do not add to this list! **
match_ignore_ascii_case! { name,
"before" | "after" | "first-line" | "first-letter" => true,
@@ -2080,37 +2092,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()),
@@ -2124,72 +2151,102 @@ where
return Err(input.new_custom_error(e));
},
};
- let is_pseudo_element =
- !is_single_colon || P::pseudo_element_allows_single_colon(&name);
+ let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&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_part(parser) && name.eq_ignore_ascii_case("part") {
+ if !state.allows_part() {
+ return Err(
+ input.new_custom_error(SelectorParseErrorKind::InvalidState)
+ );
+ }
+ let name = input.parse_nested_block(|input| {
+ Ok(input.expect_ident()?.as_ref().into())
+ })?;
+ return Ok(Some(SimpleSelectorParseResult::PartPseudo(name)));
+ }
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,11 +2275,8 @@ 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 {
@@ -2237,6 +2291,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 {
@@ -2302,6 +2361,7 @@ pub mod tests {
type AttrValue = DummyAtom;
type Identifier = DummyAtom;
type ClassName = DummyAtom;
+ type PartName = DummyAtom;
type LocalName = DummyAtom;
type NamespaceUrl = DummyAtom;
type NamespacePrefix = DummyAtom;
@@ -2340,6 +2400,10 @@ pub mod tests {
true
}
+ fn parse_part(&self) -> bool {
+ true
+ }
+
fn parse_non_ts_pseudo_class(
&self,
location: SourceLocation,
@@ -2789,11 +2853,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());
@@ -2914,6 +2978,14 @@ pub mod tests {
assert!(parse("::slotted(div).foo").is_err());
assert!(parse("::slotted(div + bar)").is_err());
assert!(parse("::slotted(div) + foo").is_err());
+
+ assert!(parse("::part()").is_err());
+ assert!(parse("::part(42)").is_err());
+ // Though note https://github.com/w3c/csswg-drafts/issues/3502
+ assert!(parse("::part(foo bar)").is_err());
+ assert!(parse("::part(foo):hover").is_ok());
+ assert!(parse("::part(foo) + bar").is_err());
+
assert!(parse("div ::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div.foo)").is_ok());