/rust/registry/src/index.crates.io-1949cf8c6b5b557f/parcel_selectors-0.28.3/matching.rs
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | use crate::attr::{AttrSelectorOperation, NamespaceConstraint, ParsedAttrSelectorOperation}; |
6 | | use crate::bloom::{BloomFilter, BLOOM_HASH_MASK}; |
7 | | use crate::nth_index_cache::NthIndexCacheInner; |
8 | | use crate::parser::{AncestorHashes, Combinator, Component, LocalName, NthType}; |
9 | | use crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList}; |
10 | | use crate::tree::Element; |
11 | | use smallvec::SmallVec; |
12 | | use std::borrow::Borrow; |
13 | | use std::iter; |
14 | | |
15 | | pub use crate::context::*; |
16 | | |
17 | | // The bloom filter for descendant CSS selectors will have a <1% false |
18 | | // positive rate until it has this many selectors in it, then it will |
19 | | // rapidly increase. |
20 | | pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096; |
21 | | |
22 | | bitflags! { |
23 | | /// Set of flags that are set on either the element or its parent (depending |
24 | | /// on the flag) if the element could potentially match a selector. |
25 | | pub struct ElementSelectorFlags: usize { |
26 | | /// When a child is added or removed from the parent, all the children |
27 | | /// must be restyled, because they may match :nth-last-child, |
28 | | /// :last-of-type, :nth-last-of-type, or :only-of-type. |
29 | | const HAS_SLOW_SELECTOR = 1 << 0; |
30 | | |
31 | | /// When a child is added or removed from the parent, any later |
32 | | /// children must be restyled, because they may match :nth-child, |
33 | | /// :first-of-type, or :nth-of-type. |
34 | | const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1; |
35 | | |
36 | | /// When a child is added or removed from the parent, the first and |
37 | | /// last children must be restyled, because they may match :first-child, |
38 | | /// :last-child, or :only-child. |
39 | | const HAS_EDGE_CHILD_SELECTOR = 1 << 2; |
40 | | |
41 | | /// The element has an empty selector, so when a child is appended we |
42 | | /// might need to restyle the parent completely. |
43 | | const HAS_EMPTY_SELECTOR = 1 << 3; |
44 | | } |
45 | | } |
46 | | |
47 | | impl ElementSelectorFlags { |
48 | | /// Returns the subset of flags that apply to the element. |
49 | 0 | pub fn for_self(self) -> ElementSelectorFlags { |
50 | 0 | self & (ElementSelectorFlags::HAS_EMPTY_SELECTOR) |
51 | 0 | } |
52 | | |
53 | | /// Returns the subset of flags that apply to the parent. |
54 | 0 | pub fn for_parent(self) -> ElementSelectorFlags { |
55 | 0 | self |
56 | 0 | & (ElementSelectorFlags::HAS_SLOW_SELECTOR |
57 | 0 | | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS |
58 | 0 | | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) |
59 | 0 | } |
60 | | } |
61 | | |
62 | | /// Holds per-compound-selector data. |
63 | | struct LocalMatchingContext<'a, 'b, 'i, Impl: SelectorImpl<'i>> { |
64 | | shared: &'a mut MatchingContext<'b, 'i, Impl>, |
65 | | matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk, |
66 | | } |
67 | | |
68 | | #[inline(always)] |
69 | 0 | pub fn matches_selector_list<'i, E>( |
70 | 0 | selector_list: &SelectorList<'i, E::Impl>, |
71 | 0 | element: &E, |
72 | 0 | context: &mut MatchingContext<'_, 'i, E::Impl>, |
73 | 0 | ) -> bool |
74 | 0 | where |
75 | 0 | E: Element<'i>, |
76 | | { |
77 | | // This is pretty much any(..) but manually inlined because the compiler |
78 | | // refuses to do so from querySelector / querySelectorAll. |
79 | 0 | for selector in &selector_list.0 { |
80 | 0 | let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {}); |
81 | | |
82 | 0 | if matches { |
83 | 0 | return true; |
84 | 0 | } |
85 | | } |
86 | | |
87 | 0 | false |
88 | 0 | } |
89 | | |
90 | | #[inline(always)] |
91 | 0 | fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { |
92 | | // Check the first three hashes. Note that we can check for zero before |
93 | | // masking off the high bits, since if any of the first three hashes is |
94 | | // zero the fourth will be as well. We also take care to avoid the |
95 | | // special-case complexity of the fourth hash until we actually reach it, |
96 | | // because we usually don't. |
97 | | // |
98 | | // To be clear: this is all extremely hot. |
99 | 0 | for i in 0..3 { |
100 | 0 | let packed = hashes.packed_hashes[i]; |
101 | 0 | if packed == 0 { |
102 | | // No more hashes left - unable to fast-reject. |
103 | 0 | return true; |
104 | 0 | } |
105 | | |
106 | 0 | if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) { |
107 | | // Hooray! We fast-rejected on this hash. |
108 | 0 | return false; |
109 | 0 | } |
110 | | } |
111 | | |
112 | | // Now do the slighty-more-complex work of synthesizing the fourth hash, |
113 | | // and check it against the filter if it exists. |
114 | 0 | let fourth = hashes.fourth_hash(); |
115 | 0 | fourth == 0 || bf.might_contain_hash(fourth) |
116 | 0 | } |
117 | | |
118 | | /// A result of selector matching, includes 3 failure types, |
119 | | /// |
120 | | /// NotMatchedAndRestartFromClosestLaterSibling |
121 | | /// NotMatchedAndRestartFromClosestDescendant |
122 | | /// NotMatchedGlobally |
123 | | /// |
124 | | /// When NotMatchedGlobally appears, stop selector matching completely since |
125 | | /// the succeeding selectors never matches. |
126 | | /// It is raised when |
127 | | /// Child combinator cannot find the candidate element. |
128 | | /// Descendant combinator cannot find the candidate element. |
129 | | /// |
130 | | /// When NotMatchedAndRestartFromClosestDescendant appears, the selector |
131 | | /// matching does backtracking and restarts from the closest Descendant |
132 | | /// combinator. |
133 | | /// It is raised when |
134 | | /// NextSibling combinator cannot find the candidate element. |
135 | | /// LaterSibling combinator cannot find the candidate element. |
136 | | /// Child combinator doesn't match on the found element. |
137 | | /// |
138 | | /// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector |
139 | | /// matching does backtracking and restarts from the closest LaterSibling |
140 | | /// combinator. |
141 | | /// It is raised when |
142 | | /// NextSibling combinator doesn't match on the found element. |
143 | | /// |
144 | | /// For example, when the selector "d1 d2 a" is provided and we cannot *find* |
145 | | /// an appropriate ancestor element for "d1", this selector matching raises |
146 | | /// NotMatchedGlobally since even if "d2" is moved to more upper element, the |
147 | | /// candidates for "d1" becomes less than before and d1 . |
148 | | /// |
149 | | /// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is |
150 | | /// provided and we cannot *find* an appropriate brother element for b1, |
151 | | /// the selector matching raises NotMatchedAndRestartFromClosestDescendant. |
152 | | /// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1". |
153 | | /// |
154 | | /// The additional example is child and sibling. When the selector |
155 | | /// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on |
156 | | /// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling. |
157 | | /// However since the selector "c1" raises |
158 | | /// NotMatchedAndRestartFromClosestDescendant. So the selector |
159 | | /// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1". |
160 | | #[derive(Clone, Copy, Eq, PartialEq)] |
161 | | enum SelectorMatchingResult { |
162 | | Matched, |
163 | | NotMatchedAndRestartFromClosestLaterSibling, |
164 | | NotMatchedAndRestartFromClosestDescendant, |
165 | | NotMatchedGlobally, |
166 | | } |
167 | | |
168 | | /// Whether the :hover and :active quirk applies. |
169 | | /// |
170 | | /// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk |
171 | | #[derive(Clone, Copy, Debug, PartialEq)] |
172 | | enum MatchesHoverAndActiveQuirk { |
173 | | Yes, |
174 | | No, |
175 | | } |
176 | | |
177 | | /// Matches a selector, fast-rejecting against a bloom filter. |
178 | | /// |
179 | | /// We accept an offset to allow consumers to represent and match against |
180 | | /// partial selectors (indexed from the right). We use this API design, rather |
181 | | /// than having the callers pass a SelectorIter, because creating a SelectorIter |
182 | | /// requires dereferencing the selector to get the length, which adds an |
183 | | /// unnecessary cache miss for cases when we can fast-reject with AncestorHashes |
184 | | /// (which the caller can store inline with the selector pointer). |
185 | | #[inline(always)] |
186 | 0 | pub fn matches_selector<'i, E, F>( |
187 | 0 | selector: &Selector<'i, E::Impl>, |
188 | 0 | offset: usize, |
189 | 0 | hashes: Option<&AncestorHashes>, |
190 | 0 | element: &E, |
191 | 0 | context: &mut MatchingContext<'_, 'i, E::Impl>, |
192 | 0 | flags_setter: &mut F, |
193 | 0 | ) -> bool |
194 | 0 | where |
195 | 0 | E: Element<'i>, |
196 | 0 | F: FnMut(&E, ElementSelectorFlags), |
197 | | { |
198 | | // Use the bloom filter to fast-reject. |
199 | 0 | if let Some(hashes) = hashes { |
200 | 0 | if let Some(filter) = context.bloom_filter { |
201 | 0 | if !may_match(hashes, filter) { |
202 | 0 | return false; |
203 | 0 | } |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | 0 | matches_complex_selector(selector.iter_from(offset), element, context, flags_setter) |
208 | 0 | } |
209 | | |
210 | | /// Whether a compound selector matched, and whether it was the rightmost |
211 | | /// selector inside the complex selector. |
212 | | pub enum CompoundSelectorMatchingResult { |
213 | | /// The selector was fully matched. |
214 | | FullyMatched, |
215 | | /// The compound selector matched, and the next combinator offset is |
216 | | /// `next_combinator_offset`. |
217 | | Matched { next_combinator_offset: usize }, |
218 | | /// The selector didn't match. |
219 | | NotMatched, |
220 | | } |
221 | | |
222 | | /// Matches a compound selector belonging to `selector`, starting at offset |
223 | | /// `from_offset`, matching left to right. |
224 | | /// |
225 | | /// Requires that `from_offset` points to a `Combinator`. |
226 | | /// |
227 | | /// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the |
228 | | /// complex selector, but it happens to be the case we don't need it. |
229 | 0 | pub fn matches_compound_selector_from<'i, E>( |
230 | 0 | selector: &Selector<'i, E::Impl>, |
231 | 0 | mut from_offset: usize, |
232 | 0 | context: &mut MatchingContext<'_, 'i, E::Impl>, |
233 | 0 | element: &E, |
234 | 0 | ) -> CompoundSelectorMatchingResult |
235 | 0 | where |
236 | 0 | E: Element<'i>, |
237 | | { |
238 | 0 | if cfg!(debug_assertions) && from_offset != 0 { |
239 | 0 | selector.combinator_at_parse_order(from_offset - 1); // This asserts. |
240 | 0 | } |
241 | | |
242 | 0 | let mut local_context = LocalMatchingContext { |
243 | 0 | shared: context, |
244 | 0 | matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No, |
245 | 0 | }; |
246 | | |
247 | | // Find the end of the selector or the next combinator, then match |
248 | | // backwards, so that we match in the same order as |
249 | | // matches_complex_selector, which is usually faster. |
250 | 0 | let start_offset = from_offset; |
251 | 0 | for component in selector.iter_raw_parse_order_from(from_offset) { |
252 | 0 | if matches!(*component, Component::Combinator(..)) { |
253 | 0 | debug_assert_ne!(from_offset, 0, "Selector started with a combinator?"); |
254 | 0 | break; |
255 | 0 | } |
256 | | |
257 | 0 | from_offset += 1; |
258 | | } |
259 | | |
260 | 0 | debug_assert!(from_offset >= 1); |
261 | 0 | debug_assert!(from_offset <= selector.len()); |
262 | | |
263 | 0 | let iter = selector.iter_from(selector.len() - from_offset); |
264 | 0 | debug_assert!( |
265 | 0 | iter.clone().next().is_some() |
266 | 0 | || (from_offset != selector.len() |
267 | 0 | && matches!( |
268 | 0 | selector.combinator_at_parse_order(from_offset), |
269 | | Combinator::SlotAssignment | Combinator::PseudoElement |
270 | | )), |
271 | | "Got the math wrong: {:?} | {:?} | {} {}", |
272 | | selector, |
273 | 0 | selector.iter_raw_match_order().as_slice(), |
274 | | from_offset, |
275 | | start_offset |
276 | | ); |
277 | | |
278 | 0 | for component in iter { |
279 | 0 | if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) { |
280 | 0 | return CompoundSelectorMatchingResult::NotMatched; |
281 | 0 | } |
282 | | } |
283 | | |
284 | 0 | if from_offset != selector.len() { |
285 | 0 | return CompoundSelectorMatchingResult::Matched { |
286 | 0 | next_combinator_offset: from_offset, |
287 | 0 | }; |
288 | 0 | } |
289 | | |
290 | 0 | CompoundSelectorMatchingResult::FullyMatched |
291 | 0 | } |
292 | | |
293 | | /// Matches a complex selector. |
294 | | #[inline(always)] |
295 | 0 | pub fn matches_complex_selector<'i, E, F>( |
296 | 0 | mut iter: SelectorIter<'_, 'i, E::Impl>, |
297 | 0 | element: &E, |
298 | 0 | context: &mut MatchingContext<'_, 'i, E::Impl>, |
299 | 0 | flags_setter: &mut F, |
300 | 0 | ) -> bool |
301 | 0 | where |
302 | 0 | E: Element<'i>, |
303 | 0 | F: FnMut(&E, ElementSelectorFlags), |
304 | | { |
305 | | // If this is the special pseudo-element mode, consume the ::pseudo-element |
306 | | // before proceeding, since the caller has already handled that part. |
307 | 0 | if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() { |
308 | | // Consume the pseudo. |
309 | 0 | match *iter.next().unwrap() { |
310 | 0 | Component::PseudoElement(ref pseudo) => { |
311 | 0 | if let Some(ref f) = context.pseudo_element_matching_fn { |
312 | 0 | if !f(pseudo) { |
313 | 0 | return false; |
314 | 0 | } |
315 | 0 | } |
316 | | } |
317 | | _ => { |
318 | 0 | debug_assert!( |
319 | 0 | false, |
320 | | "Used MatchingMode::ForStatelessPseudoElement \ |
321 | | in a non-pseudo selector" |
322 | | ); |
323 | | } |
324 | | } |
325 | | |
326 | 0 | if !iter.matches_for_stateless_pseudo_element() { |
327 | 0 | return false; |
328 | 0 | } |
329 | | |
330 | | // Advance to the non-pseudo-element part of the selector. |
331 | 0 | let next_sequence = iter.next_sequence().unwrap(); |
332 | 0 | debug_assert_eq!(next_sequence, Combinator::PseudoElement); |
333 | 0 | } |
334 | | |
335 | 0 | let result = matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes); |
336 | | |
337 | 0 | matches!(result, SelectorMatchingResult::Matched) |
338 | 0 | } |
339 | | |
340 | | #[inline] |
341 | 0 | fn matches_hover_and_active_quirk<'i, Impl: SelectorImpl<'i>>( |
342 | 0 | selector_iter: &SelectorIter<'_, 'i, Impl>, |
343 | 0 | context: &MatchingContext<'_, 'i, Impl>, |
344 | 0 | rightmost: Rightmost, |
345 | 0 | ) -> MatchesHoverAndActiveQuirk { |
346 | 0 | if context.quirks_mode() != QuirksMode::Quirks { |
347 | 0 | return MatchesHoverAndActiveQuirk::No; |
348 | 0 | } |
349 | | |
350 | 0 | if context.is_nested() { |
351 | 0 | return MatchesHoverAndActiveQuirk::No; |
352 | 0 | } |
353 | | |
354 | | // This compound selector had a pseudo-element to the right that we |
355 | | // intentionally skipped. |
356 | 0 | if rightmost == Rightmost::Yes && context.matching_mode() == MatchingMode::ForStatelessPseudoElement { |
357 | 0 | return MatchesHoverAndActiveQuirk::No; |
358 | 0 | } |
359 | | |
360 | 0 | let all_match = selector_iter.clone().all(|simple| match *simple { |
361 | | Component::LocalName(_) |
362 | | | Component::AttributeInNoNamespaceExists { .. } |
363 | | | Component::AttributeInNoNamespace { .. } |
364 | | | Component::AttributeOther(_) |
365 | | | Component::ID(_) |
366 | | | Component::Class(_) |
367 | | | Component::PseudoElement(_) |
368 | | | Component::Negation(_) |
369 | | | Component::Nth(_) |
370 | | | Component::NthOf(..) |
371 | 0 | | Component::Empty => false, |
372 | 0 | Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), |
373 | 0 | _ => true, |
374 | 0 | }); |
375 | | |
376 | 0 | if all_match { |
377 | 0 | MatchesHoverAndActiveQuirk::Yes |
378 | | } else { |
379 | 0 | MatchesHoverAndActiveQuirk::No |
380 | | } |
381 | 0 | } |
382 | | |
383 | | #[derive(Clone, Copy, PartialEq)] |
384 | | enum Rightmost { |
385 | | Yes, |
386 | | No, |
387 | | } |
388 | | |
389 | | #[inline(always)] |
390 | 0 | fn next_element_for_combinator<'i, E>( |
391 | 0 | element: &E, |
392 | 0 | combinator: Combinator, |
393 | 0 | selector: &SelectorIter<'_, 'i, E::Impl>, |
394 | 0 | context: &MatchingContext<'_, 'i, E::Impl>, |
395 | 0 | ) -> Option<E> |
396 | 0 | where |
397 | 0 | E: Element<'i>, |
398 | | { |
399 | 0 | match combinator { |
400 | 0 | Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(), |
401 | | Combinator::Child | Combinator::Descendant | Combinator::Deep | Combinator::DeepDescendant => { |
402 | 0 | match element.parent_element() { |
403 | 0 | Some(e) => return Some(e), |
404 | 0 | None => {} |
405 | | } |
406 | | |
407 | 0 | if !element.parent_node_is_shadow_root() { |
408 | 0 | return None; |
409 | 0 | } |
410 | | |
411 | | // https://drafts.csswg.org/css-scoping/#host-element-in-tree: |
412 | | // |
413 | | // For the purpose of Selectors, a shadow host also appears in |
414 | | // its shadow tree, with the contents of the shadow tree treated |
415 | | // as its children. (In other words, the shadow host is treated as |
416 | | // replacing the shadow root node.) |
417 | | // |
418 | | // and also: |
419 | | // |
420 | | // When considered within its own shadow trees, the shadow host is |
421 | | // featureless. Only the :host, :host(), and :host-context() |
422 | | // pseudo-classes are allowed to match it. |
423 | | // |
424 | | // Since we know that the parent is a shadow root, we necessarily |
425 | | // are in a shadow tree of the host, and the next selector will only |
426 | | // match if the selector is a featureless :host selector. |
427 | 0 | if !selector.clone().is_featureless_host_selector() { |
428 | 0 | return None; |
429 | 0 | } |
430 | | |
431 | 0 | element.containing_shadow_host() |
432 | | } |
433 | 0 | Combinator::Part => element.containing_shadow_host(), |
434 | | Combinator::SlotAssignment => { |
435 | 0 | debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element())); |
436 | 0 | let scope = context.current_host?; |
437 | 0 | let mut current_slot = element.assigned_slot()?; |
438 | 0 | while current_slot.containing_shadow_host().unwrap().opaque() != scope { |
439 | 0 | current_slot = current_slot.assigned_slot()?; |
440 | | } |
441 | 0 | Some(current_slot) |
442 | | } |
443 | 0 | Combinator::PseudoElement => element.pseudo_element_originating_element(), |
444 | | } |
445 | 0 | } |
446 | | |
447 | 0 | fn matches_complex_selector_internal<'i, E, F>( |
448 | 0 | mut selector_iter: SelectorIter<'_, 'i, E::Impl>, |
449 | 0 | element: &E, |
450 | 0 | context: &mut MatchingContext<'_, 'i, E::Impl>, |
451 | 0 | flags_setter: &mut F, |
452 | 0 | rightmost: Rightmost, |
453 | 0 | ) -> SelectorMatchingResult |
454 | 0 | where |
455 | 0 | E: Element<'i>, |
456 | 0 | F: FnMut(&E, ElementSelectorFlags), |
457 | | { |
458 | 0 | debug!("Matching complex selector {:?} for {:?}", selector_iter, element); |
459 | | |
460 | 0 | let matches_compound_selector = |
461 | 0 | matches_compound_selector(&mut selector_iter, element, context, flags_setter, rightmost); |
462 | | |
463 | 0 | let combinator = selector_iter.next_sequence(); |
464 | 0 | if combinator.map_or(false, |c| c.is_sibling()) { |
465 | 0 | flags_setter(element, ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS); |
466 | 0 | } |
467 | | |
468 | 0 | if !matches_compound_selector { |
469 | 0 | return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; |
470 | 0 | } |
471 | | |
472 | 0 | let combinator = match combinator { |
473 | 0 | None => return SelectorMatchingResult::Matched, |
474 | 0 | Some(c) => c, |
475 | | }; |
476 | | |
477 | 0 | let candidate_not_found = match combinator { |
478 | | Combinator::NextSibling | Combinator::LaterSibling => { |
479 | 0 | SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant |
480 | | } |
481 | | Combinator::Child |
482 | | | Combinator::Deep |
483 | | | Combinator::DeepDescendant |
484 | | | Combinator::Descendant |
485 | | | Combinator::SlotAssignment |
486 | | | Combinator::Part |
487 | 0 | | Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally, |
488 | | }; |
489 | | |
490 | 0 | let mut next_element = next_element_for_combinator(element, combinator, &selector_iter, &context); |
491 | | |
492 | | // Stop matching :visited as soon as we find a link, or a combinator for |
493 | | // something that isn't an ancestor. |
494 | 0 | let mut visited_handling = if element.is_link() || combinator.is_sibling() { |
495 | 0 | VisitedHandlingMode::AllLinksUnvisited |
496 | | } else { |
497 | 0 | context.visited_handling() |
498 | | }; |
499 | | |
500 | | loop { |
501 | 0 | let element = match next_element { |
502 | 0 | None => return candidate_not_found, |
503 | 0 | Some(next_element) => next_element, |
504 | | }; |
505 | | |
506 | 0 | let result = context.with_visited_handling_mode(visited_handling, |context| { |
507 | 0 | matches_complex_selector_internal(selector_iter.clone(), &element, context, flags_setter, Rightmost::No) |
508 | 0 | }); |
509 | | |
510 | 0 | match (result, combinator) { |
511 | | // Return the status immediately. |
512 | | (SelectorMatchingResult::Matched, _) |
513 | | | (SelectorMatchingResult::NotMatchedGlobally, _) |
514 | | | (_, Combinator::NextSibling) => { |
515 | 0 | return result; |
516 | | } |
517 | | |
518 | | // Upgrade the failure status to |
519 | | // NotMatchedAndRestartFromClosestDescendant. |
520 | | (_, Combinator::PseudoElement) | (_, Combinator::Child) => { |
521 | 0 | return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant; |
522 | | } |
523 | | |
524 | | // If the failure status is |
525 | | // NotMatchedAndRestartFromClosestDescendant and combinator is |
526 | | // Combinator::LaterSibling, give up this Combinator::LaterSibling |
527 | | // matching and restart from the closest descendant combinator. |
528 | | (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => { |
529 | 0 | return result; |
530 | | } |
531 | | |
532 | | // The Combinator::Descendant combinator and the status is |
533 | | // NotMatchedAndRestartFromClosestLaterSibling or |
534 | | // NotMatchedAndRestartFromClosestDescendant, or the |
535 | | // Combinator::LaterSibling combinator and the status is |
536 | | // NotMatchedAndRestartFromClosestDescendant, we can continue to |
537 | | // matching on the next candidate element. |
538 | 0 | _ => {} |
539 | | } |
540 | | |
541 | 0 | if element.is_link() { |
542 | 0 | visited_handling = VisitedHandlingMode::AllLinksUnvisited; |
543 | 0 | } |
544 | | |
545 | 0 | next_element = next_element_for_combinator(&element, combinator, &selector_iter, &context); |
546 | | } |
547 | 0 | } |
548 | | |
549 | | #[inline] |
550 | 0 | fn matches_local_name<'i, E>(element: &E, local_name: &LocalName<'i, E::Impl>) -> bool |
551 | 0 | where |
552 | 0 | E: Element<'i>, |
553 | | { |
554 | 0 | let name = select_name( |
555 | 0 | element.is_html_element_in_html_document(), |
556 | 0 | &local_name.name, |
557 | 0 | &local_name.lower_name, |
558 | 0 | ) |
559 | 0 | .borrow(); |
560 | 0 | element.has_local_name(name) |
561 | 0 | } |
562 | | |
563 | | /// Determines whether the given element matches the given compound selector. |
564 | | #[inline] |
565 | 0 | fn matches_compound_selector<'i, E, F>( |
566 | 0 | selector_iter: &mut SelectorIter<'_, 'i, E::Impl>, |
567 | 0 | element: &E, |
568 | 0 | context: &mut MatchingContext<'_, 'i, E::Impl>, |
569 | 0 | flags_setter: &mut F, |
570 | 0 | rightmost: Rightmost, |
571 | 0 | ) -> bool |
572 | 0 | where |
573 | 0 | E: Element<'i>, |
574 | 0 | F: FnMut(&E, ElementSelectorFlags), |
575 | | { |
576 | 0 | let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost); |
577 | | |
578 | | // Handle some common cases first. |
579 | | // We may want to get rid of this at some point if we can make the |
580 | | // generic case fast enough. |
581 | 0 | let mut selector = selector_iter.next(); |
582 | 0 | if let Some(&Component::LocalName(ref local_name)) = selector { |
583 | 0 | if !matches_local_name(element, local_name) { |
584 | 0 | return false; |
585 | 0 | } |
586 | 0 | selector = selector_iter.next(); |
587 | 0 | } |
588 | 0 | let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity(); |
589 | 0 | if let Some(&Component::ID(ref id)) = selector { |
590 | 0 | if !element.has_id(id, class_and_id_case_sensitivity) { |
591 | 0 | return false; |
592 | 0 | } |
593 | 0 | selector = selector_iter.next(); |
594 | 0 | } |
595 | 0 | while let Some(&Component::Class(ref class)) = selector { |
596 | 0 | if !element.has_class(class, class_and_id_case_sensitivity) { |
597 | 0 | return false; |
598 | 0 | } |
599 | 0 | selector = selector_iter.next(); |
600 | | } |
601 | 0 | let selector = match selector { |
602 | 0 | Some(s) => s, |
603 | 0 | None => return true, |
604 | | }; |
605 | | |
606 | 0 | let mut local_context = LocalMatchingContext { |
607 | 0 | shared: context, |
608 | 0 | matches_hover_and_active_quirk, |
609 | 0 | }; |
610 | 0 | iter::once(selector) |
611 | 0 | .chain(selector_iter) |
612 | 0 | .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter)) |
613 | 0 | } |
614 | | |
615 | | /// Determines whether the given element matches the given single selector. |
616 | 0 | fn matches_simple_selector<'i, E, F>( |
617 | 0 | selector: &Component<'i, E::Impl>, |
618 | 0 | element: &E, |
619 | 0 | context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>, |
620 | 0 | flags_setter: &mut F, |
621 | 0 | ) -> bool |
622 | 0 | where |
623 | 0 | E: Element<'i>, |
624 | 0 | F: FnMut(&E, ElementSelectorFlags), |
625 | | { |
626 | 0 | debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); |
627 | | |
628 | 0 | match *selector { |
629 | 0 | Component::Combinator(_) => unreachable!(), |
630 | 0 | Component::Part(ref parts) => { |
631 | 0 | let mut hosts = SmallVec::<[E; 4]>::new(); |
632 | | |
633 | 0 | let mut host = match element.containing_shadow_host() { |
634 | 0 | Some(h) => h, |
635 | 0 | None => return false, |
636 | | }; |
637 | | |
638 | 0 | let current_host = context.shared.current_host; |
639 | 0 | if current_host != Some(host.opaque()) { |
640 | | loop { |
641 | 0 | let outer_host = host.containing_shadow_host(); |
642 | 0 | if outer_host.as_ref().map(|h| h.opaque()) == current_host { |
643 | 0 | break; |
644 | 0 | } |
645 | 0 | let outer_host = match outer_host { |
646 | 0 | Some(h) => h, |
647 | 0 | None => return false, |
648 | | }; |
649 | | // TODO(emilio): if worth it, we could early return if |
650 | | // host doesn't have the exportparts attribute. |
651 | 0 | hosts.push(host); |
652 | 0 | host = outer_host; |
653 | | } |
654 | 0 | } |
655 | | |
656 | | // Translate the part into the right scope. |
657 | 0 | parts.iter().all(|part| { |
658 | 0 | let mut part = part.clone(); |
659 | 0 | for host in hosts.iter().rev() { |
660 | 0 | part = match host.imported_part(&part) { |
661 | 0 | Some(p) => p, |
662 | 0 | None => return false, |
663 | | }; |
664 | | } |
665 | 0 | element.is_part(&part) |
666 | 0 | }) |
667 | | } |
668 | 0 | Component::Slotted(ref selector) => { |
669 | | // <slots> are never flattened tree slottables. |
670 | 0 | !element.is_html_slot_element() |
671 | 0 | && context |
672 | 0 | .shared |
673 | 0 | .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter)) |
674 | | } |
675 | 0 | Component::PseudoElement(ref pseudo) => element.match_pseudo_element(pseudo, context.shared), |
676 | 0 | Component::LocalName(ref local_name) => matches_local_name(element, local_name), |
677 | 0 | Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true, |
678 | 0 | Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { |
679 | 0 | element.has_namespace(&url.borrow()) |
680 | | } |
681 | | Component::ExplicitNoNamespace => { |
682 | 0 | let ns = crate::parser::namespace_empty_string::<E::Impl>(); |
683 | 0 | element.has_namespace(&ns.borrow()) |
684 | | } |
685 | 0 | Component::ID(ref id) => element.has_id(id, context.shared.classes_and_ids_case_sensitivity()), |
686 | 0 | Component::Class(ref class) => element.has_class(class, context.shared.classes_and_ids_case_sensitivity()), |
687 | | Component::AttributeInNoNamespaceExists { |
688 | 0 | ref local_name, |
689 | 0 | ref local_name_lower, |
690 | | } => { |
691 | 0 | let is_html = element.is_html_element_in_html_document(); |
692 | 0 | element.attr_matches( |
693 | 0 | &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()), |
694 | 0 | select_name(is_html, local_name, local_name_lower), |
695 | 0 | &AttrSelectorOperation::Exists, |
696 | | ) |
697 | | } |
698 | | Component::AttributeInNoNamespace { |
699 | 0 | ref local_name, |
700 | 0 | ref value, |
701 | 0 | operator, |
702 | 0 | case_sensitivity, |
703 | 0 | never_matches, |
704 | | } => { |
705 | 0 | if never_matches { |
706 | 0 | return false; |
707 | 0 | } |
708 | 0 | let is_html = element.is_html_element_in_html_document(); |
709 | 0 | element.attr_matches( |
710 | 0 | &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()), |
711 | 0 | local_name, |
712 | 0 | &AttrSelectorOperation::WithValue { |
713 | 0 | operator, |
714 | 0 | case_sensitivity: case_sensitivity.to_unconditional(is_html), |
715 | 0 | expected_value: value, |
716 | 0 | }, |
717 | | ) |
718 | | } |
719 | 0 | Component::AttributeOther(ref attr_sel) => { |
720 | 0 | if attr_sel.never_matches { |
721 | 0 | return false; |
722 | 0 | } |
723 | 0 | let is_html = element.is_html_element_in_html_document(); |
724 | | let empty_string; |
725 | 0 | let namespace = match attr_sel.namespace() { |
726 | 0 | Some(ns) => ns, |
727 | | None => { |
728 | 0 | empty_string = crate::parser::namespace_empty_string::<E::Impl>(); |
729 | 0 | NamespaceConstraint::Specific(&empty_string) |
730 | | } |
731 | | }; |
732 | 0 | element.attr_matches( |
733 | 0 | &namespace, |
734 | 0 | select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower), |
735 | 0 | &match attr_sel.operation { |
736 | 0 | ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists, |
737 | | ParsedAttrSelectorOperation::WithValue { |
738 | 0 | operator, |
739 | 0 | case_sensitivity, |
740 | 0 | ref expected_value, |
741 | 0 | } => AttrSelectorOperation::WithValue { |
742 | 0 | operator, |
743 | 0 | case_sensitivity: case_sensitivity.to_unconditional(is_html), |
744 | 0 | expected_value, |
745 | 0 | }, |
746 | | }, |
747 | | ) |
748 | | } |
749 | 0 | Component::NonTSPseudoClass(ref pc) => { |
750 | 0 | if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes |
751 | 0 | && !context.shared.is_nested() |
752 | 0 | && pc.is_active_or_hover() |
753 | 0 | && !element.is_link() |
754 | | { |
755 | 0 | return false; |
756 | 0 | } |
757 | | |
758 | 0 | element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter) |
759 | | } |
760 | 0 | Component::Root => element.is_root(), |
761 | | Component::Empty => { |
762 | 0 | flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); |
763 | 0 | element.is_empty() |
764 | | } |
765 | 0 | Component::Host(ref selector) => { |
766 | 0 | context.shared.shadow_host().map_or(false, |host| host == element.opaque()) |
767 | 0 | && selector.as_ref().map_or(true, |selector| { |
768 | 0 | context |
769 | 0 | .shared |
770 | 0 | .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter)) |
771 | 0 | }) |
772 | | } |
773 | 0 | Component::Scope => match context.shared.scope_element { |
774 | 0 | Some(ref scope_element) => element.opaque() == *scope_element, |
775 | 0 | None => element.is_root(), |
776 | | }, |
777 | 0 | Component::Nth(data) => match data.ty { |
778 | 0 | NthType::Child => match (data.a, data.b) { |
779 | 0 | (0, 1) => matches_first_child(element, flags_setter), |
780 | 0 | (a, b) => matches_generic_nth_child(element, context, a, b, false, false, flags_setter), |
781 | | }, |
782 | 0 | NthType::LastChild => match (data.a, data.b) { |
783 | 0 | (0, 1) => matches_last_child(element, flags_setter), |
784 | 0 | (a, b) => matches_generic_nth_child(element, context, a, b, false, true, flags_setter), |
785 | | }, |
786 | | NthType::OnlyChild => { |
787 | 0 | matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter) |
788 | | } |
789 | 0 | NthType::OfType => matches_generic_nth_child(element, context, data.a, data.b, true, false, flags_setter), |
790 | 0 | NthType::LastOfType => matches_generic_nth_child(element, context, data.a, data.b, true, true, flags_setter), |
791 | | NthType::OnlyOfType => { |
792 | 0 | matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) |
793 | 0 | && matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) |
794 | | } |
795 | 0 | _ => todo!(), |
796 | | }, |
797 | 0 | Component::NthOf(..) => todo!(), |
798 | 0 | Component::Is(ref list) | Component::Where(ref list) | Component::Any(_, ref list) => { |
799 | 0 | context.shared.nest(|context| { |
800 | 0 | for selector in &**list { |
801 | 0 | if matches_complex_selector(selector.iter(), element, context, flags_setter) { |
802 | 0 | return true; |
803 | 0 | } |
804 | | } |
805 | 0 | false |
806 | 0 | }) |
807 | | } |
808 | 0 | Component::Negation(ref list) => context.shared.nest_for_negation(|context| { |
809 | 0 | for selector in &**list { |
810 | 0 | if matches_complex_selector(selector.iter(), element, context, flags_setter) { |
811 | 0 | return false; |
812 | 0 | } |
813 | | } |
814 | 0 | true |
815 | 0 | }), |
816 | 0 | Component::Nesting | Component::Has(..) => unreachable!(), |
817 | | } |
818 | 0 | } |
819 | | |
820 | | #[inline(always)] |
821 | 0 | fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T { |
822 | 0 | if is_html { |
823 | 0 | local_name_lower |
824 | | } else { |
825 | 0 | local_name |
826 | | } |
827 | 0 | } |
828 | | |
829 | | #[inline] |
830 | 0 | fn matches_generic_nth_child<'i, E, F>( |
831 | 0 | element: &E, |
832 | 0 | context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>, |
833 | 0 | a: i32, |
834 | 0 | b: i32, |
835 | 0 | is_of_type: bool, |
836 | 0 | is_from_end: bool, |
837 | 0 | flags_setter: &mut F, |
838 | 0 | ) -> bool |
839 | 0 | where |
840 | 0 | E: Element<'i>, |
841 | 0 | F: FnMut(&E, ElementSelectorFlags), |
842 | | { |
843 | 0 | if element.ignores_nth_child_selectors() { |
844 | 0 | return false; |
845 | 0 | } |
846 | | |
847 | 0 | flags_setter( |
848 | 0 | element, |
849 | 0 | if is_from_end { |
850 | 0 | ElementSelectorFlags::HAS_SLOW_SELECTOR |
851 | | } else { |
852 | 0 | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS |
853 | | }, |
854 | | ); |
855 | | |
856 | | // Grab a reference to the appropriate cache. |
857 | 0 | let mut cache = context.shared.nth_index_cache.as_mut().map(|c| c.get(is_of_type, is_from_end)); |
858 | | |
859 | | // Lookup or compute the index. |
860 | 0 | let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) { |
861 | 0 | i |
862 | | } else { |
863 | 0 | let i = nth_child_index(element, is_of_type, is_from_end, cache.as_deref_mut()); |
864 | 0 | if let Some(c) = cache.as_mut() { |
865 | 0 | c.insert(element.opaque(), i) |
866 | 0 | } |
867 | 0 | i |
868 | | }; |
869 | 0 | debug_assert_eq!( |
870 | | index, |
871 | 0 | nth_child_index(element, is_of_type, is_from_end, None), |
872 | | "invalid cache" |
873 | | ); |
874 | | |
875 | | // Is there a non-negative integer n such that An+B=index? |
876 | 0 | match index.checked_sub(b) { |
877 | 0 | None => false, |
878 | 0 | Some(an) => match an.checked_div(a) { |
879 | 0 | Some(n) => n >= 0 && a * n == an, |
880 | 0 | None /* a == 0 */ => an == 0, |
881 | | }, |
882 | | } |
883 | 0 | } |
884 | | |
885 | | #[inline] |
886 | 0 | fn nth_child_index<'i, E>( |
887 | 0 | element: &E, |
888 | 0 | is_of_type: bool, |
889 | 0 | is_from_end: bool, |
890 | 0 | mut cache: Option<&mut NthIndexCacheInner>, |
891 | 0 | ) -> i32 |
892 | 0 | where |
893 | 0 | E: Element<'i>, |
894 | | { |
895 | | // The traversal mostly processes siblings left to right. So when we walk |
896 | | // siblings to the right when computing NthLast/NthLastOfType we're unlikely |
897 | | // to get cache hits along the way. As such, we take the hit of walking the |
898 | | // siblings to the left checking the cache in the is_from_end case (this |
899 | | // matches what Gecko does). The indices-from-the-left is handled during the |
900 | | // regular look further below. |
901 | 0 | if let Some(ref mut c) = cache { |
902 | 0 | if is_from_end && !c.is_empty() { |
903 | 0 | let mut index: i32 = 1; |
904 | 0 | let mut curr = element.clone(); |
905 | 0 | while let Some(e) = curr.prev_sibling_element() { |
906 | 0 | curr = e; |
907 | 0 | if !is_of_type || element.is_same_type(&curr) { |
908 | 0 | if let Some(i) = c.lookup(curr.opaque()) { |
909 | 0 | return i - index; |
910 | 0 | } |
911 | 0 | index += 1; |
912 | 0 | } |
913 | | } |
914 | 0 | } |
915 | 0 | } |
916 | | |
917 | 0 | let mut index: i32 = 1; |
918 | 0 | let mut curr = element.clone(); |
919 | 0 | let next = |e: E| { |
920 | 0 | if is_from_end { |
921 | 0 | e.next_sibling_element() |
922 | | } else { |
923 | 0 | e.prev_sibling_element() |
924 | | } |
925 | 0 | }; |
926 | 0 | while let Some(e) = next(curr) { |
927 | 0 | curr = e; |
928 | 0 | if !is_of_type || element.is_same_type(&curr) { |
929 | | // If we're computing indices from the left, check each element in the |
930 | | // cache. We handle the indices-from-the-right case at the top of this |
931 | | // function. |
932 | 0 | if !is_from_end { |
933 | 0 | if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) { |
934 | 0 | return i + index; |
935 | 0 | } |
936 | 0 | } |
937 | 0 | index += 1; |
938 | 0 | } |
939 | | } |
940 | | |
941 | 0 | index |
942 | 0 | } |
943 | | |
944 | | #[inline] |
945 | 0 | fn matches_first_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool |
946 | 0 | where |
947 | 0 | E: Element<'i>, |
948 | 0 | F: FnMut(&E, ElementSelectorFlags), |
949 | | { |
950 | 0 | flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); |
951 | 0 | element.prev_sibling_element().is_none() |
952 | 0 | } |
953 | | |
954 | | #[inline] |
955 | 0 | fn matches_last_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool |
956 | 0 | where |
957 | 0 | E: Element<'i>, |
958 | 0 | F: FnMut(&E, ElementSelectorFlags), |
959 | | { |
960 | 0 | flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); |
961 | 0 | element.next_sibling_element().is_none() |
962 | 0 | } |