mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Move rust-selectors in-tree.
This commit is contained in:
parent
f7e75fd001
commit
8915e53cee
16 changed files with 2616 additions and 18 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"}
|
||||
|
|
22
components/selectors/Cargo.toml
Normal file
22
components/selectors/Cargo.toml
Normal 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"
|
25
components/selectors/README.md
Normal file
25
components/selectors/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
rust-selectors
|
||||
==============
|
||||
|
||||
* [](
|
||||
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 [Kuchiki’s `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 don’t already have a tree data structure,
|
||||
consider using [Kuchiki](https://github.com/SimonSapin/kuchiki) itself.
|
312
components/selectors/bloom.rs
Normal file
312
components/selectors/bloom.rs
Normal 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;
|
||||
})
|
||||
}
|
||||
}
|
17
components/selectors/lib.rs
Normal file
17
components/selectors/lib.rs
Normal 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};
|
573
components/selectors/matching.rs
Normal file
573
components/selectors/matching.rs
Normal 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()
|
||||
}
|
1473
components/selectors/parser.rs
Normal file
1473
components/selectors/parser.rs
Normal file
File diff suppressed because it is too large
Load diff
178
components/selectors/tree.rs
Normal file
178
components/selectors/tree.rs
Normal 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) {}
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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"}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue