style: Add an extra flag to flag ::part() to selectors.

This grows the selector struct, but only in 32-bit, since in 64-bit we take
space from the alignment padding that we're paying due to having the size of the
slice as a word.

Differential Revision: https://phabricator.services.mozilla.com/D32645
This commit is contained in:
Emilio Cobos Álvarez 2019-06-11 17:42:23 +00:00
parent 96594be65d
commit fac050325c
No known key found for this signature in database
GPG key ID: E1152D0994E4BF8A
2 changed files with 92 additions and 28 deletions

View file

@ -96,18 +96,24 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
&mut self, &mut self,
parsed_pseudo: bool, parsed_pseudo: bool,
parsed_slotted: bool, parsed_slotted: bool,
parsed_part: bool,
) -> ThinArc<SpecificityAndFlags, Component<Impl>> { ) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
// Compute the specificity and flags. // 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 { if parsed_pseudo {
spec.0 |= HAS_PSEUDO_BIT; flags |= SelectorFlags::HAS_PSEUDO;
} }
if parsed_slotted { if parsed_slotted {
spec.0 |= HAS_SLOTTED_BIT; flags |= SelectorFlags::HAS_SLOTTED;
} }
if parsed_part {
self.build_with_specificity_and_flags(spec) 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
@ -188,28 +194,44 @@ fn split_from_end<T>(s: &[T], at: usize) -> (&[T], &[T]) {
s.split_at(s.len() - at) s.split_at(s.len() - at)
} }
pub const HAS_PSEUDO_BIT: u32 = 1 << 30; bitflags! {
pub const HAS_SLOTTED_BIT: u32 = 1 << 31; /// 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)] #[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 { impl SpecificityAndFlags {
#[inline] #[inline]
pub fn specificity(&self) -> u32 { pub fn specificity(&self) -> u32 {
self.0 & !(HAS_PSEUDO_BIT | HAS_SLOTTED_BIT) self.specificity
} }
#[inline] #[inline]
pub fn has_pseudo_element(&self) -> bool { pub fn has_pseudo_element(&self) -> bool {
(self.0 & HAS_PSEUDO_BIT) != 0 self.flags.intersects(SelectorFlags::HAS_PSEUDO)
} }
#[inline] #[inline]
pub fn is_slotted(&self) -> bool { 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)
} }
} }

View file

@ -6,7 +6,7 @@ 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, SpecificityAndFlags}; use crate::builder::{SelectorBuilder, SelectorFlags, SpecificityAndFlags};
use crate::context::QuirksMode; use crate::context::QuirksMode;
use crate::sink::Push; use crate::sink::Push;
pub use crate::visitor::{SelectorVisitor, Visit}; pub use crate::visitor::{SelectorVisitor, Visit};
@ -601,6 +601,11 @@ impl<Impl: SelectorImpl> Selector<Impl> {
self.0.header.header.is_slotted() self.0.header.header.is_slotted()
} }
#[inline]
pub fn is_part(&self) -> bool {
self.0.header.header.is_part()
}
#[inline] #[inline]
pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> {
if !self.has_pseudo_element() { if !self.has_pseudo_element() {
@ -720,7 +725,8 @@ 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.
pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> Self { #[allow(unused)]
pub (crate) fn from_vec(vec: Vec<Component<Impl>>, specificity: u32, flags: SelectorFlags) -> Self {
let mut builder = SelectorBuilder::default(); let mut builder = SelectorBuilder::default();
for component in vec.into_iter() { for component in vec.into_iter() {
if let Some(combinator) = component.as_combinator() { if let Some(combinator) = component.as_combinator() {
@ -729,7 +735,10 @@ impl<Impl: SelectorImpl> Selector<Impl> {
builder.push_simple_selector(component); builder.push_simple_selector(component);
} }
} }
let spec = SpecificityAndFlags(specificity_and_flags); let spec = SpecificityAndFlags {
specificity,
flags,
};
Selector(builder.build_with_specificity_and_flags(spec)) Selector(builder.build_with_specificity_and_flags(spec))
} }
@ -1445,6 +1454,7 @@ where
let mut has_pseudo_element = false; let mut has_pseudo_element = false;
let mut slotted = 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 state = match parse_compound_selector(parser, input, &mut builder)? { let state = match parse_compound_selector(parser, input, &mut builder)? {
@ -1461,7 +1471,7 @@ where
if state.intersects(SelectorParsingState::AFTER_PSEUDO) { if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT); has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); 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); debug_assert!(has_pseudo_element || slotted || part);
break; break;
} }
@ -1500,9 +1510,7 @@ where
builder.push_combinator(combinator); builder.push_combinator(combinator);
} }
// TODO(emilio): We'll have to flag part() somehow as well, but we need more Ok(Selector(builder.build(has_pseudo_element, slotted, part)))
// bits!
Ok(Selector(builder.build(has_pseudo_element, slotted)))
} }
impl<Impl: SelectorImpl> Selector<Impl> { impl<Impl: SelectorImpl> Selector<Impl> {
@ -2249,7 +2257,7 @@ where
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::builder::HAS_PSEUDO_BIT; use crate::builder::SelectorFlags;
use crate::parser; use crate::parser;
use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss};
use std::collections::HashMap; use std::collections::HashMap;
@ -2532,6 +2540,7 @@ pub mod tests {
lower_name: DummyAtom::from("eeÉ"), lower_name: DummyAtom::from("eeÉ"),
})], })],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2545,6 +2554,7 @@ pub mod tests {
}), }),
], ],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
// When the default namespace is not set, *| should be elided. // When the default namespace is not set, *| should be elided.
@ -2557,6 +2567,7 @@ pub mod tests {
lower_name: DummyAtom::from("e"), lower_name: DummyAtom::from("e"),
})], })],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
// When the default namespace is set, *| should _not_ be elided (as foo // When the default namespace is set, *| should _not_ be elided (as foo
@ -2577,13 +2588,15 @@ pub mod tests {
}), }),
], ],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
parse("*"), parse("*"),
Ok(SelectorList::from_vec(vec![Selector::from_vec( Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::ExplicitUniversalType], vec![Component::ExplicitUniversalType],
specificity(0, 0, 0) specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2594,13 +2607,15 @@ pub mod tests {
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
], ],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
parse_expected("*|*", Some("*")), parse_expected("*|*", Some("*")),
Ok(SelectorList::from_vec(vec![Selector::from_vec( Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::ExplicitUniversalType], vec![Component::ExplicitUniversalType],
specificity(0, 0, 0) specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2614,6 +2629,7 @@ pub mod tests {
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
], ],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2624,6 +2640,7 @@ pub mod tests {
Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())),
], ],
specificity(0, 2, 0), specificity(0, 2, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2631,6 +2648,7 @@ pub mod tests {
Ok(SelectorList::from_vec(vec![Selector::from_vec( Ok(SelectorList::from_vec(vec![Selector::from_vec(
vec![Component::ID(DummyAtom::from("bar"))], vec![Component::ID(DummyAtom::from("bar"))],
specificity(1, 0, 0), specificity(1, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2645,6 +2663,7 @@ pub mod tests {
Component::ID(DummyAtom::from("bar")), Component::ID(DummyAtom::from("bar")),
], ],
specificity(1, 1, 1), specificity(1, 1, 1),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2660,6 +2679,7 @@ pub mod tests {
Component::ID(DummyAtom::from("bar")), Component::ID(DummyAtom::from("bar")),
], ],
specificity(1, 1, 1), specificity(1, 1, 1),
Default::default(),
)])) )]))
); );
// Default namespace does not apply to attribute selectors // Default namespace does not apply to attribute selectors
@ -2673,6 +2693,7 @@ pub mod tests {
local_name_lower: DummyAtom::from("foo"), local_name_lower: DummyAtom::from("foo"),
}], }],
specificity(0, 1, 0), specificity(0, 1, 0),
Default::default(),
)])) )]))
); );
assert!(parse_ns("svg|circle", &parser).is_err()); assert!(parse_ns("svg|circle", &parser).is_err());
@ -2690,6 +2711,7 @@ pub mod tests {
}), }),
], ],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2700,6 +2722,7 @@ pub mod tests {
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
], ],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
// Default namespace does not apply to attribute selectors // Default namespace does not apply to attribute selectors
@ -2718,6 +2741,7 @@ pub mod tests {
}, },
], ],
specificity(0, 1, 0), specificity(0, 1, 0),
Default::default(),
)])) )]))
); );
// Default namespace does apply to type selectors // Default namespace does apply to type selectors
@ -2732,6 +2756,7 @@ pub mod tests {
}), }),
], ],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2742,6 +2767,7 @@ pub mod tests {
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
], ],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2752,6 +2778,7 @@ pub mod tests {
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
], ],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
// Default namespace applies to universal and type selectors inside :not and :matches, // Default namespace applies to universal and type selectors inside :not and :matches,
@ -2768,6 +2795,7 @@ pub mod tests {
), ),
], ],
specificity(0, 1, 0), specificity(0, 1, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2785,6 +2813,7 @@ pub mod tests {
), ),
], ],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2805,6 +2834,7 @@ pub mod tests {
), ),
], ],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2818,6 +2848,7 @@ pub mod tests {
case_sensitivity: ParsedCaseSensitivity::CaseSensitive, case_sensitivity: ParsedCaseSensitivity::CaseSensitive,
}], }],
specificity(0, 1, 0), specificity(0, 1, 0),
Default::default(),
)])) )]))
); );
// https://github.com/mozilla/servo/issues/1723 // https://github.com/mozilla/servo/issues/1723
@ -2828,7 +2859,8 @@ pub mod tests {
Component::Combinator(Combinator::PseudoElement), Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::Before), Component::PseudoElement(PseudoElement::Before),
], ],
specificity(0, 0, 1) | HAS_PSEUDO_BIT, specificity(0, 0, 1),
SelectorFlags::HAS_PSEUDO,
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2839,7 +2871,8 @@ pub mod tests {
Component::PseudoElement(PseudoElement::Before), Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover),
], ],
specificity(0, 1, 1) | HAS_PSEUDO_BIT, specificity(0, 1, 1),
SelectorFlags::HAS_PSEUDO,
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2851,7 +2884,8 @@ pub mod tests {
Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover),
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()); assert!(parse("::before:hover:lang(foo)").is_err());
@ -2874,7 +2908,8 @@ pub mod tests {
Component::Combinator(Combinator::PseudoElement), Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::After), Component::PseudoElement(PseudoElement::After),
], ],
specificity(0, 0, 2) | HAS_PSEUDO_BIT, specificity(0, 0, 2),
SelectorFlags::HAS_PSEUDO,
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2886,6 +2921,7 @@ pub mod tests {
Component::Class(DummyAtom::from("ok")), Component::Class(DummyAtom::from("ok")),
], ],
(1 << 20) + (1 << 10) + (0 << 0), (1 << 20) + (1 << 10) + (0 << 0),
Default::default(),
)])) )]))
); );
parser.default_ns = None; parser.default_ns = None;
@ -2901,6 +2937,7 @@ pub mod tests {
.into(), .into(),
)], )],
specificity(1, 0, 0), specificity(1, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2918,6 +2955,7 @@ pub mod tests {
.into(), .into(),
)], )],
specificity(0, 0, 1), specificity(0, 0, 1),
Default::default(),
)])) )]))
); );
// https://github.com/servo/servo/issues/16017 // https://github.com/servo/servo/issues/16017
@ -2930,6 +2968,7 @@ pub mod tests {
.into(), .into(),
)], )],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
assert_eq!( assert_eq!(
@ -2944,6 +2983,7 @@ pub mod tests {
.into(), .into(),
)], )],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
// *| should be elided if there is no default namespace. // *| should be elided if there is no default namespace.
@ -2957,6 +2997,7 @@ pub mod tests {
.into(), .into(),
)], )],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );
@ -2972,6 +3013,7 @@ pub mod tests {
.into(), .into(),
)], )],
specificity(0, 0, 0), specificity(0, 0, 0),
Default::default(),
)])) )]))
); );