mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
style: Implement parsing and serialization for nth-child(An+B of selector list) and :nth-last-child(An+B of selector list)
:nth-{,last-}child parsing is disabled by default for now by pref layout.css.nth-child-of.enabled. Differential Revision: https://phabricator.services.mozilla.com/D165895
This commit is contained in:
parent
b7d64ee6a4
commit
1c8408e97e
6 changed files with 123 additions and 11 deletions
|
@ -698,6 +698,7 @@ where
|
||||||
Component::Is(ref list) | Component::Where(ref list) | Component::Has(ref list) => {
|
Component::Is(ref list) | Component::Where(ref list) | Component::Has(ref list) => {
|
||||||
list.size_of(ops)
|
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::PseudoElement(ref pseudo) => (*pseudo).size_of(ops),
|
||||||
Component::Combinator(..) |
|
Component::Combinator(..) |
|
||||||
Component::ExplicitAnyNamespace |
|
Component::ExplicitAnyNamespace |
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
//! is non-trivial. This module encapsulates those details and presents an
|
//! is non-trivial. This module encapsulates those details and presents an
|
||||||
//! easy-to-use API for the parser.
|
//! 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 crate::sink::Push;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use derive_more::{Add, AddAssign};
|
use derive_more::{Add, AddAssign};
|
||||||
|
@ -323,17 +323,23 @@ where
|
||||||
Component::NonTSPseudoClass(..) => {
|
Component::NonTSPseudoClass(..) => {
|
||||||
specificity.class_like_selectors += 1;
|
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) => {
|
Component::Negation(ref list) | Component::Is(ref list) | Component::Has(ref list) => {
|
||||||
// https://drafts.csswg.org/selectors/#specificity-rules:
|
// https://drafts.csswg.org/selectors/#specificity-rules:
|
||||||
//
|
//
|
||||||
// The specificity of an :is(), :not(), or :has() pseudo-class
|
// The specificity of an :is(), :not(), or :has() pseudo-class
|
||||||
// is replaced by the specificity of the most specific complex
|
// is replaced by the specificity of the most specific complex
|
||||||
// selector in its selector list argument.
|
// selector in its selector list argument.
|
||||||
let mut max = 0;
|
*specificity += selector_list_specificity(list);
|
||||||
for selector in &**list {
|
|
||||||
max = std::cmp::max(selector.specificity(), max);
|
|
||||||
}
|
|
||||||
*specificity += Specificity::from(max);
|
|
||||||
},
|
},
|
||||||
Component::Where(..) |
|
Component::Where(..) |
|
||||||
Component::ExplicitUniversalType |
|
Component::ExplicitUniversalType |
|
||||||
|
@ -346,6 +352,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds the maximum specificity of elements in the list and returns it.
|
||||||
|
fn selector_list_specificity<Impl: SelectorImpl>(list: &[Selector<Impl>]) -> Specificity {
|
||||||
|
let max = list
|
||||||
|
.iter()
|
||||||
|
.map(|selector| selector.specificity())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
Specificity::from(max)
|
||||||
|
}
|
||||||
|
|
||||||
let mut specificity = Default::default();
|
let mut specificity = Default::default();
|
||||||
for simple_selector in iter {
|
for simple_selector in iter {
|
||||||
simple_selector_specificity(&simple_selector, &mut specificity);
|
simple_selector_specificity(&simple_selector, &mut specificity);
|
||||||
|
|
|
@ -388,7 +388,8 @@ fn hover_and_active_quirk_applies<Impl: SelectorImpl>(
|
||||||
Component::PseudoElement(_) |
|
Component::PseudoElement(_) |
|
||||||
Component::Negation(_) |
|
Component::Negation(_) |
|
||||||
Component::Empty |
|
Component::Empty |
|
||||||
Component::Nth(_) => false,
|
Component::Nth(_) |
|
||||||
|
Component::NthOf(_) => false,
|
||||||
Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(),
|
Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(),
|
||||||
_ => true,
|
_ => true,
|
||||||
})
|
})
|
||||||
|
@ -803,6 +804,10 @@ where
|
||||||
Component::Nth(ref nth_data) => {
|
Component::Nth(ref nth_data) => {
|
||||||
matches_generic_nth_child(element, context.shared, 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| {
|
Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| {
|
||||||
for selector in &**list {
|
for selector in &**list {
|
||||||
if matches_complex_selector(selector.iter(), element, context) {
|
if matches_complex_selector(selector.iter(), element, context) {
|
||||||
|
|
|
@ -256,6 +256,11 @@ pub trait Parser<'i> {
|
||||||
false
|
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.
|
/// Whether to parse the `:where` pseudo-class.
|
||||||
fn parse_is_and_where(&self) -> bool {
|
fn parse_is_and_where(&self) -> bool {
|
||||||
false
|
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<Impl: SelectorImpl>(
|
||||||
|
#[shmem(field_bound)] ThinArc<NthSelectorData, Selector<Impl>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<Impl: SelectorImpl> NthOfSelectorData<Impl> {
|
||||||
|
/// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S])
|
||||||
|
#[inline]
|
||||||
|
pub fn new(nth_data: &NthSelectorData, mut selectors: SelectorList<Impl>) -> 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<Impl>] {
|
||||||
|
&self.0.slice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A CSS simple selector or combinator. We store both in the same enum for
|
/// A CSS simple selector or combinator. We store both in the same enum for
|
||||||
/// optimal packing and cache performance, see [1].
|
/// optimal packing and cache performance, see [1].
|
||||||
///
|
///
|
||||||
|
@ -1167,6 +1204,7 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
Empty,
|
Empty,
|
||||||
Scope,
|
Scope,
|
||||||
Nth(NthSelectorData),
|
Nth(NthSelectorData),
|
||||||
|
NthOf(NthOfSelectorData<Impl>),
|
||||||
NonTSPseudoClass(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::NonTSPseudoClass),
|
NonTSPseudoClass(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::NonTSPseudoClass),
|
||||||
/// The ::slotted() pseudo-element:
|
/// The ::slotted() pseudo-element:
|
||||||
///
|
///
|
||||||
|
@ -1358,6 +1396,11 @@ impl<Impl: SelectorImpl> Component<Impl> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
NthOf(ref nth_of_data) => {
|
||||||
|
if !visitor.visit_selector_list(nth_of_data.selectors()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1687,6 +1730,22 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
||||||
}
|
}
|
||||||
Ok(())
|
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) => {
|
Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => {
|
||||||
match *self {
|
match *self {
|
||||||
Where(..) => dest.write_str(":where(")?,
|
Where(..) => dest.write_str(":where(")?,
|
||||||
|
@ -2421,7 +2480,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_nth_pseudo_class<'i, 't, P, Impl>(
|
fn parse_nth_pseudo_class<'i, 't, P, Impl>(
|
||||||
_: &P,
|
parser: &P,
|
||||||
input: &mut CssParser<'i, 't>,
|
input: &mut CssParser<'i, 't>,
|
||||||
state: SelectorParsingState,
|
state: SelectorParsingState,
|
||||||
ty: NthType,
|
ty: NthType,
|
||||||
|
@ -2434,12 +2493,33 @@ where
|
||||||
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
}
|
}
|
||||||
let (a, b) = parse_nth(input)?;
|
let (a, b) = parse_nth(input)?;
|
||||||
Ok(Component::Nth(NthSelectorData {
|
let nth_data = NthSelectorData {
|
||||||
ty,
|
ty,
|
||||||
is_function: true,
|
is_function: true,
|
||||||
a,
|
a,
|
||||||
b,
|
b,
|
||||||
}))
|
};
|
||||||
|
if !parser.parse_nth_child_of() || ty.is_of_type() {
|
||||||
|
return Ok(Component::Nth(nth_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse "of <selector-list>".
|
||||||
|
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
|
/// Returns whether the name corresponds to a CSS2 pseudo-element that
|
||||||
|
@ -2784,6 +2864,10 @@ pub mod tests {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_nth_child_of(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_is_and_where(&self) -> bool {
|
fn parse_is_and_where(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,6 +318,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn parse_nth_child_of(&self) -> bool {
|
||||||
|
static_prefs::pref!("layout.css.nth-child-of.enabled")
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parse_is_and_where(&self) -> bool {
|
fn parse_is_and_where(&self) -> bool {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1918,7 +1918,8 @@ fn component_needs_revalidation(
|
||||||
Component::AttributeInNoNamespace { .. } |
|
Component::AttributeInNoNamespace { .. } |
|
||||||
Component::AttributeOther(_) |
|
Component::AttributeOther(_) |
|
||||||
Component::Empty |
|
Component::Empty |
|
||||||
Component::Nth(..) => true,
|
Component::Nth(_) |
|
||||||
|
Component::NthOf(_) => true,
|
||||||
Component::NonTSPseudoClass(ref p) => p.needs_cache_revalidation(),
|
Component::NonTSPseudoClass(ref p) => p.needs_cache_revalidation(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue