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
This commit is contained in:
Emilio Cobos Álvarez 2018-10-29 23:34:12 +01:00
parent 62aaf865aa
commit badb8f398a
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
2 changed files with 115 additions and 61 deletions

View file

@ -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,16 +1859,55 @@ 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();
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
));
}
};
}
}
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() {
@ -1894,33 +1940,30 @@ where
}
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);
}
}
pseudo = true;
empty = false;
break;
},
SimpleSelectorParseResult::SlottedPseudo(selector) => {
empty = false;
slot = true;
if !builder.is_empty() {
builder.push_combinator(Combinator::SlotAssignment);
}
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;
},
}
}
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());
}

View file

@ -27,6 +27,14 @@ include!(concat!(
impl ::selectors::parser::PseudoElement for PseudoElement {
type Impl = SelectorImpl;
fn valid_after_slotted(&self) -> bool {
// TODO(emilio): Remove this function or this comment after [1] is
// resolved.
//
// [1]: https://github.com/w3c/csswg-drafts/issues/3150
self.is_before_or_after()
}
fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
if !self.supports_user_action_state() {
return false;