Skip to content

Commit d8af8ea

Browse files
committed
Another try at actually using an nth index cache
This re-uses the cache only between the match operations for a single select iteration which ensures that the selector is not dropped (and its allocation address re-used) while the cache is alive thereby avoiding the ABA problem fixed by 7fdac0a. This does not require any change in user code to benefit from the increased caching even though some Debug and Clone implementations need to be done manually now.
1 parent c1ab88a commit d8af8ea

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

src/element_ref/mod.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! Element references.
22
3+
use std::fmt;
34
use std::iter::FusedIterator;
45
use std::ops::Deref;
56

67
use ego_tree::iter::{Edge, Traverse};
78
use ego_tree::NodeRef;
89
use html5ever::serialize::{serialize, SerializeOpts, TraversalScope};
10+
use selectors::NthIndexCache;
911

1012
use crate::node::Element;
1113
use crate::{Node, Selector};
@@ -47,6 +49,7 @@ impl<'a> ElementRef<'a> {
4749
scope: *self,
4850
inner,
4951
selector,
52+
nth_index_cache: NthIndexCache::default(),
5053
}
5154
}
5255

@@ -122,11 +125,33 @@ impl<'a> Deref for ElementRef<'a> {
122125
}
123126

124127
/// Iterator over descendent elements matching a selector.
125-
#[derive(Debug, Clone)]
126128
pub struct Select<'a, 'b> {
127129
scope: ElementRef<'a>,
128130
inner: Traverse<'a, Node>,
129131
selector: &'b Selector,
132+
nth_index_cache: NthIndexCache,
133+
}
134+
135+
impl fmt::Debug for Select<'_, '_> {
136+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
137+
fmt.debug_struct("Select")
138+
.field("scope", &self.scope)
139+
.field("inner", &self.inner)
140+
.field("selector", &self.selector)
141+
.field("nth_index_cache", &"..")
142+
.finish()
143+
}
144+
}
145+
146+
impl Clone for Select<'_, '_> {
147+
fn clone(&self) -> Self {
148+
Self {
149+
scope: self.scope,
150+
inner: self.inner.clone(),
151+
selector: self.selector,
152+
nth_index_cache: NthIndexCache::default(),
153+
}
154+
}
130155
}
131156

