Move rust-selectors in-tree.

This commit is contained in:
Bobby Holley 2017-02-07 22:46:35 -08:00
parent f7e75fd001
commit 8915e53cee
16 changed files with 2616 additions and 18 deletions

18
Cargo.lock generated
View file

@ -934,7 +934,7 @@ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"servo_url 0.0.1",
"style 0.0.1",
"style_traits 0.0.1",
@ -1355,7 +1355,7 @@ dependencies = [
"rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1401,7 +1401,7 @@ dependencies = [
"script 0.0.1",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_config 0.0.1",
@ -2292,7 +2292,7 @@ dependencies = [
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_atoms 0.0.1",
@ -2336,7 +2336,7 @@ dependencies = [
"profile_traits 0.0.1",
"range 0.0.1",
"script_traits 0.0.1",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"servo_url 0.0.1",
"style 0.0.1",
]
@ -2387,7 +2387,6 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2767,7 +2766,7 @@ dependencies = [
"rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_atoms 0.0.1",
@ -2793,7 +2792,7 @@ dependencies = [
"parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"servo_atoms 0.0.1",
"servo_config 0.0.1",
"servo_url 0.0.1",
@ -2830,7 +2829,7 @@ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.17.0",
"servo_url 0.0.1",
"style 0.0.1",
"style_traits 0.0.1",
@ -3517,7 +3516,6 @@ dependencies = [
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
"checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
"checksum selectors 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48ed8bbe599ed52966241c818c92dc024702666f51b4e8ab538b2108c1d6d43a"
"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
"checksum semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d5b7638a1f03815d94e88cb3b3c08e87f0db4d683ef499d1836aaf70a45623f"
"checksum serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "793aa8d4a777e46a68bbf88998cd957e638427ba5bfb0de22c92ff277b65bd21"

View file

@ -34,7 +34,7 @@ range = {path = "../range"}
rayon = "0.6"
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
selectors = "0.17"
selectors = { path = "../selectors" }
serde = "0.8"
serde_derive = "0.8"
servo_geometry = {path = "../geometry"}

View file

@ -30,7 +30,7 @@ rayon = "0.6"
script = {path = "../script"}
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
selectors = "0.17"
selectors = { path = "../selectors" }
serde_derive = "0.8"
serde_json = "0.8"
servo_config = {path = "../config"}

View file

@ -70,7 +70,7 @@ regex = "0.2"
rustc-serialize = "0.3"
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
selectors = "0.17"
selectors = { path = "../selectors" }
serde = "0.8"
servo_atoms = {path = "../atoms"}
servo_config = {path = "../config", features = ["servo"] }

View file

@ -28,6 +28,6 @@ plugins = {path = "../plugins"}
profile_traits = {path = "../profile_traits"}
range = {path = "../range"}
script_traits = {path = "../script_traits"}
selectors = "0.17"
selectors = { path = "../selectors" }
servo_url = {path = "../url"}
style = {path = "../style"}

View file

@ -0,0 +1,22 @@
[package]
name = "selectors"
version = "0.17.0"
authors = ["Simon Sapin <simon.sapin@exyr.org>", "Alan Jeffrey <ajeffrey@mozilla.com>"]
documentation = "https://docs.rs/selectors/"
description = "CSS Selectors matching for Rust"
repository = "https://github.com/servo/servo"
readme = "README.md"
keywords = ["css", "selectors"]
license = "MPL-2.0"
[lib]
name = "selectors"
path = "lib.rs"
[dependencies]
bitflags = "0.7"
matches = "0.1"
cssparser = ">=0.6, <0.8"
fnv = "1.0"

View file

@ -0,0 +1,25 @@
rust-selectors
==============
* [![Build Status](https://travis-ci.org/servo/rust-selectors.svg?branch=master)](
https://travis-ci.org/servo/rust-selectors)
* [Documentation](https://docs.rs/selectors/)
* [crates.io](https://crates.io/crates/selectors)
CSS Selectors library for Rust.
Includes parsing and serilization of selectors,
as well as matching against a generic tree of elements.
Pseudo-elements and most pseudo-classes are generic as well.
**Warning:** breaking changes are made to this library fairly frequently
(13 times in 2016, for example).
However you can use this crate without updating it that often,
old versions stay available on crates.io and Cargo will only automatically update
to versions that are numbered as compatible.
To see how to use this library with your own tree representation,
see [Kuchikis `src/select.rs`](https://github.com/SimonSapin/kuchiki/blob/master/src/select.rs).
(Note however that Kuchiki is not always up to date with the latest rust-selectors version,
so that code may need to be tweaked.)
If you dont already have a tree data structure,
consider using [Kuchiki](https://github.com/SimonSapin/kuchiki) itself.

View file

@ -0,0 +1,312 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
//! Simple counting bloom filters.
use fnv::FnvHasher;
use std::hash::{Hash, Hasher};
const KEY_SIZE: usize = 12;
const ARRAY_SIZE: usize = 1 << KEY_SIZE;
const KEY_MASK: u32 = (1 << KEY_SIZE) - 1;
const KEY_SHIFT: usize = 16;
/// A counting Bloom filter with 8-bit counters. For now we assume
/// that having two hash functions is enough, but we may revisit that
/// decision later.
///
/// The filter uses an array with 2**KeySize entries.
///
/// Assuming a well-distributed hash function, a Bloom filter with
/// array size M containing N elements and
/// using k hash function has expected false positive rate exactly
///
/// $ (1 - (1 - 1/M)^{kN})^k $
///
/// because each array slot has a
///
/// $ (1 - 1/M)^{kN} $
///
/// chance of being 0, and the expected false positive rate is the
/// probability that all of the k hash functions will hit a nonzero
/// slot.
///
/// For reasonable assumptions (M large, kN large, which should both
/// hold if we're worried about false positives) about M and kN this
/// becomes approximately
///
/// $$ (1 - \exp(-kN/M))^k $$
///
/// For our special case of k == 2, that's $(1 - \exp(-2N/M))^2$,
/// or in other words
///
/// $$ N/M = -0.5 * \ln(1 - \sqrt(r)) $$
///
/// where r is the false positive rate. This can be used to compute
/// the desired KeySize for a given load N and false positive rate r.
///
/// If N/M is assumed small, then the false positive rate can
/// further be approximated as 4*N^2/M^2. So increasing KeySize by
/// 1, which doubles M, reduces the false positive rate by about a
/// factor of 4, and a false positive rate of 1% corresponds to
/// about M/N == 20.
///
/// What this means in practice is that for a few hundred keys using a
/// KeySize of 12 gives false positive rates on the order of 0.25-4%.
///
/// Similarly, using a KeySize of 10 would lead to a 4% false
/// positive rate for N == 100 and to quite bad false positive
/// rates for larger N.
pub struct BloomFilter {
counters: [u8; ARRAY_SIZE],
}
impl Clone for BloomFilter {
#[inline]
fn clone(&self) -> BloomFilter {
BloomFilter {
counters: self.counters,
}
}
}
impl BloomFilter {
/// Creates a new bloom filter.
#[inline]
pub fn new() -> BloomFilter {
BloomFilter {
counters: [0; ARRAY_SIZE],
}
}
#[inline]
fn first_slot(&self, hash: u32) -> &u8 {
&self.counters[hash1(hash) as usize]
}
#[inline]
fn first_mut_slot(&mut self, hash: u32) -> &mut u8 {
&mut self.counters[hash1(hash) as usize]
}
#[inline]
fn second_slot(&self, hash: u32) -> &u8 {
&self.counters[hash2(hash) as usize]
}
#[inline]
fn second_mut_slot(&mut self, hash: u32) -> &mut u8 {
&mut self.counters[hash2(hash) as usize]
}
#[inline]
pub fn clear(&mut self) {
self.counters = [0; ARRAY_SIZE]
}
#[inline]
fn insert_hash(&mut self, hash: u32) {
{
let slot1 = self.first_mut_slot(hash);
if !full(slot1) {
*slot1 += 1
}
}
{
let slot2 = self.second_mut_slot(hash);
if !full(slot2) {
*slot2 += 1
}
}
}
/// Inserts an item into the bloom filter.
#[inline]
pub fn insert<T: Hash>(&mut self, elem: &T) {
self.insert_hash(hash(elem))
}
#[inline]
fn remove_hash(&mut self, hash: u32) {
{
let slot1 = self.first_mut_slot(hash);
if !full(slot1) {
*slot1 -= 1
}
}
{
let slot2 = self.second_mut_slot(hash);
if !full(slot2) {
*slot2 -= 1
}
}
}
/// Removes an item from the bloom filter.
#[inline]
pub fn remove<T: Hash>(&mut self, elem: &T) {
self.remove_hash(hash(elem))
}
#[inline]
fn might_contain_hash(&self, hash: u32) -> bool {
*self.first_slot(hash) != 0 && *self.second_slot(hash) != 0
}
/// Check whether the filter might contain an item. This can
/// sometimes return true even if the item is not in the filter,
/// but will never return false for items that are actually in the
/// filter.
#[inline]
pub fn might_contain<T: Hash>(&self, elem: &T) -> bool {
self.might_contain_hash(hash(elem))
}
}
#[inline]
fn full(slot: &u8) -> bool {
*slot == 0xff
}
#[inline]
fn hash<T: Hash>(elem: &T) -> u32 {
let mut hasher = FnvHasher::default();
elem.hash(&mut hasher);
let hash: u64 = hasher.finish();
(hash >> 32) as u32 ^ (hash as u32)
}
#[inline]
fn hash1(hash: u32) -> u32 {
hash & KEY_MASK
}
#[inline]
fn hash2(hash: u32) -> u32 {
(hash >> KEY_SHIFT) & KEY_MASK
}
#[test]
fn create_and_insert_some_stuff() {
let mut bf = BloomFilter::new();
for i in 0_usize .. 1000 {
bf.insert(&i);
}
for i in 0_usize .. 1000 {
assert!(bf.might_contain(&i));
}
let false_positives =
(1001_usize .. 2000).filter(|i| bf.might_contain(i)).count();
assert!(false_positives < 150, "{} is not < 150", false_positives); // 15%.
for i in 0_usize .. 100 {
bf.remove(&i);
}
for i in 100_usize .. 1000 {
assert!(bf.might_contain(&i));
}
let false_positives = (0_usize .. 100).filter(|i| bf.might_contain(i)).count();
assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%.
bf.clear();
for i in 0_usize .. 2000 {
assert!(!bf.might_contain(&i));
}
}
#[cfg(feature = "unstable")]
#[cfg(test)]
mod bench {
extern crate test;
use std::hash::{Hash, Hasher, SipHasher};
use super::BloomFilter;
#[bench]
fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) {
b.iter(|| {
let mut bf = BloomFilter::new();
for i in 0_usize .. 1000 {
bf.insert(&i);
}
for i in 0_usize .. 100 {
bf.remove(&i);
}
for i in 100_usize .. 200 {
test::black_box(bf.might_contain(&i));
}
});
}
#[bench]
fn might_contain(b: &mut test::Bencher) {
let mut bf = BloomFilter::new();
for i in 0_usize .. 1000 {
bf.insert(&i);
}
let mut i = 0_usize;
b.bench_n(1000, |b| {
b.iter(|| {
test::black_box(bf.might_contain(&i));
i += 1;
});
});
}
#[bench]
fn insert(b: &mut test::Bencher) {
let mut bf = BloomFilter::new();
b.bench_n(1000, |b| {
let mut i = 0_usize;
b.iter(|| {
test::black_box(bf.insert(&i));
i += 1;
});
});
}
#[bench]
fn remove(b: &mut test::Bencher) {
let mut bf = BloomFilter::new();
for i in 0_usize .. 1000 {
bf.insert(&i);
}
b.bench_n(1000, |b| {
let mut i = 0_usize;
b.iter(|| {
bf.remove(&i);
i += 1;
});
});
test::black_box(bf.might_contain(&0_usize));
}
#[bench]
fn hash_a_uint(b: &mut test::Bencher) {
let mut i = 0_usize;
b.iter(|| {
let mut hasher = SipHasher::default();
i.hash(&mut hasher);
test::black_box(hasher.finish());
i += 1;
})
}
}

View file

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
#[macro_use] extern crate bitflags;
#[macro_use] extern crate cssparser;
#[macro_use] extern crate matches;
extern crate fnv;
pub mod bloom;
pub mod matching;
pub mod parser;
mod tree;
pub use parser::{SelectorImpl, Parser, SelectorList};
pub use tree::Element;
pub use tree::{MatchAttr, MatchAttrGeneric};

View file

@ -0,0 +1,573 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 std::borrow::Borrow;
use bloom::BloomFilter;
use parser::{CaseSensitivity, Combinator, ComplexSelector, LocalName};
use parser::{SimpleSelector, Selector, SelectorImpl};
use tree::Element;
/// The reason why we're doing selector matching.
///
/// If this is for styling, this will include the flags in the parent element.
///
/// This is done because Servo doesn't need those flags at all when it's not
/// styling (e.g., when you're doing document.querySelector). For example, a
/// slow selector in an API like querySelector doesn't imply that the parent
/// could match it.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MatchingReason {
ForStyling,
Other,
}
impl MatchingReason {
#[inline]
fn for_styling(&self) -> bool {
*self == MatchingReason::ForStyling
}
}
// The bloom filter for descendant CSS selectors will have a <1% false
// positive rate until it has this many selectors in it, then it will
// rapidly increase.
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
bitflags! {
/// Set of flags that determine the different kind of elements affected by
/// the selector matching process.
///
/// This is used to implement efficient sharing.
pub flags StyleRelations: u16 {
/// Whether this element has matched any rule that is determined by a
/// sibling (when using the `+` or `~` combinators).
const AFFECTED_BY_SIBLINGS = 1 << 0,
/// Whether this element has matched any rule whose matching is
/// determined by its position in the tree (i.e., first-child,
/// nth-child, etc.).
const AFFECTED_BY_CHILD_INDEX = 1 << 1,
/// Whether this flag is affected by any state (i.e., non
/// tree-structural pseudo-class).
const AFFECTED_BY_STATE = 1 << 2,
/// Whether this element is affected by an ID selector.
const AFFECTED_BY_ID_SELECTOR = 1 << 3,
/// Whether this element is affected by a non-common style-affecting
/// attribute.
const AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR = 1 << 4,
/// Whether this element matches the :empty pseudo class.
const AFFECTED_BY_EMPTY = 1 << 5,
/// Whether this element has a style attribute. Computed
/// externally.
const AFFECTED_BY_STYLE_ATTRIBUTE = 1 << 6,
/// Whether this element is affected by presentational hints. This is
/// computed externally (that is, in Servo).
const AFFECTED_BY_PRESENTATIONAL_HINTS = 1 << 7,
/// Whether this element has pseudo-element styles. Computed externally.
const AFFECTED_BY_PSEUDO_ELEMENTS = 1 << 8,
/// Whether this element has effective animation styles. Computed
/// externally.
const AFFECTED_BY_ANIMATIONS = 1 << 9,
/// Whether this element has effective transition styles. Computed
/// externally.
const AFFECTED_BY_TRANSITIONS = 1 << 10,
}
}
bitflags! {
/// Set of flags that are set on the parent depending on whether a child
/// could potentially match a selector.
///
/// These setters, in the case of Servo, must be atomic, due to the parallel
/// traversal.
pub flags ElementFlags: u8 {
/// When a child is added or removed from this element, all the children
/// must be restyled, because they may match :nth-last-child,
/// :last-of-type, :nth-last-of-type, or :only-of-type.
const HAS_SLOW_SELECTOR = 1 << 0,
/// When a child is added or removed from this element, any later
/// children must be restyled, because they may match :nth-child,
/// :first-of-type, or :nth-of-type.
const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1,
/// When a child is added or removed from this element, the first and
/// last children must be restyled, because they may match :first-child,
/// :last-child, or :only-child.
const HAS_EDGE_CHILD_SELECTOR = 1 << 2,
/// The element has an empty selector, so when a child is appended we
/// might need to restyle the parent completely.
const HAS_EMPTY_SELECTOR = 1 << 3,
}
}
pub fn matches<E>(selector_list: &[Selector<E::Impl>],
element: &E,
parent_bf: Option<&BloomFilter>,
reason: MatchingReason)
-> bool
where E: Element
{
selector_list.iter().any(|selector| {
selector.pseudo_element.is_none() &&
matches_complex_selector(&*selector.complex_selector, element, parent_bf, &mut StyleRelations::empty(), reason)
})
}
/// Determines whether the given element matches the given complex selector.
///
/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
/// will almost certainly break as elements will start mistakenly sharing styles. (See
/// `can_share_style_with` in `servo/components/style/matching.rs`.)
pub fn matches_complex_selector<E>(selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> bool
where E: Element
{
match matches_complex_selector_internal(selector, element, parent_bf, relations, reason) {
SelectorMatchingResult::Matched => {
match selector.next {
Some((_, Combinator::NextSibling)) |
Some((_, Combinator::LaterSibling)) => *relations |= AFFECTED_BY_SIBLINGS,
_ => {}
}
true
}
_ => false
}
}
/// A result of selector matching, includes 3 failure types,
///
/// NotMatchedAndRestartFromClosestLaterSibling
/// NotMatchedAndRestartFromClosestDescendant
/// NotMatchedGlobally
///
/// When NotMatchedGlobally appears, stop selector matching completely since
/// the succeeding selectors never matches.
/// It is raised when
/// Child combinator cannot find the candidate element.
/// Descendant combinator cannot find the candidate element.
///
/// When NotMatchedAndRestartFromClosestDescendant appears, the selector
/// matching does backtracking and restarts from the closest Descendant
/// combinator.
/// It is raised when
/// NextSibling combinator cannot find the candidate element.
/// LaterSibling combinator cannot find the candidate element.
/// Child combinator doesn't match on the found element.
///
/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector
/// matching does backtracking and restarts from the closest LaterSibling
/// combinator.
/// It is raised when
/// NextSibling combinator doesn't match on the found element.
///
/// For example, when the selector "d1 d2 a" is provided and we cannot *find*
/// an appropriate ancestor element for "d1", this selector matching raises
/// NotMatchedGlobally since even if "d2" is moved to more upper element, the
/// candidates for "d1" becomes less than before and d1 .
///
/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is
/// provided and we cannot *find* an appropriate brother element for b1,
/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.
/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1".
///
/// The additional example is child and sibling. When the selector
/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on
/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling.
/// However since the selector "c1" raises
/// NotMatchedAndRestartFromClosestDescendant. So the selector
/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1".
#[derive(PartialEq, Eq, Copy, Clone)]
enum SelectorMatchingResult {
Matched,
NotMatchedAndRestartFromClosestLaterSibling,
NotMatchedAndRestartFromClosestDescendant,
NotMatchedGlobally,
}
/// Quickly figures out whether or not the complex selector is worth doing more
/// work on. If the simple selectors don't match, or there's a child selector
/// that does not appear in the bloom parent bloom filter, we can exit early.
fn can_fast_reject<E>(mut selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> Option<SelectorMatchingResult>
where E: Element
{
if !selector.compound_selector.iter().all(|simple_selector| {
matches_simple_selector(simple_selector, element, parent_bf, relations, reason) }) {
return Some(SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling);
}
let bf: &BloomFilter = match parent_bf {
None => return None,
Some(ref bf) => bf,
};
// See if the bloom filter can exclude any of the descendant selectors, and
// reject if we can.
loop {
match selector.next {
None => break,
Some((ref cs, Combinator::Descendant)) => selector = &**cs,
Some((ref cs, _)) => {
selector = &**cs;
continue;
}
};
for ss in selector.compound_selector.iter() {
match *ss {
SimpleSelector::LocalName(LocalName { ref name, ref lower_name }) => {
if !bf.might_contain(name)
&& !bf.might_contain(lower_name) {
return Some(SelectorMatchingResult::NotMatchedGlobally);
}
},
SimpleSelector::Namespace(ref namespace) => {
if !bf.might_contain(&namespace.url) {
return Some(SelectorMatchingResult::NotMatchedGlobally);
}
},
SimpleSelector::ID(ref id) => {
if !bf.might_contain(id) {
return Some(SelectorMatchingResult::NotMatchedGlobally);
}
},
SimpleSelector::Class(ref class) => {
if !bf.might_contain(class) {
return Some(SelectorMatchingResult::NotMatchedGlobally);
}
},
_ => {},
}
}
}
// Can't fast reject.
None
}
fn matches_complex_selector_internal<E>(selector: &ComplexSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> SelectorMatchingResult
where E: Element
{
if let Some(result) = can_fast_reject(selector, element, parent_bf, relations, reason) {
return result;
}
match selector.next {
None => SelectorMatchingResult::Matched,
Some((ref next_selector, combinator)) => {
let (siblings, candidate_not_found) = match combinator {
Combinator::Child => (false, SelectorMatchingResult::NotMatchedGlobally),
Combinator::Descendant => (false, SelectorMatchingResult::NotMatchedGlobally),
Combinator::NextSibling => (true, SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant),
Combinator::LaterSibling => (true, SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant),
};
let mut next_element = if siblings {
element.prev_sibling_element()
} else {
element.parent_element()
};
loop {
let element = match next_element {
None => return candidate_not_found,
Some(next_element) => next_element,
};
let result = matches_complex_selector_internal(&**next_selector,
&element,
parent_bf,
relations,
reason);
match (result, combinator) {
// Return the status immediately.
(SelectorMatchingResult::Matched, _) => return result,
(SelectorMatchingResult::NotMatchedGlobally, _) => return result,
// Upgrade the failure status to
// NotMatchedAndRestartFromClosestDescendant.
(_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,
// Return the status directly.
(_, Combinator::NextSibling) => return result,
// If the failure status is NotMatchedAndRestartFromClosestDescendant
// and combinator is Combinator::LaterSibling, give up this Combinator::LaterSibling matching
// and restart from the closest descendant combinator.
(SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => return result,
// The Combinator::Descendant combinator and the status is
// NotMatchedAndRestartFromClosestLaterSibling or
// NotMatchedAndRestartFromClosestDescendant,
// or the Combinator::LaterSibling combinator and the status is
// NotMatchedAndRestartFromClosestDescendant
// can continue to matching on the next candidate element.
_ => {},
}
next_element = if siblings {
element.prev_sibling_element()
} else {
element.parent_element()
};
}
}
}
}
/// Determines whether the given element matches the given single selector.
#[inline]
fn matches_simple_selector<E>(
selector: &SimpleSelector<E::Impl>,
element: &E,
parent_bf: Option<&BloomFilter>,
relations: &mut StyleRelations,
reason: MatchingReason)
-> bool
where E: Element
{
macro_rules! relation_if {
($ex:expr, $flag:ident) => {
if $ex {
*relations |= $flag;
true
} else {
false
}
}
}
match *selector {
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()
}
SimpleSelector::Namespace(ref namespace) => {
element.get_namespace() == namespace.url.borrow()
}
// TODO: case-sensitivity depends on the document type and quirks mode
SimpleSelector::ID(ref id) => {
relation_if!(element.get_id().map_or(false, |attr| attr == *id),
AFFECTED_BY_ID_SELECTOR)
}
SimpleSelector::Class(ref class) => {
element.has_class(class)
}
SimpleSelector::AttrExists(ref attr) => {
let matches = element.match_attr_has(attr);
if matches && !E::Impl::attr_exists_selector_is_shareable(attr) {
*relations |= AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR;
}
matches
}
SimpleSelector::AttrEqual(ref attr, ref value, case_sensitivity) => {
let matches = match case_sensitivity {
CaseSensitivity::CaseSensitive => element.match_attr_equals(attr, value),
CaseSensitivity::CaseInsensitive => element.match_attr_equals_ignore_ascii_case(attr, value),
};
if matches && !E::Impl::attr_equals_selector_is_shareable(attr, value) {
*relations |= AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR;
}
matches
}
SimpleSelector::AttrIncludes(ref attr, ref value) => {
relation_if!(element.match_attr_includes(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrDashMatch(ref attr, ref value) => {
relation_if!(element.match_attr_dash(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrPrefixMatch(ref attr, ref value) => {
relation_if!(element.match_attr_prefix(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrSubstringMatch(ref attr, ref value) => {
relation_if!(element.match_attr_substring(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrSuffixMatch(ref attr, ref value) => {
relation_if!(element.match_attr_suffix(attr, value),
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR)
}
SimpleSelector::AttrIncludesNeverMatch(..) |
SimpleSelector::AttrPrefixNeverMatch(..) |
SimpleSelector::AttrSubstringNeverMatch(..) |
SimpleSelector::AttrSuffixNeverMatch(..) => {
false
}
SimpleSelector::NonTSPseudoClass(ref pc) => {
relation_if!(element.match_non_ts_pseudo_class(pc),
AFFECTED_BY_STATE)
}
SimpleSelector::FirstChild => {
relation_if!(matches_first_child(element, reason), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::LastChild => {
relation_if!(matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::OnlyChild => {
relation_if!(matches_first_child(element, reason) && matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::Root => {
// We never share styles with an element with no parent, so no point
// in creating a new StyleRelation.
element.is_root()
}
SimpleSelector::Empty => {
if reason.for_styling() {
element.insert_flags(HAS_EMPTY_SELECTOR);
}
relation_if!(element.is_empty(), AFFECTED_BY_EMPTY)
}
SimpleSelector::NthChild(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, false, false, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthLastChild(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, false, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthOfType(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, true, false, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::NthLastOfType(a, b) => {
relation_if!(matches_generic_nth_child(element, a, b, true, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::FirstOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::LastOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::OnlyOfType => {
relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason) &&
matches_generic_nth_child(element, 0, 1, true, true, reason),
AFFECTED_BY_CHILD_INDEX)
}
SimpleSelector::Negation(ref negated) => {
!negated.iter().all(|s| {
matches_complex_selector(s, element, parent_bf, relations, reason)
})
}
}
}
#[inline]
fn matches_generic_nth_child<E>(element: &E,
a: i32,
b: i32,
is_of_type: bool,
is_from_end: bool,
reason: MatchingReason) -> bool
where E: Element
{
// Selectors Level 4 changed from Level 3:
// This can match without a parent element:
// https://drafts.csswg.org/selectors-4/#child-index
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(if is_from_end {
HAS_SLOW_SELECTOR
} else {
HAS_SLOW_SELECTOR_LATER_SIBLINGS
});
}
}
let mut index = 1;
let mut next_sibling = if is_from_end {
element.next_sibling_element()
} else {
element.prev_sibling_element()
};
loop {
let sibling = match next_sibling {
None => break,
Some(next_sibling) => next_sibling
};
if is_of_type {
if element.get_local_name() == sibling.get_local_name() &&
element.get_namespace() == sibling.get_namespace() {
index += 1;
}
} else {
index += 1;
}
next_sibling = if is_from_end {
sibling.next_sibling_element()
} else {
sibling.prev_sibling_element()
};
}
if a == 0 {
b == index
} else {
(index - b) / a >= 0 &&
(index - b) % a == 0
}
}
#[inline]
fn matches_first_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
// Selectors Level 4 changed from Level 3:
// This can match without a parent element:
// https://drafts.csswg.org/selectors-4/#child-index
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
element.prev_sibling_element().is_none()
}
#[inline]
fn matches_last_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
// Selectors Level 4 changed from Level 3:
// This can match without a parent element:
// https://drafts.csswg.org/selectors-4/#child-index
if reason.for_styling() {
if let Some(parent) = element.parent_element() {
parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
}
}
element.next_sibling_element().is_none()
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,178 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
//! style.
use matching::ElementFlags;
use parser::{AttrSelector, SelectorImpl};
use std::ascii::AsciiExt;
/// The definition of whitespace per CSS Selectors Level 3 § 4.
pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
// Attribute matching routines. Consumers with simple implementations can implement
// MatchAttrGeneric instead.
pub trait MatchAttr {
type Impl: SelectorImpl;
fn match_attr_has(
&self,
attr: &AttrSelector<Self::Impl>) -> bool;
fn match_attr_equals(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_equals_ignore_ascii_case(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_includes(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_dash(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_prefix(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_substring(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
fn match_attr_suffix(
&self,
attr: &AttrSelector<Self::Impl>,
value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
}
pub trait MatchAttrGeneric {
type Impl: SelectorImpl;
fn match_attr<F>(&self, attr: &AttrSelector<Self::Impl>, test: F) -> bool where F: Fn(&str) -> bool;
}
impl<T> MatchAttr for T where T: MatchAttrGeneric, T::Impl: SelectorImpl<AttrValue = String> {
type Impl = T::Impl;
fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool {
self.match_attr(attr, |_| true)
}
fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |v| v == value)
}
fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>,
value: &String) -> bool {
self.match_attr(attr, |v| v.eq_ignore_ascii_case(value))
}
fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.split(SELECTOR_WHITESPACE).any(|v| v == value)
})
}
fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
// The attribute must start with the pattern.
if !attr_value.starts_with(value) {
return false
}
// If the strings are the same, we're done.
if attr_value.len() == value.len() {
return true
}
// The attribute is long than the pattern, so the next character must be '-'.
attr_value.as_bytes()[value.len()] == '-' as u8
})
}
fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.starts_with(value)
})
}
fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.contains(value)
})
}
fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
self.match_attr(attr, |attr_value| {
attr_value.ends_with(value)
})
}
}
pub trait Element: MatchAttr + Sized {
fn parent_element(&self) -> Option<Self>;
// Skips non-element nodes
fn first_child_element(&self) -> Option<Self>;
// Skips non-element nodes
fn last_child_element(&self) -> Option<Self>;
// Skips non-element nodes
fn prev_sibling_element(&self) -> Option<Self>;
// Skips non-element nodes
fn next_sibling_element(&self) -> Option<Self>;
fn is_html_element_in_html_document(&self) -> bool;
fn get_local_name(&self) -> &<Self::Impl as SelectorImpl>::BorrowedLocalName;
fn get_namespace(&self) -> &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl;
fn match_non_ts_pseudo_class(&self, pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass) -> bool;
fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool;
/// Returns whether this element matches `:empty`.
///
/// That is, whether it does not contain any child element or any non-zero-length text node.
/// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo
fn is_empty(&self) -> bool;
/// Returns whether this element matches `:root`,
/// i.e. whether it is the root element of a document.
///
/// Note: this can be false even if `.parent_element()` is `None`
/// if the parent node is a `DocumentFragment`.
fn is_root(&self) -> bool;
// Ordinarily I wouldn't use callbacks like this, but the alternative is
// really messy, since there is a `JSRef` and a `RefCell` involved. Maybe
// in the future when we have associated types and/or a more convenient
// JS GC story... --pcwalton
fn each_class<F>(&self, callback: F) where F: FnMut(&<Self::Impl as SelectorImpl>::ClassName);
/// Add flags to the element. See the `ElementFlags` docs for details.
///
/// This may be called while the element *or one of its children* is being
/// matched. Therefore the implementation must be thread-safe if children
/// may be matched in parallel.
fn insert_flags(&self, _flags: ElementFlags) {}
/// Clears the relevant ElementFlags. This is *not* called from
/// rust-selectors, but provided as part of the Element interface since it
/// makes sense.
fn clear_flags(&self) {}
}

View file

@ -46,7 +46,7 @@ phf = "0.7.20"
pdqsort = "0.1.0"
rayon = "0.6"
rustc-serialize = "0.3"
selectors = "0.17"
selectors = { path = "../selectors" }
serde = {version = "0.8", optional = true}
serde_derive = {version = "0.8", optional = true}
servo_atoms = {path = "../atoms", optional = true}

View file

@ -23,7 +23,7 @@ libc = "0.2"
log = {version = "0.3.5", features = ["release_max_level_info"]}
num_cpus = "1.1.0"
parking_lot = "0.3"
selectors = "0.17"
selectors = {path = "../../components/selectors"}
servo_url = {path = "../../components/url"}
style = {path = "../../components/style", features = ["gecko"]}
style_traits = {path = "../../components/style_traits"}

View file

@ -22,7 +22,7 @@ owning_ref = "0.2.2"
parking_lot = "0.3"
rayon = "0.6"
rustc-serialize = "0.3"
selectors = "0.17"
selectors = {path = "../../../components/selectors"}
servo_atoms = {path = "../../../components/atoms"}
servo_config = {path = "../../../components/config"}
style = {path = "../../../components/style"}

View file

@ -22,7 +22,7 @@ libc = "0.2"
log = {version = "0.3.5", features = ["release_max_level_info"]}
num_cpus = "1.1.0"
parking_lot = "0.3"
selectors = "0.17"
selectors = {path = "../../../components/selectors"}
servo_url = {path = "../../../components/url"}
style_traits = {path = "../../../components/style_traits"}
geckoservo = {path = "../../../ports/geckolib"}