context.rs (16592B)
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::CaseSensitivity; 6 use crate::bloom::BloomFilter; 7 use crate::nth_index_cache::{NthIndexCache, NthIndexCacheInner}; 8 use crate::parser::{Selector, SelectorImpl}; 9 use crate::relative_selector::cache::RelativeSelectorCache; 10 use crate::relative_selector::filter::RelativeSelectorFilterMap; 11 use crate::tree::{Element, OpaqueElement}; 12 13 /// What kind of selector matching mode we should use. 14 /// 15 /// There are two modes of selector matching. The difference is only noticeable 16 /// in presence of pseudo-elements. 17 #[derive(Clone, Copy, Debug, PartialEq)] 18 pub enum MatchingMode { 19 /// Don't ignore any pseudo-element selectors. 20 Normal, 21 22 /// Ignores any stateless pseudo-element selectors in the rightmost sequence 23 /// of simple selectors. 24 /// 25 /// This is useful, for example, to match against ::before when you aren't a 26 /// pseudo-element yourself. 27 /// 28 /// For example, in presence of `::before:hover`, it would never match, but 29 /// `::before` would be ignored as in "matching". 30 /// 31 /// It's required for all the selectors you match using this mode to have a 32 /// pseudo-element. 33 ForStatelessPseudoElement, 34 } 35 36 /// The mode to use when matching unvisited and visited links. 37 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 38 pub enum VisitedHandlingMode { 39 /// All links are matched as if they are unvisted. 40 AllLinksUnvisited, 41 /// All links are matched as if they are visited and unvisited (both :link 42 /// and :visited match). 43 /// 44 /// This is intended to be used from invalidation code, to be conservative 45 /// about whether we need to restyle a link. 46 AllLinksVisitedAndUnvisited, 47 /// A element's "relevant link" is the element being matched if it is a link 48 /// or the nearest ancestor link. The relevant link is matched as though it 49 /// is visited, and all other links are matched as if they are unvisited. 50 RelevantLinkVisited, 51 } 52 53 impl VisitedHandlingMode { 54 #[inline] 55 pub fn matches_visited(&self) -> bool { 56 matches!( 57 *self, 58 VisitedHandlingMode::RelevantLinkVisited 59 | VisitedHandlingMode::AllLinksVisitedAndUnvisited 60 ) 61 } 62 63 #[inline] 64 pub fn matches_unvisited(&self) -> bool { 65 matches!( 66 *self, 67 VisitedHandlingMode::AllLinksUnvisited 68 | VisitedHandlingMode::AllLinksVisitedAndUnvisited 69 ) 70 } 71 } 72 73 /// The mode to use whether we should matching rules inside @starting-style. 74 /// https://drafts.csswg.org/css-transitions-2/#starting-style 75 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 76 pub enum IncludeStartingStyle { 77 /// All without rules inside @starting-style. This is for the most common case because the 78 /// primary/pseudo styles doesn't use rules inside @starting-style. 79 No, 80 /// Get the starting style. The starting style for an element as the after-change style with 81 /// @starting-style rules applied in addition. In other words, this matches all rules, 82 /// including rules inside @starting-style. 83 Yes, 84 } 85 86 /// Whether we need to set selector invalidation flags on elements for this 87 /// match request. 88 #[derive(Clone, Copy, Debug, PartialEq)] 89 pub enum NeedsSelectorFlags { 90 No, 91 Yes, 92 } 93 94 /// Whether we're matching in the contect of invalidation. 95 #[derive(Clone, Copy, PartialEq)] 96 pub enum MatchingForInvalidation { 97 No, 98 Yes, 99 YesForComparison, 100 } 101 102 impl MatchingForInvalidation { 103 /// Are we matching for invalidation? 104 pub fn is_for_invalidation(&self) -> bool { 105 matches!(*self, Self::Yes | Self::YesForComparison) 106 } 107 } 108 109 /// Which quirks mode is this document in. 110 /// 111 /// See: https://quirks.spec.whatwg.org/ 112 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 113 pub enum QuirksMode { 114 /// Quirks mode. 115 Quirks, 116 /// Limited quirks mode. 117 LimitedQuirks, 118 /// No quirks mode. 119 NoQuirks, 120 } 121 122 impl QuirksMode { 123 #[inline] 124 pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity { 125 match self { 126 QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, 127 QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, 128 } 129 } 130 } 131 132 /// Set of caches (And cache-likes) that speed up expensive selector matches. 133 #[derive(Default)] 134 pub struct SelectorCaches { 135 /// A cache to speed up nth-index-like selectors. 136 pub nth_index: NthIndexCache, 137 /// A cache to speed up relative selector matches. See module documentation. 138 pub relative_selector: RelativeSelectorCache, 139 /// A map of bloom filters to fast-reject relative selector matches. 140 pub relative_selector_filter_map: RelativeSelectorFilterMap, 141 } 142 143 /// Data associated with the matching process for a element. This context is 144 /// used across many selectors for an element, so it's not appropriate for 145 /// transient data that applies to only a single selector. 146 pub struct MatchingContext<'a, Impl> 147 where 148 Impl: SelectorImpl, 149 { 150 /// Input with the matching mode we should use when matching selectors. 151 matching_mode: MatchingMode, 152 /// Input with the bloom filter used to fast-reject selectors. 153 pub bloom_filter: Option<&'a BloomFilter>, 154 /// The element which is going to match :scope pseudo-class. It can be 155 /// either one :scope element, or the scoping element. 156 /// 157 /// Note that, although in theory there can be multiple :scope elements, 158 /// in current specs, at most one is specified, and when there is one, 159 /// scoping element is not relevant anymore, so we use a single field for 160 /// them. 161 /// 162 /// When this is None, :scope will match the root element. 163 /// 164 /// See https://drafts.csswg.org/selectors-4/#scope-pseudo 165 pub scope_element: Option<OpaqueElement>, 166 167 /// The current shadow host we're collecting :host rules for. 168 pub current_host: Option<OpaqueElement>, 169 170 /// Controls how matching for links is handled. 171 visited_handling: VisitedHandlingMode, 172 173 /// Controls if we should match rules in @starting-style. 174 pub include_starting_style: IncludeStartingStyle, 175 176 /// Whether there are any rules inside @starting-style. 177 pub has_starting_style: bool, 178 179 /// Whether we're currently matching a featureless element. 180 pub featureless: bool, 181 182 /// The current nesting level of selectors that we're matching. 183 nesting_level: usize, 184 185 /// Whether we're inside a negation or not. 186 in_negation: bool, 187 188 /// An optional hook function for checking whether a pseudo-element 189 /// should match when matching_mode is ForStatelessPseudoElement. 190 pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>, 191 192 /// Extra implementation-dependent matching data. 193 pub extra_data: Impl::ExtraMatchingData<'a>, 194 195 /// The current element we're anchoring on for evaluating the relative selector. 196 current_relative_selector_anchor: Option<OpaqueElement>, 197 198 quirks_mode: QuirksMode, 199 needs_selector_flags: NeedsSelectorFlags, 200 201 /// Whether we're matching in the contect of invalidation. 202 matching_for_invalidation: MatchingForInvalidation, 203 204 /// Caches to speed up expensive selector matches. 205 pub selector_caches: &'a mut SelectorCaches, 206 207 classes_and_ids_case_sensitivity: CaseSensitivity, 208 _impl: ::std::marker::PhantomData<Impl>, 209 } 210 211 impl<'a, Impl> MatchingContext<'a, Impl> 212 where 213 Impl: SelectorImpl, 214 { 215 /// Constructs a new `MatchingContext`. 216 pub fn new( 217 matching_mode: MatchingMode, 218 bloom_filter: Option<&'a BloomFilter>, 219 selector_caches: &'a mut SelectorCaches, 220 quirks_mode: QuirksMode, 221 needs_selector_flags: NeedsSelectorFlags, 222 matching_for_invalidation: MatchingForInvalidation, 223 ) -> Self { 224 Self::new_for_visited( 225 matching_mode, 226 bloom_filter, 227 selector_caches, 228 VisitedHandlingMode::AllLinksUnvisited, 229 IncludeStartingStyle::No, 230 quirks_mode, 231 needs_selector_flags, 232 matching_for_invalidation, 233 ) 234 } 235 236 /// Constructs a new `MatchingContext` for use in visited matching. 237 pub fn new_for_visited( 238 matching_mode: MatchingMode, 239 bloom_filter: Option<&'a BloomFilter>, 240 selector_caches: &'a mut SelectorCaches, 241 visited_handling: VisitedHandlingMode, 242 include_starting_style: IncludeStartingStyle, 243 quirks_mode: QuirksMode, 244 needs_selector_flags: NeedsSelectorFlags, 245 matching_for_invalidation: MatchingForInvalidation, 246 ) -> Self { 247 Self { 248 matching_mode, 249 bloom_filter, 250 visited_handling, 251 include_starting_style, 252 has_starting_style: false, 253 quirks_mode, 254 classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), 255 needs_selector_flags, 256 matching_for_invalidation, 257 scope_element: None, 258 current_host: None, 259 featureless: false, 260 nesting_level: 0, 261 in_negation: false, 262 pseudo_element_matching_fn: None, 263 extra_data: Default::default(), 264 current_relative_selector_anchor: None, 265 selector_caches, 266 _impl: ::std::marker::PhantomData, 267 } 268 } 269 270 // Grab a reference to the appropriate cache. 271 #[inline] 272 pub fn nth_index_cache( 273 &mut self, 274 is_of_type: bool, 275 is_from_end: bool, 276 selectors: &[Selector<Impl>], 277 ) -> &mut NthIndexCacheInner { 278 self.selector_caches 279 .nth_index 280 .get(is_of_type, is_from_end, selectors) 281 } 282 283 /// Whether we're matching a nested selector. 284 #[inline] 285 pub fn is_nested(&self) -> bool { 286 self.nesting_level != 0 287 } 288 289 /// Whether we're matching inside a :not(..) selector. 290 #[inline] 291 pub fn in_negation(&self) -> bool { 292 self.in_negation 293 } 294 295 /// The quirks mode of the document. 296 #[inline] 297 pub fn quirks_mode(&self) -> QuirksMode { 298 self.quirks_mode 299 } 300 301 /// The matching-mode for this selector-matching operation. 302 #[inline] 303 pub fn matching_mode(&self) -> MatchingMode { 304 self.matching_mode 305 } 306 307 /// Whether we need to set selector flags. 308 #[inline] 309 pub fn needs_selector_flags(&self) -> bool { 310 self.needs_selector_flags == NeedsSelectorFlags::Yes 311 } 312 313 /// Whether or not we're matching to invalidate. 314 #[inline] 315 pub fn matching_for_invalidation(&self) -> bool { 316 self.matching_for_invalidation.is_for_invalidation() 317 } 318 319 /// Whether or not we're comparing for invalidation, if we are matching for invalidation. 320 #[inline] 321 pub fn matching_for_invalidation_comparison(&self) -> Option<bool> { 322 match self.matching_for_invalidation { 323 MatchingForInvalidation::No => None, 324 MatchingForInvalidation::Yes => Some(false), 325 MatchingForInvalidation::YesForComparison => Some(true), 326 } 327 } 328 329 /// Run the given matching function for before/after invalidation comparison. 330 #[inline] 331 pub fn for_invalidation_comparison<F, R>(&mut self, f: F) -> R 332 where 333 F: FnOnce(&mut Self) -> R, 334 { 335 debug_assert!( 336 self.matching_for_invalidation(), 337 "Not matching for invalidation?" 338 ); 339 let prev = self.matching_for_invalidation; 340 self.matching_for_invalidation = MatchingForInvalidation::YesForComparison; 341 let result = f(self); 342 self.matching_for_invalidation = prev; 343 result 344 } 345 346 /// The case-sensitivity for class and ID selectors 347 #[inline] 348 pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { 349 self.classes_and_ids_case_sensitivity 350 } 351 352 /// Runs F with a deeper nesting level. 353 #[inline] 354 pub fn nest<F, R>(&mut self, f: F) -> R 355 where 356 F: FnOnce(&mut Self) -> R, 357 { 358 self.nesting_level += 1; 359 let result = f(self); 360 self.nesting_level -= 1; 361 result 362 } 363 364 /// Runs F with a deeper nesting level, and marking ourselves in a negation, 365 /// for a :not(..) selector, for example. 366 #[inline] 367 pub fn nest_for_negation<F, R>(&mut self, f: F) -> R 368 where 369 F: FnOnce(&mut Self) -> R, 370 { 371 let old_in_negation = self.in_negation; 372 self.in_negation = !self.in_negation; 373 let result = self.nest(f); 374 self.in_negation = old_in_negation; 375 result 376 } 377 378 #[inline] 379 pub fn visited_handling(&self) -> VisitedHandlingMode { 380 self.visited_handling 381 } 382 383 /// Runs F with a different featureless element flag. 384 #[inline] 385 pub fn with_featureless<F, R>(&mut self, featureless: bool, f: F) -> R 386 where 387 F: FnOnce(&mut Self) -> R, 388 { 389 let orig = self.featureless; 390 self.featureless = featureless; 391 let result = f(self); 392 self.featureless = orig; 393 result 394 } 395 396 /// Returns whether the currently matching element is acting as a featureless element (e.g., 397 /// because we've crossed a shadow boundary). This is used to implement the :host selector 398 /// rules properly. 399 #[inline] 400 pub fn featureless(&self) -> bool { 401 self.featureless 402 } 403 404 /// Runs F with a different VisitedHandlingMode. 405 #[inline] 406 pub fn with_visited_handling_mode<F, R>( 407 &mut self, 408 handling_mode: VisitedHandlingMode, 409 f: F, 410 ) -> R 411 where 412 F: FnOnce(&mut Self) -> R, 413 { 414 let original_handling_mode = self.visited_handling; 415 self.visited_handling = handling_mode; 416 let result = f(self); 417 self.visited_handling = original_handling_mode; 418 result 419 } 420 421 /// Runs F with a given shadow host which is the root of the tree whose 422 /// rules we're matching. 423 #[inline] 424 pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R 425 where 426 E: Element, 427 F: FnOnce(&mut Self) -> R, 428 { 429 let original_host = self.current_host.take(); 430 self.current_host = host.map(|h| h.opaque()); 431 let result = f(self); 432 self.current_host = original_host; 433 result 434 } 435 436 /// Returns the current shadow host whose shadow root we're matching rules 437 /// against. 438 #[inline] 439 pub fn shadow_host(&self) -> Option<OpaqueElement> { 440 self.current_host 441 } 442 443 /// Runs F with a deeper nesting level, with the given element as the anchor, 444 /// for a :has(...) selector, for example. 445 #[inline] 446 pub fn nest_for_relative_selector<F, R>(&mut self, anchor: OpaqueElement, f: F) -> R 447 where 448 F: FnOnce(&mut Self) -> R, 449 { 450 debug_assert!( 451 self.current_relative_selector_anchor.is_none(), 452 "Nesting should've been rejected at parse time" 453 ); 454 self.current_relative_selector_anchor = Some(anchor); 455 let result = self.nest(f); 456 self.current_relative_selector_anchor = None; 457 result 458 } 459 460 /// Runs F with a deeper nesting level, with the given element as the scope. 461 #[inline] 462 pub fn nest_for_scope<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R 463 where 464 F: FnOnce(&mut Self) -> R, 465 { 466 let original_scope_element = self.scope_element; 467 self.scope_element = scope; 468 let result = f(self); 469 self.scope_element = original_scope_element; 470 result 471 } 472 473 /// Runs F with a deeper nesting level, with the given element as the scope, for 474 /// matching `scope-start` and/or `scope-end` conditions. 475 #[inline] 476 pub fn nest_for_scope_condition<F, R>(&mut self, scope: Option<OpaqueElement>, f: F) -> R 477 where 478 F: FnOnce(&mut Self) -> R, 479 { 480 let original_matching_mode = self.matching_mode; 481 // We may as well be matching for a pseudo-element inside `@scope`, but 482 // the scope-defining selectors wouldn't be matching them. 483 self.matching_mode = MatchingMode::Normal; 484 let result = self.nest_for_scope(scope, f); 485 self.matching_mode = original_matching_mode; 486 result 487 } 488 489 /// Returns the current anchor element to evaluate the relative selector against. 490 #[inline] 491 pub fn relative_selector_anchor(&self) -> Option<OpaqueElement> { 492 self.current_relative_selector_anchor 493 } 494 }