1#![deny(missing_docs)]
6
7use crate::attr::{AttrIdentifier, AttrValue};
10use crate::computed_value_flags::ComputedValueFlags;
11use crate::dom::{OpaqueNode, TElement, TNode};
12use crate::invalidation::element::document_state::InvalidationMatchingData;
13use crate::invalidation::element::element_wrapper::ElementSnapshot;
14use crate::properties::longhands::display::computed_value::T as Display;
15use crate::properties::{ComputedValues, PropertyFlags};
16use crate::selector_parser::AttrValue as SelectorAttrValue;
17use crate::selector_parser::{PseudoElementCascadeType, SelectorParser};
18use crate::values::{AtomIdent, AtomString};
19use crate::{Atom, CaseSensitivityExt, LocalName, Namespace, Prefix};
20use cssparser::{serialize_identifier, CowRcStr, Parser as CssParser, SourceLocation, ToCss};
21use dom::{DocumentState, ElementState};
22use fxhash::FxHashMap;
23use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
24use selectors::parser::SelectorParseErrorKind;
25use selectors::visitor::SelectorVisitor;
26use std::fmt;
27use std::mem;
28use std::ops::{Deref, DerefMut};
29use style_traits::{ParseError, StyleParseErrorKind};
30
31#[derive(
35 Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem,
36)]
37#[allow(missing_docs)]
38#[repr(usize)]
39pub enum PseudoElement {
40 After = 0,
42 Before,
43 Selection,
44 Backdrop,
55 DetailsSummary,
56 DetailsContent,
57 Marker,
58 ServoAnonymousBox,
59 ServoAnonymousTable,
60 ServoAnonymousTableCell,
61 ServoAnonymousTableRow,
62 ServoTableGrid,
63 ServoTableWrapper,
64}
65
66pub const PSEUDO_COUNT: usize = PseudoElement::ServoTableWrapper as usize + 1;
68
69impl ToCss for PseudoElement {
70 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
71 where
72 W: fmt::Write,
73 {
74 use self::PseudoElement::*;
75 dest.write_str(match *self {
76 After => "::after",
77 Before => "::before",
78 Selection => "::selection",
79 Backdrop => "::backdrop",
80 DetailsSummary => "::-servo-details-summary",
81 DetailsContent => "::-servo-details-content",
82 Marker => "::marker",
83 ServoAnonymousBox => "::-servo-anonymous-box",
84 ServoAnonymousTable => "::-servo-anonymous-table",
85 ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
86 ServoAnonymousTableRow => "::-servo-anonymous-table-row",
87 ServoTableGrid => "::-servo-table-grid",
88 ServoTableWrapper => "::-servo-table-wrapper",
89 })
90 }
91}
92
93impl ::selectors::parser::PseudoElement for PseudoElement {
94 type Impl = SelectorImpl;
95}
96
97pub const EAGER_PSEUDO_COUNT: usize = 3;
99
100impl PseudoElement {
101 #[inline]
103 pub fn eager_index(&self) -> usize {
104 debug_assert!(self.is_eager());
105 self.clone() as usize
106 }
107
108 #[inline]
110 pub fn index(&self) -> usize {
111 self.clone() as usize
112 }
113
114 pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] {
116 Default::default()
117 }
118
119 #[inline]
121 pub fn from_eager_index(i: usize) -> Self {
122 assert!(i < EAGER_PSEUDO_COUNT);
123 let result: PseudoElement = unsafe { mem::transmute(i) };
124 debug_assert!(result.is_eager());
125 result
126 }
127
128 #[inline]
130 pub fn is_before_or_after(&self) -> bool {
131 self.is_before() || self.is_after()
132 }
133
134 #[inline]
136 pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
137 false
138 }
139
140 #[inline]
142 pub fn is_marker(&self) -> bool {
143 *self == PseudoElement::Marker
144 }
145
146 #[inline]
148 pub fn is_selection(&self) -> bool {
149 *self == PseudoElement::Selection
150 }
151
152 #[inline]
154 pub fn is_before(&self) -> bool {
155 *self == PseudoElement::Before
156 }
157
158 #[inline]
160 pub fn is_after(&self) -> bool {
161 *self == PseudoElement::After
162 }
163
164 #[inline]
166 pub fn is_first_letter(&self) -> bool {
167 false
168 }
169
170 #[inline]
172 pub fn is_first_line(&self) -> bool {
173 false
174 }
175
176 #[inline]
178 pub fn is_color_swatch(&self) -> bool {
179 false
180 }
181
182 #[inline]
184 pub fn is_eager(&self) -> bool {
185 self.cascade_type() == PseudoElementCascadeType::Eager
186 }
187
188 #[inline]
190 pub fn is_lazy(&self) -> bool {
191 self.cascade_type() == PseudoElementCascadeType::Lazy
192 }
193
194 pub fn is_anon_box(&self) -> bool {
196 self.is_precomputed()
197 }
198
199 #[inline]
202 pub fn skip_item_display_fixup(&self) -> bool {
203 !self.is_before_or_after()
204 }
205
206 #[inline]
208 pub fn is_precomputed(&self) -> bool {
209 self.cascade_type() == PseudoElementCascadeType::Precomputed
210 }
211
212 #[inline]
220 pub fn cascade_type(&self) -> PseudoElementCascadeType {
221 match *self {
222 PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => {
223 PseudoElementCascadeType::Eager
224 },
225 PseudoElement::Backdrop |
226 PseudoElement::DetailsSummary |
227 PseudoElement::Marker => PseudoElementCascadeType::Lazy,
228 PseudoElement::DetailsContent |
229 PseudoElement::ServoAnonymousBox |
230 PseudoElement::ServoAnonymousTable |
231 PseudoElement::ServoAnonymousTableCell |
232 PseudoElement::ServoAnonymousTableRow |
233 PseudoElement::ServoTableGrid |
234 PseudoElement::ServoTableWrapper => PseudoElementCascadeType::Precomputed,
235 }
236 }
237
238 pub fn canonical(&self) -> PseudoElement {
241 self.clone()
242 }
243
244 pub fn pseudo_info(&self) {
246 ()
247 }
248
249 #[inline]
251 pub fn property_restriction(&self) -> Option<PropertyFlags> {
252 None
253 }
254
255 pub fn should_exist(&self, style: &ComputedValues) -> bool {
258 let display = style.get_box().clone_display();
259 if display == Display::None {
260 return false;
261 }
262 if self.is_before_or_after() && style.ineffective_content_property() {
263 return false;
264 }
265
266 true
267 }
268}
269
270pub type Lang = Box<str>;
272
273#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)]
275pub struct CustomState(pub AtomIdent);
276
277#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
280#[allow(missing_docs)]
281pub enum NonTSPseudoClass {
282 Active,
283 AnyLink,
284 Autofill,
285 Checked,
286 CustomState(CustomState),
288 Default,
289 Defined,
290 Disabled,
291 Enabled,
292 Focus,
293 FocusWithin,
294 FocusVisible,
295 Fullscreen,
296 Hover,
297 InRange,
298 Indeterminate,
299 Invalid,
300 Lang(Lang),
301 Link,
302 Modal,
303 MozMeterOptimum,
304 MozMeterSubOptimum,
305 MozMeterSubSubOptimum,
306 Optional,
307 OutOfRange,
308 PlaceholderShown,
309 PopoverOpen,
310 ReadOnly,
311 ReadWrite,
312 Required,
313 ServoNonZeroBorder,
314 Target,
315 UserInvalid,
316 UserValid,
317 Valid,
318 Visited,
319}
320
321impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
322 type Impl = SelectorImpl;
323
324 #[inline]
325 fn is_active_or_hover(&self) -> bool {
326 matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
327 }
328
329 #[inline]
330 fn is_user_action_state(&self) -> bool {
331 matches!(
332 *self,
333 NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
334 )
335 }
336
337 fn visit<V>(&self, _: &mut V) -> bool
338 where
339 V: SelectorVisitor<Impl = Self::Impl>,
340 {
341 true
342 }
343}
344
345impl ToCss for NonTSPseudoClass {
346 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
347 where
348 W: fmt::Write,
349 {
350 use self::NonTSPseudoClass::*;
351 if let Lang(ref lang) = *self {
352 dest.write_str(":lang(")?;
353 serialize_identifier(lang, dest)?;
354 return dest.write_char(')');
355 }
356
357 dest.write_str(match *self {
358 Self::Active => ":active",
359 Self::AnyLink => ":any-link",
360 Self::Autofill => ":autofill",
361 Self::Checked => ":checked",
362 Self::Default => ":default",
363 Self::Defined => ":defined",
364 Self::Disabled => ":disabled",
365 Self::Enabled => ":enabled",
366 Self::Focus => ":focus",
367 Self::FocusVisible => ":focus-visible",
368 Self::FocusWithin => ":focus-within",
369 Self::Fullscreen => ":fullscreen",
370 Self::Hover => ":hover",
371 Self::InRange => ":in-range",
372 Self::Indeterminate => ":indeterminate",
373 Self::Invalid => ":invalid",
374 Self::Link => ":link",
375 Self::Modal => ":modal",
376 Self::MozMeterOptimum => ":-moz-meter-optimum",
377 Self::MozMeterSubOptimum => ":-moz-meter-sub-optimum",
378 Self::MozMeterSubSubOptimum => ":-moz-meter-sub-sub-optimum",
379 Self::Optional => ":optional",
380 Self::OutOfRange => ":out-of-range",
381 Self::PlaceholderShown => ":placeholder-shown",
382 Self::PopoverOpen => ":popover-open",
383 Self::ReadOnly => ":read-only",
384 Self::ReadWrite => ":read-write",
385 Self::Required => ":required",
386 Self::ServoNonZeroBorder => ":-servo-nonzero-border",
387 Self::Target => ":target",
388 Self::UserInvalid => ":user-invalid",
389 Self::UserValid => ":user-valid",
390 Self::Valid => ":valid",
391 Self::Visited => ":visited",
392 Self::Lang(_) | Self::CustomState(_) => unreachable!(),
393 })
394 }
395}
396
397impl NonTSPseudoClass {
398 pub fn state_flag(&self) -> ElementState {
401 match *self {
402 Self::Active => ElementState::ACTIVE,
403 Self::AnyLink => ElementState::VISITED_OR_UNVISITED,
404 Self::Autofill => ElementState::AUTOFILL,
405 Self::Checked => ElementState::CHECKED,
406 Self::Default => ElementState::DEFAULT,
407 Self::Defined => ElementState::DEFINED,
408 Self::Disabled => ElementState::DISABLED,
409 Self::Enabled => ElementState::ENABLED,
410 Self::Focus => ElementState::FOCUS,
411 Self::FocusVisible => ElementState::FOCUSRING,
412 Self::FocusWithin => ElementState::FOCUS_WITHIN,
413 Self::Fullscreen => ElementState::FULLSCREEN,
414 Self::Hover => ElementState::HOVER,
415 Self::InRange => ElementState::INRANGE,
416 Self::Indeterminate => ElementState::INDETERMINATE,
417 Self::Invalid => ElementState::INVALID,
418 Self::Link => ElementState::UNVISITED,
419 Self::Modal => ElementState::MODAL,
420 Self::MozMeterOptimum => ElementState::OPTIMUM,
421 Self::MozMeterSubOptimum => ElementState::SUB_OPTIMUM,
422 Self::MozMeterSubSubOptimum => ElementState::SUB_SUB_OPTIMUM,
423 Self::Optional => ElementState::OPTIONAL_,
424 Self::OutOfRange => ElementState::OUTOFRANGE,
425 Self::PlaceholderShown => ElementState::PLACEHOLDER_SHOWN,
426 Self::PopoverOpen => ElementState::POPOVER_OPEN,
427 Self::ReadOnly => ElementState::READONLY,
428 Self::ReadWrite => ElementState::READWRITE,
429 Self::Required => ElementState::REQUIRED,
430 Self::Target => ElementState::URLTARGET,
431 Self::UserInvalid => ElementState::USER_INVALID,
432 Self::UserValid => ElementState::USER_VALID,
433 Self::Valid => ElementState::VALID,
434 Self::Visited => ElementState::VISITED,
435 Self::CustomState(_) | Self::Lang(_) | Self::ServoNonZeroBorder => ElementState::empty(),
436 }
437 }
438
439 pub fn document_state_flag(&self) -> DocumentState {
441 DocumentState::empty()
442 }
443
444 pub fn needs_cache_revalidation(&self) -> bool {
446 self.state_flag().is_empty()
447 }
448}
449
450#[derive(Clone, Debug, PartialEq)]
453#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
454pub struct SelectorImpl;
455
456#[derive(Debug, Default)]
459pub struct ExtraMatchingData<'a> {
460 pub invalidation_data: InvalidationMatchingData,
462
463 pub cascade_input_flags: ComputedValueFlags,
466
467 pub originating_element_style: Option<&'a ComputedValues>,
470}
471
472impl ::selectors::SelectorImpl for SelectorImpl {
473 type PseudoElement = PseudoElement;
474 type NonTSPseudoClass = NonTSPseudoClass;
475
476 type ExtraMatchingData<'a> = ExtraMatchingData<'a>;
477 type AttrValue = AtomString;
478 type Identifier = AtomIdent;
479 type LocalName = LocalName;
480 type NamespacePrefix = Prefix;
481 type NamespaceUrl = Namespace;
482 type BorrowedLocalName = web_atoms::LocalName;
483 type BorrowedNamespaceUrl = web_atoms::Namespace;
484}
485
486impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
487 type Impl = SelectorImpl;
488 type Error = StyleParseErrorKind<'i>;
489
490 #[inline]
491 fn parse_nth_child_of(&self) -> bool {
492 false
493 }
494
495 #[inline]
496 fn parse_is_and_where(&self) -> bool {
497 true
498 }
499
500 #[inline]
501 fn parse_has(&self) -> bool {
502 false
503 }
504
505 #[inline]
506 fn parse_parent_selector(&self) -> bool {
507 true
508 }
509
510 #[inline]
511 fn parse_part(&self) -> bool {
512 true
513 }
514
515 #[inline]
516 fn allow_forgiving_selectors(&self) -> bool {
517 !self.for_supports_rule
518 }
519
520 fn parse_non_ts_pseudo_class(
521 &self,
522 location: SourceLocation,
523 name: CowRcStr<'i>,
524 ) -> Result<NonTSPseudoClass, ParseError<'i>> {
525 let pseudo_class = match_ignore_ascii_case! { &name,
526 "active" => NonTSPseudoClass::Active,
527 "any-link" => NonTSPseudoClass::AnyLink,
528 "autofill" => NonTSPseudoClass::Autofill,
529 "checked" => NonTSPseudoClass::Checked,
530 "default" => NonTSPseudoClass::Default,
531 "defined" => NonTSPseudoClass::Defined,
532 "disabled" => NonTSPseudoClass::Disabled,
533 "enabled" => NonTSPseudoClass::Enabled,
534 "focus" => NonTSPseudoClass::Focus,
535 "focus-visible" => NonTSPseudoClass::FocusVisible,
536 "focus-within" => NonTSPseudoClass::FocusWithin,
537 "fullscreen" => NonTSPseudoClass::Fullscreen,
538 "hover" => NonTSPseudoClass::Hover,
539 "indeterminate" => NonTSPseudoClass::Indeterminate,
540 "invalid" => NonTSPseudoClass::Invalid,
541 "link" => NonTSPseudoClass::Link,
542 "optional" => NonTSPseudoClass::Optional,
543 "out-of-range" => NonTSPseudoClass::OutOfRange,
544 "placeholder-shown" => NonTSPseudoClass::PlaceholderShown,
545 "popover-open" => NonTSPseudoClass::PopoverOpen,
546 "read-only" => NonTSPseudoClass::ReadOnly,
547 "read-write" => NonTSPseudoClass::ReadWrite,
548 "required" => NonTSPseudoClass::Required,
549 "target" => NonTSPseudoClass::Target,
550 "user-invalid" => NonTSPseudoClass::UserInvalid,
551 "user-valid" => NonTSPseudoClass::UserValid,
552 "valid" => NonTSPseudoClass::Valid,
553 "visited" => NonTSPseudoClass::Visited,
554 "-moz-meter-optimum" => NonTSPseudoClass::MozMeterOptimum,
555 "-moz-meter-sub-optimum" => NonTSPseudoClass::MozMeterSubOptimum,
556 "-moz-meter-sub-sub-optimum" => NonTSPseudoClass::MozMeterSubSubOptimum,
557 "-servo-nonzero-border" => {
558 if !self.in_user_agent_stylesheet() {
559 return Err(location.new_custom_error(
560 SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into())
561 ))
562 }
563 NonTSPseudoClass::ServoNonZeroBorder
564 },
565 _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
566 };
567
568 Ok(pseudo_class)
569 }
570
571 fn parse_non_ts_functional_pseudo_class<'t>(
572 &self,
573 name: CowRcStr<'i>,
574 parser: &mut CssParser<'i, 't>,
575 after_part: bool,
576 ) -> Result<NonTSPseudoClass, ParseError<'i>> {
577 use self::NonTSPseudoClass::*;
578 let pseudo_class = match_ignore_ascii_case! { &name,
579 "lang" if !after_part => {
580 Lang(parser.expect_ident_or_string()?.as_ref().into())
581 },
582 _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
583 };
584
585 Ok(pseudo_class)
586 }
587
588 fn parse_pseudo_element(
589 &self,
590 location: SourceLocation,
591 name: CowRcStr<'i>,
592 ) -> Result<PseudoElement, ParseError<'i>> {
593 use self::PseudoElement::*;
594 let pseudo_element = match_ignore_ascii_case! { &name,
595 "before" => Before,
596 "after" => After,
597 "backdrop" => Backdrop,
598 "selection" => Selection,
599 "marker" => Marker,
600 "-servo-details-summary" => {
601 if !self.in_user_agent_stylesheet() {
602 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
603 }
604 DetailsSummary
605 },
606 "-servo-details-content" => {
607 if !self.in_user_agent_stylesheet() {
608 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
609 }
610 DetailsContent
611 },
612 "-servo-anonymous-box" => {
613 if !self.in_user_agent_stylesheet() {
614 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
615 }
616 ServoAnonymousBox
617 },
618 "-servo-anonymous-table" => {
619 if !self.in_user_agent_stylesheet() {
620 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
621 }
622 ServoAnonymousTable
623 },
624 "-servo-anonymous-table-row" => {
625 if !self.in_user_agent_stylesheet() {
626 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
627 }
628 ServoAnonymousTableRow
629 },
630 "-servo-anonymous-table-cell" => {
631 if !self.in_user_agent_stylesheet() {
632 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
633 }
634 ServoAnonymousTableCell
635 },
636 "-servo-table-grid" => {
637 if !self.in_user_agent_stylesheet() {
638 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
639 }
640 ServoTableGrid
641 },
642 "-servo-table-wrapper" => {
643 if !self.in_user_agent_stylesheet() {
644 return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
645 }
646 ServoTableWrapper
647 },
648 _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
649
650 };
651
652 Ok(pseudo_element)
653 }
654
655 fn default_namespace(&self) -> Option<Namespace> {
656 self.namespaces.default.as_ref().map(|ns| ns.clone())
657 }
658
659 fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
660 self.namespaces.prefixes.get(prefix).cloned()
661 }
662
663 fn parse_host(&self) -> bool {
664 true
665 }
666
667 fn parse_slotted(&self) -> bool {
668 true
669 }
670}
671
672impl SelectorImpl {
673 #[inline]
676 pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
677 where
678 F: FnMut(PseudoElement),
679 {
680 for i in 0..EAGER_PSEUDO_COUNT {
681 fun(PseudoElement::from_eager_index(i));
682 }
683 }
684}
685
686#[derive(Debug)]
688pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
689
690impl SnapshotMap {
691 pub fn new() -> Self {
693 SnapshotMap(FxHashMap::default())
694 }
695
696 pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
698 self.0.get(&el.as_node().opaque())
699 }
700}
701
702impl Deref for SnapshotMap {
703 type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
704
705 fn deref(&self) -> &Self::Target {
706 &self.0
707 }
708}
709
710impl DerefMut for SnapshotMap {
711 fn deref_mut(&mut self) -> &mut Self::Target {
712 &mut self.0
713 }
714}
715
716#[derive(Debug, Default, MallocSizeOf)]
718pub struct ServoElementSnapshot {
719 pub state: Option<ElementState>,
721 pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
723 pub changed_attrs: Vec<LocalName>,
725 pub class_changed: bool,
727 pub id_changed: bool,
729 pub other_attributes_changed: bool,
731}
732
733impl ServoElementSnapshot {
734 pub fn new() -> Self {
736 Self::default()
737 }
738
739 pub fn id_changed(&self) -> bool {
741 self.id_changed
742 }
743
744 pub fn class_changed(&self) -> bool {
746 self.class_changed
747 }
748
749 pub fn other_attr_changed(&self) -> bool {
751 self.other_attributes_changed
752 }
753
754 fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
755 self.attrs
756 .as_ref()
757 .unwrap()
758 .iter()
759 .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
760 .map(|&(_, ref v)| v)
761 }
762
763 #[inline]
765 pub fn each_attr_changed<F>(&self, mut callback: F)
766 where
767 F: FnMut(&LocalName),
768 {
769 for name in &self.changed_attrs {
770 callback(name)
771 }
772 }
773
774 fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
775 where
776 F: FnMut(&AttrValue) -> bool,
777 {
778 self.attrs
779 .as_ref()
780 .unwrap()
781 .iter()
782 .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
783 }
784}
785
786impl ElementSnapshot for ServoElementSnapshot {
787 fn state(&self) -> Option<ElementState> {
788 self.state.clone()
789 }
790
791 fn has_attrs(&self) -> bool {
792 self.attrs.is_some()
793 }
794
795 fn id_attr(&self) -> Option<&Atom> {
796 self.get_attr(&ns!(), &local_name!("id"))
797 .map(|v| v.as_atom())
798 }
799
800 fn is_part(&self, part_name: &AtomIdent) -> bool {
801 self.get_attr(&ns!(), &local_name!("part"))
802 .is_some_and(|v| {
803 v.as_tokens()
804 .iter()
805 .any(|atom| CaseSensitivity::CaseSensitive.eq_atom(atom, part_name))
806 })
807 }
808
809 fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
810 None
811 }
812
813 fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
814 self.get_attr(&ns!(), &local_name!("class"))
815 .map_or(false, |v| {
816 v.as_tokens()
817 .iter()
818 .any(|atom| case_sensitivity.eq_atom(atom, name))
819 })
820 }
821
822 fn each_class<F>(&self, mut callback: F)
823 where
824 F: FnMut(&AtomIdent),
825 {
826 if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
827 for class in v.as_tokens() {
828 callback(AtomIdent::cast(class));
829 }
830 }
831 }
832
833 fn lang_attr(&self) -> Option<SelectorAttrValue> {
834 self.get_attr(&ns!(xml), &local_name!("lang"))
835 .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
836 .map(|v| SelectorAttrValue::from(v as &str))
837 }
838
839 #[inline]
841 fn has_custom_states(&self) -> bool {
842 false
843 }
844
845 #[inline]
847 fn has_custom_state(&self, _state: &AtomIdent) -> bool {
848 false
849 }
850
851 #[inline]
852 fn each_custom_state<F>(&self, mut _callback: F)
853 where
854 F: FnMut(&AtomIdent),
855 {
856 }
857}
858
859impl ServoElementSnapshot {
860 pub fn attr_matches(
862 &self,
863 ns: &NamespaceConstraint<&Namespace>,
864 local_name: &LocalName,
865 operation: &AttrSelectorOperation<&AtomString>,
866 ) -> bool {
867 match *ns {
868 NamespaceConstraint::Specific(ref ns) => self
869 .get_attr(ns, local_name)
870 .map_or(false, |value| value.eval_selector(operation)),
871 NamespaceConstraint::Any => {
872 self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
873 },
874 }
875 }
876}
877
878pub fn extended_filtering(tag: &str, range: &str) -> bool {
881 range.split(',').any(|lang_range| {
882 let mut range_subtags = lang_range.split('\x2d');
884 let mut tag_subtags = tag.split('\x2d');
885
886 if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
889 if !(range_subtag.eq_ignore_ascii_case(tag_subtag) ||
890 range_subtag.eq_ignore_ascii_case("*"))
891 {
892 return false;
893 }
894 }
895
896 let mut current_tag_subtag = tag_subtags.next();
897
898 for range_subtag in range_subtags {
900 if range_subtag == "*" {
902 continue;
903 }
904 match current_tag_subtag.clone() {
905 Some(tag_subtag) => {
906 if range_subtag.eq_ignore_ascii_case(tag_subtag) {
908 current_tag_subtag = tag_subtags.next();
909 continue;
910 }
911 if tag_subtag.len() == 1 {
913 return false;
914 }
915 current_tag_subtag = tag_subtags.next();
917 if current_tag_subtag.is_none() {
918 return false;
919 }
920 },
921 None => {
923 return false;
924 },
925 }
926 }
927 true
929 })
930}