diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index c3e733cbeb0..cddaf7195a1 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -96,18 +96,24 @@ impl SelectorBuilder { &mut self, parsed_pseudo: bool, parsed_slotted: bool, + parsed_part: bool, ) -> ThinArc> { // Compute the specificity and flags. - let mut spec = SpecificityAndFlags(specificity(self.simple_selectors.iter())); + let specificity = specificity(self.simple_selectors.iter()); + let mut flags = SelectorFlags::empty(); if parsed_pseudo { - spec.0 |= HAS_PSEUDO_BIT; + flags |= SelectorFlags::HAS_PSEUDO; } - if parsed_slotted { - spec.0 |= HAS_SLOTTED_BIT; + flags |= SelectorFlags::HAS_SLOTTED; } - - self.build_with_specificity_and_flags(spec) + 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 @@ -188,28 +194,44 @@ fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { s.split_at(s.len() - at) } -pub const HAS_PSEUDO_BIT: u32 = 1 << 30; -pub const HAS_SLOTTED_BIT: u32 = 1 << 31; +bitflags! { + /// Flags that indicate at which point of parsing a selector are we. + #[derive(Default, ToShmem)] + pub (crate) struct SelectorFlags : u8 { + const HAS_PSEUDO = 1 << 0; + const HAS_SLOTTED = 1 << 1; + const HAS_PART = 1 << 2; + } +} -/// We use ten bits for each specificity kind (id, class, element), and the two -/// high bits for the pseudo and slotted flags. #[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub struct SpecificityAndFlags(pub u32); +pub struct SpecificityAndFlags { + /// There are two free bits here, since we use ten bits for each specificity + /// kind (id, class, element). + pub (crate) specificity: u32, + /// There's padding after this field due to the size of the flags. + pub (crate) flags: SelectorFlags, +} impl SpecificityAndFlags { #[inline] pub fn specificity(&self) -> u32 { - self.0 & !(HAS_PSEUDO_BIT | HAS_SLOTTED_BIT) + self.specificity } #[inline] pub fn has_pseudo_element(&self) -> bool { - (self.0 & HAS_PSEUDO_BIT) != 0 + self.flags.intersects(SelectorFlags::HAS_PSEUDO) } #[inline] pub fn is_slotted(&self) -> bool { - (self.0 & HAS_SLOTTED_BIT) != 0 + self.flags.intersects(SelectorFlags::HAS_SLOTTED) + } + + #[inline] + pub fn is_part(&self) -> bool { + self.flags.intersects(SelectorFlags::HAS_PART) } } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index f049555730c..fa43aaa38a2 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -6,7 +6,7 @@ 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, SpecificityAndFlags}; +use crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags}; use crate::context::QuirksMode; use crate::sink::Push; pub use crate::visitor::{SelectorVisitor, Visit}; @@ -601,6 +601,11 @@ impl Selector { self.0.header.header.is_slotted() } + #[inline] + pub fn is_part(&self) -> bool { + self.0.header.header.is_part() + } + #[inline] pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { if !self.has_pseudo_element() { @@ -720,7 +725,8 @@ impl Selector { } /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. - pub fn from_vec(vec: Vec>, specificity_and_flags: u32) -> Self { + #[allow(unused)] + pub (crate) fn from_vec(vec: Vec>, specificity: u32, flags: SelectorFlags) -> Self { let mut builder = SelectorBuilder::default(); for component in vec.into_iter() { if let Some(combinator) = component.as_combinator() { @@ -729,7 +735,10 @@ impl Selector { builder.push_simple_selector(component); } } - let spec = SpecificityAndFlags(specificity_and_flags); + let spec = SpecificityAndFlags { + specificity, + flags, + }; Selector(builder.build_with_specificity_and_flags(spec)) } @@ -1445,6 +1454,7 @@ where let mut has_pseudo_element = false; let mut slotted = false; + let mut part = false; 'outer_loop: loop { // Parse a sequence of simple selectors. let state = match parse_compound_selector(parser, input, &mut builder)? { @@ -1461,7 +1471,7 @@ where if state.intersects(SelectorParsingState::AFTER_PSEUDO) { has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT); slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); - let part = state.intersects(SelectorParsingState::AFTER_PART); + part = state.intersects(SelectorParsingState::AFTER_PART); debug_assert!(has_pseudo_element || slotted || part); break; } @@ -1500,9 +1510,7 @@ where builder.push_combinator(combinator); } - // TODO(emilio): We'll have to flag part() somehow as well, but we need more - // bits! - Ok(Selector(builder.build(has_pseudo_element, slotted))) + Ok(Selector(builder.build(has_pseudo_element, slotted, part))) } impl Selector { @@ -2249,7 +2257,7 @@ where #[cfg(test)] pub mod tests { use super::*; - use crate::builder::HAS_PSEUDO_BIT; + use crate::builder::SelectorFlags; use crate::parser; use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; use std::collections::HashMap; @@ -2532,6 +2540,7 @@ pub mod tests { lower_name: DummyAtom::from("eeÉ"), })], specificity(0, 0, 1), + Default::default(), )])) ); assert_eq!( @@ -2545,6 +2554,7 @@ pub mod tests { }), ], specificity(0, 0, 1), + Default::default(), )])) ); // When the default namespace is not set, *| should be elided. @@ -2557,6 +2567,7 @@ pub mod tests { lower_name: DummyAtom::from("e"), })], specificity(0, 0, 1), + Default::default(), )])) ); // When the default namespace is set, *| should _not_ be elided (as foo @@ -2577,13 +2588,15 @@ pub mod tests { }), ], specificity(0, 0, 1), + Default::default(), )])) ); assert_eq!( parse("*"), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ExplicitUniversalType], - specificity(0, 0, 0) + specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2594,13 +2607,15 @@ pub mod tests { Component::ExplicitUniversalType, ], specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( parse_expected("*|*", Some("*")), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ExplicitUniversalType], - specificity(0, 0, 0) + specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2614,6 +2629,7 @@ pub mod tests { Component::ExplicitUniversalType, ], specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2624,6 +2640,7 @@ pub mod tests { Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), ], specificity(0, 2, 0), + Default::default(), )])) ); assert_eq!( @@ -2631,6 +2648,7 @@ pub mod tests { Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::ID(DummyAtom::from("bar"))], specificity(1, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2645,6 +2663,7 @@ pub mod tests { Component::ID(DummyAtom::from("bar")), ], specificity(1, 1, 1), + Default::default(), )])) ); assert_eq!( @@ -2660,6 +2679,7 @@ pub mod tests { Component::ID(DummyAtom::from("bar")), ], specificity(1, 1, 1), + Default::default(), )])) ); // Default namespace does not apply to attribute selectors @@ -2673,6 +2693,7 @@ pub mod tests { local_name_lower: DummyAtom::from("foo"), }], specificity(0, 1, 0), + Default::default(), )])) ); assert!(parse_ns("svg|circle", &parser).is_err()); @@ -2690,6 +2711,7 @@ pub mod tests { }), ], specificity(0, 0, 1), + Default::default(), )])) ); assert_eq!( @@ -2700,6 +2722,7 @@ pub mod tests { Component::ExplicitUniversalType, ], specificity(0, 0, 0), + Default::default(), )])) ); // Default namespace does not apply to attribute selectors @@ -2718,6 +2741,7 @@ pub mod tests { }, ], specificity(0, 1, 0), + Default::default(), )])) ); // Default namespace does apply to type selectors @@ -2732,6 +2756,7 @@ pub mod tests { }), ], specificity(0, 0, 1), + Default::default(), )])) ); assert_eq!( @@ -2742,6 +2767,7 @@ pub mod tests { Component::ExplicitUniversalType, ], specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2752,6 +2778,7 @@ pub mod tests { Component::ExplicitUniversalType, ], specificity(0, 0, 0), + Default::default(), )])) ); // Default namespace applies to universal and type selectors inside :not and :matches, @@ -2768,6 +2795,7 @@ pub mod tests { ), ], specificity(0, 1, 0), + Default::default(), )])) ); assert_eq!( @@ -2785,6 +2813,7 @@ pub mod tests { ), ], specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2805,6 +2834,7 @@ pub mod tests { ), ], specificity(0, 0, 1), + Default::default(), )])) ); assert_eq!( @@ -2818,6 +2848,7 @@ pub mod tests { case_sensitivity: ParsedCaseSensitivity::CaseSensitive, }], specificity(0, 1, 0), + Default::default(), )])) ); // https://github.com/mozilla/servo/issues/1723 @@ -2828,7 +2859,8 @@ pub mod tests { Component::Combinator(Combinator::PseudoElement), Component::PseudoElement(PseudoElement::Before), ], - specificity(0, 0, 1) | HAS_PSEUDO_BIT, + specificity(0, 0, 1), + SelectorFlags::HAS_PSEUDO, )])) ); assert_eq!( @@ -2839,7 +2871,8 @@ pub mod tests { Component::PseudoElement(PseudoElement::Before), Component::NonTSPseudoClass(PseudoClass::Hover), ], - specificity(0, 1, 1) | HAS_PSEUDO_BIT, + specificity(0, 1, 1), + SelectorFlags::HAS_PSEUDO, )])) ); assert_eq!( @@ -2851,7 +2884,8 @@ pub mod tests { Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover), ], - specificity(0, 2, 1) | HAS_PSEUDO_BIT, + specificity(0, 2, 1), + SelectorFlags::HAS_PSEUDO, )])) ); assert!(parse("::before:hover:lang(foo)").is_err()); @@ -2874,7 +2908,8 @@ pub mod tests { Component::Combinator(Combinator::PseudoElement), Component::PseudoElement(PseudoElement::After), ], - specificity(0, 0, 2) | HAS_PSEUDO_BIT, + specificity(0, 0, 2), + SelectorFlags::HAS_PSEUDO, )])) ); assert_eq!( @@ -2886,6 +2921,7 @@ pub mod tests { Component::Class(DummyAtom::from("ok")), ], (1 << 20) + (1 << 10) + (0 << 0), + Default::default(), )])) ); parser.default_ns = None; @@ -2901,6 +2937,7 @@ pub mod tests { .into(), )], specificity(1, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2918,6 +2955,7 @@ pub mod tests { .into(), )], specificity(0, 0, 1), + Default::default(), )])) ); // https://github.com/servo/servo/issues/16017 @@ -2930,6 +2968,7 @@ pub mod tests { .into(), )], specificity(0, 0, 0), + Default::default(), )])) ); assert_eq!( @@ -2944,6 +2983,7 @@ pub mod tests { .into(), )], specificity(0, 0, 0), + Default::default(), )])) ); // *| should be elided if there is no default namespace. @@ -2957,6 +2997,7 @@ pub mod tests { .into(), )], specificity(0, 0, 0), + Default::default(), )])) ); @@ -2972,6 +3013,7 @@ pub mod tests { .into(), )], specificity(0, 0, 0), + Default::default(), )])) );