Auto merge of #17179 - bholley:one_selector_allocation, r=emilio

shrink Rule and store all heap-allocated selector data inline

https://bugzilla.mozilla.org/show_bug.cgi?id=1370107

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17179)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-06-05 20:10:28 -07:00 committed by GitHub
commit 74ea8ce3ed
22 changed files with 840 additions and 901 deletions

11
Cargo.lock generated
View file

@ -2523,6 +2523,7 @@ dependencies = [
"phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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", "size_of_test 0.0.1",
"smallvec 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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]] [[package]]
name = "servo_atoms" name = "servo_atoms"
version = "0.0.1" version = "0.0.1"
@ -2913,6 +2923,7 @@ dependencies = [
"selectors 0.19.0", "selectors 0.19.0",
"serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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_atoms 0.0.1",
"servo_config 0.0.1", "servo_config 0.0.1",
"servo_url 0.0.1", "servo_url 0.0.1",

View file

@ -2061,7 +2061,7 @@ impl ElementMethods for Element {
Err(()) => Err(Error::Syntax), Err(()) => Err(Error::Syntax),
Ok(selectors) => { Ok(selectors) => {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None); 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() { for element in root.inclusive_ancestors() {
if let Some(element) = Root::downcast::<Element>(element) { if let Some(element) = Root::downcast::<Element>(element) {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None); 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)); return Ok(Some(element));
} }
} }

View file

@ -345,7 +345,7 @@ impl<'a> Iterator for QuerySelectorIterator {
type Item = Root<Node>; type Item = Root<Node>;
fn next(&mut self) -> Option<Root<Node>> { fn next(&mut self) -> Option<Root<Node>> {
let selectors = &self.selectors.0; let selectors = &self.selectors;
// TODO(cgaebel): Is it worth it to build a bloom filter here // TODO(cgaebel): Is it worth it to build a bloom filter here
// (instead of passing `None`)? Probably. // (instead of passing `None`)? Probably.
@ -722,7 +722,7 @@ impl Node {
Ok(selectors) => { Ok(selectors) => {
let mut ctx = MatchingContext::new(MatchingMode::Normal, None); let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| { 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)
})) }))
} }
} }

View file

@ -28,6 +28,7 @@ cssparser = "0.13.7"
fnv = "1.0" fnv = "1.0"
phf = "0.7.18" phf = "0.7.18"
precomputed-hash = "0.1" precomputed-hash = "0.1"
servo_arc = { path = "../servo_arc" }
smallvec = "0.4" smallvec = "0.4"
[dev-dependencies] [dev-dependencies]

View file

