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.rs160
1 files changed, 103 insertions, 57 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs
index fc4b26bb3a3..3e82a49b348 100644
--- a/components/selectors/parser.rs
+++ b/components/selectors/parser.rs
@@ -36,6 +36,11 @@ pub trait PseudoElement: Sized + ToCss {
) -> bool {
false
}
+
+ /// Whether this pseudo-element is valid after a ::slotted(..) pseudo.
+ fn valid_after_slotted(&self) -> bool {
+ false
+ }
}
/// A trait that represents a pseudo-class.
@@ -69,6 +74,8 @@ pub enum SelectorParseErrorKind<'i> {
DanglingCombinator,
NonSimpleSelectorInNegation,
NonCompoundSelector,
+ NonPseudoElementAfterSlotted,
+ InvalidPseudoElementAfterSlotted,
UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedColon(Token<'i>),
PseudoElementExpectedIdent(Token<'i>),
@@ -1832,7 +1839,6 @@ where
input.skip_whitespace();
let mut empty = true;
- let mut slot = false;
if !parse_type_selector(parser, input, builder)? {
if let Some(url) = parser.default_namespace() {
// If there was no explicit type selector, but there is a
@@ -1845,6 +1851,7 @@ where
}
let mut pseudo = false;
+ let mut slot = false;
loop {
let parse_result =
match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
@@ -1852,75 +1859,111 @@ where
Some(result) => result,
};
+ empty = false;
+
+ let slotted_selector;
+ let pseudo_element;
+
match parse_result {
SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s);
- empty = false
+ continue;
},
SimpleSelectorParseResult::PseudoElement(p) => {
- // Try to parse state to its right. There are only 3 allowable
- // state selectors that can go on pseudo-elements.
- let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new();
-
- 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));
- },
+ 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)
}
-
- 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) {
+ Some(SimpleSelectorParseResult::SimpleSelector(..)) |
+ Some(SimpleSelectorParseResult::SlottedPseudo(..)) => {
return Err(input.new_custom_error(
- SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
+ SelectorParseErrorKind::NonPseudoElementAfterSlotted
));
}
- state_selectors.push(Component::NonTSPseudoClass(pseudo_class));
- }
+ };
+ }
+ }
- if !builder.is_empty() {
- builder.push_combinator(Combinator::PseudoElement);
+ 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));
+ },
}
- builder.push_simple_selector(Component::PseudoElement(p));
- for state_selector in state_selectors.drain() {
- builder.push_simple_selector(state_selector);
- }
+ 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()),
+ ))
+ },
+ };
- pseudo = true;
- empty = false;
- break;
- },
- SimpleSelectorParseResult::SlottedPseudo(selector) => {
- empty = false;
- slot = true;
- if !builder.is_empty() {
- builder.push_combinator(Combinator::SlotAssignment);
+ 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),
+ ));
}
- builder.push_simple_selector(Component::Slotted(selector));
- // FIXME(emilio): ::slotted() should support ::before and
- // ::after after it, so we shouldn't break, but we shouldn't
- // push more type selectors either.
- break;
- },
+ 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);
+ }
+ }
+
+ break;
}
if empty {
// An empty selector is invalid.
@@ -2133,6 +2176,10 @@ pub mod tests {
PseudoClass::Active | PseudoClass::Lang(..) => false,
}
}
+
+ fn valid_after_slotted(&self) -> bool {
+ true
+ }
}
impl parser::NonTSPseudoClass for PseudoClass {
@@ -2818,8 +2865,7 @@ pub mod tests {
assert!(parse("div + slot::slotted(div)").is_ok());
assert!(parse("div + slot::slotted(div.foo)").is_ok());
assert!(parse("slot::slotted(div,foo)::first-line").is_err());
- // TODO
- assert!(parse("::slotted(div)::before").is_err());
+ assert!(parse("::slotted(div)::before").is_ok());
assert!(parse("slot::slotted(div,foo)").is_err());
}