diff options
author | Emilio Cobos Álvarez <emilio@crisal.io> | 2018-10-29 23:34:12 +0100 |
---|---|---|
committer | Emilio Cobos Álvarez <emilio@crisal.io> | 2018-11-05 12:29:30 +0100 |
commit | badb8f398aefd244febfaf09a63eb68ec1a09470 (patch) | |
tree | b3d903c0a34020bee5291f4bb24ce2fed7b7f320 /components/selectors/parser.rs | |
parent | 62aaf865aa6ea1346066c04e9a79bdc08859cd5f (diff) | |
download | servo-badb8f398aefd244febfaf09a63eb68ec1a09470.tar.gz servo-badb8f398aefd244febfaf09a63eb68ec1a09470.zip |
style: Support ::before / ::after on ::slotted pseudos.
See https://github.com/w3c/csswg-drafts/issues/3150 for the issue that would
expand this to all pseudos.
Differential Revision: https://phabricator.services.mozilla.com/D9994
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r-- | components/selectors/parser.rs | 160 |
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()); } |