@ -1,326 +0,0 @@
/* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
* http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
* <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<T> {
data: *const [T],
counts: Arc<Box<[T]>>,
}
unsafe impl<T: Send + Sync> Send for ArcSlice<T> {}
unsafe impl<T: Send + Sync> Sync for ArcSlice<T> {}
/// 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<T> {
data: *const [T],
counts: Weak<Box<[T]>>,
}
unsafe impl<T: Send + Sync> Send for WeakSlice<T> {}
unsafe impl<T: Send + Sync> Sync for WeakSlice<T> {}
impl<T> ArcSlice<T> {
/// Construct a new `ArcSlice` containing the elements of `slice`.
///
/// This reuses the allocation of `slice`.
pub fn new(slice: Box<[T]>) -> ArcSlice<T> {
ArcSlice {
data: &*slice,
counts: Arc::new(slice),
}
}
/// Downgrade self into a weak slice.
pub fn downgrade(&self) -> WeakSlice<T> {
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<T> {
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<T> {
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<T> {
let hi = self.len();
self.slice(lo, hi)
}
}
impl<T> Clone for ArcSlice<T> {
fn clone(&self) -> ArcSlice<T> {
ArcSlice {
data: self.data,
counts: self.counts.clone()
}
}
}
impl<T> ops::Deref for ArcSlice<T> {
type Target = [T];
fn deref<'a>(&'a self) -> &'a [T] {
unsafe { &*self.data }
}
}
impl<T> AsRef<[T]> for ArcSlice<T> {
fn as_ref(&self) -> &[T] { &**self }
}
impl<T: PartialEq> PartialEq for ArcSlice<T> {
fn eq(&self, other: &ArcSlice<T>) -> bool { **self == **other }
fn ne(&self, other: &ArcSlice<T>) -> bool { **self != **other }
}
impl<T: Eq> Eq for ArcSlice<T> {}
impl<T: PartialOrd> PartialOrd for ArcSlice<T> {
fn partial_cmp(&self, other: &ArcSlice<T>) -> Option<cmp::Ordering> {
(**self).partial_cmp(&**other)
}
fn lt(&self, other: &ArcSlice<T>) -> bool { **self < **other }
fn le(&self, other: &ArcSlice<T>) -> bool { **self <= **other }
fn gt(&self, other: &ArcSlice<T>) -> bool { **self > **other }
fn ge(&self, other: &ArcSlice<T>) -> bool { **self >= **other }
}
impl<T: Ord> Ord for ArcSlice<T> {
fn cmp(&self, other: &ArcSlice<T>) -> cmp::Ordering { (**self).cmp(&**other) }
}
impl<T: Hash> Hash for ArcSlice<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&**self, state)
}
}
impl<T: fmt::Debug> fmt::Debug for ArcSlice<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<T> WeakSlice<T> {
/// 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<ArcSlice<T>> {
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<i32> = 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<T: Send>() {}
fn assert_sync<T: Send>() {}
assert_send::<ArcSlice<u8>>();
assert_sync::<ArcSlice<u8>>();
assert_send::<WeakSlice<u8>>();
assert_sync::<WeakSlice<u8>>();
}
#[test]
fn test_drop() {
let drop_flag = Arc::new(Mutex::new(0));
struct Foo(Arc<Mutex<i32>>);
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);
}
}

View file

@ -9,9 +9,9 @@ extern crate fnv;
extern crate phf; extern crate phf;
extern crate precomputed_hash; extern crate precomputed_hash;
#[cfg(test)] #[macro_use] extern crate size_of_test; #[cfg(test)] #[macro_use] extern crate size_of_test;
extern crate servo_arc;
extern crate smallvec; extern crate smallvec;
pub mod arcslice;
pub mod attr; pub mod attr;
pub mod bloom; pub mod bloom;
pub mod matching; pub mod matching;

View file

@ -4,8 +4,8 @@
use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint}; use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
use bloom::BloomFilter; use bloom::BloomFilter;
use parser::{Combinator, ComplexSelector, Component, LocalName}; use parser::{AncestorHashes, Combinator, Component, LocalName};
use parser::{Selector, SelectorInner, SelectorIter}; use parser::{Selector, SelectorIter, SelectorList};
use std::borrow::Borrow; use std::borrow::Borrow;
use tree::Element; use tree::Element;
@ -152,27 +152,30 @@ impl<'a> MatchingContext<'a> {
} }
} }
pub fn matches_selector_list<E>(selector_list: &[Selector<E::Impl>], pub fn matches_selector_list<E>(selector_list: &SelectorList<E::Impl>,
element: &E, element: &E,
context: &mut MatchingContext) context: &mut MatchingContext)
-> bool -> bool
where E: Element where E: Element
{ {
selector_list.iter().any(|selector| { selector_list.0.iter().any(|selector_and_hashes| {
matches_selector(&selector.inner, matches_selector(&selector_and_hashes.selector,
0,
&selector_and_hashes.hashes,
element, element,
context, context,
&mut |_, _| {}) &mut |_, _| {})
}) })
} }
fn may_match<E>(sel: &SelectorInner<E::Impl>, #[inline(always)]
fn may_match<E>(hashes: &AncestorHashes,
bf: &BloomFilter) bf: &BloomFilter)
-> bool -> bool
where E: Element, where E: Element,
{ {
// Check against the list of precomputed hashes. // 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 we hit the 0 sentinel hash, that means the rest are zero as well.
if *hash == 0 { if *hash == 0 {
break; break;
@ -330,8 +333,18 @@ enum SelectorMatchingResult {
NotMatchedGlobally, NotMatchedGlobally,
} }
/// Matches an inner selector. /// Matches a selector, fast-rejecting against a bloom filter.
pub fn matches_selector<E, F>(selector: &SelectorInner<E::Impl>, ///
/// 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<E, F>(selector: &Selector<E::Impl>,
offset: usize,
hashes: &AncestorHashes,
element: &E, element: &E,
context: &mut MatchingContext, context: &mut MatchingContext,
flags_setter: &mut F) flags_setter: &mut F)
@ -341,18 +354,17 @@ pub fn matches_selector<E, F>(selector: &SelectorInner<E::Impl>,
{ {
// Use the bloom filter to fast-reject. // Use the bloom filter to fast-reject.
if let Some(filter) = context.bloom_filter { if let Some(filter) = context.bloom_filter {
if !may_match::<E>(&selector, filter) { if !may_match::<E>(hashes, filter) {
return false; return false;
} }
} }
matches_complex_selector(&selector.complex, element, context, flags_setter) matches_complex_selector(selector, offset, element, context, flags_setter)
} }
/// Matches a complex selector. /// Matches a complex selector.
/// pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
/// Use `matches_selector` if you need to skip pseudos. offset: usize,
pub fn matches_complex_selector<E, F>(complex_selector: &ComplexSelector<E::Impl>,
element: &E, element: &E,
context: &mut MatchingContext, context: &mut MatchingContext,
flags_setter: &mut F) flags_setter: &mut F)
@ -360,11 +372,15 @@ pub fn matches_complex_selector<E, F>(complex_selector: &ComplexSelector<E::Impl
where E: Element, where E: Element,
F: FnMut(&E, ElementSelectorFlags), F: FnMut(&E, ElementSelectorFlags),
{ {
let mut iter = complex_selector.iter(); let mut iter = if offset == 0 {
complex_selector.iter()
} else {
complex_selector.iter_from(offset)
};
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
if context.matching_mode == MatchingMode::ForStatelessPseudoElement { if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
assert!(complex_selector.iter().any(|c| { assert!(iter.clone().any(|c| {
matches!(*c, Component::PseudoElement(..)) matches!(*c, Component::PseudoElement(..))
})); }));
} }

View file

@ -2,11 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use arcslice::ArcSlice;
use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator}; use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator};
use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint}; use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint};
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter}; use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
use precomputed_hash::PrecomputedHash; use precomputed_hash::PrecomputedHash;
use servo_arc::{Arc, HeaderWithLength, ThinArc};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
@ -130,7 +130,27 @@ pub trait Parser {
} }
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
pub struct SelectorList<Impl: SelectorImpl>(pub Vec<Selector<Impl>>); pub struct SelectorAndHashes<Impl: SelectorImpl> {
pub selector: Selector<Impl>,
pub hashes: AncestorHashes,
}
impl<Impl: SelectorImpl> SelectorAndHashes<Impl> {
pub fn new(selector: Selector<Impl>) -> Self {
let hashes = AncestorHashes::new(&selector);
Self::new_with_hashes(selector, hashes)
}
pub fn new_with_hashes(selector: Selector<Impl>, hashes: AncestorHashes) -> Self {
SelectorAndHashes {
selector: selector,
hashes: hashes,
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct SelectorList<Impl: SelectorImpl>(pub Vec<SelectorAndHashes<Impl>>);
impl<Impl: SelectorImpl> SelectorList<Impl> { impl<Impl: SelectorImpl> SelectorList<Impl> {
/// Parse a comma-separated list of Selectors. /// Parse a comma-separated list of Selectors.
@ -139,35 +159,36 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
/// Return the Selectors or Err if there is an invalid selector. /// Return the Selectors or Err if there is an invalid selector.
pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()> pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
where P: Parser<Impl=Impl> { where P: Parser<Impl=Impl> {
input.parse_comma_separated(|input| parse_selector(parser, input)) input.parse_comma_separated(|input| parse_selector(parser, input).map(SelectorAndHashes::new))
.map(SelectorList) .map(SelectorList)
} }
/// Creates a SelectorList from a Vec of selectors. Used in tests.
pub fn from_vec(v: Vec<Selector<Impl>>) -> 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; const NUM_ANCESTOR_HASHES: usize = 4;
/// The cores parts of a selector used for matching. This exists to make that /// Ancestor hashes for the bloom filter. We precompute these and store them
/// information accessibly separately from the specificity and pseudo-element /// inline with selectors to optimize cache performance during matching.
/// 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<Impl: SelectorImpl> {
/// The selector data.
pub complex: ComplexSelector<Impl>,
/// Ancestor hashes for the bloom filter. We precompute these and store
/// them inline to optimize cache performance during selector matching.
/// This matters a lot. /// This matters a lot.
pub ancestor_hashes: [u32; NUM_ANCESTOR_HASHES], #[derive(Eq, PartialEq, Clone, Debug)]
pub struct AncestorHashes(pub [u32; NUM_ANCESTOR_HASHES]);
impl AncestorHashes {
pub fn new<Impl: SelectorImpl>(s: &Selector<Impl>) -> Self {
Self::from_iter(s.iter())
} }
impl<Impl: SelectorImpl> SelectorInner<Impl> { pub fn from_iter<Impl: SelectorImpl>(iter: SelectorIter<Impl>) -> Self {
pub fn new(c: ComplexSelector<Impl>) -> Self {
let mut hashes = [0; NUM_ANCESTOR_HASHES]; let mut hashes = [0; NUM_ANCESTOR_HASHES];
{
// Compute ancestor hashes for the bloom filter. // Compute ancestor hashes for the bloom filter.
let mut hash_iter = c.iter_ancestors() let mut hash_iter = AncestorIter::new(iter)
.map(|x| x.ancestor_hash()) .map(|x| x.ancestor_hash())
.filter(|x| x.is_some()) .filter(|x| x.is_some())
.map(|x| x.unwrap()); .map(|x| x.unwrap());
@ -177,79 +198,13 @@ impl<Impl: SelectorImpl> SelectorInner<Impl> {
None => break, None => break,
} }
} }
}
SelectorInner { AncestorHashes(hashes)
complex: c,
ancestor_hashes: hashes,
}
}
/// Creates a SelectorInner from a Vec of Components. Used in tests.
pub fn from_vec(vec: Vec<Component<Impl>>) -> Self {
let complex = ComplexSelector::from_vec(vec);
Self::new(complex)
}
}
#[derive(PartialEq, Eq, Clone)]
pub struct Selector<Impl: SelectorImpl> {
pub inner: SelectorInner<Impl>,
specificity_and_flags: u32,
}
impl<Impl: SelectorImpl> ::std::borrow::Borrow<SelectorInner<Impl>> for Selector<Impl> {
fn borrow(&self) -> &SelectorInner<Impl> {
&self.inner
} }
} }
const HAS_PSEUDO_BIT: u32 = 1 << 30; const HAS_PSEUDO_BIT: u32 = 1 << 30;
impl<Impl: SelectorImpl> Selector<Impl> {
pub fn specificity(&self) -> u32 {
self.specificity_and_flags & !HAS_PSEUDO_BIT
}
pub fn new_for_unit_testing(inner: SelectorInner<Impl>, 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 { pub trait SelectorMethods {
type Impl: SelectorImpl; type Impl: SelectorImpl;
@ -260,16 +215,6 @@ pub trait SelectorMethods {
impl<Impl: SelectorImpl> SelectorMethods for Selector<Impl> { impl<Impl: SelectorImpl> SelectorMethods for Selector<Impl> {
type Impl = Impl; type Impl = Impl;
fn visit<V>(&self, visitor: &mut V) -> bool
where V: SelectorVisitor<Impl = Impl>,
{
self.inner.complex.visit(visitor)
}
}
impl<Impl: SelectorImpl> SelectorMethods for ComplexSelector<Impl> {
type Impl = Impl;
fn visit<V>(&self, visitor: &mut V) -> bool fn visit<V>(&self, visitor: &mut V) -> bool
where V: SelectorVisitor<Impl = Impl>, where V: SelectorVisitor<Impl = Impl>,
{ {
@ -362,7 +307,20 @@ pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
Impl::NamespaceUrl::default() 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 /// 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 /// at the level of sequences of simple selectors separated by combinators. Most
/// callers want the higher-level iterator. /// callers want the higher-level iterator.
@ -371,9 +329,45 @@ pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
/// canonical iteration order is right-to-left (selector matching order). The /// canonical iteration order is right-to-left (selector matching order). The
/// iterators abstract over these details. /// iterators abstract over these details.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct ComplexSelector<Impl: SelectorImpl>(ArcSlice<Component<Impl>>); pub struct Selector<Impl: SelectorImpl>(ThinArc<SpecificityAndFlags, Component<Impl>>);
impl<Impl: SelectorImpl> Selector<Impl> {
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<Impl: SelectorImpl> ComplexSelector<Impl> {
/// Returns an iterator over the next sequence of simple selectors. When /// Returns an iterator over the next sequence of simple selectors. When
/// a combinator is reached, the iterator will return None, and /// a combinator is reached, the iterator will return None, and
/// next_sequence() may be called to continue to the next sequence. /// next_sequence() may be called to continue to the next sequence.
@ -384,6 +378,15 @@ impl<Impl: SelectorImpl> ComplexSelector<Impl> {
} }
} }
pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> {
// 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, /// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from right to left. /// from right to left.
pub fn iter_raw(&self) -> Rev<slice::Iter<Component<Impl>>> { pub fn iter_raw(&self) -> Rev<slice::Iter<Component<Impl>>> {
@ -393,34 +396,13 @@ impl<Impl: SelectorImpl> ComplexSelector<Impl> {
/// Returns an iterator over the entire sequence of simple selectors and combinators, /// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from left to right. /// from left to right.
pub fn iter_raw_rev(&self) -> slice::Iter<Component<Impl>> { pub fn iter_raw_rev(&self) -> slice::Iter<Component<Impl>> {
self.0.iter() self.0.slice.iter()
} }
/// Returns an iterator over ancestor simple selectors. All combinators and /// Creates a Selector from a vec of Components. Used in tests.
/// non-ancestor simple selectors will be skipped. pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> Self {
pub fn iter_ancestors(&self) -> AncestorIter<Impl> { let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len());
AncestorIter::new(self.iter()) Selector(Arc::into_thin(Arc::from_header_and_iter(header, vec.into_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<Component<Impl>>) -> Self {
ComplexSelector(ArcSlice::new(vec.into_boxed_slice()))
} }
} }
@ -590,7 +572,7 @@ pub enum Component<Impl: SelectorImpl> {
impl<Impl: SelectorImpl> Component<Impl> { impl<Impl: SelectorImpl> Component<Impl> {
/// Compute the ancestor hash to check against the bloom filter. /// Compute the ancestor hash to check against the bloom filter.
fn ancestor_hash(&self) -> Option<u32> { pub fn ancestor_hash(&self) -> Option<u32> {
match *self { match *self {
Component::LocalName(LocalName { ref name, ref lower_name }) => { Component::LocalName(LocalName { ref name, ref lower_name }) => {
// Only insert the local-name into the filter if it's all lowercase. // Only insert the local-name into the filter if it's all lowercase.
@ -644,12 +626,6 @@ impl<Impl: SelectorImpl> Debug for Selector<Impl> {
} }
} }
impl<Impl: SelectorImpl> Debug for SelectorInner<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.complex.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for ComplexSelector<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
}
impl<Impl: SelectorImpl> Debug for Component<Impl> { impl<Impl: SelectorImpl> Debug for Component<Impl> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
} }
@ -665,22 +641,16 @@ impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
let mut iter = self.0.iter(); let mut iter = self.0.iter();
let first = iter.next() let first = iter.next()
.expect("Empty SelectorList, should contain at least one selector"); .expect("Empty SelectorList, should contain at least one selector");
first.to_css(dest)?; first.selector.to_css(dest)?;
for selector in iter { for selector_and_hashes in iter {
dest.write_str(", ")?; dest.write_str(", ")?;
selector.to_css(dest)?; selector_and_hashes.selector.to_css(dest)?;
} }
Ok(()) Ok(())
} }
} }
impl<Impl: SelectorImpl> ToCss for Selector<Impl> { impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.inner.complex.to_css(dest)
}
}
impl<Impl: SelectorImpl> ToCss for ComplexSelector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
for item in self.iter_raw_rev() { for item in self.iter_raw_rev() {
item.to_css(dest)?; item.to_css(dest)?;
@ -889,13 +859,13 @@ impl From<Specificity> for u32 {
} }
} }
fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>) -> u32 fn specificity<Impl>(iter: SelectorIter<Impl>) -> u32
where Impl: SelectorImpl where Impl: SelectorImpl
{ {
complex_selector_specificity(complex_selector).into() complex_selector_specificity(iter).into()
} }
fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>) fn complex_selector_specificity<Impl>(mut iter: SelectorIter<Impl>)
-> Specificity -> Specificity
where Impl: SelectorImpl where Impl: SelectorImpl
{ {
@ -944,9 +914,7 @@ fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>)
} }
} }
let mut specificity = Default::default(); let mut specificity = Default::default();
let mut iter = selector.iter();
loop { loop {
for simple_selector in &mut iter { for simple_selector in &mut iter {
simple_selector_specificity(&simple_selector, &mut specificity); simple_selector_specificity(&simple_selector, &mut specificity);
@ -958,43 +926,20 @@ fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>)
specificity 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<Impl> = SmallVec<[Component<Impl>; 32]>;
/// Build up a Selector. /// Build up a Selector.
/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; /// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
/// ///
/// `Err` means invalid selector. /// `Err` means invalid selector.
fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector<Impl>, ()> fn parse_selector<P, Impl>(
where P: Parser<Impl=Impl>, 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<Impl> = SmallVec<[Component<Impl>; 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<P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser) input: &mut CssParser)
-> Result<(ComplexSelector<Impl>, bool), ()> -> Result<Selector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl where P: Parser<Impl=Impl>, Impl: SelectorImpl
{ {
let mut sequence = ParseVec::new(); let mut sequence = ParseVec::new();
@ -1042,21 +987,29 @@ fn parse_complex_selector<P, Impl>(
sequence.push(Component::Combinator(combinator)); sequence.push(Component::Combinator(combinator));
} }
let complex = ComplexSelector(ArcSlice::new(sequence.into_vec().into_boxed_slice())); let mut spec = SpecificityAndFlags(specificity(SelectorIter {
Ok((complex, parsed_pseudo_element)) iter: sequence.iter().rev(),
next_combinator: None,
}));
if parsed_pseudo_element {
spec.0 |= HAS_PSEUDO_BIT;
} }
impl<Impl: SelectorImpl> ComplexSelector<Impl> { let header = HeaderWithLength::new(spec, sequence.len());
/// Parse a complex selector, without any pseudo-element. let complex = Selector(Arc::into_thin(Arc::from_header_and_iter(header, sequence.into_iter())));
Ok(complex)
}
impl<Impl: SelectorImpl> Selector<Impl> {
/// Parse a selector, without any pseudo-element.
pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()> pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
where P: Parser<Impl=Impl> where P: Parser<Impl=Impl>
{ {
let (complex, has_pseudo_element) = let selector = parse_selector(parser, input)?;
parse_complex_selector(parser, input)?; if selector.has_pseudo_element() {
if has_pseudo_element {
return Err(()) return Err(())
} }
Ok(complex) Ok(selector)
} }
} }
@ -1761,7 +1714,7 @@ pub mod tests {
let result = SelectorList::parse(parser, &mut CssParser::new(input)); let result = SelectorList::parse(parser, &mut CssParser::new(input));
if let Ok(ref selectors) = result { if let Ok(ref selectors) = result {
assert_eq!(selectors.0.len(), 1); 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 result
} }
@ -1784,76 +1737,71 @@ pub mod tests {
assert_eq!(parse(""), Err(())) ; assert_eq!(parse(""), Err(())) ;
assert_eq!(parse(":lang(4)"), Err(())) ; assert_eq!(parse(":lang(4)"), Err(())) ;
assert_eq!(parse(":lang(en US)"), Err(())) ; assert_eq!(parse(":lang(en US)"), Err(())) ;
assert_eq!(parse("EeÉ"), Ok(SelectorList(vec!(Selector { assert_eq!(parse("EeÉ"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!( Selector::from_vec(vec!(
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("EeÉ"), name: DummyAtom::from("EeÉ"),
lower_name: DummyAtom::from("eeÉ") })), lower_name: DummyAtom::from("eeÉ") })
), ), specificity(0, 0, 1))
specificity_and_flags: specificity(0, 0, 1), ))));
})))); assert_eq!(parse("|e"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("|e"), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(
Component::ExplicitNoNamespace, Component::ExplicitNoNamespace,
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("e"), name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") lower_name: DummyAtom::from("e")
}), })), specificity(0, 0, 1))
)), ))));
specificity_and_flags: specificity(0, 0, 1),
}))));
// https://github.com/servo/servo/issues/16020 // https://github.com/servo/servo/issues/16020
assert_eq!(parse("*|e"), Ok(SelectorList(vec!(Selector { assert_eq!(parse("*|e"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!( Selector::from_vec(vec!(
Component::ExplicitAnyNamespace, Component::ExplicitAnyNamespace,
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("e"), name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") lower_name: DummyAtom::from("e")
}), })
)), ), specificity(0, 0, 1))
specificity_and_flags: specificity(0, 0, 1), ))));
})))); assert_eq!(parse("*"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("*"), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
)), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse("|*"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("|*"), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(
Component::ExplicitNoNamespace, Component::ExplicitNoNamespace,
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
)), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse("*|*"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("*|*"), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(
Component::ExplicitAnyNamespace, Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
)), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec![
Component::Class(DummyAtom::from("foo")), Component::Class(DummyAtom::from("foo")),
Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())) Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned()))
]), ), specificity(0, 2, 0))
specificity_and_flags: specificity(0, 2, 0), ))));
})))); assert_eq!(parse("#bar"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(Component::ID(DummyAtom::from("bar")))), Component::ID(DummyAtom::from("bar"))
specificity_and_flags: specificity(1, 0, 0), ), specificity(1, 0, 0))
})))); ))));
assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector { assert_eq!(parse("e.foo#bar"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(Component::LocalName(LocalName { Selector::from_vec(vec!(
Component::LocalName(LocalName {
name: DummyAtom::from("e"), name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") }), lower_name: DummyAtom::from("e")
}),
Component::Class(DummyAtom::from("foo")), Component::Class(DummyAtom::from("foo")),
Component::ID(DummyAtom::from("bar")))), Component::ID(DummyAtom::from("bar"))
specificity_and_flags: specificity(1, 1, 1), ), specificity(1, 1, 1))
})))); ))));
assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector { assert_eq!(parse("e.foo #bar"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!( Selector::from_vec(vec!(
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("e"), name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") lower_name: DummyAtom::from("e")
@ -1861,108 +1809,92 @@ pub mod tests {
Component::Class(DummyAtom::from("foo")), Component::Class(DummyAtom::from("foo")),
Component::Combinator(Combinator::Descendant), Component::Combinator(Combinator::Descendant),
Component::ID(DummyAtom::from("bar")), Component::ID(DummyAtom::from("bar")),
)), ), specificity(1, 1, 1))
specificity_and_flags: specificity(1, 1, 1), ))));
}))));
// Default namespace does not apply to attribute selectors // Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652 // https://github.com/mozilla/servo/pull/1652
let mut parser = DummyParser::default(); let mut parser = DummyParser::default();
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector { assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec![ Selector::from_vec(vec!(
Component::AttributeInNoNamespaceExists { Component::AttributeInNoNamespaceExists {
local_name: DummyAtom::from("Foo"), local_name: DummyAtom::from("Foo"),
local_name_lower: DummyAtom::from("foo"), local_name_lower: DummyAtom::from("foo"),
} }
]), ), specificity(0, 1, 0))
specificity_and_flags: specificity(0, 1, 0), ))));
}))));
assert_eq!(parse_ns("svg|circle", &parser), Err(())); assert_eq!(parse_ns("svg|circle", &parser), Err(()));
parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into()));
assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList(vec![Selector { assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec( Selector::from_vec(vec!(
vec![
Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("circle"), name: DummyAtom::from("circle"),
lower_name: DummyAtom::from("circle"), lower_name: DummyAtom::from("circle"),
}) })
]), ), specificity(0, 0, 1))
specificity_and_flags: specificity(0, 0, 1), ))));
}]))); assert_eq!(parse_ns("svg|*", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns("svg|*", &parser), Ok(SelectorList(vec![Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(
vec![
Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
]), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
}])));
// Default namespace does not apply to attribute selectors // Default namespace does not apply to attribute selectors
// https://github.com/mozilla/servo/pull/1652 // https://github.com/mozilla/servo/pull/1652
// but it does apply to implicit type selectors // but it does apply to implicit type selectors
// https://github.com/servo/rust-selectors/pull/82 // https://github.com/servo/rust-selectors/pull/82
parser.default_ns = Some(MATHML.into()); parser.default_ns = Some(MATHML.into());
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector { assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec( Selector::from_vec(vec!(
vec![
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::AttributeInNoNamespaceExists { Component::AttributeInNoNamespaceExists {
local_name: DummyAtom::from("Foo"), local_name: DummyAtom::from("Foo"),
local_name_lower: DummyAtom::from("foo"), local_name_lower: DummyAtom::from("foo"),
}, },
]), ), specificity(0, 1, 0))
specificity_and_flags: specificity(0, 1, 0), ))));
}))));
// Default namespace does apply to type selectors // Default namespace does apply to type selectors
assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector { assert_eq!(parse_ns("e", &parser), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec( Selector::from_vec(vec!(
vec!(
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("e"), name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") }), lower_name: DummyAtom::from("e") }),
)), ), specificity(0, 0, 1))
specificity_and_flags: specificity(0, 0, 1), ))));
})))); assert_eq!(parse_ns("*", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns("*", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(
vec!(
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
)), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse_ns("*|*", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns("*|*", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(
vec!(
Component::ExplicitAnyNamespace, Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
)), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
}))));
// Default namespace applies to universal and type selectors inside :not and :matches, // Default namespace applies to universal and type selectors inside :not and :matches,
// but not otherwise. // but not otherwise.
assert_eq!(parse_ns(":not(.cl)", &parser), Ok(SelectorList(vec!(Selector { assert_eq!(parse_ns(":not(.cl)", &parser), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!( Selector::from_vec(vec!(
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::Negation(vec![ Component::Negation(vec![
Component::Class(DummyAtom::from("cl")) Component::Class(DummyAtom::from("cl"))
].into_boxed_slice()), ].into_boxed_slice()),
)), ), specificity(0, 1, 0))
specificity_and_flags: specificity(0, 1, 0), ))));
})))); assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::Negation(vec![ Component::Negation(vec![
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
].into_boxed_slice()), ].into_boxed_slice()),
)), ), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse_ns(":not(e)", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns(":not(e)", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
Component::Negation(vec![ Component::Negation(vec![
Component::DefaultNamespace(MATHML.into()), Component::DefaultNamespace(MATHML.into()),
@ -1971,12 +1903,10 @@ pub mod tests {
lower_name: DummyAtom::from("e") lower_name: DummyAtom::from("e")
}), }),
].into_boxed_slice()) ].into_boxed_slice())
)), ), specificity(0, 0, 1))
specificity_and_flags: specificity(0, 0, 1), ))));
})))); assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(
vec![
Component::AttributeInNoNamespace { Component::AttributeInNoNamespace {
local_name: DummyAtom::from("attr"), local_name: DummyAtom::from("attr"),
local_name_lower: DummyAtom::from("attr"), local_name_lower: DummyAtom::from("attr"),
@ -1985,37 +1915,27 @@ pub mod tests {
never_matches: false, never_matches: false,
case_sensitivity: ParsedCaseSensitivity::CaseSensitive, case_sensitivity: ParsedCaseSensitivity::CaseSensitive,
} }
]), ), specificity(0, 1, 0))
specificity_and_flags: specificity(0, 1, 0), ))));
}])));
// https://github.com/mozilla/servo/issues/1723 // https://github.com/mozilla/servo/issues/1723
assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector { assert_eq!(parse("::before"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec( Selector::from_vec(vec!(
vec![
Component::PseudoElement(PseudoElement::Before), Component::PseudoElement(PseudoElement::Before),
] ), specificity(0, 0, 1) | HAS_PSEUDO_BIT)
), ))));
specificity_and_flags: specificity(0, 0, 1) | HAS_PSEUDO_BIT, assert_eq!(parse("::before:hover"), Ok(SelectorList::from_vec(vec!(
})))); Selector::from_vec(vec!(
assert_eq!(parse("::before:hover"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(
vec![
Component::PseudoElement(PseudoElement::Before), Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover),
] ), specificity(0, 1, 1) | HAS_PSEUDO_BIT)
), ))));
specificity_and_flags: specificity(0, 1, 1) | HAS_PSEUDO_BIT, assert_eq!(parse("::before:hover:hover"), Ok(SelectorList::from_vec(vec!(
})))); Selector::from_vec(vec!(
assert_eq!(parse("::before:hover:hover"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::from_vec(
vec![
Component::PseudoElement(PseudoElement::Before), Component::PseudoElement(PseudoElement::Before),
Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover),
Component::NonTSPseudoClass(PseudoClass::Hover), Component::NonTSPseudoClass(PseudoClass::Hover),
] ), specificity(0, 2, 1) | HAS_PSEUDO_BIT)
), ))));
specificity_and_flags: specificity(0, 2, 1) | HAS_PSEUDO_BIT,
}))));
assert_eq!(parse("::before:hover:active"), Err(())); assert_eq!(parse("::before:hover:active"), Err(()));
assert_eq!(parse("::before:hover .foo"), Err(())); assert_eq!(parse("::before:hover .foo"), Err(()));
assert_eq!(parse("::before .foo"), Err(())); assert_eq!(parse("::before .foo"), Err(()));
@ -2024,41 +1944,35 @@ pub mod tests {
// https://github.com/servo/servo/issues/15335 // https://github.com/servo/servo/issues/15335
assert_eq!(parse(":: before"), Err(())); assert_eq!(parse(":: before"), Err(()));
assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector { assert_eq!(parse("div ::after"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec( Selector::from_vec(vec!(
vec![
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: DummyAtom::from("div"), name: DummyAtom::from("div"),
lower_name: DummyAtom::from("div") }), lower_name: DummyAtom::from("div") }),
Component::Combinator(Combinator::Descendant), Component::Combinator(Combinator::Descendant),
Component::Combinator(Combinator::PseudoElement), Component::Combinator(Combinator::PseudoElement),
Component::PseudoElement(PseudoElement::After), Component::PseudoElement(PseudoElement::After),
]), ), specificity(0, 0, 2) | HAS_PSEUDO_BIT)
specificity_and_flags: specificity(0, 0, 2) | HAS_PSEUDO_BIT, ))));
})))); assert_eq!(parse("#d1 > .ok"), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector { Selector::from_vec(vec!(
inner: SelectorInner::from_vec(
vec![
Component::ID(DummyAtom::from("d1")), Component::ID(DummyAtom::from("d1")),
Component::Combinator(Combinator::Child), Component::Combinator(Combinator::Child),
Component::Class(DummyAtom::from("ok")), Component::Class(DummyAtom::from("ok")),
]), ), (1 << 20) + (1 << 10) + (0 << 0))
specificity_and_flags: (1 << 20) + (1 << 10) + (0 << 0), ))));
}])));
parser.default_ns = None; parser.default_ns = None;
assert_eq!(parse(":not(#provel.old)"), Err(())); assert_eq!(parse(":not(#provel.old)"), Err(()));
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!(parse("table[rules]:not([rules = \"none\"]):not([rules = \"\"])").is_ok());
assert_eq!(parse(":not(#provel)"), Ok(SelectorList(vec!(Selector { assert_eq!(parse(":not(#provel)"), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(Component::Negation( Selector::from_vec(vec!(Component::Negation(vec!(
vec![
Component::ID(DummyAtom::from("provel")), Component::ID(DummyAtom::from("provel")),
].into_boxed_slice() ).into_boxed_slice()
))), )), specificity(1, 0, 0))
specificity_and_flags: specificity(1, 0, 0), ))));
})))); assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(Component::Negation(
inner: SelectorInner::from_vec(vec!(Component::Negation(
vec![ vec![
Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::LocalName(LocalName { Component::LocalName(LocalName {
@ -2066,52 +1980,47 @@ pub mod tests {
lower_name: DummyAtom::from("circle") lower_name: DummyAtom::from("circle")
}), }),
].into_boxed_slice() ].into_boxed_slice()
))), )), specificity(0, 0, 1))
specificity_and_flags: specificity(0, 0, 1), ))));
}))));
// https://github.com/servo/servo/issues/16017 // https://github.com/servo/servo/issues/16017
assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList(vec!(Selector { assert_eq!(parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec!(
inner: SelectorInner::from_vec(vec!(Component::Negation( Selector::from_vec(vec!(Component::Negation(
vec![ vec![
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
].into_boxed_slice() ].into_boxed_slice()
))), )), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse_ns(":not(|*)", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns(":not(|*)", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(Component::Negation(
inner: SelectorInner::from_vec(vec!(Component::Negation(
vec![ vec![
Component::ExplicitNoNamespace, Component::ExplicitNoNamespace,
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
].into_boxed_slice() ].into_boxed_slice()
))), )), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse_ns(":not(*|*)", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns(":not(*|*)", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(Component::Negation(
inner: SelectorInner::from_vec(vec!(Component::Negation(
vec![ vec![
Component::ExplicitAnyNamespace, Component::ExplicitAnyNamespace,
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
].into_boxed_slice() ].into_boxed_slice()
))), )), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
})))); assert_eq!(parse_ns(":not(svg|*)", &parser), Ok(SelectorList::from_vec(vec!(
assert_eq!(parse_ns(":not(svg|*)", &parser), Ok(SelectorList(vec!(Selector { Selector::from_vec(vec!(Component::Negation(
inner: SelectorInner::from_vec(vec!(Component::Negation(
vec![ vec![
Component::Namespace(DummyAtom("svg".into()), SVG.into()), Component::Namespace(DummyAtom("svg".into()), SVG.into()),
Component::ExplicitUniversalType, Component::ExplicitUniversalType,
].into_boxed_slice() ].into_boxed_slice()
))), )), specificity(0, 0, 0))
specificity_and_flags: specificity(0, 0, 0), ))));
}))));
} }
#[test] #[test]
fn test_pseudo_iter() { fn test_pseudo_iter() {
let selector = &parse("q::before").unwrap().0[0]; let selector = &parse("q::before").unwrap().0[0].selector;
assert!(!selector.is_universal()); 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(), Some(&Component::PseudoElement(PseudoElement::Before)));
assert_eq!(iter.next(), None); assert_eq!(iter.next(), None);
let combinator = iter.next_sequence(); let combinator = iter.next_sequence();
@ -2123,15 +2032,15 @@ pub mod tests {
#[test] #[test]
fn test_universal() { fn test_universal() {
let selector = &parse("*|*::before").unwrap().0[0]; let selector = &parse("*|*::before").unwrap().0[0].selector;
assert!(selector.is_universal()); assert!(selector.is_universal());
} }
#[test] #[test]
fn test_empty_pseudo_iter() { fn test_empty_pseudo_iter() {
let selector = &parse("::before").unwrap().0[0]; let selector = &parse("::before").unwrap().0[0].selector;
assert!(selector.is_universal()); 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(), Some(&Component::PseudoElement(PseudoElement::Before)));
assert_eq!(iter.next(), None); assert_eq!(iter.next(), None);
assert_eq!(iter.next_sequence(), None); assert_eq!(iter.next_sequence(), None);
@ -2155,11 +2064,11 @@ pub mod tests {
#[test] #[test]
fn visitor() { fn visitor() {
let mut test_visitor = TestVisitor { seen: vec![], }; 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())); assert!(test_visitor.seen.contains(&":hover".into()));
let mut test_visitor = TestVisitor { seen: vec![], }; 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())); assert!(test_visitor.seen.contains(&":hover".into()));
} }
} }

View file

@ -11,10 +11,8 @@ use precomputed_hash::PrecomputedHash;
use std::fmt; use std::fmt;
use visitor::SelectorVisitor; use visitor::SelectorVisitor;
size_of_test!(size_of_selector, Selector<Impl>, 48); size_of_test!(size_of_selector, Selector<Impl>, 8);
size_of_test!(size_of_pseudo_element, gecko_like_types::PseudoElement, 1); size_of_test!(size_of_pseudo_element, gecko_like_types::PseudoElement, 1);
size_of_test!(size_of_selector_inner, SelectorInner<Impl>, 40);
size_of_test!(size_of_complex_selector, ComplexSelector<Impl>, 24);
size_of_test!(size_of_component, Component<Impl>, 32); size_of_test!(size_of_component, Component<Impl>, 32);
size_of_test!(size_of_pseudo_class, PseudoClass, 24); size_of_test!(size_of_pseudo_class, PseudoClass, 24);

View file

@ -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"}

View file

@ -8,11 +8,13 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // 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 waste storage on the weak reference count.
//! * We don't do extra RMU operations to handle the possibility of weak references. //! * We don't do extra RMU operations to handle the possibility of weak references.
//! * We can experiment with arena allocation (todo). //! * We can experiment with arena allocation (todo).
//! * We can add methods to support our custom use cases [1]. //! * 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 //! [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1360883
@ -20,8 +22,12 @@
// duplicate those here. // duplicate those here.
#![allow(missing_docs)] #![allow(missing_docs)]
#[cfg(feature = "servo")] extern crate serde;
extern crate nodrop;
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use heapsize::HeapSizeOf; use heapsize::HeapSizeOf;
use nodrop::NoDrop;
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{isize, usize}; use std::{isize, usize};
@ -30,8 +36,11 @@ use std::cmp::Ordering;
use std::convert::From; use std::convert::From;
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::iter::{ExactSizeIterator, Iterator};
use std::mem; use std::mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::ptr;
use std::slice;
use std::sync::atomic; use std::sync::atomic;
use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
@ -412,3 +421,257 @@ impl<T: Serialize> Serialize for Arc<T>
(**self).serialize(serializer) (**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<H, T: ?Sized> {
/// 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<H, T> Arc<HeaderSlice<H, [T]>> {
/// 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<I>(header: H, mut items: I) -> Self
where I: Iterator<Item=T> + ExactSizeIterator
{
use ::std::mem::size_of;
assert!(size_of::<T>() != 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::<ArcInner<HeaderSlice<H, [T; 1]>>>();
// 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<HeaderSlice<H, [T]>>;
let fake_ref: &ArcInner<HeaderSlice<H, [T]>> = 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<HeaderSlice<H, [T]>>;
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::<usize>());
let mut vec = Vec::<usize>::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<HeaderSlice<H, [T]>>;
// 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::<Self>(), size_of::<usize>() * 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<H> {
/// The fixed-sized data.
pub header: H,
/// The slice length.
length: usize,
}
impl<H> HeaderWithLength<H> {
/// Creates a new HeaderWithLength.
pub fn new(header: H, length: usize) -> Self {
HeaderWithLength {
header: header,
length: length,
}
}
}
pub struct ThinArc<H, T> {
ptr: *mut ArcInner<HeaderSlice<HeaderWithLength<H>, [T; 1]>>,
}
unsafe impl<H: Sync + Send, T: Sync + Send> Send for ThinArc<H, T> {}
unsafe impl<H: Sync + Send, T: Sync + Send> Sync for ThinArc<H, T> {}
// Synthesize a fat pointer from a thin pointer.
//
// See the comment around the analogous operation in from_header_and_iter.
fn thin_to_thick<H, T>(thin: *mut ArcInner<HeaderSlice<HeaderWithLength<H>, [T; 1]>>)
-> *mut ArcInner<HeaderSlice<HeaderWithLength<H>, [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<HeaderSlice<HeaderWithLength<H>, [T]>>
}
impl<H, T> ThinArc<H, T> {
/// 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<F, U>(&self, f: F) -> U
where F: FnOnce(&Arc<HeaderSlice<HeaderWithLength<H>, [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<H, T> Deref for ThinArc<H, T> {
type Target = HeaderSlice<HeaderWithLength<H>, [T]>;
fn deref(&self) -> &Self::Target {
unsafe { &(*thin_to_thick(self.ptr)).data }
}
}
impl<H, T> Clone for ThinArc<H, T> {
fn clone(&self) -> Self {
ThinArc::with_arc(self, |a| Arc::into_thin(a.clone()))
}
}
impl<H, T> Drop for ThinArc<H, T> {
fn drop(&mut self) {
let _ = Arc::from_thin(ThinArc { ptr: self.ptr });
}
}
impl<H, T> Arc<HeaderSlice<HeaderWithLength<H>, [T]>> {
/// Converts an Arc into a ThinArc. This consumes the Arc, so the refcount
/// is not modified.
pub fn into_thin(a: Self) -> ThinArc<H, T> {
assert!(a.header.length == a.slice.len(),
"Length needs to be correct for ThinArc to work");
let fat_ptr: *mut ArcInner<HeaderSlice<HeaderWithLength<H>, [T]>> = a.ptr;
mem::forget(a);
let thin_ptr = fat_ptr as *mut [usize] as *mut usize;
ThinArc {
ptr: thin_ptr as *mut ArcInner<HeaderSlice<HeaderWithLength<H>, [T; 1]>>
}
}
/// Converts a ThinArc into an Arc. This consumes the ThinArc, so the refcount
/// is not modified.
pub fn from_thin(a: ThinArc<H, T>) -> Self {
let ptr = thin_to_thick(a.ptr);
mem::forget(a);
Arc {
ptr: ptr
}
}
}
impl<H: PartialEq, T: PartialEq> PartialEq for ThinArc<H, T> {
fn eq(&self, other: &ThinArc<H, T>) -> bool {
ThinArc::with_arc(self, |a| {
ThinArc::with_arc(other, |b| {
*a == *b
})
})
}
}
impl<H: Eq, T: Eq> Eq for ThinArc<H, T> {}
#[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);
}
}

View file

@ -61,6 +61,7 @@ rayon = "0.7.1"
selectors = { path = "../selectors" } selectors = { path = "../selectors" }
serde = {version = "0.9", optional = true} serde = {version = "0.9", optional = true}
serde_derive = {version = "0.9", optional = true} serde_derive = {version = "0.9", optional = true}
servo_arc = { path = "../servo_arc" }
servo_atoms = {path = "../atoms", optional = true} servo_atoms = {path = "../atoms", optional = true}
servo_config = {path = "../config", optional = true} servo_config = {path = "../config", optional = true}
smallvec = "0.4" smallvec = "0.4"

View file

@ -8,7 +8,7 @@ use cssparser::{Parser, ToCss};
use element_state::ElementState; use element_state::ElementState;
use gecko_bindings::structs::CSSPseudoClassType; use gecko_bindings::structs::CSSPseudoClassType;
use selector_parser::{SelectorParser, PseudoElementCascadeType}; use selector_parser::{SelectorParser, PseudoElementCascadeType};
use selectors::parser::{ComplexSelector, SelectorMethods}; use selectors::parser::{Selector, SelectorMethods};
use selectors::visitor::SelectorVisitor; use selectors::visitor::SelectorVisitor;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
@ -47,7 +47,7 @@ macro_rules! pseudo_class_name {
/// ///
/// TODO(emilio): We disallow combinators and pseudos here, so we /// TODO(emilio): We disallow combinators and pseudos here, so we
/// should use SimpleSelector instead /// should use SimpleSelector instead
MozAny(Box<[ComplexSelector<SelectorImpl>]>), MozAny(Box<[Selector<SelectorImpl>]>),
} }
} }
} }
@ -273,7 +273,7 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
}, )* }, )*
"-moz-any" => { "-moz-any" => {
let selectors = parser.parse_comma_separated(|input| { let selectors = parser.parse_comma_separated(|input| {
ComplexSelector::parse(self, input) Selector::parse(self, input)
})?; })?;
// Selectors inside `:-moz-any` may not include combinators. // Selectors inside `:-moz-any` may not include combinators.
if selectors.iter().flat_map(|x| x.iter_raw()).any(|s| s.is_combinator()) { if selectors.iter().flat_map(|x| x.iter_raw()).any(|s| s.is_combinator()) {

View file

@ -1405,7 +1405,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozPlaceholder => false, NonTSPseudoClass::MozPlaceholder => false,
NonTSPseudoClass::MozAny(ref sels) => { NonTSPseudoClass::MozAny(ref sels) => {
sels.iter().any(|s| { 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) | NonTSPseudoClass::MozSystemMetric(ref s) |

View file

@ -218,7 +218,7 @@ impl StylesheetInvalidationSet {
let mut scope: Option<InvalidationScope> = None; let mut scope: Option<InvalidationScope> = None;
let mut scan = true; let mut scan = true;
let mut iter = selector.inner.complex.iter(); let mut iter = selector.iter();
loop { loop {
for component in &mut iter { for component in &mut iter {
@ -262,8 +262,8 @@ impl StylesheetInvalidationSet {
match *rule { match *rule {
Style(ref lock) => { Style(ref lock) => {
let style_rule = lock.read_with(guard); let style_rule = lock.read_with(guard);
for selector in &style_rule.selectors.0 { for selector_and_hashes in &style_rule.selectors.0 {
self.collect_scopes(selector); self.collect_scopes(&selector_and_hashes.selector);
if self.fully_invalid { if self.fully_invalid {
return; return;
} }

View file

@ -72,8 +72,8 @@ extern crate pdqsort;
#[cfg(feature = "gecko")] extern crate precomputed_hash; #[cfg(feature = "gecko")] extern crate precomputed_hash;
extern crate rayon; extern crate rayon;
extern crate selectors; extern crate selectors;
#[cfg(feature = "servo")] extern crate serde;
#[cfg(feature = "servo")] #[macro_use] extern crate serde_derive; #[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")] #[macro_use] extern crate servo_atoms;
#[cfg(feature = "servo")] extern crate servo_config; #[cfg(feature = "servo")] extern crate servo_config;
#[cfg(feature = "servo")] extern crate servo_url; #[cfg(feature = "servo")] extern crate servo_url;
@ -129,7 +129,6 @@ pub mod sequential;
pub mod sink; pub mod sink;
pub mod str; pub mod str;
pub mod style_adjuster; pub mod style_adjuster;
pub mod stylearc;
pub mod stylesheet_set; pub mod stylesheet_set;
pub mod stylesheets; pub mod stylesheets;
pub mod thread_state; pub mod thread_state;
@ -139,6 +138,10 @@ pub mod traversal;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub mod values; 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 std::fmt;
use style_traits::ToCss; use style_traits::ToCss;

View file

@ -22,7 +22,8 @@ use selectors::Element;
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint}; use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector}; 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 selectors::visitor::SelectorVisitor;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::cell::Cell; use std::cell::Cell;
@ -642,7 +643,7 @@ impl<'a, E> Element for ElementWrapper<'a, E>
use selectors::matching::matches_complex_selector; use selectors::matching::matches_complex_selector;
if let NonTSPseudoClass::MozAny(ref selectors) = *pseudo_class { if let NonTSPseudoClass::MozAny(ref selectors) = *pseudo_class {
return selectors.iter().any(|s| { 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)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Dependency { pub struct Dependency {
/// The dependency selector.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
selector: SelectorInner<SelectorImpl>, selector: Selector<SelectorImpl>,
/// 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. /// The hint associated with this dependency.
pub hint: RestyleHint, pub hint: RestyleHint,
/// The sensitivities associated with this dependency. /// The sensitivities associated with this dependency.
@ -875,8 +883,12 @@ pub struct Dependency {
} }
impl SelectorMapEntry for Dependency { impl SelectorMapEntry for Dependency {
fn selector(&self) -> &SelectorInner<SelectorImpl> { fn selector(&self) -> SelectorIter<SelectorImpl> {
&self.selector 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 { impl DependencySet {
/// Adds a selector to this `DependencySet`. /// Adds a selector to this `DependencySet`.
pub fn note_selector(&mut self, selector: &Selector<SelectorImpl>) { pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes<SelectorImpl>) {
let mut combinator = None; 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 index = 0;
let mut child_combinators_seen = 0; let mut child_combinators_seen = 0;
let mut saw_descendant_combinator = false; let mut saw_descendant_combinator = false;
@ -992,17 +1004,21 @@ impl DependencySet {
None => RestyleHint::for_self(), None => RestyleHint::for_self(),
}; };
let dep_selector = if sequence_start == 0 { // Reuse the bloom hashes if this is the base selector. Otherwise,
// Reuse the bloom hashes if this is the base selector. // rebuild them.
selector.inner.clone() let hashes = if sequence_start == 0 {
selector_and_hashes.hashes.clone()
} else { } 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 { self.dependencies.insert(Dependency {
sensitivities: visitor.sensitivities, sensitivities: visitor.sensitivities,
hint: hint, 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, MatchingContext::new_for_visited(MatchingMode::Normal, None,
VisitedHandlingMode::AllLinksUnvisited); VisitedHandlingMode::AllLinksUnvisited);
let matched_then = let matched_then =
matches_selector(&dep.selector, &snapshot_el, matches_selector(&dep.selector,
dep.selector_offset,
&dep.hashes,
&snapshot_el,
&mut then_context, &mut then_context,
&mut |_, _| {}); &mut |_, _| {});
let mut now_context = let mut now_context =
MatchingContext::new_for_visited(MatchingMode::Normal, bloom_filter, MatchingContext::new_for_visited(MatchingMode::Normal, bloom_filter,
VisitedHandlingMode::AllLinksUnvisited); VisitedHandlingMode::AllLinksUnvisited);
let matches_now = let matches_now =
matches_selector(&dep.selector, el, matches_selector(&dep.selector,
dep.selector_offset,
&dep.hashes,
el,
&mut now_context, &mut now_context,
&mut |_, _| {}); &mut |_, _| {});
@ -1162,12 +1184,18 @@ impl DependencySet {
dep.sensitivities.states.intersects(IN_VISITED_OR_UNVISITED_STATE) { dep.sensitivities.states.intersects(IN_VISITED_OR_UNVISITED_STATE) {
then_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited; then_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited;
let matched_then = let matched_then =
matches_selector(&dep.selector, &snapshot_el, matches_selector(&dep.selector,
dep.selector_offset,
&dep.hashes,
&snapshot_el,
&mut then_context, &mut then_context,
&mut |_, _| {}); &mut |_, _| {});
now_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited; now_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited;
let matches_now = let matches_now =
matches_selector(&dep.selector, el, matches_selector(&dep.selector,
dep.selector_offset,
&dep.hashes,
el,
&mut now_context, &mut now_context,
&mut |_, _| {}); &mut |_, _| {});
if matched_then != matches_now { if matched_then != matches_now {

View file

@ -12,7 +12,7 @@ use pdqsort::sort_by;
use rule_tree::CascadeLevel; use rule_tree::CascadeLevel;
use selector_parser::SelectorImpl; use selector_parser::SelectorImpl;
use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags}; 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 selectors::parser::LocalName as LocalNameSelector;
use smallvec::VecLike; use smallvec::VecLike;
use std::borrow::Borrow; use std::borrow::Borrow;
@ -22,13 +22,20 @@ use stylist::{ApplicableDeclarationBlock, Rule};
/// A trait to abstract over a given selector map entry. /// A trait to abstract over a given selector map entry.
pub trait SelectorMapEntry : Sized + Clone { pub trait SelectorMapEntry : Sized + Clone {
/// Get the selector we should use to index in the selector map. /// Gets the selector we should use to index in the selector map.
fn selector(&self) -> &SelectorInner<SelectorImpl>; fn selector(&self) -> SelectorIter<SelectorImpl>;
/// Gets the ancestor hashes associated with the selector.
fn hashes(&self) -> &AncestorHashes;
} }
impl SelectorMapEntry for SelectorInner<SelectorImpl> { impl SelectorMapEntry for SelectorAndHashes<SelectorImpl> {
fn selector(&self) -> &SelectorInner<SelectorImpl> { fn selector(&self) -> SelectorIter<SelectorImpl> {
self self.selector.iter()
}
fn hashes(&self) -> &AncestorHashes {
&self.hashes
} }
} }
@ -224,7 +231,9 @@ impl SelectorMap<Rule> {
F: FnMut(&E, ElementSelectorFlags), F: FnMut(&E, ElementSelectorFlags),
{ {
for rule in rules { for rule in rules {
if matches_selector(&rule.selector.inner, if matches_selector(&rule.selector,
0,
&rule.hashes,
element, element,
context, context,
flags_setter) { flags_setter) {
@ -390,12 +399,12 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
/// ///
/// Effectively, pseudo-elements are ignored, given only state pseudo-classes /// Effectively, pseudo-elements are ignored, given only state pseudo-classes
/// may appear before them. /// may appear before them.
fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>, #[inline(always)]
fn find_from_right<F, R>(mut iter: SelectorIter<SelectorImpl>,
mut f: F) mut f: F)
-> Option<R> -> Option<R>
where F: FnMut(&Component<SelectorImpl>) -> Option<R>, where F: FnMut(&Component<SelectorImpl>) -> Option<R>,
{ {
let mut iter = selector.complex.iter();
for ss in &mut iter { for ss in &mut iter {
if let Some(r) = f(ss) { if let Some(r) = f(ss) {
return Some(r) return Some(r)
@ -414,9 +423,10 @@ fn find_from_right<F, R>(selector: &SelectorInner<SelectorImpl>,
} }
/// Retrieve the first ID name in the selector, or None otherwise. /// Retrieve the first ID name in the selector, or None otherwise.
pub fn get_id_name(selector: &SelectorInner<SelectorImpl>) #[inline(always)]
pub fn get_id_name(iter: SelectorIter<SelectorImpl>)
-> Option<Atom> { -> Option<Atom> {
find_from_right(selector, |ss| { find_from_right(iter, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the // TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode. // document type and quirks mode.
if let Component::ID(ref id) = *ss { if let Component::ID(ref id) = *ss {
@ -427,9 +437,10 @@ pub fn get_id_name(selector: &SelectorInner<SelectorImpl>)
} }
/// Retrieve the FIRST class name in the selector, or None otherwise. /// Retrieve the FIRST class name in the selector, or None otherwise.
pub fn get_class_name(selector: &SelectorInner<SelectorImpl>) #[inline(always)]
pub fn get_class_name(iter: SelectorIter<SelectorImpl>)
-> Option<Atom> { -> Option<Atom> {
find_from_right(selector, |ss| { find_from_right(iter, |ss| {
// TODO(pradeep): Implement case-sensitivity based on the // TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode. // document type and quirks mode.
if let Component::Class(ref class) = *ss { if let Component::Class(ref class) = *ss {
@ -440,9 +451,10 @@ pub fn get_class_name(selector: &SelectorInner<SelectorImpl>)
} }
/// Retrieve the name if it is a type selector, or None otherwise. /// Retrieve the name if it is a type selector, or None otherwise.
pub fn get_local_name(selector: &SelectorInner<SelectorImpl>) #[inline(always)]
pub fn get_local_name(iter: SelectorIter<SelectorImpl>)
-> Option<LocalNameSelector<SelectorImpl>> { -> Option<LocalNameSelector<SelectorImpl>> {
find_from_right(selector, |ss| { find_from_right(iter, |ss| {
if let Component::LocalName(ref n) = *ss { if let Component::LocalName(ref n) = *ss {
return Some(LocalNameSelector { return Some(LocalNameSelector {
name: n.name.clone(), name: n.name.clone(),

View file

@ -28,7 +28,8 @@ use selectors::attr::NamespaceConstraint;
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode}; use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS; 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 selectors::visitor::SelectorVisitor;
use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
use sink::Push; use sink::Push;
@ -159,7 +160,7 @@ pub struct Stylist {
/// on state that is not otherwise visible to the cache, like attributes or /// on state that is not otherwise visible to the cache, like attributes or
/// tree-structural state like child index and pseudos). /// tree-structural state like child index and pseudos).
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
selectors_for_cache_revalidation: SelectorMap<SelectorInner<SelectorImpl>>, selectors_for_cache_revalidation: SelectorMap<SelectorAndHashes<SelectorImpl>>,
/// The total number of selectors. /// The total number of selectors.
num_selectors: usize, num_selectors: usize,
@ -454,10 +455,10 @@ impl Stylist {
CssRule::Style(ref locked) => { CssRule::Style(ref locked) => {
let style_rule = locked.read_with(&guard); let style_rule = locked.read_with(&guard);
self.num_declarations += style_rule.block.read_with(&guard).len(); 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; 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 self.pseudos_map
.entry(pseudo.canonical()) .entry(pseudo.canonical())
.or_insert_with(PerPseudoElementSelectorMap::new) .or_insert_with(PerPseudoElementSelectorMap::new)
@ -466,20 +467,21 @@ impl Stylist {
self.element_map.borrow_for_origin(&stylesheet.origin) 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(), locked.clone(),
self.rules_source_order)); self.rules_source_order));
self.dependencies.note_selector(selector); self.dependencies.note_selector(selector_and_hashes);
if needs_revalidation(selector) { if needs_revalidation(&selector_and_hashes.selector) {
self.selectors_for_cache_revalidation.insert(selector.inner.clone()); 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, attribute_dependencies: &mut self.attribute_dependencies,
style_attribute_dependency: &mut self.style_attribute_dependency, style_attribute_dependency: &mut self.style_attribute_dependency,
state_dependencies: &mut self.state_dependencies, state_dependencies: &mut self.state_dependencies,
}); });
selector.visit(&mut MappedIdVisitor { selector_and_hashes.selector.visit(&mut MappedIdVisitor {
mapped_ids: &mut self.mapped_ids, mapped_ids: &mut self.mapped_ids,
}); });
} }
@ -1130,8 +1132,10 @@ impl Stylist {
// the lookups, which means that the bitvecs are comparable. We verify // the lookups, which means that the bitvecs are comparable. We verify
// this in the caller by asserting that the bitvecs are same-length. // this in the caller by asserting that the bitvecs are same-length.
let mut results = BitVec::new(); let mut results = BitVec::new();
self.selectors_for_cache_revalidation.lookup(*element, &mut |selector| { self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| {
results.push(matches_selector(selector, results.push(matches_selector(&selector_and_hashes.selector,
0,
&selector_and_hashes.hashes,
element, element,
&mut matching_context, &mut matching_context,
flags_setter)); flags_setter));
@ -1410,6 +1414,9 @@ pub struct Rule {
/// can ruin performance when there are a lot of rules. /// can ruin performance when there are a lot of rules.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub selector: Selector<SelectorImpl>, pub selector: Selector<SelectorImpl>,
/// 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. /// The actual style rule.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub style_rule: Arc<Locked<StyleRule>>, pub style_rule: Arc<Locked<StyleRule>>,
@ -1418,8 +1425,12 @@ pub struct Rule {
} }
impl SelectorMapEntry for Rule { impl SelectorMapEntry for Rule {
fn selector(&self) -> &SelectorInner<SelectorImpl> { fn selector(&self) -> SelectorIter<SelectorImpl> {
&self.selector.inner self.selector.iter()
}
fn hashes(&self) -> &AncestorHashes {
&self.hashes
} }
} }
@ -1444,12 +1455,14 @@ impl Rule {
/// Creates a new Rule. /// Creates a new Rule.
pub fn new(selector: Selector<SelectorImpl>, pub fn new(selector: Selector<SelectorImpl>,
hashes: AncestorHashes,
style_rule: Arc<Locked<StyleRule>>, style_rule: Arc<Locked<StyleRule>>,
source_order: usize) source_order: usize)
-> Self -> Self
{ {
Rule { Rule {
selector: selector, selector: selector,
hashes: hashes,
style_rule: style_rule, style_rule: style_rule,
source_order: source_order, source_order: source_order,
} }

View file

@ -90,9 +90,8 @@ fn test_parse_stylesheet() {
}, },
}))), }))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList(vec![ selectors: SelectorList::from_vec(vec!(
Selector::new_for_unit_testing( Selector::from_vec(vec!(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: local_name!("input"), name: local_name!("input"),
@ -106,10 +105,8 @@ fn test_parse_stylesheet() {
case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive, case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive,
never_matches: false, never_matches: false,
} }
]), ), (0 << 20) + (1 << 10) + (1 << 0))
(0 << 20) + (1 << 10) + (1 << 0) )),
),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(PropertyDeclaration::Display(longhands::display::SpecifiedValue::none), (PropertyDeclaration::Display(longhands::display::SpecifiedValue::none),
Importance::Important), Importance::Important),
@ -123,28 +120,23 @@ fn test_parse_stylesheet() {
}, },
}))), }))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList(vec![ selectors: SelectorList::from_vec(vec!(
Selector::new_for_unit_testing( Selector::from_vec(vec!(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: local_name!("html"), name: local_name!("html"),
lower_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!(
),
Selector::new_for_unit_testing(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::LocalName(LocalName { Component::LocalName(LocalName {
name: local_name!("body"), name: local_name!("body"),
lower_name: local_name!("body"), lower_name: local_name!("body"),
}), })
]), ), (0 << 20) + (0 << 10) + (1 << 0)
(0 << 20) + (0 << 10) + (1 << 0)
), ),
]), )),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(PropertyDeclaration::Display(longhands::display::SpecifiedValue::block), (PropertyDeclaration::Display(longhands::display::SpecifiedValue::block),
Importance::Normal), Importance::Normal),
@ -155,18 +147,15 @@ fn test_parse_stylesheet() {
}, },
}))), }))),
CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule { CssRule::Style(Arc::new(stylesheet.shared_lock.wrap(StyleRule {
selectors: SelectorList(vec![ selectors: SelectorList::from_vec(vec!(
Selector::new_for_unit_testing( Selector::from_vec(vec!(
SelectorInner::from_vec(vec![
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::ID(Atom::from("d1")), Component::ID(Atom::from("d1")),
Component::Combinator(Combinator::Child), Component::Combinator(Combinator::Child),
Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")), Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
Component::Class(Atom::from("ok")), Component::Class(Atom::from("ok"))
]), ), (1 << 20) + (1 << 10) + (0 << 0))
(1 << 20) + (1 << 10) + (0 << 0) )),
),
]),
block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![ block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
(PropertyDeclaration::BackgroundColor( (PropertyDeclaration::BackgroundColor(
longhands::background_color::SpecifiedValue { longhands::background_color::SpecifiedValue {

View file

@ -45,7 +45,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec<Vec<Rule>>, SharedRwLock) {
let guard = shared_lock.read(); let guard = shared_lock.read();
let rule = locked.read_with(&guard); let rule = locked.read_with(&guard);
rule.selectors.0.iter().map(|s| { 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()
}).collect(), shared_lock) }).collect(), shared_lock)
} }
@ -68,7 +68,7 @@ fn parse_selectors(selectors: &[&str]) -> Vec<Selector<SelectorImpl>> {
.map(|x| SelectorParser::parse_author_origin_no_namespace(x).unwrap().0 .map(|x| SelectorParser::parse_author_origin_no_namespace(x).unwrap().0
.into_iter() .into_iter()
.nth(0) .nth(0)
.unwrap()) .unwrap().selector)
.collect() .collect()
} }
@ -126,7 +126,6 @@ fn test_revalidation_selectors() {
"p:first-child span", "p:first-child span",
]).into_iter() ]).into_iter()
.filter(|s| needs_revalidation(&s)) .filter(|s| needs_revalidation(&s))
.map(|s| s.inner.complex)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let reference = parse_selectors(&[ let reference = parse_selectors(&[
@ -167,7 +166,6 @@ fn test_revalidation_selectors() {
// Selectors in the ancestor chain (needed for cousin sharing). // Selectors in the ancestor chain (needed for cousin sharing).
"p:first-child span", "p:first-child span",
]).into_iter() ]).into_iter()
.map(|s| s.inner.complex)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(test.len(), reference.len()); assert_eq!(test.len(), reference.len());
@ -189,22 +187,22 @@ fn test_rule_ordering_same_specificity() {
#[test] #[test]
fn test_get_id_name() { fn test_get_id_name() {
let (rules_list, _) = get_mock_rules(&[".intro", "#top"]); 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[0][0].selector.iter()), 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[1][0].selector.iter()), Some(Atom::from("top")));
} }
#[test] #[test]
fn test_get_class_name() { fn test_get_class_name() {
let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); 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[0][0].selector.iter()), 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[1][0].selector.iter()), None);
} }
#[test] #[test]
fn test_get_local_name() { fn test_get_local_name() {
let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]); let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]);
let check = |i: usize, names: Option<(&str, &str)>| { 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 { == names.map(|(name, lower_name)| LocalNameSelector {
name: LocalName::from(name), name: LocalName::from(name),
lower_name: LocalName::from(lower_name) })) lower_name: LocalName::from(lower_name) }))

View file

@ -19,6 +19,11 @@ fn size_of_selectors_dummy_types() {
assert_eq!(align_of::<dummies::Atom>(), align_of::<style::Atom>()); assert_eq!(align_of::<dummies::Atom>(), align_of::<style::Atom>());
} }
// 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); 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, // This is huge, but we allocate it on the stack and then never move it,