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 { ) -> bool {
false 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. /// A trait that represents a pseudo-class.
@ -69,6 +74,8 @@ pub enum SelectorParseErrorKind<'i> {
DanglingCombinator, DanglingCombinator,
NonSimpleSelectorInNegation, NonSimpleSelectorInNegation,
NonCompoundSelector, NonCompoundSelector,
NonPseudoElementAfterSlotted,
InvalidPseudoElementAfterSlotted,
UnexpectedTokenInAttributeSelector(Token<'i>), UnexpectedTokenInAttributeSelector(Token<'i>),
PseudoElementExpectedColon(Token<'i>), PseudoElementExpectedColon(Token<'i>),
PseudoElementExpectedIdent(Token<'i>), PseudoElementExpectedIdent(Token<'i>),
@ -1832,7 +1839,6 @@ where
input.skip_whitespace(); input.skip_whitespace();
let mut empty = true; let mut empty = true;
let mut slot = false;
if !parse_type_selector(parser, input, builder)? { if !parse_type_selector(parser, input, builder)? {
if let Some(url) = parser.default_namespace() { if let Some(url) = parser.default_namespace() {
// If there was no explicit type selector, but there is a // If there was no explicit type selector, but there is a
@ -1845,6 +1851,7 @@ where
} }
let mut pseudo = false; let mut pseudo = false;
let mut slot = false;
loop { loop {
let parse_result = let parse_result =
match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? { match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
@ -1852,75 +1859,111 @@ where
Some(result) => result, Some(result) => result,
}; };
empty = false;
let slotted_selector;
let pseudo_element;
match parse_result { match parse_result {
SimpleSelectorParseResult::SimpleSelector(s) => { SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s); builder.push_simple_selector(s);
empty = false continue;
}, },
SimpleSelectorParseResult::PseudoElement(p) => { SimpleSelectorParseResult::PseudoElement(p) => {
// Try to parse state to its right. There are only 3 allowable slotted_selector = None;
// state selectors that can go on pseudo-elements. pseudo_element = Some(p);
let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new(); }
SimpleSelectorParseResult::SlottedPseudo(selector) => {
slotted_selector = Some(selector);
let maybe_pseudo = parse_one_simple_selector(
parser,
input,
/* inside_negation = */ false,
)?;
loop { pseudo_element = match maybe_pseudo {
let location = input.current_source_location(); None => None,
match input.next_including_whitespace() { Some(SimpleSelectorParseResult::PseudoElement(pseudo)) => {
Ok(&Token::Colon) => {}, if !pseudo.valid_after_slotted() {
Ok(&Token::WhiteSpace(_)) | Err(_) => break, return Err(input.new_custom_error(
Ok(t) => { SelectorParseErrorKind::InvalidPseudoElementAfterSlotted
let e = SelectorParseErrorKind::PseudoElementExpectedColon(t.clone()); ));
return Err(location.new_custom_error(e)); }
}, Some(pseudo)
} }
Some(SimpleSelectorParseResult::SimpleSelector(..)) |
let location = input.current_source_location(); Some(SimpleSelectorParseResult::SlottedPseudo(..)) => {
// 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( 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);
}
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;
},
} }
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));
},
}
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),
));
}
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 { if empty {
// An empty selector is invalid. // An empty selector is invalid.
@ -2133,6 +2176,10 @@ pub mod tests {
PseudoClass::Active | PseudoClass::Lang(..) => false, PseudoClass::Active | PseudoClass::Lang(..) => false,
} }
} }
fn valid_after_slotted(&self) -> bool {
true
}
} }
impl parser::NonTSPseudoClass for PseudoClass { 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)").is_ok());
assert!(parse("div + slot::slotted(div.foo)").is_ok()); assert!(parse("div + slot::slotted(div.foo)").is_ok());
assert!(parse("slot::slotted(div,foo)::first-line").is_err()); assert!(parse("slot::slotted(div,foo)::first-line").is_err());
// TODO assert!(parse("::slotted(div)::before").is_ok());
assert!(parse("::slotted(div)::before").is_err());
assert!(parse("slot::slotted(div,foo)").is_err()); assert!(parse("slot::slotted(div,foo)").is_err());
} }

View file

@ -27,6 +27,14 @@ include!(concat!(
impl ::selectors::parser::PseudoElement for PseudoElement { impl ::selectors::parser::PseudoElement for PseudoElement {
type Impl = SelectorImpl; 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 { fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
if !self.supports_user_action_state() { if !self.supports_user_action_state() {
return false; return false;