mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +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::Root |
|
||||||
Component::Empty |
|
Component::Empty |
|
||||||
Component::Scope |
|
Component::Scope |
|
||||||
|
Component::ParentSelector |
|
||||||
Component::Nth(..) |
|
Component::Nth(..) |
|
||||||
Component::Host(None) => 0,
|
Component::Host(None) => 0,
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,31 +96,16 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
|
||||||
|
|
||||||
/// Consumes the builder, producing a Selector.
|
/// Consumes the builder, producing a Selector.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn build(
|
pub fn build(&mut self) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
||||||
&mut self,
|
|
||||||
parsed_pseudo: bool,
|
|
||||||
parsed_slotted: bool,
|
|
||||||
parsed_part: bool,
|
|
||||||
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
|
||||||
// Compute the specificity and flags.
|
// Compute the specificity and flags.
|
||||||
let specificity = specificity(self.simple_selectors.iter());
|
let sf = specificity_and_flags(self.simple_selectors.iter());
|
||||||
let mut flags = SelectorFlags::empty();
|
self.build_with_specificity_and_flags(sf)
|
||||||
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 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so
|
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so
|
||||||
/// that unit tests can pass an explicit specificity.
|
/// that unit tests can pass an explicit specificity.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn build_with_specificity_and_flags(
|
pub(crate) fn build_with_specificity_and_flags(
|
||||||
&mut self,
|
&mut self,
|
||||||
spec: SpecificityAndFlags,
|
spec: SpecificityAndFlags,
|
||||||
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
|
||||||
|
@ -203,6 +188,7 @@ bitflags! {
|
||||||
const HAS_PSEUDO = 1 << 0;
|
const HAS_PSEUDO = 1 << 0;
|
||||||
const HAS_SLOTTED = 1 << 1;
|
const HAS_SLOTTED = 1 << 1;
|
||||||
const HAS_PART = 1 << 2;
|
const HAS_PART = 1 << 2;
|
||||||
|
const HAS_PARENT = 1 << 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +213,11 @@ impl SpecificityAndFlags {
|
||||||
self.flags.intersects(SelectorFlags::HAS_PSEUDO)
|
self.flags.intersects(SelectorFlags::HAS_PSEUDO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn has_parent_selector(&self) -> bool {
|
||||||
|
self.flags.intersects(SelectorFlags::HAS_PARENT)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_slotted(&self) -> bool {
|
pub fn is_slotted(&self) -> bool {
|
||||||
self.flags.intersects(SelectorFlags::HAS_SLOTTED)
|
self.flags.intersects(SelectorFlags::HAS_SLOTTED)
|
||||||
|
@ -241,7 +232,7 @@ impl SpecificityAndFlags {
|
||||||
const MAX_10BIT: u32 = (1u32 << 10) - 1;
|
const MAX_10BIT: u32 = (1u32 << 10) - 1;
|
||||||
|
|
||||||
#[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
struct Specificity {
|
pub(crate) struct Specificity {
|
||||||
id_selectors: u32,
|
id_selectors: u32,
|
||||||
class_like_selectors: u32,
|
class_like_selectors: u32,
|
||||||
element_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
|
where
|
||||||
Impl: SelectorImpl,
|
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
|
where
|
||||||
Impl: SelectorImpl,
|
Impl: SelectorImpl,
|
||||||
{
|
{
|
||||||
fn simple_selector_specificity<Impl>(
|
fn component_specificity<Impl>(
|
||||||
simple_selector: &Component<Impl>,
|
simple_selector: &Component<Impl>,
|
||||||
specificity: &mut Specificity,
|
specificity: &mut Specificity,
|
||||||
|
flags: &mut SelectorFlags,
|
||||||
) where
|
) where
|
||||||
Impl: SelectorImpl,
|
Impl: SelectorImpl,
|
||||||
{
|
{
|
||||||
match *simple_selector {
|
match *simple_selector {
|
||||||
Component::Combinator(..) => {
|
Component::Combinator(..) => {},
|
||||||
unreachable!("Found combinator in simple selectors vector?");
|
Component::ParentSelector => flags.insert(SelectorFlags::HAS_PARENT),
|
||||||
},
|
Component::Part(..) => {
|
||||||
Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => {
|
flags.insert(SelectorFlags::HAS_PART);
|
||||||
specificity.element_selectors += 1
|
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) => {
|
Component::Slotted(ref selector) => {
|
||||||
|
flags.insert(SelectorFlags::HAS_SLOTTED);
|
||||||
specificity.element_selectors += 1;
|
specificity.element_selectors += 1;
|
||||||
// Note that due to the way ::slotted works we only compete with
|
// Note that due to the way ::slotted works we only compete with
|
||||||
// other ::slotted rules, so the above rule doesn't really
|
// other ::slotted rules, so the above rule doesn't really
|
||||||
|
@ -301,12 +301,18 @@ where
|
||||||
//
|
//
|
||||||
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
||||||
*specificity += Specificity::from(selector.specificity());
|
*specificity += Specificity::from(selector.specificity());
|
||||||
|
if selector.has_parent_selector() {
|
||||||
|
flags.insert(SelectorFlags::HAS_PARENT);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Component::Host(ref selector) => {
|
Component::Host(ref selector) => {
|
||||||
specificity.class_like_selectors += 1;
|
specificity.class_like_selectors += 1;
|
||||||
if let Some(ref selector) = *selector {
|
if let Some(ref selector) = *selector {
|
||||||
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
||||||
*specificity += Specificity::from(selector.specificity());
|
*specificity += Specificity::from(selector.specificity());
|
||||||
|
if selector.has_parent_selector() {
|
||||||
|
flags.insert(SelectorFlags::HAS_PARENT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Component::ID(..) => {
|
Component::ID(..) => {
|
||||||
|
@ -331,17 +337,25 @@ where
|
||||||
// specificity of a regular pseudo-class with that of its
|
// specificity of a regular pseudo-class with that of its
|
||||||
// selector argument S.
|
// selector argument S.
|
||||||
specificity.class_like_selectors += 1;
|
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:
|
// 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.
|
||||||
*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::ExplicitUniversalType |
|
||||||
Component::ExplicitAnyNamespace |
|
Component::ExplicitAnyNamespace |
|
||||||
Component::ExplicitNoNamespace |
|
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 specificity = Default::default();
|
||||||
|
let mut flags = Default::default();
|
||||||
for simple_selector in iter {
|
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))
|
.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 {
|
Component::Scope => match context.shared.scope_element {
|
||||||
Some(ref scope_element) => element.opaque() == *scope_element,
|
Some(ref scope_element) => element.opaque() == *scope_element,
|
||||||
None => element.is_root(),
|
None => element.is_root(),
|
||||||
|
|
|
@ -6,7 +6,10 @@ use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace};
|
||||||
use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation};
|
use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation};
|
||||||
use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};
|
use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};
|
||||||
use crate::bloom::BLOOM_HASH_MASK;
|
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::context::QuirksMode;
|
||||||
use crate::sink::Push;
|
use crate::sink::Push;
|
||||||
pub use crate::visitor::SelectorVisitor;
|
pub use crate::visitor::SelectorVisitor;
|
||||||
|
@ -16,7 +19,7 @@ use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind
|
||||||
use cssparser::{CowRcStr, Delimiter, SourceLocation};
|
use cssparser::{CowRcStr, Delimiter, SourceLocation};
|
||||||
use cssparser::{Parser as CssParser, ToCss, Token};
|
use cssparser::{Parser as CssParser, ToCss, Token};
|
||||||
use precomputed_hash::PrecomputedHash;
|
use precomputed_hash::PrecomputedHash;
|
||||||
use servo_arc::ThinArc;
|
use servo_arc::{HeaderWithLength, ThinArc, UniqueArc};
|
||||||
use size_of_test::size_of_test;
|
use size_of_test::size_of_test;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::{Borrow, Cow};
|
||||||
|
@ -271,6 +274,11 @@ pub trait Parser<'i> {
|
||||||
false
|
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.
|
/// Whether the given function name is an alias for the `:is()` function.
|
||||||
fn is_is_alias(&self, _name: &str) -> bool {
|
fn is_is_alias(&self, _name: &str) -> bool {
|
||||||
false
|
false
|
||||||
|
@ -431,7 +439,8 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a SelectorList from a Vec of selectors. Used in tests.
|
/// 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))
|
SelectorList(SmallVec::from_vec(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -626,11 +635,21 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
self.0.header.header.specificity()
|
self.0.header.header.specificity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flags(&self) -> SelectorFlags {
|
||||||
|
self.0.header.header.flags
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has_pseudo_element(&self) -> bool {
|
pub fn has_pseudo_element(&self) -> bool {
|
||||||
self.0.header.header.has_pseudo_element()
|
self.0.header.header.has_pseudo_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn has_parent_selector(&self) -> bool {
|
||||||
|
self.0.header.header.has_parent_selector()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_slotted(&self) -> bool {
|
pub fn is_slotted(&self) -> bool {
|
||||||
self.0.header.header.is_slotted()
|
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.
|
/// 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(
|
pub(crate) fn from_vec(
|
||||||
vec: Vec<Component<Impl>>,
|
vec: Vec<Component<Impl>>,
|
||||||
specificity: u32,
|
specificity: u32,
|
||||||
|
@ -803,6 +822,166 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
Selector(builder.build_with_specificity_and_flags(spec))
|
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.
|
/// Returns count of simple selectors and combinators in the Selector.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
|
@ -1173,11 +1352,11 @@ pub struct NthOfSelectorData<Impl: SelectorImpl>(
|
||||||
impl<Impl: SelectorImpl> NthOfSelectorData<Impl> {
|
impl<Impl: SelectorImpl> NthOfSelectorData<Impl> {
|
||||||
/// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S])
|
/// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S])
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(nth_data: &NthSelectorData, mut selectors: SelectorList<Impl>) -> Self {
|
pub fn new<I>(nth_data: &NthSelectorData, selectors: I) -> Self
|
||||||
Self(ThinArc::from_header_and_iter(
|
where
|
||||||
*nth_data,
|
I: Iterator<Item = Selector<Impl>> + ExactSizeIterator,
|
||||||
selectors.0.drain(..),
|
{
|
||||||
))
|
Self(ThinArc::from_header_and_iter(*nth_data, selectors))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the An+B part of the selector
|
/// Returns the An+B part of the selector
|
||||||
|
@ -1238,6 +1417,7 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
Root,
|
Root,
|
||||||
Empty,
|
Empty,
|
||||||
Scope,
|
Scope,
|
||||||
|
ParentSelector,
|
||||||
Nth(NthSelectorData),
|
Nth(NthSelectorData),
|
||||||
NthOf(NthOfSelectorData<Impl>),
|
NthOf(NthOfSelectorData<Impl>),
|
||||||
NonTSPseudoClass(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::NonTSPseudoClass),
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str("Selector(")?;
|
f.write_str("Selector(")?;
|
||||||
self.to_css(f)?;
|
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"),
|
Root => dest.write_str(":root"),
|
||||||
Empty => dest.write_str(":empty"),
|
Empty => dest.write_str(":empty"),
|
||||||
Scope => dest.write_str(":scope"),
|
Scope => dest.write_str(":scope"),
|
||||||
|
ParentSelector => dest.write_char('&'),
|
||||||
Host(ref selector) => {
|
Host(ref selector) => {
|
||||||
dest.write_str(":host")?;
|
dest.write_str(":host")?;
|
||||||
if let Some(ref selector) = *selector {
|
if let Some(ref selector) = *selector {
|
||||||
|
@ -1832,9 +2018,6 @@ where
|
||||||
{
|
{
|
||||||
let mut builder = SelectorBuilder::default();
|
let mut builder = SelectorBuilder::default();
|
||||||
|
|
||||||
let mut has_pseudo_element = false;
|
|
||||||
let mut slotted = false;
|
|
||||||
let mut part = false;
|
|
||||||
'outer_loop: loop {
|
'outer_loop: loop {
|
||||||
// Parse a sequence of simple selectors.
|
// Parse a sequence of simple selectors.
|
||||||
let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
|
let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
|
||||||
|
@ -1847,10 +2030,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
|
debug_assert!(state.intersects(
|
||||||
slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
|
SelectorParsingState::AFTER_PSEUDO_ELEMENT |
|
||||||
part = state.intersects(SelectorParsingState::AFTER_PART);
|
SelectorParsingState::AFTER_SLOTTED |
|
||||||
debug_assert!(has_pseudo_element || slotted || part);
|
SelectorParsingState::AFTER_PART
|
||||||
|
));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1893,7 +2077,7 @@ where
|
||||||
builder.push_combinator(combinator);
|
builder.push_combinator(combinator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Selector(builder.build(has_pseudo_element, slotted, part)))
|
Ok(Selector(builder.build()))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Impl: SelectorImpl> Selector<Impl> {
|
impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
|
@ -2517,7 +2701,7 @@ where
|
||||||
}
|
}
|
||||||
// Whitespace between "of" and the selector list is optional
|
// Whitespace between "of" and the selector list is optional
|
||||||
// https://github.com/w3c/csswg-drafts/issues/8285
|
// https://github.com/w3c/csswg-drafts/issues/8285
|
||||||
let selectors = SelectorList::parse_with_state(
|
let mut selectors = SelectorList::parse_with_state(
|
||||||
parser,
|
parser,
|
||||||
input,
|
input,
|
||||||
state |
|
state |
|
||||||
|
@ -2526,7 +2710,8 @@ where
|
||||||
ParseErrorRecovery::DiscardList,
|
ParseErrorRecovery::DiscardList,
|
||||||
)?;
|
)?;
|
||||||
Ok(Component::NthOf(NthOfSelectorData::new(
|
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());
|
let id = Component::ID(id.as_ref().into());
|
||||||
SimpleSelectorParseResult::SimpleSelector(id)
|
SimpleSelectorParseResult::SimpleSelector(id)
|
||||||
},
|
},
|
||||||
Token::Delim('.') => {
|
Token::Delim(delim) if delim == '.' || (delim == '&' && parser.parse_parent_selector()) => {
|
||||||
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
}
|
}
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
let class = match *input.next_including_whitespace()? {
|
SimpleSelectorParseResult::SimpleSelector(if delim == '&' {
|
||||||
Token::Ident(ref class) => class,
|
Component::ParentSelector
|
||||||
ref t => {
|
} else {
|
||||||
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
|
let class = match *input.next_including_whitespace()? {
|
||||||
return Err(location.new_custom_error(e));
|
Token::Ident(ref class) => class,
|
||||||
},
|
ref t => {
|
||||||
};
|
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
|
||||||
let class = Component::Class(class.as_ref().into());
|
return Err(location.new_custom_error(e));
|
||||||
SimpleSelectorParseResult::SimpleSelector(class)
|
},
|
||||||
|
};
|
||||||
|
Component::Class(class.as_ref().into())
|
||||||
|
})
|
||||||
},
|
},
|
||||||
Token::SquareBracketBlock => {
|
Token::SquareBracketBlock => {
|
||||||
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
|
@ -2884,6 +3072,10 @@ pub mod tests {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_parent_selector(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_part(&self) -> bool {
|
fn parse_part(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -2978,12 +3170,11 @@ pub mod tests {
|
||||||
let mut parser_input = ParserInput::new(input);
|
let mut parser_input = ParserInput::new(input);
|
||||||
let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input));
|
let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input));
|
||||||
if let Ok(ref selectors) = result {
|
if let Ok(ref selectors) = result {
|
||||||
assert_eq!(selectors.0.len(), 1);
|
|
||||||
// We can't assume that the serialized parsed selector will equal
|
// We can't assume that the serialized parsed selector will equal
|
||||||
// the input; for example, if there is no default namespace, '*|foo'
|
// the input; for example, if there is no default namespace, '*|foo'
|
||||||
// should serialize to 'foo'.
|
// should serialize to 'foo'.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
selectors.0[0].to_css_string(),
|
selectors.to_css_string(),
|
||||||
match expected {
|
match expected {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => input,
|
None => input,
|
||||||
|
@ -3493,6 +3684,31 @@ pub mod tests {
|
||||||
assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok());
|
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]
|
#[test]
|
||||||
fn test_pseudo_iter() {
|
fn test_pseudo_iter() {
|
||||||
let selector = &parse("q::before").unwrap().0[0];
|
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> {
|
impl<H: PartialEq, T: PartialEq> PartialEq for ThinArc<H, T> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn eq(&self, other: &ThinArc<H, T>) -> bool {
|
fn eq(&self, other: &ThinArc<H, T>) -> bool {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue