diff --git a/Cargo.lock b/Cargo.lock index 4e5e1c777a2..b5981a9cf6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2523,6 +2523,7 @@ dependencies = [ "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_arc 0.0.1", "size_of_test 0.0.1", "smallvec 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2688,6 +2689,15 @@ dependencies = [ "url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "servo_arc" +version = "0.0.1" +dependencies = [ + "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "servo_atoms" version = "0.0.1" @@ -2913,6 +2923,7 @@ dependencies = [ "selectors 0.19.0", "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_arc 0.0.1", "servo_atoms 0.0.1", "servo_config 0.0.1", "servo_url 0.0.1", diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index be708113def..f875986edd2 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -2061,7 +2061,7 @@ impl ElementMethods for Element { Err(()) => Err(Error::Syntax), Ok(selectors) => { let mut ctx = MatchingContext::new(MatchingMode::Normal, None); - Ok(matches_selector_list(&selectors.0, &Root::from_ref(self), &mut ctx)) + Ok(matches_selector_list(&selectors, &Root::from_ref(self), &mut ctx)) } } } @@ -2080,7 +2080,7 @@ impl ElementMethods for Element { for element in root.inclusive_ancestors() { if let Some(element) = Root::downcast::(element) { let mut ctx = MatchingContext::new(MatchingMode::Normal, None); - if matches_selector_list(&selectors.0, &element, &mut ctx) { + if matches_selector_list(&selectors, &element, &mut ctx) { return Ok(Some(element)); } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index cc817587eda..8dde9cc7b01 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -345,7 +345,7 @@ impl<'a> Iterator for QuerySelectorIterator { type Item = Root; fn next(&mut self) -> Option> { - let selectors = &self.selectors.0; + let selectors = &self.selectors; // TODO(cgaebel): Is it worth it to build a bloom filter here // (instead of passing `None`)? Probably. @@ -722,7 +722,7 @@ impl Node { Ok(selectors) => { let mut ctx = MatchingContext::new(MatchingMode::Normal, None); Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| { - matches_selector_list(&selectors.0, element, &mut ctx) + matches_selector_list(&selectors, element, &mut ctx) })) } } diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index effb612bb43..15aba3d1811 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -28,6 +28,7 @@ cssparser = "0.13.7" fnv = "1.0" phf = "0.7.18" precomputed-hash = "0.1" +servo_arc = { path = "../servo_arc" } smallvec = "0.4" [dev-dependencies] diff --git a/components/selectors/arcslice.rs b/components/selectors/arcslice.rs deleted file mode 100644 index e5722ba6505..00000000000 --- a/components/selectors/arcslice.rs +++ /dev/null @@ -1,326 +0,0 @@ -/* Licensed under the Apache License, Version 2.0 or the MIT license - * , at your - * option. This file may not be copied, modified, or distributed - * except according to those terms. - * - * See the COPYRIGHT file at the top-level directory of this distribution */ -//! A thread-safe reference-counted slice type. -//! -//! Forked from https://github.com/huonw/shared_slice , which doesn't work on -//! rust stable. - -use std::{cmp, fmt, ops}; -use std::hash::{Hash, Hasher}; -use std::sync::{Arc, Weak}; - - -/// A reference-counted slice type. -pub struct ArcSlice { - data: *const [T], - counts: Arc>, -} - -unsafe impl Send for ArcSlice {} -unsafe impl Sync for ArcSlice {} - -/// A non-owning reference-counted slice type. -/// -/// This is to `ArcSlice` as `std::sync::Weak` is to `std::sync::Arc`, and -/// allows one to have cyclic references without stopping memory from -/// being deallocated. -pub struct WeakSlice { - data: *const [T], - counts: Weak>, -} -unsafe impl Send for WeakSlice {} -unsafe impl Sync for WeakSlice {} - -impl ArcSlice { - /// Construct a new `ArcSlice` containing the elements of `slice`. - /// - /// This reuses the allocation of `slice`. - pub fn new(slice: Box<[T]>) -> ArcSlice { - ArcSlice { - data: &*slice, - counts: Arc::new(slice), - } - } - - /// Downgrade self into a weak slice. - pub fn downgrade(&self) -> WeakSlice { - WeakSlice { - data: self.data, - counts: Arc::downgrade(&self.counts) - } - } - - /// Construct a new `ArcSlice` that only points to elements at - /// indices `lo` (inclusive) through `hi` (exclusive). - /// - /// This consumes `self` to avoid unnecessary reference-count - /// modifications. Use `.clone()` if it is necessary to refer to - /// `self` after calling this. - /// - /// # Panics - /// - /// Panics if `lo > hi` or if either are strictly greater than - /// `self.len()`. - pub fn slice(mut self, lo: usize, hi: usize) -> ArcSlice { - self.data = &self[lo..hi]; - self - } - /// Construct a new `ArcSlice` that only points to elements at - /// indices up to `hi` (exclusive). - /// - /// This consumes `self` to avoid unnecessary reference-count - /// modifications. Use `.clone()` if it is necessary to refer to - /// `self` after calling this. - /// - /// # Panics - /// - /// Panics if `hi > self.len()`. - pub fn slice_to(self, hi: usize) -> ArcSlice { - self.slice(0, hi) - } - /// Construct a new `ArcSlice` that only points to elements at - /// indices starting at `lo` (inclusive). - /// - /// This consumes `self` to avoid unnecessary reference-count - /// modifications. Use `.clone()` if it is necessary to refer to - /// `self` after calling this. - /// - /// # Panics - /// - /// Panics if `lo > self.len()`. - pub fn slice_from(self, lo: usize) -> ArcSlice { - let hi = self.len(); - self.slice(lo, hi) - } -} - -impl Clone for ArcSlice { - fn clone(&self) -> ArcSlice { - ArcSlice { - data: self.data, - counts: self.counts.clone() - } - } -} - -impl ops::Deref for ArcSlice { - type Target = [T]; - fn deref<'a>(&'a self) -> &'a [T] { - unsafe { &*self.data } - } -} - -impl AsRef<[T]> for ArcSlice { - fn as_ref(&self) -> &[T] { &**self } -} - -impl PartialEq for ArcSlice { - fn eq(&self, other: &ArcSlice) -> bool { **self == **other } - fn ne(&self, other: &ArcSlice) -> bool { **self != **other } -} -impl Eq for ArcSlice {} - -impl PartialOrd for ArcSlice { - fn partial_cmp(&self, other: &ArcSlice) -> Option { - (**self).partial_cmp(&**other) - } - fn lt(&self, other: &ArcSlice) -> bool { **self < **other } - fn le(&self, other: &ArcSlice) -> bool { **self <= **other } - fn gt(&self, other: &ArcSlice) -> bool { **self > **other } - fn ge(&self, other: &ArcSlice) -> bool { **self >= **other } -} -impl Ord for ArcSlice { - fn cmp(&self, other: &ArcSlice) -> cmp::Ordering { (**self).cmp(&**other) } -} - -impl Hash for ArcSlice { - fn hash(&self, state: &mut H) { - Hash::hash(&**self, state) - } -} - -impl fmt::Debug for ArcSlice { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl WeakSlice { - /// Attempt to upgrade `self` to a strongly-counted `ArcSlice`. - /// - /// Returns `None` if this is not possible (the data has already - /// been freed). - pub fn upgrade(&self) -> Option> { - self.counts.upgrade().map(|counts| { - ArcSlice { - data: self.data, - counts: counts - } - }) - } -} - -#[cfg(test)] -mod tests { - use std::cell::Cell; - use std::cmp::Ordering; - use std::sync::{Arc, Mutex}; - use super::{ArcSlice, WeakSlice}; - #[test] - fn clone() { - let x = ArcSlice::new(Box::new([Cell::new(false)])); - let y = x.clone(); - - assert_eq!(x[0].get(), false); - assert_eq!(y[0].get(), false); - - x[0].set(true); - assert_eq!(x[0].get(), true); - assert_eq!(y[0].get(), true); - } - - #[test] - fn test_upgrade_downgrade() { - let x = ArcSlice::new(Box::new([1])); - let y: WeakSlice<_> = x.downgrade(); - - assert_eq!(y.upgrade(), Some(x.clone())); - - drop(x); - - assert!(y.upgrade().is_none()) - } - - #[test] - fn test_total_cmp() { - let x = ArcSlice::new(Box::new([1, 2, 3])); - let y = ArcSlice::new(Box::new([1, 2, 3])); - let z = ArcSlice::new(Box::new([1, 2, 4])); - - assert_eq!(x, x); - assert_eq!(x, y); - assert!(x != z); - assert!(y != z); - - assert!(x < z); - assert!(x <= z); - assert!(!(x > z)); - assert!(!(x >= z)); - - assert!(!(z < x)); - assert!(!(z <= x)); - assert!(z > x); - assert!(z >= x); - - assert_eq!(x.partial_cmp(&x), Some(Ordering::Equal)); - assert_eq!(x.partial_cmp(&y), Some(Ordering::Equal)); - assert_eq!(x.partial_cmp(&z), Some(Ordering::Less)); - assert_eq!(z.partial_cmp(&y), Some(Ordering::Greater)); - - assert_eq!(x.cmp(&x), Ordering::Equal); - assert_eq!(x.cmp(&y), Ordering::Equal); - assert_eq!(x.cmp(&z), Ordering::Less); - assert_eq!(z.cmp(&y), Ordering::Greater); - } - - #[test] - fn test_partial_cmp() { - use std::f64; - let x = ArcSlice::new(Box::new([1.0, f64::NAN])); - let y = ArcSlice::new(Box::new([1.0, f64::NAN])); - let z = ArcSlice::new(Box::new([2.0, f64::NAN])); - let w = ArcSlice::new(Box::new([f64::NAN, 1.0])); - assert!(!(x == y)); - assert!(x != y); - - assert!(!(x < y)); - assert!(!(x <= y)); - assert!(!(x > y)); - assert!(!(x >= y)); - - assert!(x < z); - assert!(x <= z); - assert!(!(x > z)); - assert!(!(x >= z)); - - assert!(!(z < w)); - assert!(!(z <= w)); - assert!(!(z > w)); - assert!(!(z >= w)); - - assert_eq!(x.partial_cmp(&x), None); - assert_eq!(x.partial_cmp(&y), None); - assert_eq!(x.partial_cmp(&z), Some(Ordering::Less)); - assert_eq!(z.partial_cmp(&x), Some(Ordering::Greater)); - - assert_eq!(x.partial_cmp(&w), None); - assert_eq!(y.partial_cmp(&w), None); - assert_eq!(z.partial_cmp(&w), None); - assert_eq!(w.partial_cmp(&w), None); - } - - #[test] - fn test_show() { - let x = ArcSlice::new(Box::new([1, 2])); - assert_eq!(format!("{:?}", x), "[1, 2]"); - - let y: ArcSlice = ArcSlice::new(Box::new([])); - assert_eq!(format!("{:?}", y), "[]"); - } - - #[test] - fn test_slice() { - let x = ArcSlice::new(Box::new([1, 2, 3])); - let real = [1, 2, 3]; - for i in 0..3 + 1 { - for j in i..3 + 1 { - let slice: ArcSlice<_> = x.clone().slice(i, j); - assert_eq!(&*slice, &real[i..j]); - } - assert_eq!(&*x.clone().slice_to(i), &real[..i]); - assert_eq!(&*x.clone().slice_from(i), &real[i..]); - } - } - - - #[test] - fn test_send_sync() { - fn assert_send() {} - fn assert_sync() {} - - assert_send::>(); - assert_sync::>(); - assert_send::>(); - assert_sync::>(); - } - - #[test] - fn test_drop() { - let drop_flag = Arc::new(Mutex::new(0)); - struct Foo(Arc>); - - impl Drop for Foo { - fn drop(&mut self) { - let mut n = self.0.lock().unwrap(); - *n += 1; - } - } - - let whole = ArcSlice::new(Box::new([Foo(drop_flag.clone()), Foo(drop_flag.clone())])); - - drop(whole); - assert_eq!(*drop_flag.lock().unwrap(), 2); - - *drop_flag.lock().unwrap() = 0; - - let whole = ArcSlice::new(Box::new([Foo(drop_flag.clone()), Foo(drop_flag.clone())])); - let part = whole.slice(1, 2); - drop(part); - assert_eq!(*drop_flag.lock().unwrap(), 2); - } -} diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index 3e413c0c08b..a6f4acd3d84 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -9,9 +9,9 @@ extern crate fnv; extern crate phf; extern crate precomputed_hash; #[cfg(test)] #[macro_use] extern crate size_of_test; +extern crate servo_arc; extern crate smallvec; -pub mod arcslice; pub mod attr; pub mod bloom; pub mod matching; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 444d24a7d1c..3a2ddfd998a 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -4,8 +4,8 @@ use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint}; use bloom::BloomFilter; -use parser::{Combinator, ComplexSelector, Component, LocalName}; -use parser::{Selector, SelectorInner, SelectorIter}; +use parser::{AncestorHashes, Combinator, Component, LocalName}; +use parser::{Selector, SelectorIter, SelectorList}; use std::borrow::Borrow; use tree::Element; @@ -152,27 +152,30 @@ impl<'a> MatchingContext<'a> { } } -pub fn matches_selector_list(selector_list: &[Selector], +pub fn matches_selector_list(selector_list: &SelectorList, element: &E, context: &mut MatchingContext) -> bool where E: Element { - selector_list.iter().any(|selector| { - matches_selector(&selector.inner, + selector_list.0.iter().any(|selector_and_hashes| { + matches_selector(&selector_and_hashes.selector, + 0, + &selector_and_hashes.hashes, element, context, &mut |_, _| {}) }) } -fn may_match(sel: &SelectorInner, +#[inline(always)] +fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool where E: Element, { // Check against the list of precomputed hashes. - for hash in sel.ancestor_hashes.iter() { + for hash in hashes.0.iter() { // If we hit the 0 sentinel hash, that means the rest are zero as well. if *hash == 0 { break; @@ -330,8 +333,18 @@ enum SelectorMatchingResult { NotMatchedGlobally, } -/// Matches an inner selector. -pub fn matches_selector(selector: &SelectorInner, +/// Matches a selector, fast-rejecting against a bloom filter. +/// +/// We accept an offset to allow consumers to represent and match against partial +/// selectors (indexed from the right). We use this API design, rather than +/// having the callers pass a SelectorIter, because creating a SelectorIter +/// requires dereferencing the selector to get the length, which adds an +/// unncessary cache miss for cases when we can fast-reject with AncestorHashes +/// (which the caller can store inline with the selector pointer). +#[inline(always)] +pub fn matches_selector(selector: &Selector, + offset: usize, + hashes: &AncestorHashes, element: &E, context: &mut MatchingContext, flags_setter: &mut F) @@ -341,18 +354,17 @@ pub fn matches_selector(selector: &SelectorInner, { // Use the bloom filter to fast-reject. if let Some(filter) = context.bloom_filter { - if !may_match::(&selector, filter) { + if !may_match::(hashes, filter) { return false; } } - matches_complex_selector(&selector.complex, element, context, flags_setter) + matches_complex_selector(selector, offset, element, context, flags_setter) } /// Matches a complex selector. -/// -/// Use `matches_selector` if you need to skip pseudos. -pub fn matches_complex_selector(complex_selector: &ComplexSelector, +pub fn matches_complex_selector(complex_selector: &Selector, + offset: usize, element: &E, context: &mut MatchingContext, flags_setter: &mut F) @@ -360,11 +372,15 @@ pub fn matches_complex_selector(complex_selector: &ComplexSelector(pub Vec>); +pub struct SelectorAndHashes { + pub selector: Selector, + pub hashes: AncestorHashes, +} + +impl SelectorAndHashes { + pub fn new(selector: Selector) -> Self { + let hashes = AncestorHashes::new(&selector); + Self::new_with_hashes(selector, hashes) + } + + pub fn new_with_hashes(selector: Selector, hashes: AncestorHashes) -> Self { + SelectorAndHashes { + selector: selector, + hashes: hashes, + } + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct SelectorList(pub Vec>); impl SelectorList { /// Parse a comma-separated list of Selectors. @@ -139,117 +159,52 @@ impl SelectorList { /// Return the Selectors or Err if there is an invalid selector. pub fn parse

(parser: &P, input: &mut CssParser) -> Result where P: Parser { - input.parse_comma_separated(|input| parse_selector(parser, input)) + input.parse_comma_separated(|input| parse_selector(parser, input).map(SelectorAndHashes::new)) .map(SelectorList) } + + /// Creates a SelectorList from a Vec of selectors. Used in tests. + pub fn from_vec(v: Vec>) -> Self { + SelectorList(v.into_iter().map(SelectorAndHashes::new).collect()) + } } -/// Copied from Gecko, where it was noted to be unmeasured. +/// Copied from Gecko, who copied it from WebKit. Note that increasing the +/// number of hashes here will adversely affect the cache hit when fast- +/// rejecting long lists of Rules with inline hashes. const NUM_ANCESTOR_HASHES: usize = 4; -/// The cores parts of a selector used for matching. This exists to make that -/// information accessibly separately from the specificity and pseudo-element -/// information that lives on |Selector| proper. We may want to refactor things -/// and move that information elsewhere, at which point we could rename this -/// to |Selector|. -#[derive(PartialEq, Eq, Clone)] -pub struct SelectorInner { - /// The selector data. - pub complex: ComplexSelector, - /// Ancestor hashes for the bloom filter. We precompute these and store - /// them inline to optimize cache performance during selector matching. - /// This matters a lot. - pub ancestor_hashes: [u32; NUM_ANCESTOR_HASHES], -} +/// Ancestor hashes for the bloom filter. We precompute these and store them +/// inline with selectors to optimize cache performance during matching. +/// This matters a lot. +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct AncestorHashes(pub [u32; NUM_ANCESTOR_HASHES]); -impl SelectorInner { - pub fn new(c: ComplexSelector) -> Self { +impl AncestorHashes { + pub fn new(s: &Selector) -> Self { + Self::from_iter(s.iter()) + } + + pub fn from_iter(iter: SelectorIter) -> Self { let mut hashes = [0; NUM_ANCESTOR_HASHES]; - { - // Compute ancestor hashes for the bloom filter. - let mut hash_iter = c.iter_ancestors() - .map(|x| x.ancestor_hash()) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()); - for i in 0..NUM_ANCESTOR_HASHES { - hashes[i] = match hash_iter.next() { - Some(x) => x, - None => break, - } + // Compute ancestor hashes for the bloom filter. + let mut hash_iter = AncestorIter::new(iter) + .map(|x| x.ancestor_hash()) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()); + for i in 0..NUM_ANCESTOR_HASHES { + hashes[i] = match hash_iter.next() { + Some(x) => x, + None => break, } } - SelectorInner { - complex: c, - ancestor_hashes: hashes, - } - } - - /// Creates a SelectorInner from a Vec of Components. Used in tests. - pub fn from_vec(vec: Vec>) -> Self { - let complex = ComplexSelector::from_vec(vec); - Self::new(complex) - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct Selector { - pub inner: SelectorInner, - specificity_and_flags: u32, -} - -impl ::std::borrow::Borrow> for Selector { - fn borrow(&self) -> &SelectorInner { - &self.inner + AncestorHashes(hashes) } } const HAS_PSEUDO_BIT: u32 = 1 << 30; -impl Selector { - pub fn specificity(&self) -> u32 { - self.specificity_and_flags & !HAS_PSEUDO_BIT - } - - pub fn new_for_unit_testing(inner: SelectorInner, specificity: u32) -> Self { - Self { - inner: inner, - specificity_and_flags: specificity, - } - } - - pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { - if !self.has_pseudo_element() { - return None - } - - for component in self.inner.complex.iter() { - if let Component::PseudoElement(ref pseudo) = *component { - return Some(pseudo) - } - } - - debug_assert!(false, "has_pseudo_element lied!"); - None - } - - pub fn has_pseudo_element(&self) -> bool { - (self.specificity_and_flags & HAS_PSEUDO_BIT) != 0 - } - - /// Whether this selector (pseudo-element part excluded) matches every element. - /// - /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs - pub fn is_universal(&self) -> bool { - self.inner.complex.iter_raw().all(|c| matches!(*c, - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::Combinator(Combinator::PseudoElement) | - Component::PseudoElement(..) - )) - } -} - pub trait SelectorMethods { type Impl: SelectorImpl; @@ -260,16 +215,6 @@ pub trait SelectorMethods { impl SelectorMethods for Selector { type Impl = Impl; - fn visit(&self, visitor: &mut V) -> bool - where V: SelectorVisitor, - { - self.inner.complex.visit(visitor) - } -} - -impl SelectorMethods for ComplexSelector { - type Impl = Impl; - fn visit(&self, visitor: &mut V) -> bool where V: SelectorVisitor, { @@ -362,7 +307,20 @@ pub fn namespace_empty_string() -> Impl::NamespaceUrl { Impl::NamespaceUrl::default() } -/// A ComplexSelectors stores a sequence of simple selectors and combinators. The +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct SpecificityAndFlags(u32); + +impl SpecificityAndFlags { + fn specificity(&self) -> u32 { + self.0 & !HAS_PSEUDO_BIT + } + + fn has_pseudo_element(&self) -> bool { + (self.0 & HAS_PSEUDO_BIT) != 0 + } +} + +/// A Selector stores a sequence of simple selectors and combinators. The /// iterator classes allow callers to iterate at either the raw sequence level or /// at the level of sequences of simple selectors separated by combinators. Most /// callers want the higher-level iterator. @@ -371,9 +329,45 @@ pub fn namespace_empty_string() -> Impl::NamespaceUrl { /// canonical iteration order is right-to-left (selector matching order). The /// iterators abstract over these details. #[derive(Clone, Eq, PartialEq)] -pub struct ComplexSelector(ArcSlice>); +pub struct Selector(ThinArc>); + +impl Selector { + pub fn specificity(&self) -> u32 { + self.0.header.header.specificity() + } + + pub fn has_pseudo_element(&self) -> bool { + self.0.header.header.has_pseudo_element() + } + + pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { + if !self.has_pseudo_element() { + return None + } + + for component in self.iter() { + if let Component::PseudoElement(ref pseudo) = *component { + return Some(pseudo) + } + } + + debug_assert!(false, "has_pseudo_element lied!"); + None + } + + /// Whether this selector (pseudo-element part excluded) matches every element. + /// + /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs + pub fn is_universal(&self) -> bool { + self.iter_raw().all(|c| matches!(*c, + Component::ExplicitUniversalType | + Component::ExplicitAnyNamespace | + Component::Combinator(Combinator::PseudoElement) | + Component::PseudoElement(..) + )) + } + -impl ComplexSelector { /// Returns an iterator over the next sequence of simple selectors. When /// a combinator is reached, the iterator will return None, and /// next_sequence() may be called to continue to the next sequence. @@ -384,6 +378,15 @@ impl ComplexSelector { } } + pub fn iter_from(&self, offset: usize) -> SelectorIter { + // Note: selectors are stored left-to-right but logical order is right-to-left. + let iter = self.0.slice[..(self.0.slice.len() - offset)].iter().rev(); + SelectorIter { + iter: iter, + next_combinator: None, + } + } + /// Returns an iterator over the entire sequence of simple selectors and combinators, /// from right to left. pub fn iter_raw(&self) -> Rev>> { @@ -393,34 +396,13 @@ impl ComplexSelector { /// Returns an iterator over the entire sequence of simple selectors and combinators, /// from left to right. pub fn iter_raw_rev(&self) -> slice::Iter> { - self.0.iter() + self.0.slice.iter() } - /// Returns an iterator over ancestor simple selectors. All combinators and - /// non-ancestor simple selectors will be skipped. - pub fn iter_ancestors(&self) -> AncestorIter { - AncestorIter::new(self.iter()) - } - - /// Returns a ComplexSelector identical to |self| but with the rightmost |index| - /// entries removed. - pub fn slice_from(&self, index: usize) -> Self { - // Note that we convert the slice_from to slice_to because selectors are - // stored left-to-right but logical order is right-to-left. - ComplexSelector(self.0.clone().slice_to(self.0.len() - index)) - } - - /// Returns a ComplexSelector identical to |self| but with the leftmost - /// |len() - index| entries removed. - pub fn slice_to(&self, index: usize) -> Self { - // Note that we convert the slice_to to slice_from because selectors are - // stored left-to-right but logical order is right-to-left. - ComplexSelector(self.0.clone().slice_from(self.0.len() - index)) - } - - /// Creates a ComplexSelector from a vec of Components. Used in tests. - pub fn from_vec(vec: Vec>) -> Self { - ComplexSelector(ArcSlice::new(vec.into_boxed_slice())) + /// Creates a Selector from a vec of Components. Used in tests. + pub fn from_vec(vec: Vec>, specificity_and_flags: u32) -> Self { + let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len()); + Selector(Arc::into_thin(Arc::from_header_and_iter(header, vec.into_iter()))) } } @@ -590,7 +572,7 @@ pub enum Component { impl Component { /// Compute the ancestor hash to check against the bloom filter. - fn ancestor_hash(&self) -> Option { + pub fn ancestor_hash(&self) -> Option { match *self { Component::LocalName(LocalName { ref name, ref lower_name }) => { // Only insert the local-name into the filter if it's all lowercase. @@ -644,12 +626,6 @@ impl Debug for Selector { } } -impl Debug for SelectorInner { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.complex.to_css(f) } -} -impl Debug for ComplexSelector { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } -} impl Debug for Component { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } } @@ -665,22 +641,16 @@ impl ToCss for SelectorList { let mut iter = self.0.iter(); let first = iter.next() .expect("Empty SelectorList, should contain at least one selector"); - first.to_css(dest)?; - for selector in iter { + first.selector.to_css(dest)?; + for selector_and_hashes in iter { dest.write_str(", ")?; - selector.to_css(dest)?; + selector_and_hashes.selector.to_css(dest)?; } Ok(()) } } impl ToCss for Selector { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - self.inner.complex.to_css(dest) - } -} - -impl ToCss for ComplexSelector { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { for item in self.iter_raw_rev() { item.to_css(dest)?; @@ -889,13 +859,13 @@ impl From for u32 { } } -fn specificity(complex_selector: &ComplexSelector) -> u32 +fn specificity(iter: SelectorIter) -> u32 where Impl: SelectorImpl { - complex_selector_specificity(complex_selector).into() + complex_selector_specificity(iter).into() } -fn complex_selector_specificity(selector: &ComplexSelector) +fn complex_selector_specificity(mut iter: SelectorIter) -> Specificity where Impl: SelectorImpl { @@ -944,9 +914,7 @@ fn complex_selector_specificity(selector: &ComplexSelector) } } - let mut specificity = Default::default(); - let mut iter = selector.iter(); loop { for simple_selector in &mut iter { simple_selector_specificity(&simple_selector, &mut specificity); @@ -958,43 +926,20 @@ fn complex_selector_specificity(selector: &ComplexSelector) specificity } +/// We make this large because the result of parsing a selector is fed into a new +/// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, +/// Components are large enough that we don't have much cache locality benefit +/// from reserving stack space for fewer of them. +type ParseVec = SmallVec<[Component; 32]>; + /// Build up a Selector. /// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; /// /// `Err` means invalid selector. -fn parse_selector(parser: &P, input: &mut CssParser) -> Result, ()> - where P: Parser, Impl: SelectorImpl -{ - let (complex, has_pseudo_element) = parse_complex_selector(parser, input)?; - let mut specificity = specificity(&complex); - if has_pseudo_element { - specificity |= HAS_PSEUDO_BIT; - } - Ok(Selector { - specificity_and_flags: specificity, - inner: SelectorInner::new(complex), - }) -} - -/// We use a SmallVec for parsing to avoid extra reallocs compared to using a Vec -/// directly. When parsing is done, we convert the SmallVec into a Vec (which is -/// free if the vec has already spilled to the heap, and more cache-friendly if -/// it hasn't), and then steal the buffer of that vec into a boxed slice. -/// -/// If we parse N <= 4 entries, we save no reallocations. -/// If we parse 4 < N <= 8 entries, we save one reallocation. -/// If we parse N > 8 entries, we save two reallocations. -type ParseVec = SmallVec<[Component; 8]>; - -/// Parses a complex selector, including any pseudo-element. -/// -/// For now, it always forces the pseudo-element to be at the end of the -/// selector, and the boolean represents whether the last thing parsed was a -/// pseudo-element. -fn parse_complex_selector( +fn parse_selector( parser: &P, input: &mut CssParser) - -> Result<(ComplexSelector, bool), ()> + -> Result, ()> where P: Parser, Impl: SelectorImpl { let mut sequence = ParseVec::new(); @@ -1042,21 +987,29 @@ fn parse_complex_selector( sequence.push(Component::Combinator(combinator)); } - let complex = ComplexSelector(ArcSlice::new(sequence.into_vec().into_boxed_slice())); - Ok((complex, parsed_pseudo_element)) + let mut spec = SpecificityAndFlags(specificity(SelectorIter { + iter: sequence.iter().rev(), + next_combinator: None, + })); + if parsed_pseudo_element { + spec.0 |= HAS_PSEUDO_BIT; + } + + let header = HeaderWithLength::new(spec, sequence.len()); + let complex = Selector(Arc::into_thin(Arc::from_header_and_iter(header, sequence.into_iter()))); + Ok(complex) } -impl ComplexSelector { - /// Parse a complex selector, without any pseudo-element. +impl Selector { + /// Parse a selector, without any pseudo-element. pub fn parse

(parser: &P, input: &mut CssParser) -> Result where P: Parser { - let (complex, has_pseudo_element) = - parse_complex_selector(parser, input)?; - if has_pseudo_element { + let selector = parse_selector(parser, input)?; + if selector.has_pseudo_element() { return Err(()) } - Ok(complex) + Ok(selector) } } @@ -1761,7 +1714,7 @@ pub mod tests { let result = SelectorList::parse(parser, &mut CssParser::new(input)); if let Ok(ref selectors) = result { assert_eq!(selectors.0.len(), 1); - assert_eq!(selectors.0[0].to_css_string(), input); + assert_eq!(selectors.0[0].selector.to_css_string(), input); } result } @@ -1784,185 +1737,164 @@ pub mod tests { assert_eq!(parse(""), Err(())) ; assert_eq!(parse(":lang(4)"), Err(())) ; assert_eq!(parse(":lang(en US)"), Err(())) ; - assert_eq!(parse("EeÉ"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + assert_eq!(parse("EeÉ"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::LocalName(LocalName { name: DummyAtom::from("EeÉ"), - lower_name: DummyAtom::from("eeÉ") })), - ), - specificity_and_flags: specificity(0, 0, 1), - })))); - assert_eq!(parse("|e"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + lower_name: DummyAtom::from("eeÉ") }) + ), specificity(0, 0, 1)) + )))); + assert_eq!(parse("|e"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::ExplicitNoNamespace, Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e") - }), - )), - specificity_and_flags: specificity(0, 0, 1), - })))); + })), specificity(0, 0, 1)) + )))); // https://github.com/servo/servo/issues/16020 - assert_eq!(parse("*|e"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + assert_eq!(parse("*|e"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::ExplicitAnyNamespace, Component::LocalName(LocalName { name: DummyAtom::from("e"), lower_name: DummyAtom::from("e") - }), - )), - specificity_and_flags: specificity(0, 0, 1), - })))); - assert_eq!(parse("*"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + }) + ), specificity(0, 0, 1)) + )))); + assert_eq!(parse("*"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::ExplicitUniversalType, - )), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse("|*"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + ), specificity(0, 0, 0)) + )))); + assert_eq!(parse("|*"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::ExplicitNoNamespace, Component::ExplicitUniversalType, - )), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse("*|*"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + ), specificity(0, 0, 0)) + )))); + assert_eq!(parse("*|*"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::ExplicitAnyNamespace, Component::ExplicitUniversalType, - )), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec![ + ), specificity(0, 0, 0)) + )))); + assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::Class(DummyAtom::from("foo")), Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())) - ]), - specificity_and_flags: specificity(0, 2, 0), - })))); - assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::ID(DummyAtom::from("bar")))), - specificity_and_flags: specificity(1, 0, 0), - })))); - assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e") }), - Component::Class(DummyAtom::from("foo")), - Component::ID(DummyAtom::from("bar")))), - specificity_and_flags: specificity(1, 1, 1), - })))); - assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e") - }), - Component::Class(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ID(DummyAtom::from("bar")), - )), - specificity_and_flags: specificity(1, 1, 1), - })))); + ), specificity(0, 2, 0)) + )))); + assert_eq!(parse("#bar"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::ID(DummyAtom::from("bar")) + ), specificity(1, 0, 0)) + )))); + assert_eq!(parse("e.foo#bar"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e") + }), + Component::Class(DummyAtom::from("foo")), + Component::ID(DummyAtom::from("bar")) + ), specificity(1, 1, 1)) + )))); + assert_eq!(parse("e.foo #bar"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e") + }), + Component::Class(DummyAtom::from("foo")), + Component::Combinator(Combinator::Descendant), + Component::ID(DummyAtom::from("bar")), + ), specificity(1, 1, 1)) + )))); // Default namespace does not apply to attribute selectors // https://github.com/mozilla/servo/pull/1652 let mut parser = DummyParser::default(); - assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec![ + assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::AttributeInNoNamespaceExists { local_name: DummyAtom::from("Foo"), local_name_lower: DummyAtom::from("foo"), } - ]), - specificity_and_flags: specificity(0, 1, 0), - })))); + ), specificity(0, 1, 0)) + )))); assert_eq!(parse_ns("svg|circle", &parser), Err(())); parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); - assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList(vec![Selector { - inner: SelectorInner::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("circle"), - lower_name: DummyAtom::from("circle"), - }) - ]), - specificity_and_flags: specificity(0, 0, 1), - }]))); - assert_eq!(parse_ns("svg|*", &parser), Ok(SelectorList(vec![Selector { - inner: SelectorInner::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::ExplicitUniversalType, - ]), - specificity_and_flags: specificity(0, 0, 0), - }]))); + assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::Namespace(DummyAtom("svg".into()), SVG.into()), + Component::LocalName(LocalName { + name: DummyAtom::from("circle"), + lower_name: DummyAtom::from("circle"), + }) + ), specificity(0, 0, 1)) + )))); + assert_eq!(parse_ns("svg|*", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::Namespace(DummyAtom("svg".into()), SVG.into()), + Component::ExplicitUniversalType, + ), specificity(0, 0, 0)) + )))); // Default namespace does not apply to attribute selectors // https://github.com/mozilla/servo/pull/1652 // but it does apply to implicit type selectors // https://github.com/servo/rust-selectors/pull/82 parser.default_ns = Some(MATHML.into()); - assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }, - ]), - specificity_and_flags: specificity(0, 1, 0), - })))); + assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::DefaultNamespace(MATHML.into()), + Component::AttributeInNoNamespaceExists { + local_name: DummyAtom::from("Foo"), + local_name_lower: DummyAtom::from("foo"), + }, + ), specificity(0, 1, 0)) + )))); // Default namespace does apply to type selectors - assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec!( - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e") }), - )), - specificity_and_flags: specificity(0, 0, 1), - })))); - assert_eq!(parse_ns("*", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec!( - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - )), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse_ns("*|*", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec!( - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - )), - specificity_and_flags: specificity(0, 0, 0), - })))); + assert_eq!(parse_ns("e", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::DefaultNamespace(MATHML.into()), + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e") }), + ), specificity(0, 0, 1)) + )))); + assert_eq!(parse_ns("*", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::DefaultNamespace(MATHML.into()), + Component::ExplicitUniversalType, + ), specificity(0, 0, 0)) + )))); + assert_eq!(parse_ns("*|*", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::ExplicitAnyNamespace, + Component::ExplicitUniversalType, + ), specificity(0, 0, 0)) + )))); // Default namespace applies to universal and type selectors inside :not and :matches, // but not otherwise. - assert_eq!(parse_ns(":not(.cl)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + assert_eq!(parse_ns(":not(.cl)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::DefaultNamespace(MATHML.into()), Component::Negation(vec![ Component::Class(DummyAtom::from("cl")) ].into_boxed_slice()), - )), - specificity_and_flags: specificity(0, 1, 0), - })))); - assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + ), specificity(0, 1, 0)) + )))); + assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::DefaultNamespace(MATHML.into()), Component::Negation(vec![ Component::DefaultNamespace(MATHML.into()), Component::ExplicitUniversalType, ].into_boxed_slice()), - )), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse_ns(":not(e)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!( + ), specificity(0, 0, 0)) + )))); + assert_eq!(parse_ns(":not(e)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::DefaultNamespace(MATHML.into()), Component::Negation(vec![ Component::DefaultNamespace(MATHML.into()), @@ -1971,51 +1903,39 @@ pub mod tests { lower_name: DummyAtom::from("e") }), ].into_boxed_slice()) - )), - specificity_and_flags: specificity(0, 0, 1), - })))); - assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector { - inner: SelectorInner::from_vec( - vec![ - Component::AttributeInNoNamespace { - local_name: DummyAtom::from("attr"), - local_name_lower: DummyAtom::from("attr"), - operator: AttrSelectorOperator::DashMatch, - value: DummyAtom::from("foo"), - never_matches: false, - case_sensitivity: ParsedCaseSensitivity::CaseSensitive, - } - ]), - specificity_and_flags: specificity(0, 1, 0), - }]))); + ), specificity(0, 0, 1)) + )))); + assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::AttributeInNoNamespace { + local_name: DummyAtom::from("attr"), + local_name_lower: DummyAtom::from("attr"), + operator: AttrSelectorOperator::DashMatch, + value: DummyAtom::from("foo"), + never_matches: false, + case_sensitivity: ParsedCaseSensitivity::CaseSensitive, + } + ), specificity(0, 1, 0)) + )))); // https://github.com/mozilla/servo/issues/1723 - assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec![ - Component::PseudoElement(PseudoElement::Before), - ] - ), - specificity_and_flags: specificity(0, 0, 1) | HAS_PSEUDO_BIT, - })))); - assert_eq!(parse("::before:hover"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec![ - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - ] - ), - specificity_and_flags: specificity(0, 1, 1) | HAS_PSEUDO_BIT, - })))); - assert_eq!(parse("::before:hover:hover"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec![ - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - Component::NonTSPseudoClass(PseudoClass::Hover), - ] - ), - specificity_and_flags: specificity(0, 2, 1) | HAS_PSEUDO_BIT, - })))); + assert_eq!(parse("::before"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::PseudoElement(PseudoElement::Before), + ), specificity(0, 0, 1) | HAS_PSEUDO_BIT) + )))); + assert_eq!(parse("::before:hover"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::PseudoElement(PseudoElement::Before), + Component::NonTSPseudoClass(PseudoClass::Hover), + ), specificity(0, 1, 1) | HAS_PSEUDO_BIT) + )))); + assert_eq!(parse("::before:hover:hover"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::PseudoElement(PseudoElement::Before), + Component::NonTSPseudoClass(PseudoClass::Hover), + Component::NonTSPseudoClass(PseudoClass::Hover), + ), specificity(0, 2, 1) | HAS_PSEUDO_BIT) + )))); assert_eq!(parse("::before:hover:active"), Err(())); assert_eq!(parse("::before:hover .foo"), Err(())); assert_eq!(parse("::before .foo"), Err(())); @@ -2024,41 +1944,35 @@ pub mod tests { // https://github.com/servo/servo/issues/15335 assert_eq!(parse(":: before"), Err(())); - assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("div"), - lower_name: DummyAtom::from("div") }), - Component::Combinator(Combinator::Descendant), - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::After), - ]), - specificity_and_flags: specificity(0, 0, 2) | HAS_PSEUDO_BIT, - })))); - assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector { - inner: SelectorInner::from_vec( - vec![ - Component::ID(DummyAtom::from("d1")), - Component::Combinator(Combinator::Child), - Component::Class(DummyAtom::from("ok")), - ]), - specificity_and_flags: (1 << 20) + (1 << 10) + (0 << 0), - }]))); + assert_eq!(parse("div ::after"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::LocalName(LocalName { + name: DummyAtom::from("div"), + lower_name: DummyAtom::from("div") }), + Component::Combinator(Combinator::Descendant), + Component::Combinator(Combinator::PseudoElement), + Component::PseudoElement(PseudoElement::After), + ), specificity(0, 0, 2) | HAS_PSEUDO_BIT) + )))); + assert_eq!(parse("#d1 > .ok"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::ID(DummyAtom::from("d1")), + Component::Combinator(Combinator::Child), + Component::Class(DummyAtom::from("ok")), + ), (1 << 20) + (1 << 10) + (0 << 0)) + )))); parser.default_ns = None; assert_eq!(parse(":not(#provel.old)"), Err(())); assert_eq!(parse(":not(#provel > old)"), Err(())); assert!(parse("table[rules]:not([rules = \"none\"]):not([rules = \"\"])").is_ok()); - assert_eq!(parse(":not(#provel)"), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::Negation( - vec![ + assert_eq!(parse(":not(#provel)"), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!(Component::Negation(vec!( Component::ID(DummyAtom::from("provel")), - ].into_boxed_slice() - ))), - specificity_and_flags: specificity(1, 0, 0), - })))); - assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::Negation( + ).into_boxed_slice() + )), specificity(1, 0, 0)) + )))); + assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!(Component::Negation( vec![ Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::LocalName(LocalName { @@ -2066,52 +1980,47 @@ pub mod tests { lower_name: DummyAtom::from("circle") }), ].into_boxed_slice() - ))), - specificity_and_flags: specificity(0, 0, 1), - })))); + )), specificity(0, 0, 1)) + )))); // https://github.com/servo/servo/issues/16017 - assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::Negation( + assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!(Component::Negation( vec![ Component::ExplicitUniversalType, ].into_boxed_slice() - ))), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse_ns(":not(|*)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::Negation( + )), specificity(0, 0, 0)) + )))); + assert_eq!(parse_ns(":not(|*)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!(Component::Negation( vec![ Component::ExplicitNoNamespace, Component::ExplicitUniversalType, ].into_boxed_slice() - ))), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse_ns(":not(*|*)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::Negation( + )), specificity(0, 0, 0)) + )))); + assert_eq!(parse_ns(":not(*|*)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!(Component::Negation( vec![ Component::ExplicitAnyNamespace, Component::ExplicitUniversalType, ].into_boxed_slice() - ))), - specificity_and_flags: specificity(0, 0, 0), - })))); - assert_eq!(parse_ns(":not(svg|*)", &parser), Ok(SelectorList(vec!(Selector { - inner: SelectorInner::from_vec(vec!(Component::Negation( + )), specificity(0, 0, 0)) + )))); + assert_eq!(parse_ns(":not(svg|*)", &parser), Ok(SelectorList::from_vec(vec!( + Selector::from_vec(vec!(Component::Negation( vec![ Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::ExplicitUniversalType, ].into_boxed_slice() - ))), - specificity_and_flags: specificity(0, 0, 0), - })))); + )), specificity(0, 0, 0)) + )))); } #[test] fn test_pseudo_iter() { - let selector = &parse("q::before").unwrap().0[0]; + let selector = &parse("q::before").unwrap().0[0].selector; assert!(!selector.is_universal()); - let mut iter = selector.inner.complex.iter(); + let mut iter = selector.iter(); assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); assert_eq!(iter.next(), None); let combinator = iter.next_sequence(); @@ -2123,15 +2032,15 @@ pub mod tests { #[test] fn test_universal() { - let selector = &parse("*|*::before").unwrap().0[0]; + let selector = &parse("*|*::before").unwrap().0[0].selector; assert!(selector.is_universal()); } #[test] fn test_empty_pseudo_iter() { - let selector = &parse("::before").unwrap().0[0]; + let selector = &parse("::before").unwrap().0[0].selector; assert!(selector.is_universal()); - let mut iter = selector.inner.complex.iter(); + let mut iter = selector.iter(); assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); assert_eq!(iter.next(), None); assert_eq!(iter.next_sequence(), None); @@ -2155,11 +2064,11 @@ pub mod tests { #[test] fn visitor() { let mut test_visitor = TestVisitor { seen: vec![], }; - parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); + parse(":not(:hover) ~ label").unwrap().0[0].selector.visit(&mut test_visitor); assert!(test_visitor.seen.contains(&":hover".into())); let mut test_visitor = TestVisitor { seen: vec![], }; - parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); + parse("::before:hover").unwrap().0[0].selector.visit(&mut test_visitor); assert!(test_visitor.seen.contains(&":hover".into())); } } diff --git a/components/selectors/size_of_tests.rs b/components/selectors/size_of_tests.rs index 5fec3085fcb..d9ed1651cf7 100644 --- a/components/selectors/size_of_tests.rs +++ b/components/selectors/size_of_tests.rs @@ -11,10 +11,8 @@ use precomputed_hash::PrecomputedHash; use std::fmt; use visitor::SelectorVisitor; -size_of_test!(size_of_selector, Selector, 48); +size_of_test!(size_of_selector, Selector, 8); size_of_test!(size_of_pseudo_element, gecko_like_types::PseudoElement, 1); -size_of_test!(size_of_selector_inner, SelectorInner, 40); -size_of_test!(size_of_complex_selector, ComplexSelector, 24); size_of_test!(size_of_component, Component, 32); size_of_test!(size_of_pseudo_class, PseudoClass, 24); diff --git a/components/servo_arc/Cargo.toml b/components/servo_arc/Cargo.toml new file mode 100644 index 00000000000..ffb743bd20a --- /dev/null +++ b/components/servo_arc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "servo_arc" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +publish = false + +[lib] +name = "servo_arc" +path = "lib.rs" + +[features] +servo = ["serde", "heapsize"] + +[dependencies] +heapsize = {version = "0.4.0", optional = true} +serde = {version = "0.9", optional = true} +nodrop = {version = "0.1.8"} diff --git a/components/style/stylearc.rs b/components/servo_arc/lib.rs similarity index 58% rename from components/style/stylearc.rs rename to components/servo_arc/lib.rs index 9b355e7d211..5d91c9bc44f 100644 --- a/components/style/stylearc.rs +++ b/components/servo_arc/lib.rs @@ -8,11 +8,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Fork of Arc for the style system. This has the following advantages over std::Arc: +//! Fork of Arc for Servo. This has the following advantages over std::Arc: //! * We don't waste storage on the weak reference count. //! * We don't do extra RMU operations to handle the possibility of weak references. //! * We can experiment with arena allocation (todo). //! * We can add methods to support our custom use cases [1]. +//! * We have support for dynamically-sized types (see from_header_and_iter). +//! * We have support for thin arcs to unsized types (see ThinArc). //! //! [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1360883 @@ -20,8 +22,12 @@ // duplicate those here. #![allow(missing_docs)] +#[cfg(feature = "servo")] extern crate serde; +extern crate nodrop; + #[cfg(feature = "servo")] use heapsize::HeapSizeOf; +use nodrop::NoDrop; #[cfg(feature = "servo")] use serde::{Deserialize, Serialize}; use std::{isize, usize}; @@ -30,8 +36,11 @@ use std::cmp::Ordering; use std::convert::From; use std::fmt; use std::hash::{Hash, Hasher}; +use std::iter::{ExactSizeIterator, Iterator}; use std::mem; use std::ops::{Deref, DerefMut}; +use std::ptr; +use std::slice; use std::sync::atomic; use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; @@ -412,3 +421,257 @@ impl Serialize for Arc (**self).serialize(serializer) } } + +/// Structure to allow Arc-managing some fixed-sized data and a variably-sized +/// slice in a single allocation. +#[derive(Debug, Eq, PartialEq, PartialOrd)] +pub struct HeaderSlice { + /// The fixed-sized data. + pub header: H, + + /// The dynamically-sized data. + pub slice: T, +} + +#[inline(always)] +fn divide_rounding_up(dividend: usize, divisor: usize) -> usize { + (dividend + divisor - 1) / divisor +} + +impl Arc> { + /// Creates an Arc for a HeaderSlice using the given header struct and + /// iterator to generate the slice. The resulting Arc will be fat. + #[inline] + pub fn from_header_and_iter(header: H, mut items: I) -> Self + where I: Iterator + ExactSizeIterator + { + use ::std::mem::size_of; + assert!(size_of::() != 0, "Need to think about ZST"); + + // Compute the required size for the allocation. + let num_items = items.len(); + let size = { + // First, determine the alignment of a hypothetical pointer to a + // HeaderSlice. + let fake_slice_ptr_align: usize = mem::align_of::>>(); + + // Next, synthesize a totally garbage (but properly aligned) pointer + // to a sequence of T. + let fake_slice_ptr = fake_slice_ptr_align as *const T; + + // Convert that sequence to a fat pointer. The address component of + // the fat pointer will be garbage, but the length will be correct. + let fake_slice = unsafe { slice::from_raw_parts(fake_slice_ptr, num_items) }; + + // Pretend the garbage address points to our allocation target (with + // a trailing sequence of T), rather than just a sequence of T. + let fake_ptr = fake_slice as *const [T] as *const ArcInner>; + let fake_ref: &ArcInner> = unsafe { &*fake_ptr }; + + // Use size_of_val, which will combine static information about the + // type with the length from the fat pointer. The garbage address + // will not be used. + mem::size_of_val(fake_ref) + }; + + let ptr: *mut ArcInner>; + unsafe { + // Allocate the buffer. We use Vec because the underlying allocation + // machinery isn't available in stable Rust. + // + // To avoid alignment issues, we allocate words rather than bytes, + // rounding up to the nearest word size. + let words_to_allocate = divide_rounding_up(size, size_of::()); + let mut vec = Vec::::with_capacity(words_to_allocate); + vec.set_len(words_to_allocate); + let buffer = Box::into_raw(vec.into_boxed_slice()) as *mut usize as *mut u8; + + // Synthesize the fat pointer. We do this by claiming we have a direct + // pointer to a [T], and then changing the type of the borrow. The key + // point here is that the length portion of the fat pointer applies + // only to the number of elements in the dynamically-sized portion of + // the type, so the value will be the same whether it points to a [T] + // or something else with a [T] as its last member. + let fake_slice: &mut [T] = slice::from_raw_parts_mut(buffer as *mut T, num_items); + ptr = fake_slice as *mut [T] as *mut ArcInner>; + + // Write the data. + ptr::write(&mut ((*ptr).count), atomic::AtomicUsize::new(1)); + ptr::write(&mut ((*ptr).data.header), header); + let mut current: *mut T = &mut (*ptr).data.slice[0]; + for _ in 0..num_items { + ptr::write(current, items.next().expect("ExactSizeIterator over-reported length")); + current = current.offset(1); + } + assert!(items.next().is_none(), "ExactSizeIterator under-reported length"); + + // We should have consumed the buffer exactly. + debug_assert!(current as *mut u8 == buffer.offset(size as isize)); + } + + // Return the fat Arc. + assert_eq!(size_of::(), size_of::() * 2, "The Arc will be fat"); + Arc { ptr: ptr } + } +} + +/// Header data with an inline length. Consumers that use HeaderWithLength as the +/// Header type in HeaderSlice can take advantage of ThinArc. +#[derive(Debug, Eq, PartialEq, PartialOrd)] +pub struct HeaderWithLength { + /// The fixed-sized data. + pub header: H, + + /// The slice length. + length: usize, +} + +impl HeaderWithLength { + /// Creates a new HeaderWithLength. + pub fn new(header: H, length: usize) -> Self { + HeaderWithLength { + header: header, + length: length, + } + } +} + +pub struct ThinArc { + ptr: *mut ArcInner, [T; 1]>>, +} + +unsafe impl Send for ThinArc {} +unsafe impl Sync for ThinArc {} + +// Synthesize a fat pointer from a thin pointer. +// +// See the comment around the analogous operation in from_header_and_iter. +fn thin_to_thick(thin: *mut ArcInner, [T; 1]>>) + -> *mut ArcInner, [T]>> +{ + let len = unsafe { (*thin).data.header.length }; + let fake_slice: *mut [T] = unsafe { + slice::from_raw_parts_mut(thin as *mut T, len) + }; + + fake_slice as *mut ArcInner, [T]>> +} + +impl ThinArc { + /// Temporarily converts |self| into a bonafide Arc and exposes it to the + /// provided callback. The refcount is not modified. + #[inline(always)] + pub fn with_arc(&self, f: F) -> U + where F: FnOnce(&Arc, [T]>>) -> U + { + // Synthesize transient Arc, which never touches the refcount of the ArcInner. + let transient = NoDrop::new(Arc { + ptr: thin_to_thick(self.ptr) + }); + + // Expose the transient Arc to the callback, which may clone it if it wants. + let result = f(&transient); + + // Forget the transient Arc to leave the refcount untouched. + mem::forget(transient); + + // Forward the result. + result + } +} + +impl Deref for ThinArc { + type Target = HeaderSlice, [T]>; + fn deref(&self) -> &Self::Target { + unsafe { &(*thin_to_thick(self.ptr)).data } + } +} + +impl Clone for ThinArc { + fn clone(&self) -> Self { + ThinArc::with_arc(self, |a| Arc::into_thin(a.clone())) + } +} + +impl Drop for ThinArc { + fn drop(&mut self) { + let _ = Arc::from_thin(ThinArc { ptr: self.ptr }); + } +} + +impl Arc, [T]>> { + /// Converts an Arc into a ThinArc. This consumes the Arc, so the refcount + /// is not modified. + pub fn into_thin(a: Self) -> ThinArc { + assert!(a.header.length == a.slice.len(), + "Length needs to be correct for ThinArc to work"); + let fat_ptr: *mut ArcInner, [T]>> = a.ptr; + mem::forget(a); + let thin_ptr = fat_ptr as *mut [usize] as *mut usize; + ThinArc { + ptr: thin_ptr as *mut ArcInner, [T; 1]>> + } + } + + /// Converts a ThinArc into an Arc. This consumes the ThinArc, so the refcount + /// is not modified. + pub fn from_thin(a: ThinArc) -> Self { + let ptr = thin_to_thick(a.ptr); + mem::forget(a); + Arc { + ptr: ptr + } + } +} + +impl PartialEq for ThinArc { + fn eq(&self, other: &ThinArc) -> bool { + ThinArc::with_arc(self, |a| { + ThinArc::with_arc(other, |b| { + *a == *b + }) + }) + } +} + +impl Eq for ThinArc {} + +#[cfg(test)] +mod tests { + use std::clone::Clone; + use std::ops::Drop; + use std::sync::atomic; + use std::sync::atomic::Ordering::{Acquire, SeqCst}; + use super::{Arc, HeaderWithLength, ThinArc}; + + #[derive(PartialEq)] + struct Canary(*mut atomic::AtomicUsize); + + impl Drop for Canary { + fn drop(&mut self) { + unsafe { + match *self { + Canary(c) => { + (*c).fetch_add(1, SeqCst); + } + } + } + } + } + + #[test] + fn slices_and_thin() { + let mut canary = atomic::AtomicUsize::new(0); + let c = Canary(&mut canary as *mut atomic::AtomicUsize); + let v = vec![5, 6]; + let header = HeaderWithLength::new(c, v.len()); + { + let x = Arc::into_thin(Arc::from_header_and_iter(header, v.into_iter())); + let y = ThinArc::with_arc(&x, |q| q.clone()); + let _ = y.clone(); + let _ = x == x; + Arc::from_thin(x.clone()); + } + assert!(canary.load(Acquire) == 1); + } +} diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 4b33f660a68..a8751e1646b 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -61,6 +61,7 @@ rayon = "0.7.1" selectors = { path = "../selectors" } serde = {version = "0.9", optional = true} serde_derive = {version = "0.9", optional = true} +servo_arc = { path = "../servo_arc" } servo_atoms = {path = "../atoms", optional = true} servo_config = {path = "../config", optional = true} smallvec = "0.4" diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index b6051f1f34c..90d503f58ab 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -8,7 +8,7 @@ use cssparser::{Parser, ToCss}; use element_state::ElementState; use gecko_bindings::structs::CSSPseudoClassType; use selector_parser::{SelectorParser, PseudoElementCascadeType}; -use selectors::parser::{ComplexSelector, SelectorMethods}; +use selectors::parser::{Selector, SelectorMethods}; use selectors::visitor::SelectorVisitor; use std::borrow::Cow; use std::fmt; @@ -47,7 +47,7 @@ macro_rules! pseudo_class_name { /// /// TODO(emilio): We disallow combinators and pseudos here, so we /// should use SimpleSelector instead - MozAny(Box<[ComplexSelector]>), + MozAny(Box<[Selector]>), } } } @@ -273,7 +273,7 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { }, )* "-moz-any" => { let selectors = parser.parse_comma_separated(|input| { - ComplexSelector::parse(self, input) + Selector::parse(self, input) })?; // Selectors inside `:-moz-any` may not include combinators. if selectors.iter().flat_map(|x| x.iter_raw()).any(|s| s.is_combinator()) { diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index d808f148d85..d9baeb79312 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1405,7 +1405,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozPlaceholder => false, NonTSPseudoClass::MozAny(ref sels) => { sels.iter().any(|s| { - matches_complex_selector(s, self, context, flags_setter) + matches_complex_selector(s, 0, self, context, flags_setter) }) } NonTSPseudoClass::MozSystemMetric(ref s) | diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 93f0d8783ad..a222b611d44 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -218,7 +218,7 @@ impl StylesheetInvalidationSet { let mut scope: Option = None; let mut scan = true; - let mut iter = selector.inner.complex.iter(); + let mut iter = selector.iter(); loop { for component in &mut iter { @@ -262,8 +262,8 @@ impl StylesheetInvalidationSet { match *rule { Style(ref lock) => { let style_rule = lock.read_with(guard); - for selector in &style_rule.selectors.0 { - self.collect_scopes(selector); + for selector_and_hashes in &style_rule.selectors.0 { + self.collect_scopes(&selector_and_hashes.selector); if self.fully_invalid { return; } diff --git a/components/style/lib.rs b/components/style/lib.rs index 4856610b9ed..0cde26b37f0 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -72,8 +72,8 @@ extern crate pdqsort; #[cfg(feature = "gecko")] extern crate precomputed_hash; extern crate rayon; extern crate selectors; -#[cfg(feature = "servo")] extern crate serde; #[cfg(feature = "servo")] #[macro_use] extern crate serde_derive; +pub extern crate servo_arc; #[cfg(feature = "servo")] #[macro_use] extern crate servo_atoms; #[cfg(feature = "servo")] extern crate servo_config; #[cfg(feature = "servo")] extern crate servo_url; @@ -129,7 +129,6 @@ pub mod sequential; pub mod sink; pub mod str; pub mod style_adjuster; -pub mod stylearc; pub mod stylesheet_set; pub mod stylesheets; pub mod thread_state; @@ -139,6 +138,10 @@ pub mod traversal; #[allow(non_camel_case_types)] pub mod values; +// Compat shim for the old name when it lived in the style crate. +// FIXME(bholley) Remove this. +pub use servo_arc as stylearc; + use std::fmt; use style_traits::ToCss; diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index acca66d6fdb..9c62fbe01b5 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -22,7 +22,8 @@ use selectors::Element; use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector}; -use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorMethods}; +use selectors::parser::{AncestorHashes, Combinator, Component}; +use selectors::parser::{Selector, SelectorAndHashes, SelectorIter, SelectorMethods}; use selectors::visitor::SelectorVisitor; use smallvec::SmallVec; use std::cell::Cell; @@ -642,7 +643,7 @@ impl<'a, E> Element for ElementWrapper<'a, E> use selectors::matching::matches_complex_selector; if let NonTSPseudoClass::MozAny(ref selectors) = *pseudo_class { return selectors.iter().any(|s| { - matches_complex_selector(s, self, context, _setter) + matches_complex_selector(s, 0, self, context, _setter) }) } } @@ -866,8 +867,15 @@ impl Sensitivities { #[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Dependency { + /// The dependency selector. #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] - selector: SelectorInner, + selector: Selector, + /// The offset into the selector that we should match on. + selector_offset: usize, + /// The ancestor hashes associated with the above selector at the given + /// offset. + #[cfg_attr(feature = "servo", ignore_heap_size_of = "No heap data")] + hashes: AncestorHashes, /// The hint associated with this dependency. pub hint: RestyleHint, /// The sensitivities associated with this dependency. @@ -875,8 +883,12 @@ pub struct Dependency { } impl SelectorMapEntry for Dependency { - fn selector(&self) -> &SelectorInner { - &self.selector + fn selector(&self) -> SelectorIter { + self.selector.iter_from(self.selector_offset) + } + + fn hashes(&self) -> &AncestorHashes { + &self.hashes } } @@ -940,9 +952,9 @@ pub enum HintComputationContext<'a, E: 'a> impl DependencySet { /// Adds a selector to this `DependencySet`. - pub fn note_selector(&mut self, selector: &Selector) { + pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes) { let mut combinator = None; - let mut iter = selector.inner.complex.iter(); + let mut iter = selector_and_hashes.selector.iter(); let mut index = 0; let mut child_combinators_seen = 0; let mut saw_descendant_combinator = false; @@ -992,17 +1004,21 @@ impl DependencySet { None => RestyleHint::for_self(), }; - let dep_selector = if sequence_start == 0 { - // Reuse the bloom hashes if this is the base selector. - selector.inner.clone() + // Reuse the bloom hashes if this is the base selector. Otherwise, + // rebuild them. + let hashes = if sequence_start == 0 { + selector_and_hashes.hashes.clone() } else { - SelectorInner::new(selector.inner.complex.slice_from(sequence_start)) + let seq_iter = selector_and_hashes.selector.iter_from(sequence_start); + AncestorHashes::from_iter(seq_iter) }; self.dependencies.insert(Dependency { sensitivities: visitor.sensitivities, hint: hint, - selector: dep_selector, + selector: selector_and_hashes.selector.clone(), + selector_offset: sequence_start, + hashes: hashes, }); } @@ -1130,14 +1146,20 @@ impl DependencySet { MatchingContext::new_for_visited(MatchingMode::Normal, None, VisitedHandlingMode::AllLinksUnvisited); let matched_then = - matches_selector(&dep.selector, &snapshot_el, + matches_selector(&dep.selector, + dep.selector_offset, + &dep.hashes, + &snapshot_el, &mut then_context, &mut |_, _| {}); let mut now_context = MatchingContext::new_for_visited(MatchingMode::Normal, bloom_filter, VisitedHandlingMode::AllLinksUnvisited); let matches_now = - matches_selector(&dep.selector, el, + matches_selector(&dep.selector, + dep.selector_offset, + &dep.hashes, + el, &mut now_context, &mut |_, _| {}); @@ -1162,12 +1184,18 @@ impl DependencySet { dep.sensitivities.states.intersects(IN_VISITED_OR_UNVISITED_STATE) { then_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited; let matched_then = - matches_selector(&dep.selector, &snapshot_el, + matches_selector(&dep.selector, + dep.selector_offset, + &dep.hashes, + &snapshot_el, &mut then_context, &mut |_, _| {}); now_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited; let matches_now = - matches_selector(&dep.selector, el, + matches_selector(&dep.selector, + dep.selector_offset, + &dep.hashes, + el, &mut now_context, &mut |_, _| {}); if matched_then != matches_now { diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 846d7972811..a65b664ad38 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -12,7 +12,7 @@ use pdqsort::sort_by; use rule_tree::CascadeLevel; use selector_parser::SelectorImpl; use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags}; -use selectors::parser::{Component, Combinator, SelectorInner}; +use selectors::parser::{AncestorHashes, Component, Combinator, SelectorAndHashes, SelectorIter}; use selectors::parser::LocalName as LocalNameSelector; use smallvec::VecLike; use std::borrow::Borrow; @@ -22,13 +22,20 @@ use stylist::{ApplicableDeclarationBlock, Rule}; /// A trait to abstract over a given selector map entry. pub trait SelectorMapEntry : Sized + Clone { - /// Get the selector we should use to index in the selector map. - fn selector(&self) -> &SelectorInner; + /// Gets the selector we should use to index in the selector map. + fn selector(&self) -> SelectorIter; + + /// Gets the ancestor hashes associated with the selector. + fn hashes(&self) -> &AncestorHashes; } -impl SelectorMapEntry for SelectorInner { - fn selector(&self) -> &SelectorInner { - self +impl SelectorMapEntry for SelectorAndHashes { + fn selector(&self) -> SelectorIter { + self.selector.iter() + } + + fn hashes(&self) -> &AncestorHashes { + &self.hashes } } @@ -224,7 +231,9 @@ impl SelectorMap { F: FnMut(&E, ElementSelectorFlags), { for rule in rules { - if matches_selector(&rule.selector.inner, + if matches_selector(&rule.selector, + 0, + &rule.hashes, element, context, flags_setter) { @@ -390,12 +399,12 @@ impl SelectorMap { /// /// Effectively, pseudo-elements are ignored, given only state pseudo-classes /// may appear before them. -fn find_from_right(selector: &SelectorInner, +#[inline(always)] +fn find_from_right(mut iter: SelectorIter, mut f: F) -> Option where F: FnMut(&Component) -> Option, { - let mut iter = selector.complex.iter(); for ss in &mut iter { if let Some(r) = f(ss) { return Some(r) @@ -414,9 +423,10 @@ fn find_from_right(selector: &SelectorInner, } /// Retrieve the first ID name in the selector, or None otherwise. -pub fn get_id_name(selector: &SelectorInner) - -> Option { - find_from_right(selector, |ss| { +#[inline(always)] +pub fn get_id_name(iter: SelectorIter) + -> Option { + find_from_right(iter, |ss| { // TODO(pradeep): Implement case-sensitivity based on the // document type and quirks mode. if let Component::ID(ref id) = *ss { @@ -427,9 +437,10 @@ pub fn get_id_name(selector: &SelectorInner) } /// Retrieve the FIRST class name in the selector, or None otherwise. -pub fn get_class_name(selector: &SelectorInner) - -> Option { - find_from_right(selector, |ss| { +#[inline(always)] +pub fn get_class_name(iter: SelectorIter) + -> Option { + find_from_right(iter, |ss| { // TODO(pradeep): Implement case-sensitivity based on the // document type and quirks mode. if let Component::Class(ref class) = *ss { @@ -440,9 +451,10 @@ pub fn get_class_name(selector: &SelectorInner) } /// Retrieve the name if it is a type selector, or None otherwise. -pub fn get_local_name(selector: &SelectorInner) - -> Option> { - find_from_right(selector, |ss| { +#[inline(always)] +pub fn get_local_name(iter: SelectorIter) + -> Option> { + find_from_right(iter, |ss| { if let Component::LocalName(ref n) = *ss { return Some(LocalNameSelector { name: n.name.clone(), diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 5df0bf2717b..43b29c0275d 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -28,7 +28,8 @@ use selectors::attr::NamespaceConstraint; use selectors::bloom::BloomFilter; use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS; -use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter, SelectorMethods}; +use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorAndHashes}; +use selectors::parser::{SelectorIter, SelectorMethods}; use selectors::visitor::SelectorVisitor; use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use sink::Push; @@ -159,7 +160,7 @@ pub struct Stylist { /// on state that is not otherwise visible to the cache, like attributes or /// tree-structural state like child index and pseudos). #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] - selectors_for_cache_revalidation: SelectorMap>, + selectors_for_cache_revalidation: SelectorMap>, /// The total number of selectors. num_selectors: usize, @@ -454,10 +455,10 @@ impl Stylist { CssRule::Style(ref locked) => { let style_rule = locked.read_with(&guard); self.num_declarations += style_rule.block.read_with(&guard).len(); - for selector in &style_rule.selectors.0 { + for selector_and_hashes in &style_rule.selectors.0 { self.num_selectors += 1; - let map = if let Some(pseudo) = selector.pseudo_element() { + let map = if let Some(pseudo) = selector_and_hashes.selector.pseudo_element() { self.pseudos_map .entry(pseudo.canonical()) .or_insert_with(PerPseudoElementSelectorMap::new) @@ -466,20 +467,21 @@ impl Stylist { self.element_map.borrow_for_origin(&stylesheet.origin) }; - map.insert(Rule::new(selector.clone(), + map.insert(Rule::new(selector_and_hashes.selector.clone(), + selector_and_hashes.hashes.clone(), locked.clone(), self.rules_source_order)); - self.dependencies.note_selector(selector); - if needs_revalidation(selector) { - self.selectors_for_cache_revalidation.insert(selector.inner.clone()); + self.dependencies.note_selector(selector_and_hashes); + if needs_revalidation(&selector_and_hashes.selector) { + self.selectors_for_cache_revalidation.insert(selector_and_hashes.clone()); } - selector.visit(&mut AttributeAndStateDependencyVisitor { + selector_and_hashes.selector.visit(&mut AttributeAndStateDependencyVisitor { attribute_dependencies: &mut self.attribute_dependencies, style_attribute_dependency: &mut self.style_attribute_dependency, state_dependencies: &mut self.state_dependencies, }); - selector.visit(&mut MappedIdVisitor { + selector_and_hashes.selector.visit(&mut MappedIdVisitor { mapped_ids: &mut self.mapped_ids, }); } @@ -1130,8 +1132,10 @@ impl Stylist { // the lookups, which means that the bitvecs are comparable. We verify // this in the caller by asserting that the bitvecs are same-length. let mut results = BitVec::new(); - self.selectors_for_cache_revalidation.lookup(*element, &mut |selector| { - results.push(matches_selector(selector, + self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| { + results.push(matches_selector(&selector_and_hashes.selector, + 0, + &selector_and_hashes.hashes, element, &mut matching_context, flags_setter)); @@ -1410,6 +1414,9 @@ pub struct Rule { /// can ruin performance when there are a lot of rules. #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] pub selector: Selector, + /// The ancestor hashes associated with the selector. + #[cfg_attr(feature = "servo", ignore_heap_size_of = "No heap data")] + pub hashes: AncestorHashes, /// The actual style rule. #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] pub style_rule: Arc>, @@ -1418,8 +1425,12 @@ pub struct Rule { } impl SelectorMapEntry for Rule { - fn selector(&self) -> &SelectorInner { - &self.selector.inner + fn selector(&self) -> SelectorIter { + self.selector.iter() + } + + fn hashes(&self) -> &AncestorHashes { + &self.hashes } } @@ -1444,12 +1455,14 @@ impl Rule { /// Creates a new Rule. pub fn new(selector: Selector, + hashes: AncestorHashes, style_rule: Arc>, source_order: usize) -> Self { Rule { selector: selector, + hashes: hashes, style_rule: style_rule, source_order: source_order, } diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index ce465b0f1ac..686a3979a9a 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -90,26 +90,23 @@ fn test_parse_stylesheet() { }, }))), CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { - selectors: SelectorList(vec![ - Selector::new_for_unit_testing( - SelectorInner::from_vec(vec![ - Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), - Component::LocalName(LocalName { - name: local_name!("input"), - lower_name: local_name!("input"), - }), - Component::AttributeInNoNamespace { - local_name: local_name!("type"), - local_name_lower: local_name!("type"), - operator: AttrSelectorOperator::Equal, - value: "hidden".to_owned(), - case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive, - never_matches: false, - } - ]), - (0 << 20) + (1 << 10) + (1 << 0) - ), - ]), + selectors: SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), + Component::LocalName(LocalName { + name: local_name!("input"), + lower_name: local_name!("input"), + }), + Component::AttributeInNoNamespace { + local_name: local_name!("type"), + local_name_lower: local_name!("type"), + operator: AttrSelectorOperator::Equal, + value: "hidden".to_owned(), + case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive, + never_matches: false, + } + ), (0 << 20) + (1 << 10) + (1 << 0)) + )), block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ (PropertyDeclaration::Display(longhands::display::SpecifiedValue::none), Importance::Important), @@ -123,28 +120,23 @@ fn test_parse_stylesheet() { }, }))), CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { - selectors: SelectorList(vec![ - Selector::new_for_unit_testing( - SelectorInner::from_vec(vec![ + selectors: SelectorList::from_vec(vec!( + Selector::from_vec(vec!( Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::LocalName(LocalName { name: local_name!("html"), lower_name: local_name!("html"), }), - ]), - (0 << 20) + (0 << 10) + (1 << 0) + ), (0 << 20) + (0 << 10) + (1 << 0)), + Selector::from_vec(vec!( + Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), + Component::LocalName(LocalName { + name: local_name!("body"), + lower_name: local_name!("body"), + }) + ), (0 << 20) + (0 << 10) + (1 << 0) ), - Selector::new_for_unit_testing( - SelectorInner::from_vec(vec![ - Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), - Component::LocalName(LocalName { - name: local_name!("body"), - lower_name: local_name!("body"), - }), - ]), - (0 << 20) + (0 << 10) + (1 << 0) - ), - ]), + )), block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ (PropertyDeclaration::Display(longhands::display::SpecifiedValue::block), Importance::Normal), @@ -155,18 +147,15 @@ fn test_parse_stylesheet() { }, }))), CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { - selectors: SelectorList(vec![ - Selector::new_for_unit_testing( - SelectorInner::from_vec(vec![ - Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), - Component::ID(Atom::from("d1")), - Component::Combinator(Combinator::Child), - Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), - Component::Class(Atom::from("ok")), - ]), - (1 << 20) + (1 << 10) + (0 << 0) - ), - ]), + selectors: SelectorList::from_vec(vec!( + Selector::from_vec(vec!( + Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), + Component::ID(Atom::from("d1")), + Component::Combinator(Combinator::Child), + Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), + Component::Class(Atom::from("ok")) + ), (1 << 20) + (1 << 10) + (0 << 0)) + )), block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ (PropertyDeclaration::BackgroundColor( longhands::background_color::SpecifiedValue { diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index afd967338f9..17faaddd04a 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -45,7 +45,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { let guard = shared_lock.read(); let rule = locked.read_with(&guard); rule.selectors.0.iter().map(|s| { - Rule::new(s.clone(), locked.clone(), i) + Rule::new(s.selector.clone(), s.hashes.clone(), locked.clone(), i) }).collect() }).collect(), shared_lock) } @@ -68,7 +68,7 @@ fn parse_selectors(selectors: &[&str]) -> Vec> { .map(|x| SelectorParser::parse_author_origin_no_namespace(x).unwrap().0 .into_iter() .nth(0) - .unwrap()) + .unwrap().selector) .collect() } @@ -126,7 +126,6 @@ fn test_revalidation_selectors() { "p:first-child span", ]).into_iter() .filter(|s| needs_revalidation(&s)) - .map(|s| s.inner.complex) .collect::>(); let reference = parse_selectors(&[ @@ -167,7 +166,6 @@ fn test_revalidation_selectors() { // Selectors in the ancestor chain (needed for cousin sharing). "p:first-child span", ]).into_iter() - .map(|s| s.inner.complex) .collect::>(); assert_eq!(test.len(), reference.len()); @@ -189,22 +187,22 @@ fn test_rule_ordering_same_specificity() { #[test] fn test_get_id_name() { let (rules_list, _) = get_mock_rules(&[".intro", "#top"]); - assert_eq!(selector_map::get_id_name(&rules_list[0][0].selector.inner), None); - assert_eq!(selector_map::get_id_name(&rules_list[1][0].selector.inner), Some(Atom::from("top"))); + assert_eq!(selector_map::get_id_name(rules_list[0][0].selector.iter()), None); + assert_eq!(selector_map::get_id_name(rules_list[1][0].selector.iter()), Some(Atom::from("top"))); } #[test] fn test_get_class_name() { let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); - assert_eq!(selector_map::get_class_name(&rules_list[0][0].selector.inner), Some(Atom::from("foo"))); - assert_eq!(selector_map::get_class_name(&rules_list[1][0].selector.inner), None); + assert_eq!(selector_map::get_class_name(rules_list[0][0].selector.iter()), Some(Atom::from("foo"))); + assert_eq!(selector_map::get_class_name(rules_list[1][0].selector.iter()), None); } #[test] fn test_get_local_name() { let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]); let check = |i: usize, names: Option<(&str, &str)>| { - assert!(selector_map::get_local_name(&rules_list[i][0].selector.inner) + assert!(selector_map::get_local_name(rules_list[i][0].selector.iter()) == names.map(|(name, lower_name)| LocalNameSelector { name: LocalName::from(name), lower_name: LocalName::from(lower_name) })) diff --git a/tests/unit/stylo/size_of.rs b/tests/unit/stylo/size_of.rs index 7909a69680e..3344a65c075 100644 --- a/tests/unit/stylo/size_of.rs +++ b/tests/unit/stylo/size_of.rs @@ -19,6 +19,11 @@ fn size_of_selectors_dummy_types() { assert_eq!(align_of::(), align_of::()); } +// The size of this is critical to performance on the bloom-basic microbenchmark. +// When iterating over a large Rule array, we want to be able to fast-reject +// selectors (with the inline hashes) with as few cache misses as possible. +size_of_test!(test_size_of_rule, style::stylist::Rule, 40); + size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32); // This is huge, but we allocate it on the stack and then never move it,