style: Support multiple parts in ::part() selectors.

Differential Revision: https://phabricator.services.mozilla.com/D48753
This commit is contained in:
Emilio Cobos Álvarez 2019-10-17 00:51:54 +00:00
parent f701192e38
commit 7965ddefa6
3 changed files with 30 additions and 15 deletions

View file

@ -667,7 +667,7 @@ where
match *selector { match *selector {
Component::Combinator(_) => unreachable!(), Component::Combinator(_) => unreachable!(),
Component::Part(ref part) => element.is_part(part), Component::Part(ref parts) => parts.iter().all(|part| element.is_part(part)),
Component::Slotted(ref selector) => { Component::Slotted(ref selector) => {
// <slots> are never flattened tree slottables. // <slots> are never flattened tree slottables.
!element.is_html_slot_element() && !element.is_html_slot_element() &&

View file

@ -607,7 +607,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
} }
#[inline] #[inline]
pub fn part(&self) -> Option<&Impl::PartName> { pub fn parts(&self) -> Option<&[Impl::PartName]> {
if !self.is_part() { if !self.is_part() {
return None; return None;
} }
@ -1013,7 +1013,7 @@ pub enum Component<Impl: SelectorImpl> {
Slotted(Selector<Impl>), Slotted(Selector<Impl>),
/// The `::part` pseudo-element. /// The `::part` pseudo-element.
/// https://drafts.csswg.org/css-shadow-parts/#part /// https://drafts.csswg.org/css-shadow-parts/#part
Part(#[shmem(field_bound)] Impl::PartName), Part(#[shmem(field_bound)] Box<[Impl::PartName]>),
/// The `:host` pseudo-class: /// The `:host` pseudo-class:
/// ///
/// https://drafts.csswg.org/css-scoping/#host-selector /// https://drafts.csswg.org/css-scoping/#host-selector
@ -1302,9 +1302,14 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
selector.to_css(dest)?; selector.to_css(dest)?;
dest.write_char(')') dest.write_char(')')
}, },
Part(ref part_name) => { Part(ref part_names) => {
dest.write_str("::part(")?; dest.write_str("::part(")?;
display_to_css_identifier(part_name, dest)?; for (i, name) in part_names.iter().enumerate() {
if i != 0 {
dest.write_char(' ')?;
}
display_to_css_identifier(name, dest)?;
}
dest.write_char(')') dest.write_char(')')
}, },
PseudoElement(ref p) => p.to_css(dest), PseudoElement(ref p) => p.to_css(dest),
@ -1626,7 +1631,7 @@ enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>), SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElement), PseudoElement(Impl::PseudoElement),
SlottedPseudo(Selector<Impl>), SlottedPseudo(Selector<Impl>),
PartPseudo(Impl::PartName), PartPseudo(Box<[Impl::PartName]>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -2029,10 +2034,10 @@ where
SimpleSelectorParseResult::SimpleSelector(s) => { SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s); builder.push_simple_selector(s);
}, },
SimpleSelectorParseResult::PartPseudo(part_name) => { SimpleSelectorParseResult::PartPseudo(part_names) => {
state.insert(SelectorParsingState::AFTER_PART); state.insert(SelectorParsingState::AFTER_PART);
builder.push_combinator(Combinator::Part); builder.push_combinator(Combinator::Part);
builder.push_simple_selector(Component::Part(part_name)); builder.push_simple_selector(Component::Part(part_names));
}, },
SimpleSelectorParseResult::SlottedPseudo(selector) => { SimpleSelectorParseResult::SlottedPseudo(selector) => {
state.insert(SelectorParsingState::AFTER_SLOTTED); state.insert(SelectorParsingState::AFTER_SLOTTED);
@ -2193,10 +2198,15 @@ where
input.new_custom_error(SelectorParseErrorKind::InvalidState) input.new_custom_error(SelectorParseErrorKind::InvalidState)
); );
} }
let name = input.parse_nested_block(|input| { let names = input.parse_nested_block(|input| {
Ok(input.expect_ident()?.as_ref().into()) let mut result = Vec::with_capacity(1);
result.push(input.expect_ident()?.as_ref().into());
while !input.is_exhausted() {
result.push(input.expect_ident()?.as_ref().into());
}
Ok(result.into_boxed_slice())
})?; })?;
return Ok(Some(SimpleSelectorParseResult::PartPseudo(name))); return Ok(Some(SimpleSelectorParseResult::PartPseudo(names)));
} }
if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
if !state.allows_slotted() { if !state.allows_slotted() {
@ -3051,8 +3061,7 @@ pub mod tests {
assert!(parse("::part()").is_err()); assert!(parse("::part()").is_err());
assert!(parse("::part(42)").is_err()); assert!(parse("::part(42)").is_err());
// Though note https://github.com/w3c/csswg-drafts/issues/3502 assert!(parse("::part(foo bar)").is_ok());
assert!(parse("::part(foo bar)").is_err());
assert!(parse("::part(foo):hover").is_ok()); assert!(parse("::part(foo):hover").is_ok());
assert!(parse("::part(foo) + bar").is_err()); assert!(parse("::part(foo) + bar").is_err());

View file

@ -2032,11 +2032,17 @@ impl CascadeData {
// Part is special, since given it doesn't have any // Part is special, since given it doesn't have any
// selectors inside, it's not worth using a whole // selectors inside, it's not worth using a whole
// SelectorMap for it. // SelectorMap for it.
if let Some(part) = selector.part() { if let Some(parts) = selector.parts() {
// ::part() has all semantics, so we just need to
// put any of them in the selector map.
//
// We choose the last one quite arbitrarily,
// expecting it's slightly more likely to be more
// specific.
self.part_rules self.part_rules
.get_or_insert_with(|| Box::new(Default::default())) .get_or_insert_with(|| Box::new(Default::default()))
.for_insertion(pseudo_element) .for_insertion(pseudo_element)
.try_entry(part.clone())? .try_entry(parts.last().unwrap().clone())?
.or_insert_with(SmallVec::new) .or_insert_with(SmallVec::new)
.try_push(rule)?; .try_push(rule)?;
} else { } else {