132157
impl<'a, 'b> Iterator for Select<'a, 'b> {
@@ -136,7 +161,11 @@ impl<'a, 'b> Iterator for Select<'a, 'b> {
136161
for edge in &mut self.inner {
137162
if let Edge::Open(node) = edge {
138163
if let Some(element) = ElementRef::wrap(node) {
139-
if self.selector.matches_with_scope(&element, Some(self.scope)) {
164+
if self.selector.matches_with_scope_and_cache(
165+
&element,
166+
Some(self.scope),
167+
&mut self.nth_index_cache,
168+
) {
140169
return Some(element);
141170
}
142171
}
@@ -169,6 +198,8 @@ impl<'a> Iterator for Text<'a> {
169198
}
170199
}
171200

201+
impl FusedIterator for Text<'_> {}
202+
172203
mod element;
173204
mod serializable;
174205

src/html/mod.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
33
#[cfg(feature = "errors")]
44
use std::borrow::Cow;
5+
use std::fmt;
56
use std::iter::FusedIterator;
67

78
use ego_tree::iter::Nodes;
89
use ego_tree::Tree;
910
use html5ever::serialize::SerializeOpts;
1011
use html5ever::tree_builder::QuirksMode;
11-
use html5ever::QualName;
12-
use html5ever::{driver, serialize};
12+
use html5ever::{driver, serialize, QualName};
13+
use selectors::NthIndexCache;
1314
use tendril::TendrilSink;
1415

1516
use crate::selector::Selector;
@@ -94,6 +95,7 @@ impl Html {
9495
Select {
9596
inner: self.tree.nodes(),
9697
selector,
98+
nth_index_cache: NthIndexCache::default(),
9799
}
98100
}
99101

@@ -122,10 +124,30 @@ impl Html {
122124
}
123125

124126
/// Iterator over elements matching a selector.
125-
#[derive(Debug)]
126127
pub struct Select<'a, 'b> {
127128
inner: Nodes<'a, Node>,
128129
selector: &'b Selector,
130+
nth_index_cache: NthIndexCache,
131+
}
132+
133+
impl fmt::Debug for Select<'_, '_> {
134+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
135+
fmt.debug_struct("Select")
136+
.field("inner", &self.inner)
137+
.field("selector", &self.selector)
138+
.field("nth_index_cache", &"..")
139+
.finish()
140+
}
141+
}
142+
143+
impl Clone for Select<'_, '_> {
144+
fn clone(&self) -> Self {
145+
Self {
146+
inner: self.inner.clone(),
147+
selector: self.selector,
148+
nth_index_cache: NthIndexCache::default(),
149+
}
150+
}
129151
}
130152

131153
impl<'a, 'b> Iterator for Select<'a, 'b> {
@@ -134,7 +156,13 @@ impl<'a, 'b> Iterator for Select<'a, 'b> {
134156
fn next(&mut self) -> Option<ElementRef<'a>> {
135157
for node in self.inner.by_ref() {
136158
if let Some(element) = ElementRef::wrap(node) {
137-
if element.parent().is_some() && self.selector.matches(&element) {
159+
if element.parent().is_some()
160+
&& self.selector.matches_with_scope_and_cache(
161+
&element,
162+
None,
163+
&mut self.nth_index_cache,
164+
)
165+
{
138166
return Some(element);
139167
}
140168
}
@@ -153,7 +181,13 @@ impl<'a, 'b> DoubleEndedIterator for Select<'a, 'b> {
153181
fn next_back(&mut self) -> Option<Self::Item> {
154182
for node in self.inner.by_ref().rev() {
155183
if let Some(element) = ElementRef::wrap(node) {
156-
if element.parent().is_some() && self.selector.matches(&element) {
184+
if element.parent().is_some()
185+
&& self.selector.matches_with_scope_and_cache(
186+
&element,
187+
None,
188+
&mut self.nth_index_cache,
189+
)
190+
{
157191
return Some(element);
158192
}
159193
}

src/selector.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use html5ever::{LocalName, Namespace};
88
use selectors::{
99
matching,
1010
parser::{self, ParseRelative, SelectorList, SelectorParseErrorKind},
11+
NthIndexCache,
1112
};
1213

1314
use crate::error::SelectorErrorKind;
@@ -42,11 +43,22 @@ impl Selector {
4243
/// The optional `scope` argument is used to specify which element has `:scope` pseudo-class.
4344
/// When it is `None`, `:scope` will match the root element.
4445
pub fn matches_with_scope(&self, element: &ElementRef, scope: Option<ElementRef>) -> bool {
45-
let mut nth_index_cache = Default::default();
46+
self.matches_with_scope_and_cache(element, scope, &mut NthIndexCache::default())
47+
}
48+
49+
// The `nth_index_cache` must not be used after `self` is dropped
50+
// to avoid incorrect results (even though no undefined behaviour is possible)
51+
// due to the usage of selector memory addresses as cache keys.
52+
pub(crate) fn matches_with_scope_and_cache(
53+
&self,
54+
element: &ElementRef,
55+
scope: Option<ElementRef>,
56+
nth_index_cache: &mut NthIndexCache,
57+
) -> bool {
4658
let mut context = matching::MatchingContext::new(
4759
matching::MatchingMode::Normal,
4860
None,
49-
&mut nth_index_cache,
61+
nth_index_cache,
5062
matching::QuirksMode::NoQuirks,
5163
matching::NeedsSelectorFlags::No,
5264
matching::IgnoreNthChildForInvalidation::No,

0 commit comments

Comments
 (0)