diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 4702f15e2d8..b5b2afca512 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -698,6 +698,7 @@ where Component::Is(ref list) | Component::Where(ref list) | Component::Has(ref list) => { list.size_of(ops) }, + Component::NthOf(ref nth_of_data) => nth_of_data.size_of(ops), Component::PseudoElement(ref pseudo) => (*pseudo).size_of(ops), Component::Combinator(..) | Component::ExplicitAnyNamespace | diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index 0a21fd3f37f..099e25b67fb 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -17,7 +17,7 @@ //! is non-trivial. This module encapsulates those details and presents an //! easy-to-use API for the parser. -use crate::parser::{Combinator, Component, SelectorImpl}; +use crate::parser::{Combinator, Component, Selector, SelectorImpl}; use crate::sink::Push; use bitflags::bitflags; use derive_more::{Add, AddAssign}; @@ -323,17 +323,23 @@ where Component::NonTSPseudoClass(..) => { specificity.class_like_selectors += 1; }, + Component::NthOf(ref nth_of_data) => { + // https://drafts.csswg.org/selectors/#specificity-rules: + // + // The specificity of the :nth-last-child() pseudo-class, + // like the :nth-child() pseudo-class, combines the + // specificity of a regular pseudo-class with that of its + // selector argument S. + specificity.class_like_selectors += 1; + *specificity += selector_list_specificity(nth_of_data.selectors()); + }, Component::Negation(ref list) | Component::Is(ref list) | Component::Has(ref list) => { // https://drafts.csswg.org/selectors/#specificity-rules: // // The specificity of an :is(), :not(), or :has() pseudo-class // is replaced by the specificity of the most specific complex // selector in its selector list argument. - let mut max = 0; - for selector in &**list { - max = std::cmp::max(selector.specificity(), max); - } - *specificity += Specificity::from(max); + *specificity += selector_list_specificity(list); }, Component::Where(..) | Component::ExplicitUniversalType | @@ -346,6 +352,16 @@ where } } + /// Finds the maximum specificity of elements in the list and returns it. + fn selector_list_specificity(list: &[Selector]) -> Specificity { + let max = list + .iter() + .map(|selector| selector.specificity()) + .max() + .unwrap_or(0); + Specificity::from(max) + } + let mut specificity = Default::default(); for simple_selector in iter { simple_selector_specificity(&simple_selector, &mut specificity); diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 2732455c69d..cf8a1da09b8 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -388,7 +388,8 @@ fn hover_and_active_quirk_applies( Component::PseudoElement(_) | Component::Negation(_) | Component::Empty | - Component::Nth(_) => false, + Component::Nth(_) | + Component::NthOf(_) => false, Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), _ => true, }) @@ -803,6 +804,10 @@ where Component::Nth(ref nth_data) => { matches_generic_nth_child(element, context.shared, nth_data) }, + Component::NthOf(ref nth_of_data) => { + // TODO(zrhoffman, bug 1808228): Use selectors() when matching + matches_generic_nth_child(element, context.shared, nth_of_data.nth_data()) + }, Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { for selector in &**list { if matches_complex_selector(selector.iter(), element, context) { diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index bb5f9155d0f..3098cb0e454 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -256,6 +256,11 @@ pub trait Parser<'i> { false } + /// Whether to parse the selector list of nth-child() or nth-last-child(). + fn parse_nth_child_of(&self) -> bool { + false + } + /// Whether to parse the `:where` pseudo-class. fn parse_is_and_where(&self) -> bool { false @@ -1121,6 +1126,38 @@ impl NthSelectorData { } } +/// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g., +/// nth-child(An+B [of S]?)). +/// https://www.w3.org/TR/selectors-4/#nth-child-pseudo +#[derive(Clone, Eq, PartialEq, ToShmem)] +#[shmem(no_bounds)] +pub struct NthOfSelectorData( + #[shmem(field_bound)] ThinArc>, +); + +impl NthOfSelectorData { + /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S]) + #[inline] + pub fn new(nth_data: &NthSelectorData, mut selectors: SelectorList) -> Self { + Self(ThinArc::from_header_and_iter( + *nth_data, + selectors.0.drain(..), + )) + } + + /// Returns the An+B part of the selector + #[inline] + pub fn nth_data(&self) -> &NthSelectorData { + &self.0.header.header + } + + /// Returns the selector list part of the selector + #[inline] + pub fn selectors(&self) -> &[Selector] { + &self.0.slice + } +} + /// A CSS simple selector or combinator. We store both in the same enum for /// optimal packing and cache performance, see [1]. /// @@ -1167,6 +1204,7 @@ pub enum Component { Empty, Scope, Nth(NthSelectorData), + NthOf(NthOfSelectorData), NonTSPseudoClass(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::NonTSPseudoClass), /// The ::slotted() pseudo-element: /// @@ -1358,6 +1396,11 @@ impl Component { return false; } }, + NthOf(ref nth_of_data) => { + if !visitor.visit_selector_list(nth_of_data.selectors()) { + return false; + } + }, _ => {}, } @@ -1687,6 +1730,22 @@ impl ToCss for Component { } Ok(()) }, + NthOf(ref nth_of_data) => { + let nth_data = nth_of_data.nth_data(); + dest.write_str(match nth_data.ty { + NthType::Child => ":nth-child(", + NthType::LastChild => ":nth-last-child(", + _ => unreachable!(), + })?; + write_affine(dest, nth_data.a, nth_data.b)?; + debug_assert!( + !nth_of_data.selectors().is_empty(), + "The selector list should not be empty" + ); + dest.write_str(" of ")?; + serialize_selector_list(nth_of_data.selectors().iter(), dest)?; + dest.write_char(')') + }, Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => { match *self { Where(..) => dest.write_str(":where(")?, @@ -2421,7 +2480,7 @@ where } fn parse_nth_pseudo_class<'i, 't, P, Impl>( - _: &P, + parser: &P, input: &mut CssParser<'i, 't>, state: SelectorParsingState, ty: NthType, @@ -2434,12 +2493,33 @@ where return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } let (a, b) = parse_nth(input)?; - Ok(Component::Nth(NthSelectorData { + let nth_data = NthSelectorData { ty, is_function: true, a, b, - })) + }; + if !parser.parse_nth_child_of() || ty.is_of_type() { + return Ok(Component::Nth(nth_data)); + } + + // Try to parse "of ". + if input.try_parse(|i| i.expect_ident_matching("of")).is_err() { + return Ok(Component::Nth(nth_data)); + } + // Whitespace between "of" and the selector list is optional + // https://github.com/w3c/csswg-drafts/issues/8285 + let selectors = SelectorList::parse_with_state( + parser, + input, + state | + SelectorParsingState::SKIP_DEFAULT_NAMESPACE | + SelectorParsingState::DISALLOW_PSEUDOS, + ParseErrorRecovery::DiscardList, + )?; + Ok(Component::NthOf(NthOfSelectorData::new( + &nth_data, selectors, + ))) } /// Returns whether the name corresponds to a CSS2 pseudo-element that @@ -2784,6 +2864,10 @@ pub mod tests { true } + fn parse_nth_child_of(&self) -> bool { + true + } + fn parse_is_and_where(&self) -> bool { true } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 5758a09a376..d149cf757ac 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -318,6 +318,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { true } + #[inline] + fn parse_nth_child_of(&self) -> bool { + static_prefs::pref!("layout.css.nth-child-of.enabled") + } + #[inline] fn parse_is_and_where(&self) -> bool { true diff --git a/components/style/stylist.rs b/components/style/stylist.rs index ffcbd2da49e..f6329400b11 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1918,7 +1918,8 @@ fn component_needs_revalidation( Component::AttributeInNoNamespace { .. } | Component::AttributeOther(_) | Component::Empty | - Component::Nth(..) => true, + Component::Nth(_) | + Component::NthOf(_) => true, Component::NonTSPseudoClass(ref p) => p.needs_cache_revalidation(), _ => false, }