mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
style: [css-nesting] Parse parent selector
This parses the ampersand as a parent selector behind an (off-by-default) selectors feature. The plan is to call replace_parent_selector while we're doing the CascadeData rebuilds, which is where we can have all the ancestor nesting information. No behavior change. Differential Revision: https://phabricator.services.mozilla.com/D167237
This commit is contained in:
parent
b024f5b2e7
commit
29c6094c80
5 changed files with 345 additions and 76 deletions
|
@ -715,6 +715,7 @@ where
|
|||
Component::Root |
|
||||
Component::Empty |
|
||||
Component::Scope |
|
||||
Component::ParentSelector |
|
||||
Component::Nth(..) |
|
||||
Component::Host(None) => 0,
|
||||
}
|
||||
|
|
|
@ -96,31 +96,16 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
|
|||
|
||||
/// Consumes the builder, producing a Selector.
|
||||
#[inline(always)]
|
||||
pub fn build(
|
||||
&mut self,
|
||||
parsed_pseudo: bool,
|
||||
parsed_slotted: bool,
|
||||
parsed_part: bool,
|
||||
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
||||
pub fn build(&mut self) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
||||
// Compute the specificity and flags.
|
||||
let specificity = specificity(self.simple_selectors.iter());
|
||||
let mut flags = SelectorFlags::empty();
|
||||
if parsed_pseudo {
|
||||
flags |= SelectorFlags::HAS_PSEUDO;
|
||||
}
|
||||
if parsed_slotted {
|
||||
flags |= SelectorFlags::HAS_SLOTTED;
|
||||
}
|
||||
if parsed_part {
|
||||
flags |= SelectorFlags::HAS_PART;
|
||||
}
|
||||
self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags })
|
||||
let sf = specificity_and_flags(self.simple_selectors.iter());
|
||||
self.build_with_specificity_and_flags(sf)
|
||||
}
|
||||
|
||||
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so
|
||||
/// that unit tests can pass an explicit specificity.
|
||||
#[inline(always)]
|
||||
pub fn build_with_specificity_and_flags(
|
||||
pub(crate) fn build_with_specificity_and_flags(
|
||||
&mut self,
|
||||
spec: SpecificityAndFlags,
|
||||
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
||||
|
@ -203,6 +188,7 @@ bitflags! {
|
|||
const HAS_PSEUDO = 1 << 0;
|
||||
const HAS_SLOTTED = 1 << 1;
|
||||
const HAS_PART = 1 << 2;
|
||||
const HAS_PARENT = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,6 +213,11 @@ impl SpecificityAndFlags {
|
|||
self.flags.intersects(SelectorFlags::HAS_PSEUDO)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_parent_selector(&self) -> bool {
|
||||
self.flags.intersects(SelectorFlags::HAS_PARENT)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_slotted(&self) -> bool {
|
||||
self.flags.intersects(SelectorFlags::HAS_SLOTTED)
|
||||
|
@ -241,7 +232,7 @@ impl SpecificityAndFlags {
|
|||
const MAX_10BIT: u32 = (1u32 << 10) - 1;
|
||||
|
||||
#[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
|
||||
struct Specificity {
|
||||
pub(crate) struct Specificity {
|
||||
id_selectors: u32,
|
||||
class_like_selectors: u32,
|
||||
element_selectors: u32,
|
||||
|
@ -268,31 +259,40 @@ impl From<Specificity> for u32 {
|
|||
}
|
||||
}
|
||||
|
||||
fn specificity<Impl>(iter: slice::Iter<Component<Impl>>) -> u32
|
||||
pub(crate) fn specificity_and_flags<Impl>(iter: slice::Iter<Component<Impl>>) -> SpecificityAndFlags
|
||||
where
|
||||
Impl: SelectorImpl,
|
||||
{
|
||||
complex_selector_specificity(iter).into()
|
||||
complex_selector_specificity_and_flags(iter).into()
|
||||
}
|
||||
|
||||
fn complex_selector_specificity<Impl>(iter: slice::Iter<Component<Impl>>) -> Specificity
|
||||
fn complex_selector_specificity_and_flags<Impl>(
|
||||
iter: slice::Iter<Component<Impl>>,
|
||||
) -> SpecificityAndFlags
|
||||
where
|
||||
Impl: SelectorImpl,
|
||||
{
|
||||
fn simple_selector_specificity<Impl>(
|
||||
fn component_specificity<Impl>(
|
||||
simple_selector: &Component<Impl>,
|
||||
specificity: &mut Specificity,
|
||||
flags: &mut SelectorFlags,
|
||||
) where
|
||||
Impl: SelectorImpl,
|
||||
{
|
||||
match *simple_selector {
|
||||
Component::Combinator(..) => {
|
||||
unreachable!("Found combinator in simple selectors vector?");
|
||||
},
|
||||
Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => {
|
||||
Component::Combinator(..) => {},
|
||||
Component::ParentSelector => flags.insert(SelectorFlags::HAS_PARENT),
|
||||
Component::Part(..) => {
|
||||
flags.insert(SelectorFlags::HAS_PART);
|
||||
specificity.element_selectors += 1
|
||||
},
|
||||
Component::PseudoElement(..) => {
|
||||
flags.insert(SelectorFlags::HAS_PSEUDO);
|
||||
specificity.element_selectors += 1
|
||||
},
|
||||
Component::LocalName(..) => specificity.element_selectors += 1,
|
||||
Component::Slotted(ref selector) => {
|
||||
flags.insert(SelectorFlags::HAS_SLOTTED);
|
||||
specificity.element_selectors += 1;
|
||||
// Note that due to the way ::slotted works we only compete with
|
||||
// other ::slotted rules, so the above rule doesn't really
|
||||
|
@ -301,12 +301,18 @@ where
|
|||
//
|
||||
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
||||
*specificity += Specificity::from(selector.specificity());
|
||||
if selector.has_parent_selector() {
|
||||
flags.insert(SelectorFlags::HAS_PARENT);
|
||||
}
|
||||
},
|
||||
Component::Host(ref selector) => {
|
||||
specificity.class_like_selectors += 1;
|
||||
if let Some(ref selector) = *selector {
|
||||
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
||||
*specificity += Specificity::from(selector.specificity());
|
||||
if selector.has_parent_selector() {
|
||||
flags.insert(SelectorFlags::HAS_PARENT);
|
||||
}
|
||||
}
|
||||
},
|
||||
Component::ID(..) => {
|
||||
|
@ -331,17 +337,25 @@ where
|
|||
// specificity of a regular pseudo-class with that of its
|
||||
// selector argument S.
|
||||
specificity.class_like_selectors += 1;
|
||||
*specificity += max_selector_list_specificity(nth_of_data.selectors());
|
||||
let sf = selector_list_specificity_and_flags(nth_of_data.selectors());
|
||||
*specificity += Specificity::from(sf.specificity);
|
||||
flags.insert(sf.flags);
|
||||
},
|
||||
Component::Negation(ref list) | Component::Is(ref list) | Component::Has(ref list) => {
|
||||
Component::Where(ref list) |
|
||||
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.
|
||||
*specificity += max_selector_list_specificity(list);
|
||||
let sf = selector_list_specificity_and_flags(list);
|
||||
if !matches!(*simple_selector, Component::Where(..)) {
|
||||
*specificity += Specificity::from(sf.specificity);
|
||||
}
|
||||
flags.insert(sf.flags);
|
||||
},
|
||||
Component::Where(..) |
|
||||
Component::ExplicitUniversalType |
|
||||
Component::ExplicitAnyNamespace |
|
||||
Component::ExplicitNoNamespace |
|
||||
|
@ -352,19 +366,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Finds the maximum specificity of elements in the list and returns it.
|
||||
fn max_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 flags = Default::default();
|
||||
for simple_selector in iter {
|
||||
simple_selector_specificity(&simple_selector, &mut specificity);
|
||||
component_specificity(&simple_selector, &mut specificity, &mut flags);
|
||||
}
|
||||
SpecificityAndFlags {
|
||||
specificity: specificity.into(),
|
||||
flags,
|
||||
}
|
||||
specificity
|
||||
}
|
||||
|
||||
/// Finds the maximum specificity of elements in the list and returns it.
|
||||
pub(crate) fn selector_list_specificity_and_flags<Impl: SelectorImpl>(
|
||||
list: &[Selector<Impl>],
|
||||
) -> SpecificityAndFlags {
|
||||
let mut specificity = 0;
|
||||
let mut flags = SelectorFlags::empty();
|
||||
for selector in list.iter() {
|
||||
specificity = std::cmp::max(specificity, selector.specificity());
|
||||
if selector.has_parent_selector() {
|
||||
flags.insert(SelectorFlags::HAS_PARENT);
|
||||
}
|
||||
}
|
||||
SpecificityAndFlags { specificity, flags }
|
||||
}
|
||||
|
|
|
@ -811,6 +811,9 @@ where
|
|||
.nest(|context| matches_complex_selector(selector.iter(), element, context))
|
||||
})
|
||||
},
|
||||
// These should only work at parse time, should be replaced with :is() at CascadeData build
|
||||
// time.
|
||||
Component::ParentSelector => false,
|
||||
Component::Scope => match context.shared.scope_element {
|
||||
Some(ref scope_element) => element.opaque() == *scope_element,
|
||||
None => element.is_root(),
|
||||
|
|
|
@ -6,7 +6,10 @@ use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace};
|
|||
use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation};
|
||||
use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};
|
||||
use crate::bloom::BLOOM_HASH_MASK;
|
||||
use crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags};
|
||||
use crate::builder::{
|
||||
selector_list_specificity_and_flags, SelectorBuilder, SelectorFlags, Specificity,
|
||||
SpecificityAndFlags,
|
||||
};
|
||||
use crate::context::QuirksMode;
|
||||
use crate::sink::Push;
|
||||
pub use crate::visitor::SelectorVisitor;
|
||||
|
@ -16,7 +19,7 @@ use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind
|
|||
use cssparser::{CowRcStr, Delimiter, SourceLocation};
|
||||
use cssparser::{Parser as CssParser, ToCss, Token};
|
||||
use precomputed_hash::PrecomputedHash;
|
||||
use servo_arc::ThinArc;
|
||||
use servo_arc::{HeaderWithLength, ThinArc, UniqueArc};
|
||||
use size_of_test::size_of_test;
|
||||
use smallvec::SmallVec;
|
||||
use std::borrow::{Borrow, Cow};
|
||||
|
@ -271,6 +274,11 @@ pub trait Parser<'i> {
|
|||
false
|
||||
}
|
||||
|
||||
/// Whether to parse the '&' delimiter as a parent selector.
|
||||
fn parse_parent_selector(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether the given function name is an alias for the `:is()` function.
|
||||
fn is_is_alias(&self, _name: &str) -> bool {
|
||||
false
|
||||
|
@ -431,7 +439,8 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
|
|||
}
|
||||
|
||||
/// Creates a SelectorList from a Vec of selectors. Used in tests.
|
||||
pub fn from_vec(v: Vec<Selector<Impl>>) -> Self {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn from_vec(v: Vec<Selector<Impl>>) -> Self {
|
||||
SelectorList(SmallVec::from_vec(v))
|
||||
}
|
||||
}
|
||||
|
@ -626,11 +635,21 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
|||
self.0.header.header.specificity()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flags(&self) -> SelectorFlags {
|
||||
self.0.header.header.flags
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_pseudo_element(&self) -> bool {
|
||||
self.0.header.header.has_pseudo_element()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_parent_selector(&self) -> bool {
|
||||
self.0.header.header.has_parent_selector()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_slotted(&self) -> bool {
|
||||
self.0.header.header.is_slotted()
|
||||
|
@ -785,7 +804,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
|||
}
|
||||
|
||||
/// Creates a Selector from a vec of Components, specified in parse order. Used in tests.
|
||||
#[allow(unused)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn from_vec(
|
||||
vec: Vec<Component<Impl>>,
|
||||
specificity: u32,
|
||||
|
@ -803,6 +822,166 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
|||
Selector(builder.build_with_specificity_and_flags(spec))
|
||||
}
|
||||
|
||||
pub fn replace_parent_selector(&self, parent: &[Selector<Impl>]) -> Cow<Self> {
|
||||
if !self.has_parent_selector() {
|
||||
return Cow::Borrowed(self);
|
||||
}
|
||||
|
||||
// FIXME(emilio): Shouldn't allow replacing if parent has a pseudo-element selector
|
||||
// or what not.
|
||||
let flags = self.flags() - SelectorFlags::HAS_PARENT;
|
||||
let mut specificity = Specificity::from(self.specificity());
|
||||
let parent_specificity =
|
||||
Specificity::from(selector_list_specificity_and_flags(parent).specificity());
|
||||
|
||||
// The specificity at this point will be wrong, we replace it by the correct one after the
|
||||
// fact.
|
||||
let specificity_and_flags = SpecificityAndFlags {
|
||||
specificity: self.specificity(),
|
||||
flags,
|
||||
};
|
||||
|
||||
fn replace_parent_on_selector_list<Impl: SelectorImpl>(
|
||||
orig: &[Selector<Impl>],
|
||||
parent: &[Selector<Impl>],
|
||||
specificity: &mut Specificity,
|
||||
with_specificity: bool,
|
||||
) -> Vec<Selector<Impl>> {
|
||||
let mut any = false;
|
||||
|
||||
let result = orig
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if !s.has_parent_selector() {
|
||||
return s.clone();
|
||||
}
|
||||
any = true;
|
||||
s.replace_parent_selector(parent).into_owned()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !any || !with_specificity {
|
||||
return result;
|
||||
}
|
||||
|
||||
*specificity += Specificity::from(
|
||||
selector_list_specificity_and_flags(&result).specificity -
|
||||
selector_list_specificity_and_flags(orig).specificity,
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
fn replace_parent_on_selector<Impl: SelectorImpl>(
|
||||
orig: &Selector<Impl>,
|
||||
parent: &[Selector<Impl>],
|
||||
specificity: &mut Specificity,
|
||||
) -> Selector<Impl> {
|
||||
let new_selector = orig.replace_parent_selector(parent);
|
||||
if matches!(new_selector, Cow::Owned(..)) {
|
||||
*specificity += Specificity::from(new_selector.specificity() - orig.specificity());
|
||||
}
|
||||
new_selector.into_owned()
|
||||
}
|
||||
|
||||
let iter = self.iter_raw_match_order().map(|component| {
|
||||
use self::Component::*;
|
||||
match *component {
|
||||
LocalName(..) |
|
||||
ID(..) |
|
||||
Class(..) |
|
||||
AttributeInNoNamespaceExists { .. } |
|
||||
AttributeInNoNamespace { .. } |
|
||||
AttributeOther(..) |
|
||||
ExplicitUniversalType |
|
||||
ExplicitAnyNamespace |
|
||||
ExplicitNoNamespace |
|
||||
DefaultNamespace(..) |
|
||||
Namespace(..) |
|
||||
Root |
|
||||
Empty |
|
||||
Scope |
|
||||
Nth(..) |
|
||||
NonTSPseudoClass(..) |
|
||||
PseudoElement(..) |
|
||||
Combinator(..) |
|
||||
Host(None) |
|
||||
Part(..) => component.clone(),
|
||||
ParentSelector => {
|
||||
specificity += parent_specificity;
|
||||
Is(parent.to_vec().into_boxed_slice())
|
||||
},
|
||||
Negation(ref selectors) => {
|
||||
Negation(
|
||||
replace_parent_on_selector_list(
|
||||
selectors,
|
||||
parent,
|
||||
&mut specificity,
|
||||
/* with_specificity = */ true,
|
||||
)
|
||||
.into_boxed_slice(),
|
||||
)
|
||||
},
|
||||
Is(ref selectors) => {
|
||||
Is(replace_parent_on_selector_list(
|
||||
selectors,
|
||||
parent,
|
||||
&mut specificity,
|
||||
/* with_specificity = */ true,
|
||||
)
|
||||
.into_boxed_slice())
|
||||
},
|
||||
Where(ref selectors) => {
|
||||
Where(
|
||||
replace_parent_on_selector_list(
|
||||
selectors,
|
||||
parent,
|
||||
&mut specificity,
|
||||
/* with_specificity = */ false,
|
||||
)
|
||||
.into_boxed_slice(),
|
||||
)
|
||||
},
|
||||
Has(ref selectors) => {
|
||||
Has(replace_parent_on_selector_list(
|
||||
selectors,
|
||||
parent,
|
||||
&mut specificity,
|
||||
/* with_specificity = */ true,
|
||||
)
|
||||
.into_boxed_slice())
|
||||
},
|
||||
|
||||
Host(Some(ref selector)) => Host(Some(replace_parent_on_selector(
|
||||
selector,
|
||||
parent,
|
||||
&mut specificity,
|
||||
))),
|
||||
NthOf(ref data) => {
|
||||
let selectors = replace_parent_on_selector_list(
|
||||
data.selectors(),
|
||||
parent,
|
||||
&mut specificity,
|
||||
/* with_specificity = */ true,
|
||||
);
|
||||
NthOf(NthOfSelectorData::new(
|
||||
data.nth_data(),
|
||||
selectors.into_iter(),
|
||||
))
|
||||
},
|
||||
Slotted(ref selector) => Slotted(replace_parent_on_selector(
|
||||
selector,
|
||||
parent,
|
||||
&mut specificity,
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let header = HeaderWithLength::new(specificity_and_flags, iter.len());
|
||||
let mut items = UniqueArc::from_header_and_iter(header, iter);
|
||||
items.header_mut().specificity = specificity.into();
|
||||
Cow::Owned(Selector(items.shareable_thin()))
|
||||
}
|
||||
|
||||
/// Returns count of simple selectors and combinators in the Selector.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
|
@ -1173,11 +1352,11 @@ pub struct NthOfSelectorData<Impl: SelectorImpl>(
|
|||
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(..),
|
||||
))
|
||||
pub fn new<I>(nth_data: &NthSelectorData, selectors: I) -> Self
|
||||
where
|
||||
I: Iterator<Item = Selector<Impl>> + ExactSizeIterator,
|
||||
{
|
||||
Self(ThinArc::from_header_and_iter(*nth_data, selectors))
|
||||
}
|
||||
|
||||
/// Returns the An+B part of the selector
|
||||
|
@ -1238,6 +1417,7 @@ pub enum Component<Impl: SelectorImpl> {
|
|||
Root,
|
||||
Empty,
|
||||
Scope,
|
||||
ParentSelector,
|
||||
Nth(NthSelectorData),
|
||||
NthOf(NthOfSelectorData<Impl>),
|
||||
NonTSPseudoClass(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::NonTSPseudoClass),
|
||||
|
@ -1456,7 +1636,12 @@ impl<Impl: SelectorImpl> Debug for Selector<Impl> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("Selector(")?;
|
||||
self.to_css(f)?;
|
||||
write!(f, ", specificity = 0x{:x})", self.specificity())
|
||||
write!(
|
||||
f,
|
||||
", specificity = {:#x}, flags = {:?})",
|
||||
self.specificity(),
|
||||
self.flags()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1717,6 +1902,7 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
|||
Root => dest.write_str(":root"),
|
||||
Empty => dest.write_str(":empty"),
|
||||
Scope => dest.write_str(":scope"),
|
||||
ParentSelector => dest.write_char('&'),
|
||||
Host(ref selector) => {
|
||||
dest.write_str(":host")?;
|
||||
if let Some(ref selector) = *selector {
|
||||
|
@ -1832,9 +2018,6 @@ where
|
|||
{
|
||||
let mut builder = SelectorBuilder::default();
|
||||
|
||||
let mut has_pseudo_element = false;
|
||||
let mut slotted = false;
|
||||
let mut part = false;
|
||||
'outer_loop: loop {
|
||||
// Parse a sequence of simple selectors.
|
||||
let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
|
||||
|
@ -1847,10 +2030,11 @@ where
|
|||
}
|
||||
|
||||
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||
has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
|
||||
slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
|
||||
part = state.intersects(SelectorParsingState::AFTER_PART);
|
||||
debug_assert!(has_pseudo_element || slotted || part);
|
||||
debug_assert!(state.intersects(
|
||||
SelectorParsingState::AFTER_PSEUDO_ELEMENT |
|
||||
SelectorParsingState::AFTER_SLOTTED |
|
||||
SelectorParsingState::AFTER_PART
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1893,7 +2077,7 @@ where
|
|||
builder.push_combinator(combinator);
|
||||
}
|
||||
|
||||
Ok(Selector(builder.build(has_pseudo_element, slotted, part)))
|
||||
Ok(Selector(builder.build()))
|
||||
}
|
||||
|
||||
impl<Impl: SelectorImpl> Selector<Impl> {
|
||||
|
@ -2517,7 +2701,7 @@ where
|
|||
}
|
||||
// Whitespace between "of" and the selector list is optional
|
||||
// https://github.com/w3c/csswg-drafts/issues/8285
|
||||
let selectors = SelectorList::parse_with_state(
|
||||
let mut selectors = SelectorList::parse_with_state(
|
||||
parser,
|
||||
input,
|
||||
state |
|
||||
|
@ -2526,7 +2710,8 @@ where
|
|||
ParseErrorRecovery::DiscardList,
|
||||
)?;
|
||||
Ok(Component::NthOf(NthOfSelectorData::new(
|
||||
&nth_data, selectors,
|
||||
&nth_data,
|
||||
selectors.0.drain(..),
|
||||
)))
|
||||
}
|
||||
|
||||
|
@ -2572,20 +2757,23 @@ where
|
|||
let id = Component::ID(id.as_ref().into());
|
||||
SimpleSelectorParseResult::SimpleSelector(id)
|
||||
},
|
||||
Token::Delim('.') => {
|
||||
Token::Delim(delim) if delim == '.' || (delim == '&' && parser.parse_parent_selector()) => {
|
||||
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||
}
|
||||
let location = input.current_source_location();
|
||||
let class = match *input.next_including_whitespace()? {
|
||||
Token::Ident(ref class) => class,
|
||||
ref t => {
|
||||
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
|
||||
return Err(location.new_custom_error(e));
|
||||
},
|
||||
};
|
||||
let class = Component::Class(class.as_ref().into());
|
||||
SimpleSelectorParseResult::SimpleSelector(class)
|
||||
SimpleSelectorParseResult::SimpleSelector(if delim == '&' {
|
||||
Component::ParentSelector
|
||||
} else {
|
||||
let class = match *input.next_including_whitespace()? {
|
||||
Token::Ident(ref class) => class,
|
||||
ref t => {
|
||||
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
|
||||
return Err(location.new_custom_error(e));
|
||||
},
|
||||
};
|
||||
Component::Class(class.as_ref().into())
|
||||
})
|
||||
},
|
||||
Token::SquareBracketBlock => {
|
||||
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||
|
@ -2884,6 +3072,10 @@ pub mod tests {
|
|||
true
|
||||
}
|
||||
|
||||
fn parse_parent_selector(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_part(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -2978,12 +3170,11 @@ pub mod tests {
|
|||
let mut parser_input = ParserInput::new(input);
|
||||
let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input));
|
||||
if let Ok(ref selectors) = result {
|
||||
assert_eq!(selectors.0.len(), 1);
|
||||
// We can't assume that the serialized parsed selector will equal
|
||||
// the input; for example, if there is no default namespace, '*|foo'
|
||||
// should serialize to 'foo'.
|
||||
assert_eq!(
|
||||
selectors.0[0].to_css_string(),
|
||||
selectors.to_css_string(),
|
||||
match expected {
|
||||
Some(x) => x,
|
||||
None => input,
|
||||
|
@ -3493,6 +3684,31 @@ pub mod tests {
|
|||
assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parent_selector() {
|
||||
assert!(parse("foo &").is_ok());
|
||||
assert_eq!(
|
||||
parse("#foo &.bar"),
|
||||
Ok(SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![
|
||||
Component::ID(DummyAtom::from("foo")),
|
||||
Component::Combinator(Combinator::Descendant),
|
||||
Component::ParentSelector,
|
||||
Component::Class(DummyAtom::from("bar")),
|
||||
],
|
||||
(1 << 20) + (1 << 10) + (0 << 0),
|
||||
SelectorFlags::HAS_PARENT
|
||||
)]))
|
||||
);
|
||||
|
||||
let parent = parse(".bar, div .baz").unwrap();
|
||||
let child = parse("#foo &.bar").unwrap();
|
||||
assert_eq!(
|
||||
SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0).into_owned()]),
|
||||
parse("#foo :is(.bar, div .baz).bar").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pseudo_iter() {
|
||||
let selector = &parse("q::before").unwrap().0[0];
|
||||
|
|
|
@ -1048,6 +1048,32 @@ impl<H, T> Arc<HeaderSliceWithLength<H, [T]>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<H, T> UniqueArc<HeaderSliceWithLength<H, [T]>> {
|
||||
#[inline]
|
||||
pub fn from_header_and_iter<I>(header: HeaderWithLength<H>, items: I) -> Self
|
||||
where
|
||||
I: Iterator<Item = T> + ExactSizeIterator,
|
||||
{
|
||||
Self(Arc::from_header_and_iter(header, items))
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the header.
|
||||
pub fn header_mut(&mut self) -> &mut H {
|
||||
// We know this to be uniquely owned
|
||||
unsafe { &mut (*self.0.ptr()).data.header.header }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the slice.
|
||||
pub fn data_mut(&mut self) -> &mut [T] {
|
||||
// We know this to be uniquely owned
|
||||
unsafe { &mut (*self.0.ptr()).data.slice }
|
||||
}
|
||||
|
||||
pub fn shareable_thin(self) -> ThinArc<H, T> {
|
||||
Arc::into_thin(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: PartialEq, T: PartialEq> PartialEq for ThinArc<H, T> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &ThinArc<H, T>) -> bool {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue