1use crate::AllocErr;
9use crate::applicable_declarations::{ApplicableDeclarationList, ScopeProximity};
10use crate::context::QuirksMode;
11use crate::derives::*;
12use crate::dom::TElement;
13use crate::rule_tree::CascadeLevel;
14use crate::selector_parser::SelectorImpl;
15use crate::stylist::{CascadeData, ContainerConditionId, Rule, ScopeConditionId, Stylist};
16use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
17use dom::ElementState;
18use hashbrown::hash_map;
19use hashbrown::{HashMap, HashSet};
20use precomputed_hash::PrecomputedHash;
21use selectors::matching::MatchingContext;
22use selectors::parser::{Combinator, Component, SelectorIter};
23use smallvec::SmallVec;
24use std::borrow::Borrow;
25use std::hash::{BuildHasherDefault, Hash, Hasher};
26
27#[derive(Default)]
30pub struct PrecomputedHasher {
31 hash: u64,
32 #[cfg(debug_assertions)]
33 initialized: bool,
34}
35
36pub type RelevantAttributes = thin_vec::ThinVec<LocalName>;
38
39const RARE_PSEUDO_CLASS_STATES: ElementState = ElementState::from_bits_retain(
46 ElementState::FULLSCREEN.bits()
47 | ElementState::PICTURE_IN_PICTURE.bits()
48 | ElementState::VISITED_OR_UNVISITED.bits()
49 | ElementState::URLTARGET.bits()
50 | ElementState::INERT.bits()
51 | ElementState::FOCUS.bits()
52 | ElementState::FOCUSRING.bits()
53 | ElementState::TOPMOST_MODAL.bits()
54 | ElementState::SUPPRESS_FOR_PRINT_SELECTION.bits()
55 | ElementState::ACTIVE_VIEW_TRANSITION.bits()
56 | ElementState::HEADING_LEVEL_BITS.bits(),
57);
58
59pub type PrecomputedHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PrecomputedHasher>>;
61
62pub type PrecomputedHashSet<K> = HashSet<K, BuildHasherDefault<PrecomputedHasher>>;
64
65impl Hasher for PrecomputedHasher {
66 #[inline]
67 fn write(&mut self, _: &[u8]) {
68 unreachable!(
69 "Called into PrecomputedHasher with something that isn't \
70 a u64 or u32"
71 )
72 }
73
74 #[inline]
75 fn write_u32(&mut self, i: u32) {
76 #[cfg(debug_assertions)]
77 debug_assert!(!self.initialized);
78 debug_assert_eq!(self.hash, 0);
79 let extended = i as u64;
80 self.hash = (extended << 32) | extended;
81 #[cfg(debug_assertions)]
82 {
83 self.initialized = true;
84 }
85 }
86
87 #[inline]
88 fn write_u64(&mut self, i: u64) {
89 #[cfg(debug_assertions)]
90 debug_assert!(!self.initialized);
91 debug_assert_eq!(self.hash, 0);
92 self.hash = i;
93 #[cfg(debug_assertions)]
94 {
95 self.initialized = true;
96 }
97 }
98
99 #[inline]
100 fn finish(&self) -> u64 {
101 #[cfg(debug_assertions)]
102 debug_assert!(self.initialized);
103 self.hash
104 }
105}
106
107pub trait SelectorMapEntry: Sized + Clone {
109 fn selector(&self) -> SelectorIter<'_, SelectorImpl>;
111 fn set_bucket_matches(&mut self, _: BucketMatches) {}
113}
114
115#[derive(Clone, Debug, MallocSizeOf)]
143pub struct SelectorMap<T: 'static> {
144 pub root: SmallVec<[T; 1]>,
146 pub id_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
148 pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
150 pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
152 pub attribute_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
154 pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>,
156 pub rare_pseudo_classes: SmallVec<[T; 1]>,
158 pub other: SmallVec<[T; 1]>,
160 pub count: usize,
162}
163
164impl<T: 'static> Default for SelectorMap<T> {
165 #[inline]
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl<T> SelectorMap<T> {
172 pub fn new() -> Self {
174 SelectorMap {
175 root: SmallVec::new(),
176 id_hash: MaybeCaseInsensitiveHashMap::new(),
177 class_hash: MaybeCaseInsensitiveHashMap::new(),
178 attribute_hash: HashMap::default(),
179 local_name_hash: HashMap::default(),
180 namespace_hash: HashMap::default(),
181 rare_pseudo_classes: SmallVec::new(),
182 other: SmallVec::new(),
183 count: 0,
184 }
185 }
186
187 pub fn shrink_if_needed(&mut self) {
189 self.id_hash.shrink_if_needed();
190 self.class_hash.shrink_if_needed();
191 self.attribute_hash.shrink_if_needed();
192 self.local_name_hash.shrink_if_needed();
193 self.namespace_hash.shrink_if_needed();
194 }
195
196 pub fn clear(&mut self) {
198 self.root.clear();
199 self.id_hash.clear();
200 self.class_hash.clear();
201 self.attribute_hash.clear();
202 self.local_name_hash.clear();
203 self.namespace_hash.clear();
204 self.rare_pseudo_classes.clear();
205 self.other.clear();
206 self.count = 0;
207 }
208
209 pub fn is_empty(&self) -> bool {
211 self.count == 0
212 }
213
214 pub fn len(&self) -> usize {
216 self.count
217 }
218}
219
220impl SelectorMap<Rule> {
221 pub fn get_all_matching_rules<E>(
226 &self,
227 element: E,
228 rule_hash_target: E,
229 matching_rules_list: &mut ApplicableDeclarationList,
230 matching_context: &mut MatchingContext<E::Impl>,
231 cascade_level: CascadeLevel,
232 cascade_data: &CascadeData,
233 stylist: &Stylist,
234 ) where
235 E: TElement,
236 {
237 if self.is_empty() {
238 return;
239 }
240
241 let quirks_mode = matching_context.quirks_mode();
242
243 if rule_hash_target.is_root() {
244 SelectorMap::get_matching_rules(
245 element,
246 &self.root,
247 matching_rules_list,
248 matching_context,
249 cascade_level,
250 cascade_data,
251 stylist,
252 );
253 }
254
255 if let Some(id) = rule_hash_target.id()
256 && let Some(rules) = self.id_hash.get(id, quirks_mode)
257 {
258 SelectorMap::get_matching_rules(
259 element,
260 rules,
261 matching_rules_list,
262 matching_context,
263 cascade_level,
264 cascade_data,
265 stylist,
266 )
267 }
268
269 rule_hash_target.each_class(|class| {
270 if let Some(rules) = self.class_hash.get(class, quirks_mode) {
271 SelectorMap::get_matching_rules(
272 element,
273 rules,
274 matching_rules_list,
275 matching_context,
276 cascade_level,
277 cascade_data,
278 stylist,
279 )
280 }
281 });
282
283 rule_hash_target.each_attr_name(|name| {
284 if let Some(rules) = self.attribute_hash.get(name) {
285 SelectorMap::get_matching_rules(
286 element,
287 rules,
288 matching_rules_list,
289 matching_context,
290 cascade_level,
291 cascade_data,
292 stylist,
293 )
294 }
295 });
296
297 if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) {
298 SelectorMap::get_matching_rules(
299 element,
300 rules,
301 matching_rules_list,
302 matching_context,
303 cascade_level,
304 cascade_data,
305 stylist,
306 )
307 }
308
309 if rule_hash_target
310 .state()
311 .intersects(RARE_PSEUDO_CLASS_STATES)
312 {
313 SelectorMap::get_matching_rules(
314 element,
315 &self.rare_pseudo_classes,
316 matching_rules_list,
317 matching_context,
318 cascade_level,
319 cascade_data,
320 stylist,
321 );
322 }
323
324 if let Some(rules) = self.namespace_hash.get(rule_hash_target.namespace()) {
325 SelectorMap::get_matching_rules(
326 element,
327 rules,
328 matching_rules_list,
329 matching_context,
330 cascade_level,
331 cascade_data,
332 stylist,
333 )
334 }
335
336 SelectorMap::get_matching_rules(
337 element,
338 &self.other,
339 matching_rules_list,
340 matching_context,
341 cascade_level,
342 cascade_data,
343 stylist,
344 );
345 }
346
347 pub(crate) fn get_matching_rules<E>(
349 element: E,
350 rules: &[Rule],
351 matching_rules: &mut ApplicableDeclarationList,
352 matching_context: &mut MatchingContext<E::Impl>,
353 cascade_level: CascadeLevel,
354 cascade_data: &CascadeData,
355 stylist: &Stylist,
356 ) where
357 E: TElement,
358 {
359 for rule in rules {
360 let scope_proximity = if rule.scope_condition_id == ScopeConditionId::none() {
361 if !rule.matches_selector(element, matching_context) {
362 continue;
363 }
364 ScopeProximity::infinity()
365 } else {
366 let result =
367 cascade_data.find_scope_proximity_if_matching(rule, element, matching_context);
368 if result == ScopeProximity::infinity() {
369 continue;
370 }
371 result
372 };
373
374 if rule.container_condition_id != ContainerConditionId::none()
375 && !cascade_data.container_condition_matches(
376 rule.container_condition_id,
377 stylist,
378 element,
379 matching_context,
380 )
381 {
382 continue;
383 }
384 matching_rules.push(rule.to_applicable_declaration_block(
385 cascade_level,
386 cascade_data,
387 scope_proximity,
388 ));
389 }
390 }
391}
392
393impl<T: SelectorMapEntry> SelectorMap<T> {
394 pub fn insert(&mut self, mut entry: T, quirks_mode: QuirksMode) -> Result<(), AllocErr> {
396 self.count += 1;
397
398 let mut bucket_matches = BucketMatches::Full;
403 macro_rules! insert_into_bucket {
404 ($entry:ident, $bucket:expr) => {{
405 let vec = match $bucket {
406 Bucket::Root => &mut self.root,
407 Bucket::ID(id) => {
408 self.id_hash
409 .try_get_or_insert_with(id, quirks_mode, Default::default)?
410 },
411 Bucket::Class(class) => self.class_hash.try_get_or_insert_with(
412 class,
413 quirks_mode,
414 Default::default,
415 )?,
416 Bucket::Attribute { name, lower_name }
417 | Bucket::LocalName { name, lower_name } => {
418 let is_attribute = matches!($bucket, Bucket::Attribute { .. });
432 let hash = if is_attribute {
433 &mut self.attribute_hash
434 } else {
435 &mut self.local_name_hash
436 };
437 if name != lower_name {
438 hash.try_reserve(1)?;
439 let vec = hash.entry_ref(lower_name).or_default();
440 vec.try_reserve(1)?;
441 let mut entry = $entry.clone();
442 entry.set_bucket_matches(bucket_matches);
443 vec.push(entry);
444 }
445 hash.try_reserve(1)?;
446 hash.entry_ref(name).or_default()
447 },
448 Bucket::Namespace(url) => {
449 self.namespace_hash.try_reserve(1)?;
450 self.namespace_hash.entry_ref(url).or_default()
451 },
452 Bucket::RarePseudoClasses => &mut self.rare_pseudo_classes,
453 Bucket::Universal => &mut self.other,
454 };
455 vec.try_reserve(1)?;
456 $entry.set_bucket_matches(bucket_matches);
457 vec.push($entry);
458 }};
459 }
460
461 let bucket = {
462 let mut disjoint_buckets = SmallVec::new();
463 let bucket = find_bucket(
464 entry.selector(),
465 quirks_mode,
466 &mut disjoint_buckets,
467 &mut bucket_matches,
468 false,
469 );
470
471 if !disjoint_buckets.is_empty()
487 && disjoint_buckets
488 .iter()
489 .all(|b| b.more_specific_than(&bucket))
490 {
491 for bucket in &disjoint_buckets {
492 let mut entry = entry.clone();
493 insert_into_bucket!(entry, *bucket);
494 }
495 return Ok(());
496 }
497 bucket
498 };
499
500 insert_into_bucket!(entry, bucket);
501 Ok(())
502 }
503
504 pub fn lookup<'a, E, F>(
515 &'a self,
516 element: E,
517 quirks_mode: QuirksMode,
518 relevant_attributes: Option<&mut RelevantAttributes>,
519 f: F,
520 ) -> bool
521 where
522 E: TElement,
523 F: FnMut(&'a T) -> bool,
524 {
525 self.lookup_with_state(
526 element,
527 element.state(),
528 quirks_mode,
529 relevant_attributes,
530 f,
531 )
532 }
533
534 #[inline]
535 fn lookup_with_state<'a, E, F>(
536 &'a self,
537 element: E,
538 element_state: ElementState,
539 quirks_mode: QuirksMode,
540 mut relevant_attributes: Option<&mut RelevantAttributes>,
541 mut f: F,
542 ) -> bool
543 where
544 E: TElement,
545 F: FnMut(&'a T) -> bool,
546 {
547 if element.is_root() {
548 for entry in self.root.iter() {
549 if !f(entry) {
550 return false;
551 }
552 }
553 }
554
555 if let Some(id) = element.id()
556 && let Some(v) = self.id_hash.get(id, quirks_mode)
557 {
558 for entry in v.iter() {
559 if !f(entry) {
560 return false;
561 }
562 }
563 }
564
565 let mut done = false;
566 element.each_class(|class| {
567 if done {
568 return;
569 }
570 if let Some(v) = self.class_hash.get(class, quirks_mode) {
571 for entry in v.iter() {
572 if !f(entry) {
573 done = true;
574 return;
575 }
576 }
577 }
578 });
579
580 if done {
581 return false;
582 }
583
584 element.each_attr_name(|name| {
585 if done {
586 return;
587 }
588 if let Some(v) = self.attribute_hash.get(name) {
589 if let Some(ref mut relevant_attributes) = relevant_attributes {
590 relevant_attributes.push(name.clone());
591 }
592 for entry in v.iter() {
593 if !f(entry) {
594 done = true;
595 return;
596 }
597 }
598 }
599 });
600
601 if done {
602 return false;
603 }
604
605 if let Some(v) = self.local_name_hash.get(element.local_name()) {
606 for entry in v.iter() {
607 if !f(entry) {
608 return false;
609 }
610 }
611 }
612
613 if let Some(v) = self.namespace_hash.get(element.namespace()) {
614 for entry in v.iter() {
615 if !f(entry) {
616 return false;
617 }
618 }
619 }
620
621 if element_state.intersects(RARE_PSEUDO_CLASS_STATES) {
622 for entry in self.rare_pseudo_classes.iter() {
623 if !f(entry) {
624 return false;
625 }
626 }
627 }
628
629 for entry in self.other.iter() {
630 if !f(entry) {
631 return false;
632 }
633 }
634
635 true
636 }
637
638 #[inline]
646 pub fn lookup_with_additional<'a, E, F>(
647 &'a self,
648 element: E,
649 quirks_mode: QuirksMode,
650 additional_id: Option<&WeakAtom>,
651 additional_classes: &[Atom],
652 additional_states: ElementState,
653 mut f: F,
654 ) -> bool
655 where
656 E: TElement,
657 F: FnMut(&'a T) -> bool,
658 {
659 if !self.lookup_with_state(
661 element,
662 element.state() | additional_states,
663 quirks_mode,
664 None,
665 &mut f,
666 ) {
667 return false;
668 }
669
670 if let Some(id) = additional_id
672 && let Some(v) = self.id_hash.get(id, quirks_mode)
673 {
674 for entry in v.iter() {
675 if !f(entry) {
676 return false;
677 }
678 }
679 }
680
681 for class in additional_classes {
683 if let Some(v) = self.class_hash.get(class, quirks_mode) {
684 for entry in v.iter() {
685 if !f(entry) {
686 return false;
687 }
688 }
689 }
690 }
691
692 true
693 }
694}
695
696#[derive(PartialEq)]
697enum Bucket<'a> {
698 Universal,
699 Namespace(&'a Namespace),
700 RarePseudoClasses,
701 LocalName {
702 name: &'a LocalName,
703 lower_name: &'a LocalName,
704 },
705 Attribute {
706 name: &'a LocalName,
707 lower_name: &'a LocalName,
708 },
709 Class(&'a Atom),
710 ID(&'a Atom),
711 Root,
712}
713
714impl<'a> Bucket<'a> {
715 #[inline]
717 fn specificity(&self) -> usize {
718 match *self {
719 Bucket::Universal => 0,
720 Bucket::Namespace(..) => 1,
721 Bucket::RarePseudoClasses => 2,
722 Bucket::LocalName { .. } => 3,
723 Bucket::Attribute { .. } => 4,
724 Bucket::Class(..) => 5,
725 Bucket::ID(..) => 6,
726 Bucket::Root => 7,
727 }
728 }
729
730 #[inline]
731 fn more_or_equally_specific_than(&self, other: &Self) -> bool {
732 self.specificity() >= other.specificity()
733 }
734
735 #[inline]
736 fn more_specific_than(&self, other: &Self) -> bool {
737 self.specificity() > other.specificity()
738 }
739}
740
741type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>;
742
743#[derive(Copy, Clone, Debug, PartialEq, MallocSizeOf)]
745pub enum BucketMatches {
746 Full,
748 Subject,
750 Unknown,
752}
753
754fn specific_bucket_for<'a>(
755 component: &'a Component<SelectorImpl>,
756 quirks_mode: QuirksMode,
757 disjoint_buckets: &mut DisjointBuckets<'a>,
758 bucket_matches: &mut BucketMatches,
759 nested: bool,
760) -> Bucket<'a> {
761 match *component {
762 Component::Root => Bucket::Root,
763 Component::ID(ref id) => {
764 if quirks_mode == QuirksMode::Quirks {
765 *bucket_matches = BucketMatches::Unknown;
767 }
768 Bucket::ID(id)
769 },
770 Component::Class(ref class) => {
771 if quirks_mode == QuirksMode::Quirks {
772 *bucket_matches = BucketMatches::Unknown;
774 }
775 Bucket::Class(class)
776 },
777 Component::AttributeInNoNamespace { ref local_name, .. } => {
778 *bucket_matches = BucketMatches::Unknown;
780 Bucket::Attribute {
781 name: local_name,
782 lower_name: local_name,
783 }
784 },
785 Component::AttributeInNoNamespaceExists {
786 ref local_name,
787 ref local_name_lower,
788 } => {
789 *bucket_matches = BucketMatches::Unknown;
791 Bucket::Attribute {
792 name: local_name,
793 lower_name: local_name_lower,
794 }
795 },
796 Component::AttributeOther(ref selector) => {
797 *bucket_matches = BucketMatches::Unknown;
799 Bucket::Attribute {
800 name: &selector.local_name,
801 lower_name: &selector.local_name_lower,
802 }
803 },
804 Component::LocalName(ref selector) => {
805 if selector.name != selector.lower_name {
806 *bucket_matches = BucketMatches::Unknown;
807 }
808 Bucket::LocalName {
809 name: &selector.name,
810 lower_name: &selector.lower_name,
811 }
812 },
813 Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => {
814 Bucket::Namespace(url)
815 },
816 Component::Slotted(ref selector) => {
835 *bucket_matches = BucketMatches::Unknown;
838 find_bucket(
839 selector.iter(),
840 quirks_mode,
841 disjoint_buckets,
842 bucket_matches,
843 true,
844 )
845 },
846 Component::Host(ref selector) => {
847 *bucket_matches = BucketMatches::Unknown;
853 if let Some(selector) = selector {
854 find_bucket(
855 selector.iter(),
856 quirks_mode,
857 disjoint_buckets,
858 bucket_matches,
859 true,
860 )
861 } else {
862 Bucket::Universal
863 }
864 },
865 Component::Is(ref list) | Component::Where(ref list) => {
866 if list.len() == 1 {
867 find_bucket(
868 list.slice()[0].iter(),
869 quirks_mode,
870 disjoint_buckets,
871 bucket_matches,
872 true,
873 )
874 } else {
875 *bucket_matches = BucketMatches::Unknown;
880 for selector in list.slice() {
881 let bucket = find_bucket(
882 selector.iter(),
883 quirks_mode,
884 disjoint_buckets,
885 bucket_matches,
886 true,
887 );
888 if disjoint_buckets.last() == Some(&bucket) {
889 continue;
893 }
894 disjoint_buckets.push(bucket);
895 }
896 Bucket::Universal
897 }
898 },
899 Component::NonTSPseudoClass(ref pseudo_class)
900 if pseudo_class
901 .state_flag()
902 .intersects(RARE_PSEUDO_CLASS_STATES) =>
903 {
904 *bucket_matches = BucketMatches::Unknown;
907 Bucket::RarePseudoClasses
908 },
909 Component::PseudoElement(ref pseudo) => {
910 if pseudo.has_argument() || nested {
914 *bucket_matches = BucketMatches::Unknown;
915 }
916 Bucket::Universal
917 },
918 Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => {
919 Bucket::Universal
921 },
922 _ => {
923 *bucket_matches = BucketMatches::Unknown;
924 Bucket::Universal
925 },
926 }
927}
928
929#[inline(always)]
938fn find_bucket<'a>(
939 mut iter: SelectorIter<'a, SelectorImpl>,
940 quirks_mode: QuirksMode,
941 disjoint_buckets: &mut DisjointBuckets<'a>,
942 bucket_matches: &mut BucketMatches,
943 nested: bool,
944) -> Bucket<'a> {
945 let mut current_bucket = Bucket::Universal;
946
947 loop {
948 for ss in &mut iter {
949 let new_bucket =
950 specific_bucket_for(ss, quirks_mode, disjoint_buckets, bucket_matches, nested);
951 if current_bucket != Bucket::Universal {
954 *bucket_matches = BucketMatches::Unknown;
956 }
957 if new_bucket.more_or_equally_specific_than(¤t_bucket) {
958 current_bucket = new_bucket;
959 }
960 }
961
962 match iter.next_sequence() {
965 None => break,
966 Some(Combinator::PseudoElement) => continue,
967 Some(..) => {
968 if *bucket_matches != BucketMatches::Unknown {
970 if nested {
971 *bucket_matches = BucketMatches::Unknown;
972 } else {
973 *bucket_matches = BucketMatches::Subject;
974 }
975 }
976 break;
977 },
978 }
979 }
980
981 current_bucket
982}
983
984#[derive(Clone, Debug, MallocSizeOf)]
986pub struct MaybeCaseInsensitiveHashMap<K: PrecomputedHash + Hash + Eq, V>(PrecomputedHashMap<K, V>);
987
988impl<V> Default for MaybeCaseInsensitiveHashMap<Atom, V> {
989 #[inline]
990 fn default() -> Self {
991 MaybeCaseInsensitiveHashMap(PrecomputedHashMap::default())
992 }
993}
994
995impl<V> MaybeCaseInsensitiveHashMap<Atom, V> {
996 pub fn new() -> Self {
998 Self::default()
999 }
1000
1001 pub fn shrink_if_needed(&mut self) {
1003 self.0.shrink_if_needed()
1004 }
1005
1006 pub fn try_get_or_insert_with(
1009 &mut self,
1010 key: &WeakAtom,
1011 quirks_mode: QuirksMode,
1012 default: impl FnOnce() -> V,
1013 ) -> Result<&mut V, AllocErr> {
1014 self.0.try_reserve(1)?;
1015 let lower;
1016 let key: &WeakAtom = if quirks_mode == QuirksMode::Quirks {
1017 lower = key.to_ascii_lowercase();
1018 lower.borrow()
1019 } else {
1020 key
1021 };
1022 Ok(self.0.entry_ref(key).or_insert_with(default))
1023 }
1024
1025 #[inline]
1027 pub fn is_empty(&self) -> bool {
1028 self.0.is_empty()
1029 }
1030
1031 pub fn iter(&self) -> hash_map::Iter<'_, Atom, V> {
1033 self.0.iter()
1034 }
1035
1036 pub fn clear(&mut self) {
1038 self.0.clear()
1039 }
1040
1041 pub fn get(&self, key: &WeakAtom, quirks_mode: QuirksMode) -> Option<&V> {
1043 let lower;
1044 let key: &WeakAtom = if quirks_mode == QuirksMode::Quirks {
1045 lower = key.to_ascii_lowercase();
1046 lower.borrow()
1047 } else {
1048 key
1049 };
1050 self.0.get(key)
1051 }
1052}
1053
1054#[test]
1055#[cfg(feature = "servo")]
1056fn test_precomputed_hash_set() {
1057 let mut set = PrecomputedHashSet::default();
1058 let atom = atom!("");
1059 assert!(!set.contains(&atom));
1060 set.insert(atom.clone());
1061 assert!(set.contains(&atom));
1062}