mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
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:
parent
62aaf865aa
commit
badb8f398a
2 changed files with 115 additions and 61 deletions
|
@ -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();
|
||||
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,
|
||||
)?;
|
||||
|
||||
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));
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue