Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"ece7ba01e2f56cda5e1344dac832ae6b23f60beb4e31ce20cc4a1fedc1c1732b","CHANGES.md":"582a5cbd17faa7ebecf4d43d360bc67f8ee4cc419d056336a0dc64a1c8a1ad02","Cargo.toml":"aaa975493791920faa23d43daaae4e035f25ffe7e1488491c6ff9a909fc722f5","Cargo.toml.orig":"7b43a0508c126204728d6687b6e0ca32f199fec5dbd574802726fb6e2ec45a2d","README.md":"6865c6a9b30ddcb86ce7cd8ac95c8d9eb7b5062c05e499e4b8f53267be70a91a","attr.rs":"cfb9ba3ae47538a05b218ed63cc1360ebcf7ba448cb910f2af638f66244c1ae6","bloom.rs":"3ce89fe5366a0f3e493ad00cdca426be1d4cdd8fa9d3e995759cb794eb794f33","build.rs":"36ba09a8d2089d0cae8e310829ecf0e94bcbaa87e775a6578c7d2f0459a5b6ca","builder.rs":"fa8414e9df4ef875778c79be5bcb4c38ec420b8b5df047e21251a648933ca931","context.rs":"307f0c47e03cbb949df63e37506bc388b6e72cd0cd9d336d54c431e828ce9109","kleene_value.rs":"1932450194f7114d89151a09efa79130f34961e2556fb79b92d4c5e174adb823","lib.rs":"d54c6e13e9e952dac17d209171df8657e3cae93beaddace4906150cdec8d02e9","matching.rs":"e7ddb8d0647d3f9f690f181f967f1aecc1fd74fa622316ada871b2dfafc3a6d7","nth_index_cache.rs":"2f4e94a1010c867ce4b96619e90f1574449769fc5f3f419a2b84e4613d4ab39c","parser.rs":"a495eab352da620d7716407f171b49578ce2ccd98780bd6ccb6ce124e6512dc6","relative_selector/cache.rs":"c6460f5db8c74421af0890c4c69119450397258f64be169c91d0320e2d8626f4","relative_selector/filter.rs":"7ea15f5d310fca2254ac36989bfeee06dd0e9897ccec0b886b75b550e0225177","relative_selector/mod.rs":"29e305635f471d8467a6eca83ef83dcbda769ecc62976a708c98afa418ab76a6","sink.rs":"34d4cd4127a6e4979eba11f8e6c41dbc527c0a996be78bd2e3167916c025a04b","tree.rs":"257a2b30dcdd07bcf0d2519301c4e0678b8ae1a0ba22ad122a7a9132027b62ee","visitor.rs":"3b39f678c2f89c720d2d4fe9bb9064e283609ed49c190cc5a657705522f91dfd"},"package":"feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "f319793c6989dba83994fbd10d560b21ad4a0c85"
},
"path_in_vcs": "selectors"
}
+1
View File
@@ -0,0 +1 @@
- `parser.rs` no longer wraps values in quotes (`"..."`) but expects their `to_css` impl to already wrap it ([Gecko Bug 1854809](https://bugzilla.mozilla.org/show_bug.cgi?id=1854809))
+88
View File
@@ -0,0 +1,88 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "selectors"
version = "0.33.0"
authors = ["The Servo Project Developers"]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "CSS Selectors matching for Rust"
documentation = "https://docs.rs/selectors/"
readme = "README.md"
keywords = [
"css",
"selectors",
]
license = "MPL-2.0"
repository = "https://github.com/servo/stylo"
[lib]
name = "selectors"
path = "lib.rs"
[dependencies.bitflags]
version = "2"
[dependencies.cssparser]
version = "0.36"
[dependencies.derive_more]
version = "2"
features = [
"add",
"add_assign",
]
[dependencies.log]
version = "0.4"
[dependencies.new_debug_unreachable]
version = "1"
[dependencies.phf]
version = "0.13"
[dependencies.precomputed-hash]
version = "0.1"
[dependencies.rustc-hash]
version = "2.1.1"
[dependencies.servo_arc]
version = "0.4.3"
[dependencies.smallvec]
version = "1.0"
[dependencies.to_shmem]
version = "0.3.0"
features = ["servo"]
optional = true
[dependencies.to_shmem_derive]
version = "0.1.0"
optional = true
[build-dependencies.phf_codegen]
version = "0.13"
[features]
bench = []
to_shmem = [
"dep:to_shmem",
"dep:to_shmem_derive",
]
+37
View File
@@ -0,0 +1,37 @@
[package]
name = "selectors"
version = "0.33.0"
authors = ["The Servo Project Developers"]
documentation = "https://docs.rs/selectors/"
description = "CSS Selectors matching for Rust"
repository = "https://github.com/servo/stylo"
readme = "README.md"
keywords = ["css", "selectors"]
license = "MPL-2.0"
edition = "2021"
build = "build.rs"
[lib]
name = "selectors"
path = "lib.rs"
[features]
bench = []
to_shmem = ["dep:to_shmem", "dep:to_shmem_derive"]
[dependencies]
bitflags = "2"
cssparser = "0.36"
derive_more = { version = "2", features = ["add", "add_assign"] }
rustc-hash = "2.1.1"
log = "0.4"
phf = "0.13"
precomputed-hash = "0.1"
servo_arc = { workspace = true }
smallvec = "1.0"
to_shmem = { workspace = true, optional = true }
to_shmem_derive = { workspace = true, optional = true }
new_debug_unreachable = "1"
[build-dependencies]
phf_codegen = "0.13"
+25
View File
@@ -0,0 +1,25 @@
rust-selectors
==============
* [![Build Status](https://travis-ci.com/servo/rust-selectors.svg?branch=master)](
https://travis-ci.com/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/kuchiki-rs/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/kuchiki-rs/kuchiki) itself.
+196
View File
@@ -0,0 +1,196 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::parser::SelectorImpl;
use cssparser::ToCss;
use std::fmt;
#[cfg(feature = "to_shmem")]
use to_shmem_derive::ToShmem;
#[derive(Clone, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
#[cfg_attr(feature = "to_shmem", shmem(no_bounds))]
pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> {
#[cfg_attr(feature = "to_shmem", shmem(field_bound))]
pub namespace: Option<NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>>,
#[cfg_attr(feature = "to_shmem", shmem(field_bound))]
pub local_name: Impl::LocalName,
pub local_name_lower: Impl::LocalName,
#[cfg_attr(feature = "to_shmem", shmem(field_bound))]
pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
}
impl<Impl: SelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> {
self.namespace.as_ref().map(|ns| match ns {
NamespaceConstraint::Any => NamespaceConstraint::Any,
NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url),
})
}
}
#[derive(Clone, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
pub enum NamespaceConstraint<NamespaceUrl> {
Any,
/// Empty string for no namespace
Specific(NamespaceUrl),
}
#[derive(Clone, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
pub enum ParsedAttrSelectorOperation<AttrValue> {
Exists,
WithValue {
operator: AttrSelectorOperator,
case_sensitivity: ParsedCaseSensitivity,
value: AttrValue,
},
}
#[derive(Clone, Eq, PartialEq)]
pub enum AttrSelectorOperation<AttrValue> {
Exists,
WithValue {
operator: AttrSelectorOperator,
case_sensitivity: CaseSensitivity,
value: AttrValue,
},
}
impl<AttrValue> AttrSelectorOperation<AttrValue> {
pub fn eval_str(&self, element_attr_value: &str) -> bool
where
AttrValue: AsRef<str>,
{
match *self {
AttrSelectorOperation::Exists => true,
AttrSelectorOperation::WithValue {
operator,
case_sensitivity,
ref value,
} => operator.eval_str(element_attr_value, value.as_ref(), case_sensitivity),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
pub enum AttrSelectorOperator {
Equal,
Includes,
DashMatch,
Prefix,
Substring,
Suffix,
}
impl ToCss for AttrSelectorOperator {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
// https://drafts.csswg.org/cssom/#serializing-selectors
// See "attribute selector".
dest.write_str(match *self {
AttrSelectorOperator::Equal => "=",
AttrSelectorOperator::Includes => "~=",
AttrSelectorOperator::DashMatch => "|=",
AttrSelectorOperator::Prefix => "^=",
AttrSelectorOperator::Substring => "*=",
AttrSelectorOperator::Suffix => "$=",
})
}
}
impl AttrSelectorOperator {
pub fn eval_str(
self,
element_attr_value: &str,
attr_selector_value: &str,
case_sensitivity: CaseSensitivity,
) -> bool {
let e = element_attr_value.as_bytes();
let s = attr_selector_value.as_bytes();
let case = case_sensitivity;
match self {
AttrSelectorOperator::Equal => case.eq(e, s),
AttrSelectorOperator::Prefix => {
!s.is_empty() && e.len() >= s.len() && case.eq(&e[..s.len()], s)
},
AttrSelectorOperator::Suffix => {
!s.is_empty() && e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s)
},
AttrSelectorOperator::Substring => {
!s.is_empty() && case.contains(element_attr_value, attr_selector_value)
},
AttrSelectorOperator::Includes => {
!s.is_empty()
&& element_attr_value
.split(SELECTOR_WHITESPACE)
.any(|part| case.eq(part.as_bytes(), s))
},
AttrSelectorOperator::DashMatch => {
case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s))
},
}
}
}
/// The definition of whitespace per CSS Selectors Level 3 § 4.
pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
pub enum ParsedCaseSensitivity {
/// 's' was specified.
ExplicitCaseSensitive,
/// 'i' was specified.
AsciiCaseInsensitive,
/// No flags were specified and HTML says this is a case-sensitive attribute.
CaseSensitive,
/// No flags were specified and HTML says this is a case-insensitive attribute.
AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CaseSensitivity {
CaseSensitive,
AsciiCaseInsensitive,
}
impl CaseSensitivity {
pub fn eq(self, a: &[u8], b: &[u8]) -> bool {
match self {
CaseSensitivity::CaseSensitive => a == b,
CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b),
}
}
pub fn contains(self, haystack: &str, needle: &str) -> bool {
match self {
CaseSensitivity::CaseSensitive => haystack.contains(needle),
CaseSensitivity::AsciiCaseInsensitive => {
if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() {
haystack.bytes().enumerate().any(|(i, byte)| {
if !byte.eq_ignore_ascii_case(&n_first_byte) {
return false;
}
let after_this_byte = &haystack.as_bytes()[i + 1..];
match after_this_byte.get(..n_rest.len()) {
None => false,
Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest),
}
})
} else {
// any_str.contains("") == true,
// though these cases should be handled with *NeverMatches and never go here.
true
}
},
}
}
}
+422
View File
@@ -0,0 +1,422 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Counting and non-counting Bloom filters tuned for use as ancestor filters
//! for selector matching.
use std::fmt::{self, Debug};
// The top 8 bits of the 32-bit hash value are not used by the bloom filter.
// Consumers may rely on this to pack hashes more efficiently.
pub const BLOOM_HASH_MASK: u32 = 0x00ffffff;
const KEY_SIZE: usize = 12;
const ARRAY_SIZE: usize = 1 << KEY_SIZE;
const KEY_MASK: u32 = (1 << KEY_SIZE) - 1;
/// A counting Bloom filter with 8-bit counters.
pub type BloomFilter = CountingBloomFilter<BloomStorageU8>;
/// A counting Bloom filter with parameterized storage to handle
/// counters of different sizes. 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.
#[derive(Clone, Default)]
pub struct CountingBloomFilter<S>
where
S: BloomStorage,
{
storage: S,
}
impl<S> CountingBloomFilter<S>
where
S: BloomStorage,
{
/// Creates a new bloom filter.
#[inline]
pub fn new() -> Self {
Default::default()
}
#[inline]
pub fn clear(&mut self) {
self.storage = Default::default();
}
// Slow linear accessor to make sure the bloom filter is zeroed. This should
// never be used in release builds.
#[cfg(debug_assertions)]
pub fn is_zeroed(&self) -> bool {
self.storage.is_zeroed()
}
#[cfg(not(debug_assertions))]
pub fn is_zeroed(&self) -> bool {
unreachable!()
}
/// Inserts an item with a particular hash into the bloom filter.
#[inline]
pub fn insert_hash(&mut self, hash: u32) {
self.storage.adjust_first_slot(hash, true);
self.storage.adjust_second_slot(hash, true);
}
/// Removes an item with a particular hash from the bloom filter.
#[inline]
pub fn remove_hash(&mut self, hash: u32) {
self.storage.adjust_first_slot(hash, false);
self.storage.adjust_second_slot(hash, false);
}
/// Check whether the filter might contain an item with the given hash.
/// 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_hash(&self, hash: u32) -> bool {
!self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash)
}
}
impl<S> Debug for CountingBloomFilter<S>
where
S: BloomStorage,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut slots_used = 0;
for i in 0..ARRAY_SIZE {
if !self.storage.slot_is_empty(i) {
slots_used += 1;
}
}
write!(f, "BloomFilter({}/{})", slots_used, ARRAY_SIZE)
}
}
pub trait BloomStorage: Clone + Default {
fn slot_is_empty(&self, index: usize) -> bool;
fn adjust_slot(&mut self, index: usize, increment: bool);
fn is_zeroed(&self) -> bool;
#[inline]
fn first_slot_is_empty(&self, hash: u32) -> bool {
self.slot_is_empty(Self::first_slot_index(hash))
}
#[inline]
fn second_slot_is_empty(&self, hash: u32) -> bool {
self.slot_is_empty(Self::second_slot_index(hash))
}
#[inline]
fn adjust_first_slot(&mut self, hash: u32, increment: bool) {
self.adjust_slot(Self::first_slot_index(hash), increment)
}
#[inline]
fn adjust_second_slot(&mut self, hash: u32, increment: bool) {
self.adjust_slot(Self::second_slot_index(hash), increment)
}
#[inline]
fn first_slot_index(hash: u32) -> usize {
hash1(hash) as usize
}
#[inline]
fn second_slot_index(hash: u32) -> usize {
hash2(hash) as usize
}
}
/// Storage class for a CountingBloomFilter that has 8-bit counters.
pub struct BloomStorageU8 {
counters: [u8; ARRAY_SIZE],
}
impl BloomStorage for BloomStorageU8 {
#[inline]
fn adjust_slot(&mut self, index: usize, increment: bool) {
let slot = &mut self.counters[index];
if *slot != 0xff {
// full
if increment {
*slot += 1;
} else {
*slot -= 1;
}
}
}
#[inline]
fn slot_is_empty(&self, index: usize) -> bool {
self.counters[index] == 0
}
#[inline]
fn is_zeroed(&self) -> bool {
self.counters.iter().all(|x| *x == 0)
}
}
impl Default for BloomStorageU8 {
fn default() -> Self {
BloomStorageU8 {
counters: [0; ARRAY_SIZE],
}
}
}
impl Clone for BloomStorageU8 {
fn clone(&self) -> Self {
BloomStorageU8 {
counters: self.counters,
}
}
}
/// Storage class for a CountingBloomFilter that has 1-bit counters.
pub struct BloomStorageBool {
counters: [u8; ARRAY_SIZE / 8],
}
impl BloomStorage for BloomStorageBool {
#[inline]
fn adjust_slot(&mut self, index: usize, increment: bool) {
let bit = 1 << (index % 8);
let byte = &mut self.counters[index / 8];
// Since we have only one bit for storage, decrementing it
// should never do anything. Assert against an accidental
// decrementing of a bit that was never set.
assert!(
increment || (*byte & bit) != 0,
"should not decrement if slot is already false"
);
if increment {
*byte |= bit;
}
}
#[inline]
fn slot_is_empty(&self, index: usize) -> bool {
let bit = 1 << (index % 8);
(self.counters[index / 8] & bit) == 0
}
#[inline]
fn is_zeroed(&self) -> bool {
self.counters.iter().all(|x| *x == 0)
}
}
impl Default for BloomStorageBool {
fn default() -> Self {
BloomStorageBool {
counters: [0; ARRAY_SIZE / 8],
}
}
}
impl Clone for BloomStorageBool {
fn clone(&self) -> Self {
BloomStorageBool {
counters: self.counters,
}
}
}
#[inline]
fn hash1(hash: u32) -> u32 {
hash & KEY_MASK
}
#[inline]
fn hash2(hash: u32) -> u32 {
(hash >> KEY_SIZE) & KEY_MASK
}
#[test]
fn create_and_insert_some_stuff() {
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use std::mem::transmute;
fn hash_as_str(i: usize) -> u32 {
let mut hasher = FxHasher::default();
let s = i.to_string();
s.hash(&mut hasher);
let hash: u64 = hasher.finish();
(hash >> 32) as u32 ^ (hash as u32)
}
let mut bf = BloomFilter::new();
// Statically assert that ARRAY_SIZE is a multiple of 8, which
// BloomStorageBool relies on.
unsafe {
transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]);
}
for i in 0_usize..1000 {
bf.insert_hash(hash_as_str(i));
}
for i in 0_usize..1000 {
assert!(bf.might_contain_hash(hash_as_str(i)));
}
let false_positives = (1001_usize..2000)
.filter(|i| bf.might_contain_hash(hash_as_str(*i)))
.count();
assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%.
for i in 0_usize..100 {
bf.remove_hash(hash_as_str(i));
}
for i in 100_usize..1000 {
assert!(bf.might_contain_hash(hash_as_str(i)));
}
let false_positives = (0_usize..100)
.filter(|i| bf.might_contain_hash(hash_as_str(*i)))
.count();
assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%.
bf.clear();
for i in 0_usize..2000 {
assert!(!bf.might_contain_hash(hash_as_str(i)));
}
}
#[cfg(feature = "bench")]
#[cfg(test)]
mod bench {
extern crate test;
use super::BloomFilter;
#[derive(Default)]
struct HashGenerator(u32);
impl HashGenerator {
fn next(&mut self) -> u32 {
// Each hash is split into two twelve-bit segments, which are used
// as an index into an array. We increment each by 64 so that we
// hit the next cache line, and then another 1 so that our wrapping
// behavior leads us to different entries each time.
//
// Trying to simulate cold caches is rather difficult with the cargo
// benchmarking setup, so it may all be moot depending on the number
// of iterations that end up being run. But we might as well.
self.0 += (65) + (65 << super::KEY_SIZE);
self.0
}
}
#[bench]
fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) {
b.iter(|| {
let mut gen1 = HashGenerator::default();
let mut gen2 = HashGenerator::default();
let mut bf = BloomFilter::new();
for _ in 0_usize..1000 {
bf.insert_hash(gen1.next());
}
for _ in 0_usize..100 {
bf.remove_hash(gen2.next());
}
for _ in 100_usize..200 {
test::black_box(bf.might_contain_hash(gen2.next()));
}
});
}
#[bench]
fn might_contain_10(b: &mut test::Bencher) {
let bf = BloomFilter::new();
let mut gen = HashGenerator::default();
b.iter(|| {
for _ in 0..10 {
test::black_box(bf.might_contain_hash(gen.next()));
}
});
}
#[bench]
fn clear(b: &mut test::Bencher) {
let mut bf = Box::new(BloomFilter::new());
b.iter(|| test::black_box(&mut bf).clear());
}
#[bench]
fn insert_10(b: &mut test::Bencher) {
let mut bf = BloomFilter::new();
let mut gen = HashGenerator::default();
b.iter(|| {
for _ in 0..10 {
test::black_box(bf.insert_hash(gen.next()));
}
});
}
#[bench]
fn remove_10(b: &mut test::Bencher) {
let mut bf = BloomFilter::new();
let mut gen = HashGenerator::default();
// Note: this will underflow, and that's ok.
b.iter(|| {
for _ in 0..10 {
bf.remove_hash(gen.next())
}
});
}
}
+77
View File
@@ -0,0 +1,77 @@
/* 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 https://mozilla.org/MPL/2.0/. */
extern crate phf_codegen;
use std::env;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
fn main() {
let path = Path::new(&env::var_os("OUT_DIR").unwrap())
.join("ascii_case_insensitive_html_attributes.rs");
let mut file = BufWriter::new(File::create(&path).unwrap());
let mut set = phf_codegen::Set::new();
for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() {
set.entry(name);
}
write!(
&mut file,
"{{ static SET: ::phf::Set<&'static str> = {}; &SET }}",
set.build(),
)
.unwrap();
}
/// <https://html.spec.whatwg.org/multipage/#selectors>
static ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES: &str = r#"
accept
accept-charset
align
alink
axis
bgcolor
charset
checked
clear
codetype
color
compact
declare
defer
dir
direction
disabled
enctype
face
frame
hreflang
http-equiv
lang
language
link
media
method
multiple
nohref
noresize
noshade
nowrap
readonly
rel
rev
rules
scope
scrolling
selected
shape
target
text
type
valign
valuetype
vlink
"#;
+468
View File
@@ -0,0 +1,468 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Helper module to build up a selector safely and efficiently.
//!
//! Our selector representation is designed to optimize matching, and has
//! several requirements:
//! * All simple selectors and combinators are stored inline in the same buffer as Component
//! instances.
//! * We store the top-level compound selectors from right to left, i.e. in matching order.
//! * We store the simple selectors for each combinator from left to right, so that we match the
//! cheaper simple selectors first.
//!
//! For example, the selector:
//!
//! .bar:hover > .baz:nth-child(2) + .qux
//!
//! Gets stored as:
//!
//! [.qux, + , .baz, :nth-child(2), > , .bar, :hover]
//!
//! Meeting all these constraints without extra memmove traffic during parsing is non-trivial. This
//! module encapsulates those details and presents an easy-to-use API for the parser.
use crate::parser::{
Combinator, Component, ParseRelative, RelativeSelector, Selector, SelectorImpl,
};
use crate::sink::Push;
use bitflags::bitflags;
use derive_more::{Add, AddAssign};
use servo_arc::{Arc, ThinArc};
use smallvec::SmallVec;
use std::cmp;
use std::slice;
#[cfg(feature = "to_shmem")]
use to_shmem_derive::ToShmem;
/// Top-level SelectorBuilder struct. This should be stack-allocated by the consumer and never
/// moved (because it contains a lot of inline data that would be slow to memmove).
///
/// After instantiation, callers may call the push_simple_selector() and push_combinator() methods
/// to append selector data as it is encountered (from left to right). Once the process is
/// complete, callers should invoke build(), which transforms the contents of the SelectorBuilder
/// into a heap- allocated Selector and leaves the builder in a drained state.
#[derive(Debug)]
pub struct SelectorBuilder<Impl: SelectorImpl> {
/// The entire sequence of components. We make this large because the result of parsing a
/// selector is fed into a new Arc-ed allocation, so any spilled vec would be a wasted
/// allocation. Also, Components are large enough that we don't have much cache locality
/// benefit from reserving stack space for fewer of them.
components: SmallVec<[Component<Impl>; 32]>,
last_compound_start: Option<usize>,
}
impl<Impl: SelectorImpl> Push<Component<Impl>> for SelectorBuilder<Impl> {
fn push(&mut self, value: Component<Impl>) {
self.push_simple_selector(value);
}
}
impl<Impl: SelectorImpl> SelectorBuilder<Impl> {
/// Pushes a simple selector onto the current compound selector.
#[inline(always)]
pub fn push_simple_selector(&mut self, ss: Component<Impl>) {
debug_assert!(!ss.is_combinator());
self.components.push(ss);
}
/// Completes the current compound selector and starts a new one, delimited by the given
/// combinator.
#[inline(always)]
pub fn push_combinator(&mut self, c: Combinator) {
self.reverse_last_compound();
self.components.push(Component::Combinator(c));
self.last_compound_start = Some(self.components.len());
}
fn reverse_last_compound(&mut self) {
let start = self.last_compound_start.unwrap_or(0);
self.components[start..].reverse();
}
/// Returns true if combinators have ever been pushed to this builder.
#[inline(always)]
pub fn has_combinators(&self) -> bool {
self.last_compound_start.is_some()
}
/// Consumes the builder, producing a Selector.
#[inline(always)]
pub fn build(
&mut self,
parse_relative: ParseRelative,
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
// Compute the specificity and flags.
let sf = specificity_and_flags(
self.components.iter(),
/* for_nesting_parent = */ false,
);
self.build_with_specificity_and_flags(sf, parse_relative)
}
/// Builds with an explicit SpecificityAndFlags. This is separated from build() so that unit
/// tests can pass an explicit specificity.
#[inline(always)]
pub(crate) fn build_with_specificity_and_flags(
&mut self,
mut spec: SpecificityAndFlags,
parse_relative: ParseRelative,
) -> ThinArc<SpecificityAndFlags, Component<Impl>> {
let implicit_addition = match parse_relative {
ParseRelative::ForNesting if !spec.flags.intersects(SelectorFlags::HAS_PARENT) => {
Some((Component::ParentSelector, SelectorFlags::HAS_PARENT))
},
ParseRelative::ForScope
if !spec
.flags
.intersects(SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_PARENT) =>
{
Some((Component::ImplicitScope, SelectorFlags::HAS_SCOPE))
},
_ => None,
};
let implicit_selector_and_combinator;
let implicit_selector = if let Some((component, flag)) = implicit_addition {
spec.flags.insert(flag);
implicit_selector_and_combinator =
[Component::Combinator(Combinator::Descendant), component];
&implicit_selector_and_combinator[..]
} else {
&[]
};
// As an optimization, for a selector without combinators, we can just keep the order
// as-is.
if self.last_compound_start.is_none() {
return Arc::from_header_and_iter(
spec,
ExactChain(self.components.drain(..), implicit_selector.iter().cloned()),
);
}
self.reverse_last_compound();
Arc::from_header_and_iter(
spec,
ExactChain(
self.components.drain(..).rev(),
implicit_selector.iter().cloned(),
),
)
}
}
impl<Impl: SelectorImpl> Default for SelectorBuilder<Impl> {
#[inline(always)]
fn default() -> Self {
SelectorBuilder {
components: SmallVec::new(),
last_compound_start: None,
}
}
}
// This is effectively a Chain<>, but Chain isn't an ExactSizeIterator, see
// https://github.com/rust-lang/rust/issues/34433
struct ExactChain<A, B>(A, B);
impl<A, B, Item> ExactSizeIterator for ExactChain<A, B>
where
A: ExactSizeIterator<Item = Item>,
B: ExactSizeIterator<Item = Item>,
{
fn len(&self) -> usize {
self.0.len() + self.1.len()
}
}
impl<A, B, Item> Iterator for ExactChain<A, B>
where
A: ExactSizeIterator<Item = Item>,
B: ExactSizeIterator<Item = Item>,
{
type Item = Item;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().or_else(|| self.1.next())
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
/// Flags that indicate at which point of parsing a selector are we.
#[derive(Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
pub(crate) struct SelectorFlags(u8);
bitflags! {
impl SelectorFlags: u8 {
const HAS_PSEUDO = 1 << 0;
const HAS_SLOTTED = 1 << 1;
const HAS_PART = 1 << 2;
const HAS_PARENT = 1 << 3;
const HAS_HOST = 1 << 4;
const HAS_SCOPE = 1 << 5;
}
}
impl core::fmt::Debug for SelectorFlags {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.is_empty() {
write!(f, "{:#x}", Self::empty().bits())
} else {
bitflags::parser::to_writer(self, f)
}
}
}
impl SelectorFlags {
/// When you nest a pseudo-element with something like:
///
/// ::before { & { .. } }
///
/// It is not supposed to work, because :is(::before) is invalid. We can't propagate the
/// pseudo-flags from inner to outer selectors, to avoid breaking our invariants.
pub(crate) fn forbidden_for_nesting() -> Self {
Self::HAS_PSEUDO | Self::HAS_SLOTTED | Self::HAS_PART
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
pub struct SpecificityAndFlags {
/// There are two free bits here, since we use ten bits for each specificity
/// kind (id, class, element).
pub(crate) specificity: u32,
/// There's padding after this field due to the size of the flags.
pub(crate) flags: SelectorFlags,
}
const MAX_10BIT: u32 = (1u32 << 10) - 1;
#[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) struct Specificity {
id_selectors: u32,
class_like_selectors: u32,
element_selectors: u32,
}
impl Specificity {
// Return the specficity of a single class-like selector.
#[inline]
pub fn single_class_like() -> Self {
Specificity {
id_selectors: 0,
class_like_selectors: 1,
element_selectors: 0,
}
}
}
impl From<u32> for Specificity {
#[inline]
fn from(value: u32) -> Specificity {
assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT);
Specificity {
id_selectors: value >> 20,
class_like_selectors: (value >> 10) & MAX_10BIT,
element_selectors: value & MAX_10BIT,
}
}
}
impl From<Specificity> for u32 {
#[inline]
fn from(specificity: Specificity) -> u32 {
cmp::min(specificity.id_selectors, MAX_10BIT) << 20
| cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10
| cmp::min(specificity.element_selectors, MAX_10BIT)
}
}
fn specificity_and_flags<Impl>(
iter: slice::Iter<Component<Impl>>,
for_nesting_parent: bool,
) -> SpecificityAndFlags
where
Impl: SelectorImpl,
{
complex_selector_specificity_and_flags(iter, for_nesting_parent).into()
}
fn complex_selector_specificity_and_flags<Impl>(
iter: slice::Iter<Component<Impl>>,
for_nesting_parent: bool,
) -> SpecificityAndFlags
where
Impl: SelectorImpl,
{
fn component_specificity<Impl>(
simple_selector: &Component<Impl>,
specificity: &mut Specificity,
flags: &mut SelectorFlags,
for_nesting_parent: bool,
) where
Impl: SelectorImpl,
{
match *simple_selector {
Component::Combinator(..) => {},
Component::ParentSelector => flags.insert(SelectorFlags::HAS_PARENT),
Component::Part(..) => {
flags.insert(SelectorFlags::HAS_PART);
if !for_nesting_parent {
specificity.element_selectors += 1
}
},
Component::PseudoElement(ref pseudo) => {
use crate::parser::PseudoElement;
flags.insert(SelectorFlags::HAS_PSEUDO);
if !for_nesting_parent {
specificity.element_selectors += pseudo.specificity_count();
}
},
Component::LocalName(..) => specificity.element_selectors += 1,
Component::Slotted(ref selector) => {
flags.insert(SelectorFlags::HAS_SLOTTED);
if !for_nesting_parent {
specificity.element_selectors += 1;
// Note that due to the way ::slotted works we only compete with
// other ::slotted rules, so the above rule doesn't really
// matter, but we do it still for consistency with other
// pseudo-elements.
//
// See: https://github.com/w3c/csswg-drafts/issues/1915
*specificity += Specificity::from(selector.specificity());
}
flags.insert(selector.flags());
},
Component::Host(ref selector) => {
flags.insert(SelectorFlags::HAS_HOST);
specificity.class_like_selectors += 1;
if let Some(ref selector) = *selector {
// See: https://github.com/w3c/csswg-drafts/issues/1915
*specificity += Specificity::from(selector.specificity());
flags.insert(selector.flags());
}
},
Component::ID(..) => {
specificity.id_selectors += 1;
},
Component::Class(..)
| Component::AttributeInNoNamespace { .. }
| Component::AttributeInNoNamespaceExists { .. }
| Component::AttributeOther(..)
| Component::Root
| Component::Empty
| Component::Nth(..)
| Component::NonTSPseudoClass(..) => {
specificity.class_like_selectors += 1;
},
Component::Scope | Component::ImplicitScope => {
flags.insert(SelectorFlags::HAS_SCOPE);
if matches!(*simple_selector, Component::Scope) {
specificity.class_like_selectors += 1;
}
},
Component::NthOf(ref nth_of_data) => {
// https://drafts.csswg.org/selectors/#specificity-rules:
//
// The specificity of the :nth-last-child() pseudo-class,
// like the :nth-child() pseudo-class, combines the
// specificity of a regular pseudo-class with that of its
// selector argument S.
specificity.class_like_selectors += 1;
let sf = selector_list_specificity_and_flags(
nth_of_data.selectors().iter(),
for_nesting_parent,
);
*specificity += Specificity::from(sf.specificity);
flags.insert(sf.flags);
},
// https://drafts.csswg.org/selectors/#specificity-rules:
//
// The specificity of an :is(), :not(), or :has() pseudo-class
// is replaced by the specificity of the most specific complex
// selector in its selector list argument.
Component::Where(ref list)
| Component::Negation(ref list)
| Component::Is(ref list) => {
let sf = selector_list_specificity_and_flags(
list.slice().iter(),
/* nested = */ true,
);
if !matches!(*simple_selector, Component::Where(..)) {
*specificity += Specificity::from(sf.specificity);
}
flags.insert(sf.flags);
},
Component::Has(ref relative_selectors) => {
let sf = relative_selector_list_specificity_and_flags(
relative_selectors,
for_nesting_parent,
);
*specificity += Specificity::from(sf.specificity);
flags.insert(sf.flags);
},
Component::ExplicitUniversalType
| Component::ExplicitAnyNamespace
| Component::ExplicitNoNamespace
| Component::DefaultNamespace(..)
| Component::Namespace(..)
| Component::RelativeSelectorAnchor
| Component::Invalid(..) => {
// Does not affect specificity
},
}
}
let mut specificity = Default::default();
let mut flags = Default::default();
for simple_selector in iter {
component_specificity(
&simple_selector,
&mut specificity,
&mut flags,
for_nesting_parent,
);
}
SpecificityAndFlags {
specificity: specificity.into(),
flags,
}
}
/// Finds the maximum specificity of elements in the list and returns it.
pub(crate) fn selector_list_specificity_and_flags<'a, Impl: SelectorImpl>(
itr: impl Iterator<Item = &'a Selector<Impl>>,
for_nesting_parent: bool,
) -> SpecificityAndFlags {
let mut specificity = 0;
let mut flags = SelectorFlags::empty();
for selector in itr {
let selector_flags = selector.flags();
let selector_specificity = if for_nesting_parent
&& selector_flags.intersects(SelectorFlags::forbidden_for_nesting())
{
// In this case we need to re-compute the specificity.
specificity_and_flags(selector.iter_raw_match_order(), for_nesting_parent).specificity
} else {
selector.specificity()
};
specificity = std::cmp::max(specificity, selector_specificity);
flags.insert(selector.flags());
}
SpecificityAndFlags { specificity, flags }
}
pub(crate) fn relative_selector_list_specificity_and_flags<Impl: SelectorImpl>(
list: &[RelativeSelector<Impl>],
for_nesting_parent: bool,
) -> SpecificityAndFlags {
selector_list_specificity_and_flags(list.iter().map(|rel| &rel.selector), for_nesting_parent)
}
+494
View File
@@ -0,0 +1,494 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::attr::CaseSensitivity;
use crate::bloom::BloomFilter;
use crate::nth_index_cache::{NthIndexCache, NthIndexCacheInner};
use crate::parser::{Selector, SelectorImpl};
use crate::relative_selector::cache::RelativeSelectorCache;
use crate::relative_selector::filter::RelativeSelectorFilterMap;
use crate::tree::{Element, OpaqueElement};
/// What kind of selector matching mode we should use.
///
/// There are two modes of selector matching. The difference is only noticeable
/// in presence of pseudo-elements.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MatchingMode {
/// Don't ignore any pseudo-element selectors.
Normal,
/// Ignores any stateless pseudo-element selectors in the rightmost sequence
/// of simple selectors.
///
/// This is useful, for example, to match against ::before when you aren't a
/// pseudo-element yourself.
///
/// For example, in presence of `::before:hover`, it would never match, but
/// `::before` would be ignored as in "matching".
///
/// It's required for all the selectors you match using this mode to have a
/// pseudo-element.
ForStatelessPseudoElement,
}
/// The mode to use when matching unvisited and visited links.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VisitedHandlingMode {
/// All links are matched as if they are unvisted.
AllLinksUnvisited,
/// All links are matched as if they are visited and unvisited (both :link
/// and :visited match).
///
/// This is intended to be used from invalidation code, to be conservative
/// about whether we need to restyle a link.
AllLinksVisitedAndUnvisited,
/// A element's "relevant link" is the element being matched if it is a link
/// or the nearest ancestor link. The relevant link is matched as though it
/// is visited, and all other links are matched as if they are unvisited.
RelevantLinkVisited,
}
impl VisitedHandlingMode {
#[inline]
pub fn matches_visited(&self) -> bool {
matches!(
*self,
VisitedHandlingMode::RelevantLinkVisited
| VisitedHandlingMode::AllLinksVisitedAndUnvisited
)
}
#[inline]
pub fn matches_unvisited(&self) -> bool {
matches!(
*self,
VisitedHandlingMode::AllLinksUnvisited
| VisitedHandlingMode::AllLinksVisitedAndUnvisited
)
}
}
/// The mode to use whether we should matching rules inside @starting-style.
/// https://drafts.csswg.org/css-transitions-2/#starting-style
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IncludeStartingStyle {
/// All without rules inside @starting-style. This is for the most common case because the
/// primary/pseudo styles doesn't use rules inside @starting-style.
No,
/// Get the starting style. The starting style for an element as the after-change style with
/// @starting-style rules applied in addition. In other words, this matches all rules,
/// including rules inside @starting-style.
Yes,
}
/// Whether we need to set selector invalidation flags on elements for this
/// match request.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum NeedsSelectorFlags {
No,
Yes,
}
/// Whether we're matching in the contect of invalidation.
#[derive(Clone, Copy, PartialEq)]
pub enum MatchingForInvalidation {
No,
Yes,
YesForComparison,
}
impl MatchingForInvalidation {
/// Are we matching for invalidation?
pub fn is_for_invalidation(&self) -> bool {
matches!(*self, Self::Yes | Self::YesForComparison)
}
}
/// Which quirks mode is this document in.
///
/// See: https://quirks.spec.whatwg.org/
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum QuirksMode {
/// Quirks mode.
Quirks,
/// Limited quirks mode.
LimitedQuirks,
/// No quirks mode.
NoQuirks,
}
impl QuirksMode {
#[inline]
pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity {
match self {
QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive,
QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,
}
}
}
/// Set of caches (And cache-likes) that speed up expensive selector matches.
#[derive(Default)]
pub struct SelectorCaches {
/// A cache to speed up nth-index-like selectors.
pub nth_index: NthIndexCache,
/// A cache to speed up relative selector matches. See module documentation.
pub relative_selector: RelativeSelectorCache,
/// A map of bloom filters to fast-reject relative selector matches.
pub relative_selector_filter_map: RelativeSelectorFilterMap,
}
/// Data associated with the matching process for a element. This context is
/// used across many selectors for an element, so it's not appropriate for
/// transient data that applies to only a single selector.
pub struct MatchingContext<'a, Impl>
where
Impl: SelectorImpl,
{
/// Input with the matching mode we should use when matching selectors.
matching_mode: MatchingMode,
/// Input with the bloom filter used to fast-reject selectors.
pub bloom_filter: Option<&'a BloomFilter>,
/// The element which is going to match :scope pseudo-class. It can be
/// either one :scope element, or the scoping element.
///
/// Note that, although in theory there can be multiple :scope elements,
/// in current specs, at most one is specified, and when there is one,
/// scoping element is not relevant anymore, so we use a single field for
/// them.
///
/// When this is None, :scope will match the root element.
///
/// See https://drafts.csswg.org/selectors-4/#scope-pseudo
pub scope_element: Option<OpaqueElement>,
/// The current shadow host we're collecting :host rules for.
pub current_host: Option<OpaqueElement>,
/// Controls how matching for links is handled.
visited_handling: VisitedHandlingMode,
/// Controls if we should match rules in @starting-style.
pub include_starting_style: IncludeStartingStyle,
/// Whether there are any rules inside @starting-style.
pub has_starting_style: bool,
/// Whether we're currently matching a featureless element.
pub featureless: bool,
/// The current nesting level of selectors that we're matching.
nesting_level: usize,
/// Whether we're inside a negation or not.
in_negation: bool,
/// An optional hook function for checking whether a pseudo-element
/// should match when matching_mode is ForStatelessPseudoElement.
pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>,
/// Extra implementation-dependent matching data.
pub extra_data: Impl::ExtraMatchingData<'a>,
/// The current element we're anchoring on for evaluating the relative selector.
current_relative_selector_anchor: Option<OpaqueElement>,
quirks_mode: QuirksMode,
needs_selector_flags: NeedsSelectorFlags,
/// Whether we're matching in the contect of invalidation.
matching_for_invalidation: MatchingForInvalidation,
/// Caches to speed up expensive selector matches.
pub selector_caches: &'a mut SelectorCaches,
classes_and_ids_case_sensitivity: CaseSensitivity,
_impl: ::std::marker::PhantomData<Impl>,
}
impl<'a, Impl> MatchingContext<'a, Impl>
where
Impl: SelectorImpl,
{
/// Constructs a new `MatchingContext`.
pub fn new(
matching_mode: MatchingMode,
bloom_filter: Option<&'a BloomFilter>,
selector_caches: &'a mut SelectorCaches,
quirks_mode: QuirksMode,
needs_selector_flags: NeedsSelectorFlags,
matching_for_invalidation: MatchingForInvalidation,
) -> Self {
Self::new_for_visited(
matching_mode,
bloom_filter,
selector_caches,
VisitedHandlingMode::AllLinksUnvisited,
IncludeStartingStyle::No,
quirks_mode,
needs_selector_flags,
matching_for_invalidation,
)
}
/// Constructs a new `MatchingContext` for use in visited matching.
pub fn new_for_visited(
matching_mode: MatchingMode,
bloom_filter: Option<&'a BloomFilter>,
selector_caches: &'a mut SelectorCaches,
visited_handling: VisitedHandlingMode,
include_starting_style: IncludeStartingStyle,
quirks_mode: QuirksMode,
needs_selector_flags: NeedsSelectorFlags,
matching_for_invalidation: MatchingForInvalidation,
) -> Self {
Self {
matching_mode,
bloom_filter,
visited_handling,
include_starting_style,
has_starting_style: false,
quirks_mode,
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
needs_selector_flags,
matching_for_invalidation,
scope_element: None,
current_host: None,
featureless: false,
nesting_level: 0,
in_negation: false,
pseudo_element_matching_fn: None,
extra_data: Default::default(),
current_relative_selector_anchor: None,
selector_caches,
_impl: ::std::marker::PhantomData,
}
}
// Grab a reference to the appropriate cache.
#[inline]
pub fn nth_index_cache(
&mut self,
is_of_type: bool,
is_from_end: bool,
selectors: &[Selector<Impl>],
) -> &mut NthIndexCacheInner {
self.selector_caches
.nth_index
.get(is_of_type, is_from_end, selectors)
}
/// Whether we're matching a nested selector.
#[inline]
pub fn is_nested(&self) -> bool {
self.nesting_level != 0
}
/// Whether we're matching inside a :not(..) selector.
#[inline]
pub fn in_negation(&self) -> bool {
self.in_negation
}
/// The quirks mode of the document.
#[inline]
pub fn quirks_mode(&self) -> QuirksMode {
self.quirks_mode
}
/// The matching-mode for this selector-matching operation.
#[inline]
pub fn matching_mode(&self) -> MatchingMode {
self.matching_mode
}
/// Whether we need to set selector flags.
#[inline]
pub fn needs_selector_flags(&self) -> bool {
self.needs_selector_flags == NeedsSelectorFlags::Yes
}
/// Whether or not we're matching to invalidate.
#[inline]
pub fn matching_for_invalidation(&self) -> bool {
self.matching_for_invalidation.is_for_invalidation()
}
/// Whether or not we're comparing for invalidation, if we are matching for invalidation.
#[inline]
pub fn matching_for_invalidation_comparison(&self) -> Option<bool> {
match self.matching_for_invalidation {
MatchingForInvalidation::No => None,
MatchingForInvalidation::Yes => Some(false),
MatchingForInvalidation::YesForComparison => Some(true),
}
}
/// Run the given matching function for before/after invalidation comparison.
#[inline]
pub fn for_invalidation_comparison<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
debug_assert!(
self.matching_for_invalidation(),
"Not matching for invalidation?"
);
let prev = self.matching_for_invalidation;
self.matching_for_invalidation = MatchingForInvalidation::YesForComparison;
let result = f(self);
self.matching_for_invalidation = prev;
result
}
/// The case-sensitivity for class and ID selectors
#[inline]
pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
self.classes_and_ids_case_sensitivity
}
/// Runs F with a deeper nesting level.
#[inline]
pub fn nest<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.nesting_level += 1;
let result = f(self);
self.nesting_level -= 1;
result
}
/// Runs F with a deeper nesting level, and marking ourselves in a negation,
/// for a :not(..) selector, for example.
#[inline]
pub fn nest_for_negation<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
let old_in_negation = self.in_negation;
self.in_negation = !self.in_negation;
let result = self.nest(f);
self.in_negation = old_in_negation;
result
}
#[inline]
pub fn visited_handling(&self) -> VisitedHandlingMode {
self.visited_handling
}
/// Runs F with a different featureless element flag.
#[inline]
pub fn with_featureless<F, R>(&mut self, featureless: bool, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
let orig = self.featureless;
self.featureless = featureless;
let result = f(self);
self.featureless = orig;
result
}
/// Returns whether the currently matching element is acting as a featureless element (e.g.,
/// because we've crossed a shadow boundary). This is used to implement the :host selector
/// rules properly.
#[inline]
pub fn featureless(&self) -> bool {
self.featureless
}
/// Runs F with a different VisitedHandlingMode.
#[inline]
pub fn with_visited_handling_mode<F, R>(
&mut self,
handling_mode: VisitedHandlingMode,
f: F,
) -> R
where
F: FnOnce(&mut Self) -> R,
{
let original_handling_mode = self.visited_handling;
self.visited_handling = handling_mode;
let result = f(self);
self.visited_handling = original_handling_mode;
result
}
/// Runs F with a given shadow host which is the root of the tree whose
/// rules we're matching.
#[inline]
pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R
where
E: Element,
F: FnOnce(&mut Self) -> R,
{
let original_host = self.current_host.take();
self.current_host = host.map(|h| h.opaque());
let result = f(self);
self.current_host = original_host;
result
}
/// Returns the current shadow host whose shadow root we're matching rules
/// against.
#[inline]
pub fn shadow_host(&self) -> Option<OpaqueElement> {
self.current_host
}
/// Runs F with a deeper nesting level, with the given element as the anchor,
/// for a :has(...) selector, for example.
#[inline]
pub fn nest_for_relative_selector<F, R>(&mut self, anchor: OpaqueElement, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
debug_assert!(
self.current_relative_selector_anchor.is_none(),
"Nesting should've been rejected at parse time"
);
self.current_relative_selector_anchor = Some(anchor);
let result = self.nest(f);
self.current_relative_selector_anchor = None;
result
}
/// Runs F with a deeper nesting level, with the given element as the scope.
#[inline]
pub fn nest_for_scope<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
let original_scope_element = self.scope_element;
self.scope_element = scope;
let result = f(self);
self.scope_element = original_scope_element;
result
}
/// Runs F with a deeper nesting level, with the given element as the scope, for
/// matching `scope-start` and/or `scope-end` conditions.
#[inline]
pub fn nest_for_scope_condition<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
let original_matching_mode = self.matching_mode;
// We may as well be matching for a pseudo-element inside `@scope`, but
// the scope-defining selectors wouldn't be matching them.
self.matching_mode = MatchingMode::Normal;
let result = self.nest_for_scope(scope, f);
self.matching_mode = original_matching_mode;
result
}
/// Returns the current anchor element to evaluate the relative selector against.
#[inline]
pub fn relative_selector_anchor(&self) -> Option<OpaqueElement> {
self.current_relative_selector_anchor
}
}
+131
View File
@@ -0,0 +1,131 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Kleen logic: https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics
/// A "trilean" value based on Kleen logic.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum KleeneValue {
/// False
False = 0,
/// True
True = 1,
/// Either true or false, but we’re not sure which yet.
Unknown,
}
impl From<bool> for KleeneValue {
fn from(b: bool) -> Self {
if b {
Self::True
} else {
Self::False
}
}
}
impl KleeneValue {
/// Turns this Kleene value to a bool, taking the unknown value as an
/// argument.
pub fn to_bool(self, unknown: bool) -> bool {
match self {
Self::True => true,
Self::False => false,
Self::Unknown => unknown,
}
}
/// Return true if any result of f() is definitely true.
/// Otherwise, return the `or` of all values.
/// Returns false if empty, like that of `Iterator`.
#[inline(always)]
pub fn any<T>(iter: impl Iterator<Item = T>, f: impl FnMut(T) -> Self) -> Self {
Self::any_value(iter, Self::True, Self::False, |a, b| a | b, f)
}
/// Return false if any results of f() is definitely false.
/// Otherwise, return the `and` of all values.
/// Returns true if empty, opposite of `Iterator`.
#[inline(always)]
pub fn any_false<T>(iter: impl Iterator<Item = T>, f: impl FnMut(T) -> Self) -> Self {
Self::any_value(iter, Self::False, Self::True, |a, b| a & b, f)
}
#[inline(always)]
fn any_value<T>(
iter: impl Iterator<Item = T>,
value: Self,
on_empty: Self,
op: impl Fn(Self, Self) -> Self,
mut f: impl FnMut(T) -> Self,
) -> Self {
let mut result = None;
for item in iter {
let r = f(item);
if r == value {
return r;
}
if let Some(v) = result.as_mut() {
*v = op(*v, r);
} else {
result = Some(r);
}
}
result.unwrap_or(on_empty)
}
}
impl std::ops::Not for KleeneValue {
type Output = Self;
fn not(self) -> Self {
match self {
Self::True => Self::False,
Self::False => Self::True,
Self::Unknown => Self::Unknown,
}
}
}
// Implements the logical and operation.
impl std::ops::BitAnd for KleeneValue {
type Output = Self;
fn bitand(self, other: Self) -> Self {
if self == Self::False || other == Self::False {
return Self::False;
}
if self == Self::Unknown || other == Self::Unknown {
return Self::Unknown;
}
Self::True
}
}
// Implements the logical or operation.
impl std::ops::BitOr for KleeneValue {
type Output = Self;
fn bitor(self, other: Self) -> Self {
if self == Self::True || other == Self::True {
return Self::True;
}
if self == Self::Unknown || other == Self::Unknown {
return Self::Unknown;
}
Self::False
}
}
impl std::ops::BitOrAssign for KleeneValue {
fn bitor_assign(&mut self, other: Self) {
*self = *self | other;
}
}
impl std::ops::BitAndAssign for KleeneValue {
fn bitand_assign(&mut self, other: Self) {
*self = *self & other;
}
}
+23
View File
@@ -0,0 +1,23 @@
/* 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 https://mozilla.org/MPL/2.0/. */
// Make |cargo bench| work.
#![cfg_attr(feature = "bench", feature(test))]
pub mod attr;
pub mod bloom;
mod builder;
pub mod context;
pub mod kleene_value;
pub mod matching;
mod nth_index_cache;
pub mod parser;
pub mod relative_selector;
pub mod sink;
mod tree;
pub mod visitor;
pub use crate::nth_index_cache::NthIndexCache;
pub use crate::parser::{Parser, SelectorImpl, SelectorList};
pub use crate::tree::{Element, OpaqueElement};
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::hash::Hash;
use crate::{parser::Selector, tree::OpaqueElement, SelectorImpl};
use rustc_hash::FxHashMap;
/// A cache to speed up matching of nth-index-like selectors.
///
/// See [1] for some discussion around the design tradeoffs.
///
/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1401855#c3
#[derive(Default)]
pub struct NthIndexCache {
nth: NthIndexCacheInner,
nth_of_selectors: NthIndexOfSelectorsCaches,
nth_last: NthIndexCacheInner,
nth_last_of_selectors: NthIndexOfSelectorsCaches,
nth_of_type: NthIndexCacheInner,
nth_last_of_type: NthIndexCacheInner,
}
impl NthIndexCache {
/// Gets the appropriate cache for the given parameters.
pub fn get<Impl: SelectorImpl>(
&mut self,
is_of_type: bool,
is_from_end: bool,
selectors: &[Selector<Impl>],
) -> &mut NthIndexCacheInner {
if is_of_type {
return if is_from_end {
&mut self.nth_last_of_type
} else {
&mut self.nth_of_type
};
}
if !selectors.is_empty() {
return if is_from_end {
self.nth_last_of_selectors.lookup(selectors)
} else {
self.nth_of_selectors.lookup(selectors)
};
}
if is_from_end {
&mut self.nth_last
} else {
&mut self.nth
}
}
}
#[derive(Hash, Eq, PartialEq)]
struct SelectorListCacheKey(usize);
/// Use the selector list's pointer as the cache key
impl SelectorListCacheKey {
// :nth-child of selectors are reference-counted with `ThinArc`, so we know their pointers are stable.
fn new<Impl: SelectorImpl>(selectors: &[Selector<Impl>]) -> Self {
Self(selectors.as_ptr() as usize)
}
}
/// Use a different map of cached indices per :nth-child's or :nth-last-child's selector list
#[derive(Default)]
pub struct NthIndexOfSelectorsCaches(FxHashMap<SelectorListCacheKey, NthIndexCacheInner>);
/// Get or insert a map of cached incides for the selector list of this
/// particular :nth-child or :nth-last-child pseudoclass
impl NthIndexOfSelectorsCaches {
pub fn lookup<Impl: SelectorImpl>(
&mut self,
selectors: &[Selector<Impl>],
) -> &mut NthIndexCacheInner {
self.0
.entry(SelectorListCacheKey::new(selectors))
.or_default()
}
}
/// The concrete per-pseudo-class cache.
#[derive(Default)]
pub struct NthIndexCacheInner(FxHashMap<OpaqueElement, i32>);
impl NthIndexCacheInner {
/// Does a lookup for a given element in the cache.
pub fn lookup(&mut self, el: OpaqueElement) -> Option<i32> {
self.0.get(&el).copied()
}
/// Inserts an entry into the cache.
pub fn insert(&mut self, element: OpaqueElement, index: i32) {
self.0.insert(element, index);
}
/// Returns whether the cache is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use rustc_hash::FxHashMap;
/// Relative selector cache. This is useful for following cases.
/// First case is non-subject relative selector: Imagine `.anchor:has(<..>) ~ .foo`, with DOM
/// `.anchor + .foo + .. + .foo`. Each match on `.foo` triggers `:has()` traversal that
/// yields the same result. This is simple enough, since we just need to store
/// the exact match on that anchor pass/fail.
/// Second case is `querySelectorAll`: Imagine `querySelectorAll(':has(.a)')`, with DOM
/// `div > .. > div > .a`. When the we perform the traversal at the top div,
/// we basically end up evaluating `:has(.a)` for all anchors, which could be reused.
/// Also consider the sibling version: `querySelectorAll(':has(~ .a)')` with DOM
/// `div + .. + div + .a`.
/// TODO(dshin): Second case is not yet handled. That is tracked in Bug 1845291.
use std::hash::Hash;
use crate::parser::{RelativeSelector, SelectorKey};
use crate::{tree::OpaqueElement, SelectorImpl};
/// Match data for a given element and a selector.
#[derive(Clone, Copy)]
pub enum RelativeSelectorCachedMatch {
/// This selector matches this element.
Matched,
/// This selector does not match this element.
NotMatched,
}
impl RelativeSelectorCachedMatch {
/// Is the cached result a match?
pub fn matched(&self) -> bool {
matches!(*self, Self::Matched)
}
}
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
struct Key {
element: OpaqueElement,
selector: SelectorKey,
}
impl Key {
pub fn new<Impl: SelectorImpl>(
element: OpaqueElement,
selector: &RelativeSelector<Impl>,
) -> Self {
Key {
element,
selector: SelectorKey::new(&selector.selector),
}
}
}
/// Cache to speed up matching of relative selectors.
#[derive(Default)]
pub struct RelativeSelectorCache {
cache: FxHashMap<Key, RelativeSelectorCachedMatch>,
}
impl RelativeSelectorCache {
/// Add a relative selector match into the cache.
pub fn add<Impl: SelectorImpl>(
&mut self,
anchor: OpaqueElement,
selector: &RelativeSelector<Impl>,
matched: RelativeSelectorCachedMatch,
) {
self.cache.insert(Key::new(anchor, selector), matched);
}
/// Check if we have a cache entry for the element.
pub fn lookup<Impl: SelectorImpl>(
&mut self,
element: OpaqueElement,
selector: &RelativeSelector<Impl>,
) -> Option<RelativeSelectorCachedMatch> {
self.cache.get(&Key::new(element, selector)).copied()
}
}
@@ -0,0 +1,159 @@
/* 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 https://mozilla.org/MPL/2.0/. */
/// Bloom filter for relative selectors.
use rustc_hash::FxHashMap;
use crate::bloom::BloomFilter;
use crate::context::QuirksMode;
use crate::parser::{collect_selector_hashes, RelativeSelector, RelativeSelectorMatchHint};
use crate::tree::{Element, OpaqueElement};
use crate::SelectorImpl;
enum Entry {
/// Filter lookup happened once. Construction of the filter is expensive,
/// so this is set when the element for subtree traversal is encountered.
Lookup,
/// Filter lookup happened more than once, and the filter for this element's
/// subtree traversal is constructed. Could use special handlings for pseudo-classes
/// such as `:hover` and `:focus`, see Bug 1845503.
HasFilter(Box<BloomFilter>),
}
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
enum TraversalKind {
Children,
Descendants,
}
fn add_to_filter<E: Element>(element: &E, filter: &mut BloomFilter, kind: TraversalKind) -> bool {
let mut child = element.first_element_child();
while let Some(e) = child {
if !e.add_element_unique_hashes(filter) {
return false;
}
if kind == TraversalKind::Descendants {
if !add_to_filter(&e, filter, kind) {
return false;
}
}
child = e.next_sibling_element();
}
true
}
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
struct Key(OpaqueElement, TraversalKind);
/// Map of bloom filters for fast-rejecting relative selectors.
#[derive(Default)]
pub struct RelativeSelectorFilterMap {
map: FxHashMap<Key, Entry>,
}
fn fast_reject<Impl: SelectorImpl>(
selector: &RelativeSelector<Impl>,
quirks_mode: QuirksMode,
filter: &BloomFilter,
) -> bool {
let mut hashes = [0u32; 4];
let mut len = 0;
// For inner selectors, we only collect from the single rightmost compound.
// This is because inner selectors can cause breakouts: e.g. `.anchor:has(:is(.a .b) .c)`
// can match when `.a` is the ancestor of `.anchor`. Including `.a` would possibly fast
// reject the subtree for not having `.a`, even if the selector would match.
// Technically, if the selector's traversal is non-sibling subtree, we can traverse
// inner selectors up to the point where a descendant/child combinator is encountered
// (e.g. In `.anchor:has(:is(.a ~ .b) .c)`, `.a` is guaranteed to be the a descendant
// of `.anchor`). While that can be separately handled, well, this is simpler.
collect_selector_hashes(
selector.selector.iter(),
quirks_mode,
&mut hashes,
&mut len,
|s| s.iter(),
);
for i in 0..len {
if !filter.might_contain_hash(hashes[i]) {
// Definitely rejected.
return true;
}
}
false
}
impl RelativeSelectorFilterMap {
fn get_filter<E: Element>(&mut self, element: &E, kind: TraversalKind) -> Option<&BloomFilter> {
// Insert flag to indicate that we looked up the filter once, and
// create the filter if and only if that flag is there.
let key = Key(element.opaque(), kind);
let entry = self
.map
.entry(key)
.and_modify(|entry| {
if !matches!(entry, Entry::Lookup) {
return;
}
let mut filter = BloomFilter::new();
// Go through all children/descendants of this element and add their hashes.
if add_to_filter(element, &mut filter, kind) {
*entry = Entry::HasFilter(Box::new(filter));
}
})
.or_insert(Entry::Lookup);
match entry {
Entry::Lookup => None,
Entry::HasFilter(ref filter) => Some(filter.as_ref()),
}
}
/// Potentially reject the given selector for this element.
/// This may seem redundant in presence of the cache, but the cache keys into the
/// selector-element pair specifically, while this filter keys to the element
/// and the traversal kind, so it is useful for handling multiple selectors
/// that effectively end up looking at the same(-ish, for siblings) subtree.
pub fn fast_reject<Impl: SelectorImpl, E: Element>(
&mut self,
element: &E,
selector: &RelativeSelector<Impl>,
quirks_mode: QuirksMode,
) -> bool {
if matches!(
selector.match_hint,
RelativeSelectorMatchHint::InNextSibling
) {
// Don't bother.
return false;
}
let is_sibling = matches!(
selector.match_hint,
RelativeSelectorMatchHint::InSibling
| RelativeSelectorMatchHint::InNextSiblingSubtree
| RelativeSelectorMatchHint::InSiblingSubtree
);
let is_subtree = matches!(
selector.match_hint,
RelativeSelectorMatchHint::InSubtree
| RelativeSelectorMatchHint::InNextSiblingSubtree
| RelativeSelectorMatchHint::InSiblingSubtree
);
let kind = if is_subtree {
TraversalKind::Descendants
} else {
TraversalKind::Children
};
if is_sibling {
// Contain the entirety of the parent's children/subtree in the filter, and use that.
// This is less likely to reject, especially for sibling subtree matches; however, it's less
// expensive memory-wise, compared to storing filters for each sibling.
element.parent_element().map_or(false, |parent| {
self.get_filter(&parent, kind)
.map_or(false, |filter| fast_reject(selector, quirks_mode, filter))
})
} else {
self.get_filter(element, kind)
.map_or(false, |filter| fast_reject(selector, quirks_mode, filter))
}
}
}
@@ -0,0 +1,6 @@
/* 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 https://mozilla.org/MPL/2.0/. */
pub mod cache;
pub mod filter;
+31
View File
@@ -0,0 +1,31 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Small helpers to abstract over different containers.
#![deny(missing_docs)]
use smallvec::{Array, SmallVec};
/// A trait to abstract over a `push` method that may be implemented for
/// different kind of types.
///
/// Used to abstract over `Array`, `SmallVec` and `Vec`, and also to implement a
/// type which `push` method does only tweak a byte when we only need to check
/// for the presence of something.
pub trait Push<T> {
/// Push a value into self.
fn push(&mut self, value: T);
}
impl<T> Push<T> for Vec<T> {
fn push(&mut self, value: T) {
Vec::push(self, value);
}
}
impl<A: Array> Push<A::Item> for SmallVec<A> {
fn push(&mut self, value: A::Item) {
SmallVec::push(self, value);
}
}
+184
View File
@@ -0,0 +1,184 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
//! between layout and style.
use crate::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
use crate::bloom::BloomFilter;
use crate::matching::{ElementSelectorFlags, MatchingContext};
use crate::parser::SelectorImpl;
use std::fmt::Debug;
use std::ptr::NonNull;
/// Opaque representation of an Element, for identity comparisons.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct OpaqueElement(NonNull<()>);
unsafe impl Send for OpaqueElement {}
// This should be safe given that we do not provide a way to recover
// the original reference.
unsafe impl Sync for OpaqueElement {}
impl OpaqueElement {
/// Creates a new OpaqueElement from an arbitrarily-typed pointer.
pub fn new<T>(ptr: &T) -> Self {
unsafe {
OpaqueElement(NonNull::new_unchecked(
ptr as *const T as *const () as *mut (),
))
}
}
/// Creates a new OpaqueElement from a type-erased non-null pointer
pub fn from_non_null_ptr(ptr: NonNull<()>) -> Self {
Self(ptr)
}
/// Returns a const ptr to the contained reference. Unsafe especially
/// since Element can be recovered and potentially-mutated.
pub unsafe fn as_const_ptr<T>(&self) -> *const T {
self.0.as_ptr() as *const T
}
}
pub trait Element: Sized + Clone + Debug {
type Impl: SelectorImpl;
/// Converts self into an opaque representation.
fn opaque(&self) -> OpaqueElement;
fn parent_element(&self) -> Option<Self>;
/// Whether the parent node of this element is a shadow root.
fn parent_node_is_shadow_root(&self) -> bool;
/// The host of the containing shadow root, if any.
fn containing_shadow_host(&self) -> Option<Self>;
/// The parent of a given pseudo-element, after matching a pseudo-element
/// selector.
///
/// This is guaranteed to be called in a pseudo-element.
fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.is_pseudo_element());
self.parent_element()
}
/// Whether we're matching on a pseudo-element.
fn is_pseudo_element(&self) -> bool;
/// Skips non-element nodes
fn prev_sibling_element(&self) -> Option<Self>;
/// Skips non-element nodes
fn next_sibling_element(&self) -> Option<Self>;
/// Skips non-element nodes
fn first_element_child(&self) -> Option<Self>;
fn is_html_element_in_html_document(&self) -> bool;
fn has_local_name(&self, local_name: &<Self::Impl as SelectorImpl>::BorrowedLocalName) -> bool;
/// Empty string for no namespace
fn has_namespace(&self, ns: &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl) -> bool;
/// Whether this element and the `other` element have the same local name and namespace.
fn is_same_type(&self, other: &Self) -> bool;
fn attr_matches(
&self,
ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
local_name: &<Self::Impl as SelectorImpl>::LocalName,
operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>,
) -> bool;
fn has_attr_in_no_namespace(
&self,
local_name: &<Self::Impl as SelectorImpl>::LocalName,
) -> bool {
self.attr_matches(
&NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<Self::Impl>()),
local_name,
&AttrSelectorOperation::Exists,
)
}
fn match_non_ts_pseudo_class(
&self,
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
context: &mut MatchingContext<Self::Impl>,
) -> bool;
fn match_pseudo_element(
&self,
pe: &<Self::Impl as SelectorImpl>::PseudoElement,
context: &mut MatchingContext<Self::Impl>,
) -> bool;
/// Sets selector flags on the elemnt itself or the parent, depending on the
/// flags, which indicate what kind of work may need to be performed when
/// DOM state changes.
fn apply_selector_flags(&self, flags: ElementSelectorFlags);
/// Whether this element is a `link`.
fn is_link(&self) -> bool;
/// Returns whether the element is an HTML <slot> element.
fn is_html_slot_element(&self) -> bool;
/// Returns the assigned <slot> element this element is assigned to.
///
/// Necessary for the `::slotted` pseudo-class.
fn assigned_slot(&self) -> Option<Self> {
None
}
fn has_id(
&self,
id: &<Self::Impl as SelectorImpl>::Identifier,
case_sensitivity: CaseSensitivity,
) -> bool;
fn has_class(
&self,
name: &<Self::Impl as SelectorImpl>::Identifier,
case_sensitivity: CaseSensitivity,
) -> bool;
fn has_custom_state(&self, name: &<Self::Impl as SelectorImpl>::Identifier) -> bool;
/// Returns the mapping from the `exportparts` attribute in the reverse
/// direction, that is, in an outer-tree -> inner-tree direction.
fn imported_part(
&self,
name: &<Self::Impl as SelectorImpl>::Identifier,
) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
fn is_part(&self, name: &<Self::Impl as SelectorImpl>::Identifier) -> 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;
/// Returns whether this element should ignore matching nth child
/// selector.
fn ignores_nth_child_selectors(&self) -> bool {
false
}
/// Add hashes unique to this element to the given filter, returning true
/// if any got added.
fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool;
}
+137
View File
@@ -0,0 +1,137 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Visitor traits for selectors.
#![deny(missing_docs)]
use crate::attr::NamespaceConstraint;
use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl};
use bitflags::bitflags;
/// A trait to visit selector properties.
///
/// All the `visit_foo` methods return a boolean indicating whether the
/// traversal should continue or not.
pub trait SelectorVisitor: Sized {
/// The selector implementation this visitor wants to visit.
type Impl: SelectorImpl;
/// Visit an attribute selector that may match (there are other selectors
/// that may never match, like those containing whitespace or the empty
/// string).
fn visit_attribute_selector(
&mut self,
_namespace: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
_local_name: &<Self::Impl as SelectorImpl>::LocalName,
_local_name_lower: &<Self::Impl as SelectorImpl>::LocalName,
) -> bool {
true
}
/// Visit a simple selector.
fn visit_simple_selector(&mut self, _: &Component<Self::Impl>) -> bool {
true
}
/// Visit a nested relative selector list. The caller is responsible to call visit
/// into the internal selectors if / as needed.
///
/// The default implementation skips it altogether.
fn visit_relative_selector_list(&mut self, _list: &[RelativeSelector<Self::Impl>]) -> bool {
true
}
/// Visit a nested selector list. The caller is responsible to call visit
/// into the internal selectors if / as needed.
///
/// The default implementation does this.
fn visit_selector_list(
&mut self,
_list_kind: SelectorListKind,
list: &[Selector<Self::Impl>],
) -> bool {
for nested in list {
if !nested.visit(self) {
return false;
}
}
true
}
/// Visits a complex selector.
///
/// Gets the combinator to the right of the selector, or `None` if the
/// selector is the rightmost one.
fn visit_complex_selector(&mut self, _combinator_to_right: Option<Combinator>) -> bool {
true
}
}
bitflags! {
/// The kinds of components the visitor is visiting the selector list of, if any
#[derive(Clone, Copy, Default)]
pub struct SelectorListKind: u8 {
/// The visitor is inside :not(..)
const NEGATION = 1 << 0;
/// The visitor is inside :is(..)
const IS = 1 << 1;
/// The visitor is inside :where(..)
const WHERE = 1 << 2;
/// The visitor is inside :nth-child(.. of <selector list>) or
/// :nth-last-child(.. of <selector list>)
const NTH_OF = 1 << 3;
/// The visitor is inside :has(..)
const HAS = 1 << 4;
}
}
impl SelectorListKind {
/// Construct a SelectorListKind for the corresponding component.
pub fn from_component<Impl: SelectorImpl>(component: &Component<Impl>) -> Self {
match component {
Component::Negation(_) => SelectorListKind::NEGATION,
Component::Is(_) => SelectorListKind::IS,
Component::Where(_) => SelectorListKind::WHERE,
Component::NthOf(_) => SelectorListKind::NTH_OF,
_ => SelectorListKind::empty(),
}
}
/// Whether the visitor is inside :not(..)
pub fn in_negation(&self) -> bool {
self.intersects(SelectorListKind::NEGATION)
}
/// Whether the visitor is inside :is(..)
pub fn in_is(&self) -> bool {
self.intersects(SelectorListKind::IS)
}
/// Whether the visitor is inside :where(..)
pub fn in_where(&self) -> bool {
self.intersects(SelectorListKind::WHERE)
}
/// Whether the visitor is inside :nth-child(.. of <selector list>) or
/// :nth-last-child(.. of <selector list>)
pub fn in_nth_of(&self) -> bool {
self.intersects(SelectorListKind::NTH_OF)
}
/// Whether the visitor is inside :has(..)
pub fn in_has(&self) -> bool {
self.intersects(SelectorListKind::HAS)
}
/// Whether this nested selector is relevant for nth-of dependencies.
pub fn relevant_to_nth_of_dependencies(&self) -> bool {
// Order of nesting for `:has` and `:nth-child(.. of ..)` doesn't matter, because:
// * `:has(:nth-child(.. of ..))`: The location of the anchoring element is
// independent from where `:nth-child(.. of ..)` is applied.
// * `:nth-child(.. of :has(..))`: Invalidations inside `:has` must first use the
// `:has` machinary to find the anchor, then carry out the remaining invalidation.
self.in_nth_of() && !self.in_has()
}
}