Store selectors and combinators inline in a single sequence.

This improves cache locality and reduces allocations during parsing.

Note that this reverses the iteration order within a sequence of simple selectors,
but that shouldn't matter.
This commit is contained in:
Bobby Holley 2017-04-18 23:43:39 -07:00
parent 93fa0ae1e3
commit 6d66ec5e11
10 changed files with 703 additions and 330 deletions

View file

@ -0,0 +1,326 @@
/* 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,6 +9,7 @@ extern crate fnv;
extern crate precomputed_hash;
extern crate smallvec;
pub mod arcslice;
pub mod bloom;
pub mod matching;
pub mod parser;

View file

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use bloom::BloomFilter;
use parser::{CaseSensitivity, Combinator, ComplexSelector, LocalName};
use parser::{SimpleSelector, Selector, SelectorInner};
use parser::{SimpleSelector, Selector, SelectorInner, SelectorIter};
use std::borrow::Borrow;
use tree::Element;
@ -207,7 +207,7 @@ pub fn matches_complex_selector<E, F>(selector: &ComplexSelector<E::Impl>,
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
match matches_complex_selector_internal(selector,
match matches_complex_selector_internal(selector.iter(),
element,
relations,
flags_setter) {
@ -216,7 +216,7 @@ pub fn matches_complex_selector<E, F>(selector: &ComplexSelector<E::Impl>,
}
}
fn matches_complex_selector_internal<E, F>(selector: &ComplexSelector<E::Impl>,
fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Impl>,
element: &E,
relations: &mut StyleRelations,
flags_setter: &mut F)
@ -224,14 +224,12 @@ fn matches_complex_selector_internal<E, F>(selector: &ComplexSelector<E::Impl>,
where E: Element,
F: FnMut(&E, ElementSelectorFlags),
{
let matches_all_simple_selectors = selector.compound_selector.iter().all(|simple| {
let matches_all_simple_selectors = selector_iter.all(|simple| {
matches_simple_selector(simple, element, relations, flags_setter)
});
let siblings = selector.next.as_ref().map_or(false, |&(_, combinator)| {
matches!(combinator, Combinator::NextSibling | Combinator::LaterSibling)
});
let combinator = selector_iter.next_sequence();
let siblings = combinator.map_or(false, |c| c.is_sibling());
if siblings {
flags_setter(element, HAS_SLOW_SELECTOR_LATER_SIBLINGS);
}
@ -240,9 +238,9 @@ fn matches_complex_selector_internal<E, F>(selector: &ComplexSelector<E::Impl>,
return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
}
match selector.next {
match combinator {
None => SelectorMatchingResult::Matched,
Some((ref next_selector, combinator)) => {
Some(c) => {
let (mut next_element, candidate_not_found) = if siblings {
(element.prev_sibling_element(),
SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant)
@ -256,11 +254,11 @@ fn matches_complex_selector_internal<E, F>(selector: &ComplexSelector<E::Impl>,
None => return candidate_not_found,
Some(next_element) => next_element,
};
let result = matches_complex_selector_internal(&**next_selector,
let result = matches_complex_selector_internal(selector_iter.clone(),
&element,
relations,
flags_setter);
match (result, combinator) {
match (result, c) {
// Return the status immediately.
(SelectorMatchingResult::Matched, _) => return result,
(SelectorMatchingResult::NotMatchedGlobally, _) => return result,
@ -319,6 +317,7 @@ fn matches_simple_selector<E, F>(
}
match *selector {
SimpleSelector::Combinator(_) => unreachable!(),
SimpleSelector::LocalName(LocalName { ref name, ref lower_name }) => {
let name = if element.is_html_element_in_html_document() { lower_name } else { name };
element.get_local_name() == name.borrow()
@ -421,7 +420,7 @@ fn matches_simple_selector<E, F>(
}
SimpleSelector::Negation(ref negated) => {
!negated.iter().all(|s| {
match matches_complex_selector_internal(s,
match matches_complex_selector_internal(s.iter(),
element,
relations,
flags_setter) {

View file

@ -2,6 +2,7 @@
* 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/. */
use arcslice::ArcSlice;
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
use precomputed_hash::PrecomputedHash;
use std::ascii::AsciiExt;
@ -9,8 +10,9 @@ use std::borrow::{Borrow, Cow};
use std::cmp;
use std::fmt::{self, Display, Debug, Write};
use std::hash::Hash;
use std::iter::Rev;
use std::ops::Add;
use std::sync::Arc;
use std::slice;
use tree::SELECTOR_WHITESPACE;
use visitor::SelectorVisitor;
@ -132,7 +134,7 @@ const NUM_ANCESTOR_HASHES: usize = 4;
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct SelectorInner<Impl: SelectorImpl> {
/// The selector data.
pub complex: Arc<ComplexSelector<Impl>>,
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.
@ -140,15 +142,14 @@ pub struct SelectorInner<Impl: SelectorImpl> {
}
impl<Impl: SelectorImpl> SelectorInner<Impl> {
pub fn new(c: Arc<ComplexSelector<Impl>>) -> Self {
pub fn new(c: ComplexSelector<Impl>) -> Self {
let mut hashes = [0; NUM_ANCESTOR_HASHES];
{
// Compute ancestor hashes for the bloom filter.
let mut hash_iter =
iter_ancestors(&c).flat_map(|x| x.compound_selector.iter())
.map(|x| x.ancestor_hash())
.filter(|x| x.is_some())
.map(|x| x.unwrap());
let mut hash_iter = c.iter_ancestors()
.map(|x| x.ancestor_hash())
.filter(|x| x.is_some())
.map(|x| x.unwrap());
for i in 0..NUM_ANCESTOR_HASHES {
hashes[i] = match hash_iter.next() {
Some(x) => x,
@ -162,6 +163,12 @@ impl<Impl: SelectorImpl> SelectorInner<Impl> {
ancestor_hashes: hashes,
}
}
/// Creates a SelectorInner from a Vec of SimpleSelectors. Used in tests.
pub fn from_vec(vec: Vec<SimpleSelector<Impl>>) -> Self {
let complex = ComplexSelector::from_vec(vec);
Self::new(complex)
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
@ -194,25 +201,22 @@ impl<Impl: SelectorImpl> SelectorMethods for ComplexSelector<Impl> {
fn visit<V>(&self, visitor: &mut V) -> bool
where V: SelectorVisitor<Impl = Impl>,
{
let mut current = self;
let mut current = self.iter();
let mut combinator = None;
loop {
if !visitor.visit_complex_selector(current, combinator) {
if !visitor.visit_complex_selector(current.clone(), combinator) {
return false;
}
for selector in &current.compound_selector {
for selector in &mut current {
if !selector.visit(visitor) {
return false;
}
}
match current.next {
Some((ref next, next_combinator)) => {
current = next;
combinator = Some(next_combinator);
}
None => break,
combinator = current.next_sequence();
if combinator.is_none() {
break;
}
}
@ -262,38 +266,139 @@ impl<Impl: SelectorImpl> SelectorMethods for SimpleSelector<Impl> {
}
}
/// A ComplexSelectors stores a sequence of simple selectors and combinators. The
/// iterator classes allow callers to iterate at either the raw sequence level or
/// at the level of sequences of simple selectors separated by combinators. Most
/// callers want the higher-level iterator.
///
/// We store selectors internally left-to-right (in parsing order), but the
/// canonical iteration order is right-to-left (selector matching order). The
/// iterators abstract over these details.
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct ComplexSelector<Impl: SelectorImpl> {
pub compound_selector: Vec<SimpleSelector<Impl>>,
pub next: Option<(Arc<ComplexSelector<Impl>>, Combinator)>, // c.next is left of c
}
pub struct ComplexSelector<Impl: SelectorImpl>(ArcSlice<SimpleSelector<Impl>>);
struct AncestorIterator<'a, Impl: 'a + SelectorImpl> {
curr: Option<&'a Arc<ComplexSelector<Impl>>>,
}
impl<'a, Impl: SelectorImpl> Iterator for AncestorIterator<'a, Impl> {
type Item = &'a Arc<ComplexSelector<Impl>>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(sel) = self.curr.take() {
let (next_sel, is_ancestor) = match sel.next {
None => (None, true),
Some((ref sel, comb)) =>
(Some(sel), matches!(comb, Combinator::Child | Combinator::Descendant)),
};
self.curr = next_sel;
if is_ancestor {
break;
}
impl<Impl: SelectorImpl> ComplexSelector<Impl> {
/// Returns an iterator over the next sequence of simple selectors. When
/// a combinator is reached, the iterator will return None, and
/// next_sequence() may be called to continue to the next sequence.
pub fn iter(&self) -> SelectorIter<Impl> {
SelectorIter {
iter: self.iter_raw(),
next_combinator: None,
}
}
self.curr
/// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from right to left.
pub fn iter_raw(&self) -> Rev<slice::Iter<SimpleSelector<Impl>>> {
self.iter_raw_rev().rev()
}
/// Returns an iterator over the entire sequence of simple selectors and combinators,
/// from left to right.
pub fn iter_raw_rev(&self) -> slice::Iter<SimpleSelector<Impl>> {
self.0.iter()
}
/// Returns an iterator over ancestor simple selectors. All combinators and
/// non-ancestor simple selectors will be skipped.
pub fn iter_ancestors(&self) -> AncestorIter<Impl> {
AncestorIter::new(self.iter())
}
/// Returns a ComplexSelector identical to |self| but with the rightmost |index|
/// entries removed.
pub fn slice_from(&self, index: usize) -> Self {
// Note that we convert the slice_from to slice_to because selectors are
// stored left-to-right but logical order is right-to-left.
ComplexSelector(self.0.clone().slice_to(self.0.len() - index))
}
/// Creates a ComplexSelector from a vec of SimpleSelectors. Used in tests.
pub fn from_vec(vec: Vec<SimpleSelector<Impl>>) -> Self {
ComplexSelector(ArcSlice::new(vec.into_boxed_slice()))
}
}
fn iter_ancestors<Impl: SelectorImpl>(sel: &Arc<ComplexSelector<Impl>>) -> AncestorIterator<Impl> {
AncestorIterator {
curr: Some(sel)
pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> {
iter: Rev<slice::Iter<'a, SimpleSelector<Impl>>>,
next_combinator: Option<Combinator>,
}
// NB: Deriving this doesn't work for some reason, because it expects Impl to
// implement Clone.
impl<'a, Impl: 'a + SelectorImpl> Clone for SelectorIter<'a, Impl> {
fn clone(&self) -> Self {
SelectorIter {
iter: self.iter.clone(),
next_combinator: self.next_combinator.clone(),
}
}
}
impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
/// Prepares this iterator to point to the next sequence to the left,
/// returning the combinator if the sequence was found.
pub fn next_sequence(&mut self) -> Option<Combinator> {
self.next_combinator.take()
}
}
impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
type Item = &'a SimpleSelector<Impl>;
fn next(&mut self) -> Option<Self::Item> {
debug_assert!(self.next_combinator.is_none(), "Should call take_combinator!");
match self.iter.next() {
None => None,
Some(&SimpleSelector::Combinator(c)) => {
self.next_combinator = Some(c);
None
},
Some(x) => Some(x),
}
}
}
/// An iterator over all simple selectors belonging to ancestors.
pub struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>);
impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> {
/// Creates an AncestorIter. The passed-in iterator is assumed to point to
/// the beginning of the child sequence, which will be skipped.
fn new(inner: SelectorIter<'a, Impl>) -> Self {
let mut result = AncestorIter(inner);
result.skip_until_ancestor();
result
}
/// Skips a sequence of simple selectors and all subsequent sequences until an
/// ancestor combinator is reached.
fn skip_until_ancestor(&mut self) {
loop {
while let Some(_) = self.0.next() {}
if self.0.next_sequence().map_or(true, |x| x.is_ancestor()) {
break;
}
}
}
}
impl<'a, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> {
type Item = &'a SimpleSelector<Impl>;
fn next(&mut self) -> Option<Self::Item> {
// Grab the next simple selector in the sequence if available.
let next = self.0.next();
if next.is_some() {
return next;
}
// See if there are more sequences. If so, skip any non-ancestor sequences.
if let Some(combinator) = self.0.next_sequence() {
if !combinator.is_ancestor() {
self.skip_until_ancestor();
}
}
self.0.next()
}
}
@ -305,8 +410,29 @@ pub enum Combinator {
LaterSibling, // ~
}
impl Combinator {
/// Returns true if this combinator is a child or descendant combinator.
pub fn is_ancestor(&self) -> bool {
matches!(*self, Combinator::Child | Combinator::Descendant)
}
/// Returns true if this combinator is a next- or later-sibling combinator.
pub fn is_sibling(&self) -> bool {
matches!(*self, Combinator::NextSibling | Combinator::LaterSibling)
}
}
/// A CSS simple selector or combinator. We store both in the same enum for
/// optimal packing and cache performance, see [1].
///
/// We could rename this SimpleSelectorOrCombinator, but that's annoying to
/// type everywhere, and the combinators are generally filtered out and
/// handled separately by the iterator classes anyway.
///
/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973
#[derive(Eq, PartialEq, Clone, Hash)]
pub enum SimpleSelector<Impl: SelectorImpl> {
Combinator(Combinator),
ID(Impl::Identifier),
Class(Impl::ClassName),
LocalName(LocalName<Impl>),
@ -327,7 +453,7 @@ pub enum SimpleSelector<Impl: SelectorImpl> {
AttrSuffixNeverMatch(AttrSelector<Impl>, Impl::AttrValue), // empty value
// Pseudo-classes
Negation(Vec<Arc<ComplexSelector<Impl>>>),
Negation(Vec<ComplexSelector<Impl>>),
FirstChild, LastChild, OnlyChild,
Root,
Empty,
@ -368,6 +494,11 @@ impl<Impl: SelectorImpl> SimpleSelector<Impl> {
_ => None,
}
}
/// Returns true if this is a combinator.
pub fn is_combinator(&self) -> bool {
matches!(*self, SimpleSelector::Combinator(_))
}
}
#[derive(Eq, PartialEq, Clone, Hash, Copy, Debug)]
@ -466,31 +597,11 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
impl<Impl: SelectorImpl> ToCss for ComplexSelector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use smallvec::SmallVec;
let mut current = self;
let mut nodes = SmallVec::<[&Self;8]>::new();
nodes.push(current);
loop {
match current.next {
None => break,
Some((ref next, _)) => {
current = &**next;
nodes.push(next);
}
}
for item in self.iter_raw_rev() {
item.to_css(dest)?;
}
for selector in nodes.iter().rev() {
if let Some((_, ref combinator)) = selector.next {
combinator.to_css(dest)?;
}
for simple in &selector.compound_selector {
simple.to_css(dest)?;
}
}
Ok(())
Ok(())
}
}
@ -509,6 +620,9 @@ impl<Impl: SelectorImpl> ToCss for SimpleSelector<Impl> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use self::SimpleSelector::*;
match *self {
Combinator(ref c) => {
c.to_css(dest)
}
ID(ref s) => {
dest.write_char('#')?;
display_to_css_identifier(s, dest)
@ -699,14 +813,15 @@ fn specificity<Impl>(complex_selector: &ComplexSelector<Impl>,
specificity.into()
}
fn complex_selector_specificity<Impl>(mut selector: &ComplexSelector<Impl>)
fn complex_selector_specificity<Impl>(selector: &ComplexSelector<Impl>)
-> Specificity
where Impl: SelectorImpl {
fn compound_selector_specificity<Impl>(compound_selector: &[SimpleSelector<Impl>],
fn compound_selector_specificity<Impl>(selector_iter: &mut SelectorIter<Impl>,
specificity: &mut Specificity)
where Impl: SelectorImpl {
for simple_selector in compound_selector.iter() {
for simple_selector in selector_iter {
match *simple_selector {
SimpleSelector::Combinator(..) => unreachable!(),
SimpleSelector::LocalName(..) =>
specificity.element_selectors += 1,
SimpleSelector::ID(..) =>
@ -749,16 +864,11 @@ fn complex_selector_specificity<Impl>(mut selector: &ComplexSelector<Impl>)
}
let mut specificity = Default::default();
compound_selector_specificity(&selector.compound_selector,
&mut specificity);
let mut iter = selector.iter();
loop {
match selector.next {
None => break,
Some((ref next_selector, _)) => {
selector = &**next_selector;
compound_selector_specificity(&selector.compound_selector,
&mut specificity)
}
compound_selector_specificity(&mut iter, &mut specificity);
if iter.next_sequence().is_none() {
break;
}
}
specificity
@ -775,7 +885,7 @@ fn parse_selector<P, Impl>(parser: &P, input: &mut CssParser) -> Result<Selector
parse_complex_selector_and_pseudo_element(parser, input)?;
Ok(Selector {
specificity: specificity(&complex, pseudo_element.as_ref()),
inner: SelectorInner::new(Arc::new(complex)),
inner: SelectorInner::new(complex),
pseudo_element: pseudo_element,
})
}
@ -786,10 +896,16 @@ fn parse_complex_selector_and_pseudo_element<P, Impl>(
-> Result<(ComplexSelector<Impl>, Option<Impl::PseudoElement>), ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
let (first, mut pseudo_element) = parse_compound_selector(parser, input)?;
let mut complex = ComplexSelector { compound_selector: first, next: None };
let mut sequence = Vec::new();
let mut pseudo_element;
'outer_loop: loop {
// Parse a sequence of simple selectors.
pseudo_element = parse_compound_selector(parser, input, &mut sequence)?;
if pseudo_element.is_some() {
break;
}
'outer_loop: while pseudo_element.is_none() {
// Parse a combinator.
let combinator;
let mut any_whitespace = false;
loop {
@ -820,14 +936,10 @@ fn parse_complex_selector_and_pseudo_element<P, Impl>(
}
}
}
let (compound_selector, pseudo) = parse_compound_selector(parser, input)?;
complex = ComplexSelector {
compound_selector: compound_selector,
next: Some((Arc::new(complex), combinator))
};
pseudo_element = pseudo;
sequence.push(SimpleSelector::Combinator(combinator));
}
let complex = ComplexSelector(ArcSlice::new(sequence.into_boxed_slice()));
Ok((complex, pseudo_element))
}
@ -848,30 +960,29 @@ impl<Impl: SelectorImpl> ComplexSelector<Impl> {
/// * `Err(())`: Invalid selector, abort
/// * `Ok(None)`: Not a type selector, could be something else. `input` was not consumed.
/// * `Ok(Some(vec))`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser)
-> Result<Option<Vec<SimpleSelector<Impl>>>, ()>
fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser, sequence: &mut Vec<SimpleSelector<Impl>>)
-> Result<bool, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
match parse_qualified_name(parser, input, /* in_attr_selector = */ false)? {
None => Ok(None),
None => Ok(false),
Some((namespace, local_name)) => {
let mut compound_selector = vec!();
match namespace {
NamespaceConstraint::Specific(ns) => {
compound_selector.push(SimpleSelector::Namespace(ns))
sequence.push(SimpleSelector::Namespace(ns))
},
NamespaceConstraint::Any => (),
}
match local_name {
Some(name) => {
compound_selector.push(SimpleSelector::LocalName(LocalName {
sequence.push(SimpleSelector::LocalName(LocalName {
lower_name: from_ascii_lowercase(&name),
name: from_cow_str(name),
}))
}
None => (),
}
Ok(Some(compound_selector))
Ok(true)
}
}
}
@ -1051,7 +1162,7 @@ fn parse_negation<P, Impl>(parser: &P,
-> Result<SimpleSelector<Impl>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
input.parse_comma_separated(|input| ComplexSelector::parse(parser, input).map(Arc::new))
input.parse_comma_separated(|input| ComplexSelector::parse(parser, input))
.map(SimpleSelector::Negation)
}
@ -1062,8 +1173,9 @@ fn parse_negation<P, Impl>(parser: &P,
/// `Err(())` means invalid selector
fn parse_compound_selector<P, Impl>(
parser: &P,
input: &mut CssParser)
-> Result<(Vec<SimpleSelector<Impl>>, Option<Impl::PseudoElement>), ()>
input: &mut CssParser,
mut sequence: &mut Vec<SimpleSelector<Impl>>)
-> Result<Option<Impl::PseudoElement>, ()>
where P: Parser<Impl=Impl>, Impl: SelectorImpl
{
// Consume any leading whitespace.
@ -1075,28 +1187,26 @@ fn parse_compound_selector<P, Impl>(
}
}
let mut empty = true;
let mut compound_selector = match parse_type_selector(parser, input)? {
None => {
match parser.default_namespace() {
// If there was no explicit type selector, but there is a
// default namespace, there is an implicit "<defaultns>|*" type
// selector.
Some(url) => vec![SimpleSelector::Namespace(Namespace {
prefix: None,
url: url
})],
None => vec![],
}
if !parse_type_selector(parser, input, &mut sequence)? {
if let Some(url) = parser.default_namespace() {
// If there was no explicit type selector, but there is a
// default namespace, there is an implicit "<defaultns>|*" type
// selector.
sequence.push(SimpleSelector::Namespace(Namespace {
prefix: None,
url: url
}));
}
Some(s) => { empty = false; s }
};
} else {
empty = false;
}
let mut pseudo_element = None;
loop {
match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
None => break,
Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
compound_selector.push(s);
sequence.push(s);
empty = false
}
Some(SimpleSelectorParseResult::PseudoElement(p)) => {
@ -1110,7 +1220,7 @@ fn parse_compound_selector<P, Impl>(
// An empty selector is invalid.
Err(())
} else {
Ok((compound_selector, pseudo_element))
Ok(pseudo_element)
}
}
@ -1244,7 +1354,6 @@ pub mod tests {
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use super::*;
#[derive(PartialEq, Clone, Debug, Hash, Eq)]
@ -1408,57 +1517,45 @@ pub mod tests {
assert_eq!(parse(":lang(4)"), Err(())) ;
assert_eq!(parse(":lang(en US)"), Err(())) ;
assert_eq!(parse("EeÉ"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
inner: SelectorInner::from_vec(vec!(SimpleSelector::LocalName(LocalName {
name: DummyAtom::from("EeÉ"),
lower_name: DummyAtom::from("eeÉ") })),
next: None,
})),
),
pseudo_element: None,
specificity: specificity(0, 0, 1),
}))));
assert_eq!(parse(".foo:lang(en-US)"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec![
inner: SelectorInner::from_vec(vec![
SimpleSelector::Class(DummyAtom::from("foo")),
SimpleSelector::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned()))
],
next: None,
})),
]),
pseudo_element: None,
specificity: specificity(0, 2, 0),
}))));
assert_eq!(parse("#bar"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::ID(DummyAtom::from("bar"))),
next: None,
})),
inner: SelectorInner::from_vec(vec!(SimpleSelector::ID(DummyAtom::from("bar")))),
pseudo_element: None,
specificity: specificity(1, 0, 0),
}))));
assert_eq!(parse("e.foo#bar"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
inner: SelectorInner::from_vec(vec!(SimpleSelector::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") }),
SimpleSelector::Class(DummyAtom::from("foo")),
SimpleSelector::ID(DummyAtom::from("bar"))),
next: None,
})),
SimpleSelector::ID(DummyAtom::from("bar")))),
pseudo_element: None,
specificity: specificity(1, 1, 1),
}))));
assert_eq!(parse("e.foo #bar"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::ID(DummyAtom::from("bar"))),
next: Some((Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") }),
SimpleSelector::Class(DummyAtom::from("foo"))),
next: None,
}), Combinator::Descendant)),
})),
inner: SelectorInner::from_vec(vec!(
SimpleSelector::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e")
}),
SimpleSelector::Class(DummyAtom::from("foo")),
SimpleSelector::Combinator(Combinator::Descendant),
SimpleSelector::ID(DummyAtom::from("bar")),
)),
pseudo_element: None,
specificity: specificity(1, 1, 1),
}))));
@ -1466,25 +1563,22 @@ pub mod tests {
// https://github.com/mozilla/servo/pull/1652
let mut parser = DummyParser::default();
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::AttrExists(AttrSelector {
inner: SelectorInner::from_vec(vec!(
SimpleSelector::AttrExists(AttrSelector {
name: DummyAtom::from("Foo"),
lower_name: DummyAtom::from("foo"),
namespace: NamespaceConstraint::Specific(Namespace {
prefix: None,
url: "".into(),
}),
})),
next: None,
})),
}) }))),
pseudo_element: None,
specificity: specificity(0, 1, 0),
}))));
assert_eq!(parse_ns("svg|circle", &parser), Err(()));
parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into()));
assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList(vec![Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec![
inner: SelectorInner::from_vec(
vec![
SimpleSelector::Namespace(Namespace {
prefix: Some(DummyAtom("svg".into())),
url: SVG.into(),
@ -1493,9 +1587,7 @@ pub mod tests {
name: DummyAtom::from("circle"),
lower_name: DummyAtom::from("circle"),
})
],
next: None,
})),
]),
pseudo_element: None,
specificity: specificity(0, 0, 1),
}])));
@ -1505,8 +1597,8 @@ pub mod tests {
// https://github.com/servo/rust-selectors/pull/82
parser.default_ns = Some(MATHML.into());
assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec![
inner: SelectorInner::from_vec(
vec![
SimpleSelector::Namespace(Namespace {
prefix: None,
url: MATHML.into(),
@ -1519,16 +1611,14 @@ pub mod tests {
url: "".into(),
}),
}),
],
next: None,
})),
]),
pseudo_element: None,
specificity: specificity(0, 1, 0),
}))));
// Default namespace does apply to type selectors
assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(
inner: SelectorInner::from_vec(
vec!(
SimpleSelector::Namespace(Namespace {
prefix: None,
url: MATHML.into(),
@ -1536,15 +1626,13 @@ pub mod tests {
SimpleSelector::LocalName(LocalName {
name: DummyAtom::from("e"),
lower_name: DummyAtom::from("e") }),
),
next: None,
})),
)),
pseudo_element: None,
specificity: specificity(0, 0, 1),
}))));
assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec![
inner: SelectorInner::from_vec(
vec![
SimpleSelector::AttrDashMatch(AttrSelector {
name: DummyAtom::from("attr"),
lower_name: DummyAtom::from("attr"),
@ -1553,70 +1641,48 @@ pub mod tests {
url: "".into(),
}),
}, DummyAtom::from("foo"))
],
next: None,
})),
]),
pseudo_element: None,
specificity: specificity(0, 1, 0),
}])));
// https://github.com/mozilla/servo/issues/1723
assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(),
next: None,
})),
inner: SelectorInner::from_vec(vec![]),
pseudo_element: Some(PseudoElement::Before),
specificity: specificity(0, 0, 1),
}))));
// https://github.com/servo/servo/issues/15335
assert_eq!(parse(":: before"), Err(()));
assert_eq!(parse("div ::after"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(),
next: Some((Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::LocalName(LocalName {
inner: SelectorInner::from_vec(
vec![
SimpleSelector::LocalName(LocalName {
name: DummyAtom::from("div"),
lower_name: DummyAtom::from("div") })),
next: None,
}), Combinator::Descendant)),
})),
lower_name: DummyAtom::from("div") }),
SimpleSelector::Combinator(Combinator::Descendant),
]),
pseudo_element: Some(PseudoElement::After),
specificity: specificity(0, 0, 2),
}))));
assert_eq!(parse("#d1 > .ok"), Ok(SelectorList(vec![Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec![
inner: SelectorInner::from_vec(
vec![
SimpleSelector::ID(DummyAtom::from("d1")),
SimpleSelector::Combinator(Combinator::Child),
SimpleSelector::Class(DummyAtom::from("ok")),
],
next: Some((Arc::new(ComplexSelector {
compound_selector: vec![
SimpleSelector::ID(DummyAtom::from("d1")),
],
next: None,
}), Combinator::Child)),
})),
]),
pseudo_element: None,
specificity: (1 << 20) + (1 << 10) + (0 << 0),
}])));
assert_eq!(parse(":not(.babybel, #provel.old)"), Ok(SelectorList(vec!(Selector {
inner: SelectorInner::new(Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::Negation(
inner: SelectorInner::from_vec(vec!(SimpleSelector::Negation(
vec!(
Arc::new(ComplexSelector {
compound_selector: vec!(SimpleSelector::Class(DummyAtom::from("babybel"))),
next: None
}),
Arc::new(ComplexSelector {
compound_selector: vec!(
ComplexSelector::from_vec(vec!(SimpleSelector::Class(DummyAtom::from("babybel")))),
ComplexSelector::from_vec(vec!(
SimpleSelector::ID(DummyAtom::from("provel")),
SimpleSelector::Class(DummyAtom::from("old")),
),
next: None
}),
)
)),
next: None,
})),
)))
))),
pseudo_element: None,
specificity: specificity(1, 1, 0),
}))));

View file

@ -6,7 +6,8 @@
#![deny(missing_docs)]
use parser::{AttrSelector, Combinator, ComplexSelector, SelectorImpl, SimpleSelector};
use parser::{AttrSelector, Combinator, SelectorImpl};
use parser::{SelectorIter, SimpleSelector};
/// A trait to visit selector properties.
///
@ -33,7 +34,7 @@ pub trait SelectorVisitor {
/// Gets the combinator to the right of the selector, or `None` if the
/// selector is the leftmost one.
fn visit_complex_selector(&mut self,
_: &ComplexSelector<Self::Impl>,
_: SelectorIter<Self::Impl>,
_combinator_to_right: Option<Combinator>)
-> bool {
true

View file

@ -14,7 +14,6 @@ use selectors::visitor::SelectorVisitor;
use std::borrow::Cow;
use std::fmt;
use std::ptr;
use std::sync::Arc;
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
/// A representation of a CSS pseudo-element.
@ -218,7 +217,7 @@ macro_rules! pseudo_class_name {
///
/// TODO(emilio): We disallow combinators and pseudos here, so we
/// should use SimpleSelector instead
MozAny(Vec<Arc<ComplexSelector<SelectorImpl>>>),
MozAny(Vec<ComplexSelector<SelectorImpl>>),
}
}
}
@ -396,10 +395,10 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
}, )*
"-moz-any" => {
let selectors = parser.parse_comma_separated(|input| {
ComplexSelector::parse(self, input).map(Arc::new)
ComplexSelector::parse(self, input)
})?;
// Selectors inside `:-moz-any` may not include combinators.
if selectors.iter().any(|s| s.next.is_some()) {
if selectors.iter().flat_map(|x| x.iter_raw()).any(|s| s.is_combinator()) {
return Err(())
}
NonTSPseudoClass::MozAny(selectors)

View file

@ -17,10 +17,10 @@ use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
use selectors::{Element, MatchAttr};
use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::matching::matches_selector;
use selectors::parser::{AttrSelector, Combinator, ComplexSelector, SelectorInner, SelectorMethods, SimpleSelector};
use selectors::parser::{AttrSelector, Combinator, ComplexSelector, SelectorInner, SelectorIter};
use selectors::parser::{SelectorMethods, SimpleSelector};
use selectors::visitor::SelectorVisitor;
use std::clone::Clone;
use std::sync::Arc;
bitflags! {
/// When the ElementState of an element (like IN_HOVER_STATE) changes,
@ -509,7 +509,7 @@ impl SelectorVisitor for SensitivitiesVisitor {
type Impl = SelectorImpl;
fn visit_complex_selector(&mut self,
_: &ComplexSelector<SelectorImpl>,
_: SelectorIter<SelectorImpl>,
combinator: Option<Combinator>) -> bool {
self.hint |= combinator_to_restyle_hint(combinator);
self.needs_revalidation |= self.hint.contains(RESTYLE_LATER_SIBLINGS);
@ -567,50 +567,47 @@ impl DependencySet {
/// cache revalidation, that is, whether two siblings of the same "shape"
/// may have different style due to this selector.
pub fn note_selector(&mut self,
selector: &Arc<ComplexSelector<SelectorImpl>>)
base: &ComplexSelector<SelectorImpl>)
-> bool
{
let mut next = Some(base.clone());
let mut combinator = None;
let mut current = selector;
let mut needs_revalidation = false;
loop {
let mut sensitivities_visitor = SensitivitiesVisitor {
while let Some(current) = next.take() {
// Set up our visitor.
let mut visitor = SensitivitiesVisitor {
sensitivities: Sensitivities::new(),
hint: RestyleHint::empty(),
hint: combinator_to_restyle_hint(combinator),
needs_revalidation: false,
};
for ss in &current.compound_selector {
ss.visit(&mut sensitivities_visitor);
{
// Visit all the simple selectors.
let mut iter = current.iter();
let mut index = 0usize;
for ss in &mut iter {
ss.visit(&mut visitor);
index += 1;
}
// Prepare the next sequence of simple selectors.
if let Some(next_combinator) = iter.next_sequence() {
next = Some(current.slice_from(index + 1));
combinator = Some(next_combinator);
}
}
needs_revalidation |= sensitivities_visitor.needs_revalidation;
let SensitivitiesVisitor {
sensitivities,
mut hint,
..
} = sensitivities_visitor;
hint |= combinator_to_restyle_hint(combinator);
if !sensitivities.is_empty() {
// Note what we found.
needs_revalidation |= visitor.needs_revalidation;
if !visitor.sensitivities.is_empty() {
self.add_dependency(Dependency {
sensitivities: sensitivities,
hint: hint,
selector: SelectorInner::new(current.clone()),
sensitivities: visitor.sensitivities,
hint: visitor.hint,
selector: SelectorInner::new(current),
})
}
match current.next {
Some((ref next, next_combinator)) => {
current = next;
combinator = Some(next_combinator);
}
None => break,
}
}
needs_revalidation
@ -729,7 +726,7 @@ fn smoke_restyle_hints() {
let mut dependencies = DependencySet::new();
let mut p = Parser::new(":not(:active) ~ label");
let selector = Arc::new(ComplexSelector::parse(&parser, &mut p).unwrap());
let selector = ComplexSelector::parse(&parser, &mut p).unwrap();
dependencies.note_selector(&selector);
assert_eq!(dependencies.len(), 1);
assert_eq!(dependencies.state_deps.len(), 1);

View file

@ -1070,8 +1070,7 @@ impl SelectorMap {
// correct, and also to not trigger rule tree assertions.
let mut important = vec![];
for rule in self.other_rules.iter() {
if rule.selector.complex.compound_selector.is_empty() &&
rule.selector.complex.next.is_none() {
if rule.selector.complex.iter_raw().next().is_none() {
let style_rule = rule.style_rule.read_with(guard);
let block = style_rule.block.read_with(guard);
if block.any_normal() {
@ -1180,7 +1179,7 @@ impl SelectorMap {
/// Retrieve the first ID name in Rule, or None otherwise.
pub fn get_id_name(rule: &Rule) -> Option<Atom> {
for ss in &rule.selector.complex.compound_selector {
for ss in rule.selector.complex.iter() {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let SimpleSelector::ID(ref id) = *ss {
@ -1193,7 +1192,7 @@ impl SelectorMap {
/// Retrieve the FIRST class name in Rule, or None otherwise.
pub fn get_class_name(rule: &Rule) -> Option<Atom> {
for ss in &rule.selector.complex.compound_selector {
for ss in rule.selector.complex.iter() {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let SimpleSelector::Class(ref class) = *ss {
@ -1206,7 +1205,7 @@ impl SelectorMap {
/// Retrieve the name if it is a type selector, or None otherwise.
pub fn get_local_name(rule: &Rule) -> Option<LocalNameSelector<SelectorImpl>> {
for ss in &rule.selector.complex.compound_selector {
for ss in rule.selector.complex.iter() {
if let SimpleSelector::LocalName(ref n) = *ss {
return Some(LocalNameSelector {
name: n.name.clone(),