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