diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index b5b2afca512..95ef949d2ab 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -715,6 +715,7 @@ where Component::Root | Component::Empty | Component::Scope | + Component::ParentSelector | Component::Nth(..) | Component::Host(None) => 0, } diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index 43c5937a3e2..7689ba0d478 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -96,31 +96,16 @@ impl SelectorBuilder { /// Consumes the builder, producing a Selector. #[inline(always)] - pub fn build( - &mut self, - parsed_pseudo: bool, - parsed_slotted: bool, - parsed_part: bool, - ) -> ThinArc> { + pub fn build(&mut self) -> ThinArc> { // 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> { @@ -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 for u32 { } } -fn specificity(iter: slice::Iter>) -> u32 +pub(crate) fn specificity_and_flags(iter: slice::Iter>) -> SpecificityAndFlags where Impl: SelectorImpl, { - complex_selector_specificity(iter).into() + complex_selector_specificity_and_flags(iter).into() } -fn complex_selector_specificity(iter: slice::Iter>) -> Specificity +fn complex_selector_specificity_and_flags( + iter: slice::Iter>, +) -> SpecificityAndFlags where Impl: SelectorImpl, { - fn simple_selector_specificity( + fn component_specificity( simple_selector: &Component, 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(list: &[Selector]) -> 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( + list: &[Selector], +) -> 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 } } diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 16b491dcd5f..5e8be75eaf0 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -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(), diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 0e971cea31d..7874a78e911 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -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 SelectorList { } /// Creates a SelectorList from a Vec of selectors. Used in tests. - pub fn from_vec(v: Vec>) -> Self { + #[allow(dead_code)] + pub(crate) fn from_vec(v: Vec>) -> Self { SelectorList(SmallVec::from_vec(v)) } } @@ -626,11 +635,21 @@ impl Selector { 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 Selector { } /// 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>, specificity: u32, @@ -803,6 +822,166 @@ impl Selector { Selector(builder.build_with_specificity_and_flags(spec)) } + pub fn replace_parent_selector(&self, parent: &[Selector]) -> Cow { + 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( + orig: &[Selector], + parent: &[Selector], + specificity: &mut Specificity, + with_specificity: bool, + ) -> Vec> { + 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( + orig: &Selector, + parent: &[Selector], + specificity: &mut Specificity, + ) -> Selector { + 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 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(..), - )) + pub fn new(nth_data: &NthSelectorData, selectors: I) -> Self + where + I: Iterator> + 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 { Root, Empty, Scope, + ParentSelector, Nth(NthSelectorData), NthOf(NthOfSelectorData), NonTSPseudoClass(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::NonTSPseudoClass), @@ -1456,7 +1636,12 @@ impl Debug for Selector { 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 ToCss for Component { 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 Selector { @@ -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]; diff --git a/components/servo_arc/lib.rs b/components/servo_arc/lib.rs index 7e11c70f4e6..5169c3a1ade 100644 --- a/components/servo_arc/lib.rs +++ b/components/servo_arc/lib.rs @@ -1048,6 +1048,32 @@ impl Arc> { } } +impl UniqueArc> { + #[inline] + pub fn from_header_and_iter(header: HeaderWithLength, items: I) -> Self + where + I: Iterator + 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 { + Arc::into_thin(self.0) + } +} + impl PartialEq for ThinArc { #[inline] fn eq(&self, other: &ThinArc) -> bool {