Skip to main content

style/servo/
selector_parser.rs

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#![deny(missing_docs)]
6
7//! Servo's selector parser.
8
9use crate::FxHashMap;
10use crate::attr::{AttrIdentifier, AttrValue};
11use crate::computed_value_flags::ComputedValueFlags;
12use crate::derives::*;
13use crate::dom::{OpaqueNode, TElement, TNode};
14use crate::invalidation::element::document_state::InvalidationMatchingData;
15use crate::invalidation::element::element_wrapper::ElementSnapshot;
16use crate::properties::longhands::display::computed_value::T as Display;
17use crate::properties::{ComputedValues, PropertyFlags};
18use crate::selector_parser::AttrValue as SelectorAttrValue;
19use crate::selector_parser::{PseudoElementCascadeType, SelectorParser};
20use crate::values::{AtomIdent, AtomString};
21use crate::{Atom, CaseSensitivityExt, LocalName, Namespace, Prefix};
22use cssparser::{Parser as CssParser, ToCss, match_ignore_ascii_case, serialize_identifier};
23use dom::{DocumentState, ElementState};
24use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
25use selectors::parser::SelectorParseErrorKind;
26use std::fmt;
27use std::mem;
28use std::ops::{Deref, DerefMut};
29use style_traits::{ParseError, StyleParseErrorKind};
30
31/// A pseudo-element, both public and private.
32///
33/// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too.
34#[derive(
35    Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem,
36)]
37#[allow(missing_docs)]
38#[repr(u8)]
39pub enum PseudoElement {
40    // Eager pseudos. Keep these first so that eager_index() works.
41    After = 0,
42    Before,
43    Selection,
44    // Make sure the UA sheet has the !important rules some of the
45    // APPLIES_TO_PLACEHOLDER properties expect!
46    FirstLetter,
47    FirstLine,
48
49    // Non-eager pseudos.
50    Backdrop,
51    DetailsContent,
52    Marker,
53
54    // Implemented pseudos. These pseudo elements are representing the
55    // elements within an UA shadow DOM, and matching the elements with
56    // their appropriate styles.
57    ColorSwatch,
58    FileSelectorButton,
59    Placeholder,
60    SliderFill,
61    SliderThumb,
62    SliderTrack,
63    MozProgressBar,
64
65    // Private, Servo-specific implemented pseudos. Only matchable in UA sheet.
66    ServoTextControlInnerContainer,
67    ServoTextControlInnerEditor,
68
69    // Other Servo-specific pseudos.
70    ServoAnonymousBox,
71    ServoAnonymousTable,
72    ServoAnonymousTableCell,
73    ServoAnonymousTableRow,
74    ServoTableGrid,
75    ServoTableWrapper,
76}
77
78/// The count of all pseudo-elements.
79pub 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            FirstLetter => "::first-letter",
92            FirstLine => "::first-line",
93            Backdrop => "::backdrop",
94            DetailsContent => "::details-content",
95            Marker => "::marker",
96            ColorSwatch => "::color-swatch",
97            FileSelectorButton => "::file-selector-button",
98            Placeholder => "::placeholder",
99            SliderFill => "::slider-fill",
100            SliderTrack => "::slider-track",
101            SliderThumb => "::slider-thumb",
102            MozProgressBar => "::-moz-progress-bar",
103            ServoTextControlInnerContainer => "::-servo-text-control-inner-container",
104            ServoTextControlInnerEditor => "::-servo-text-control-inner-editor",
105            ServoAnonymousBox => "::-servo-anonymous-box",
106            ServoAnonymousTable => "::-servo-anonymous-table",
107            ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
108            ServoAnonymousTableRow => "::-servo-anonymous-table-row",
109            ServoTableGrid => "::-servo-table-grid",
110            ServoTableWrapper => "::-servo-table-wrapper",
111        })
112    }
113}
114
115impl ::selectors::parser::PseudoElement for PseudoElement {
116    fn parses_as_element_backed(&self) -> bool {
117        matches!(self, Self::DetailsContent)
118    }
119}
120
121/// The number of eager pseudo-elements. Keep this in sync with cascade_type.
122pub const EAGER_PSEUDO_COUNT: usize = 5;
123
124impl PseudoElement {
125    /// Gets the canonical index of this eagerly-cascaded pseudo-element.
126    #[inline]
127    pub fn eager_index(&self) -> usize {
128        debug_assert!(self.is_eager());
129        self.clone() as usize
130    }
131
132    /// An index for this pseudo-element to be indexed in an enumerated array.
133    #[inline]
134    pub fn index(&self) -> usize {
135        self.clone() as usize
136    }
137
138    /// An array of `None`, one per pseudo-element.
139    pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] {
140        Default::default()
141    }
142
143    /// Creates a pseudo-element from an eager index.
144    #[inline]
145    pub fn from_eager_index(i: usize) -> Self {
146        const _: () = assert!(EAGER_PSEUDO_COUNT <= (u8::MAX as usize));
147        assert!(i < EAGER_PSEUDO_COUNT);
148        let result: PseudoElement = unsafe { mem::transmute(i as u8) };
149        debug_assert!(result.is_eager());
150        result
151    }
152
153    /// Whether the current pseudo element is ::before or ::after.
154    #[inline]
155    pub fn is_before_or_after(&self) -> bool {
156        self.is_before() || self.is_after()
157    }
158
159    /// Whether this is an unknown ::-webkit- pseudo-element.
160    #[inline]
161    pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
162        false
163    }
164
165    /// Whether this pseudo-element is the ::marker pseudo.
166    #[inline]
167    pub fn is_marker(&self) -> bool {
168        *self == PseudoElement::Marker
169    }
170
171    /// Whether this pseudo-element is the ::selection pseudo.
172    #[inline]
173    pub fn is_selection(&self) -> bool {
174        *self == PseudoElement::Selection
175    }
176
177    /// Whether this pseudo-element is the ::before pseudo.
178    #[inline]
179    pub fn is_before(&self) -> bool {
180        *self == PseudoElement::Before
181    }
182
183    /// Whether this pseudo-element is the ::after pseudo.
184    #[inline]
185    pub fn is_after(&self) -> bool {
186        *self == PseudoElement::After
187    }
188
189    /// Whether the current pseudo element is :first-letter
190    #[inline]
191    pub fn is_first_letter(&self) -> bool {
192        *self == PseudoElement::FirstLetter
193    }
194
195    /// Whether the current pseudo element is :first-line
196    #[inline]
197    pub fn is_first_line(&self) -> bool {
198        *self == PseudoElement::FirstLine
199    }
200
201    /// Whether this pseudo-element is representing the color swatch
202    /// inside an `<input>` element.
203    #[inline]
204    pub fn is_color_swatch(&self) -> bool {
205        *self == PseudoElement::ColorSwatch
206    }
207
208    /// Whether this pseudo-element is eagerly-cascaded.
209    #[inline]
210    pub fn is_eager(&self) -> bool {
211        self.cascade_type() == PseudoElementCascadeType::Eager
212    }
213
214    /// Whether this pseudo-element is lazily-cascaded.
215    #[inline]
216    pub fn is_lazy(&self) -> bool {
217        self.cascade_type() == PseudoElementCascadeType::Lazy
218    }
219
220    /// Whether this pseudo-element is for an anonymous box.
221    pub fn is_anon_box(&self) -> bool {
222        self.is_precomputed()
223    }
224
225    /// Whether this pseudo-element skips flex/grid container display-based
226    /// fixup.
227    #[inline]
228    pub fn skip_item_display_fixup(&self) -> bool {
229        !self.is_before_or_after()
230    }
231
232    /// Whether this pseudo-element is precomputed.
233    #[inline]
234    pub fn is_precomputed(&self) -> bool {
235        self.cascade_type() == PseudoElementCascadeType::Precomputed
236    }
237
238    /// Returns which kind of cascade type has this pseudo.
239    ///
240    /// See the documentation for `PseudoElementCascadeType` for how we choose
241    /// which cascade type to use.
242    ///
243    /// Note: Keep eager pseudos in sync with `EAGER_PSEUDO_COUNT` and
244    /// `EMPTY_PSEUDO_ARRAY` in `style/data.rs`
245    #[inline]
246    pub fn cascade_type(&self) -> PseudoElementCascadeType {
247        match *self {
248            PseudoElement::After
249            | PseudoElement::Before
250            | PseudoElement::FirstLetter
251            | PseudoElement::FirstLine
252            | PseudoElement::Selection => PseudoElementCascadeType::Eager,
253            PseudoElement::Backdrop
254            | PseudoElement::ColorSwatch
255            | PseudoElement::FileSelectorButton
256            | PseudoElement::Marker
257            | PseudoElement::Placeholder
258            | PseudoElement::DetailsContent
259            | PseudoElement::SliderFill
260            | PseudoElement::SliderThumb
261            | PseudoElement::SliderTrack
262            | PseudoElement::MozProgressBar
263            | PseudoElement::ServoTextControlInnerContainer
264            | PseudoElement::ServoTextControlInnerEditor => PseudoElementCascadeType::Lazy,
265            PseudoElement::ServoAnonymousBox
266            | PseudoElement::ServoAnonymousTable
267            | PseudoElement::ServoAnonymousTableCell
268            | PseudoElement::ServoAnonymousTableRow
269            | PseudoElement::ServoTableGrid
270            | PseudoElement::ServoTableWrapper => PseudoElementCascadeType::Precomputed,
271        }
272    }
273
274    /// Covert non-canonical pseudo-element to canonical one, and keep a
275    /// canonical one as it is.
276    pub fn canonical(&self) -> PseudoElement {
277        self.clone()
278    }
279
280    /// Stub, only Gecko needs this
281    pub fn pseudo_info(&self) {
282        ()
283    }
284
285    /// Property flag that properties must have to apply to this pseudo-element.
286    #[inline]
287    pub fn property_restriction(&self) -> PropertyFlags {
288        match self {
289            PseudoElement::FirstLetter => PropertyFlags::APPLIES_TO_FIRST_LETTER,
290            PseudoElement::FirstLine => PropertyFlags::APPLIES_TO_FIRST_LINE,
291            PseudoElement::Marker if crate::pref!("layout.css.marker.restricted") => {
292                PropertyFlags::APPLIES_TO_MARKER
293            },
294            PseudoElement::Placeholder => PropertyFlags::APPLIES_TO_PLACEHOLDER,
295            PseudoElement::Selection => PropertyFlags::APPLIES_TO_HIGHLIGHT,
296            _ => PropertyFlags::empty(),
297        }
298    }
299
300    /// Whether this pseudo-element should actually exist if it has
301    /// the given styles.
302    pub fn should_exist(&self, style: &ComputedValues) -> bool {
303        let display = *style.get_box().get_display();
304        if display == Display::None {
305            return false;
306        }
307        if self.is_before_or_after() && style.ineffective_content_property() {
308            return false;
309        }
310
311        true
312    }
313
314    /// Whether this pseudo-element is the ::highlight pseudo.
315    pub fn is_highlight(&self) -> bool {
316        false
317    }
318
319    /// Whether this pseudo-element takes an argument.
320    #[inline]
321    pub fn has_argument(&self) -> bool {
322        false
323    }
324
325    /// Whether this pseudo-element is the ::target-text pseudo.
326    #[inline]
327    pub fn is_target_text(&self) -> bool {
328        false
329    }
330
331    /// Whether this is a highlight pseudo-element that is styled lazily during
332    /// painting rather than during the restyle traversal. These pseudos need
333    /// explicit repaint triggering when their styles change.
334    #[inline]
335    pub fn is_lazy_painted_highlight_pseudo(&self) -> bool {
336        self.is_selection() || self.is_highlight() || self.is_target_text()
337    }
338
339    /// Whether this pseudo-element is "element-backed", which means that it inherits from its regular
340    /// flat tree parent, which might not be the originating element.
341    #[inline]
342    pub fn is_element_backed(&self) -> bool {
343        use selectors::parser::PseudoElement;
344        self.parses_as_element_backed()
345            || matches!(
346                self,
347                Self::Placeholder
348                    | Self::ColorSwatch
349                    | Self::FileSelectorButton
350                    | Self::SliderFill
351                    | Self::SliderThumb
352                    | Self::SliderTrack
353                    | Self::MozProgressBar
354                    | Self::ServoTextControlInnerContainer
355                    | Self::ServoTextControlInnerEditor,
356            )
357    }
358}
359
360/// The type used for storing `:lang` arguments.
361pub type Lang = Box<str>;
362
363/// The type used to store the state argument to the `:state` pseudo-class.
364#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)]
365pub struct CustomState(pub AtomIdent);
366
367/// A non tree-structural pseudo-class.
368/// See https://drafts.csswg.org/selectors-4/#structural-pseudos
369#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
370#[allow(missing_docs)]
371pub enum NonTSPseudoClass {
372    Active,
373    AnyLink,
374    Autofill,
375    Checked,
376    /// The :state` pseudo-class.
377    CustomState(CustomState),
378    Default,
379    Defined,
380    Disabled,
381    Enabled,
382    Focus,
383    FocusWithin,
384    FocusVisible,
385    Fullscreen,
386    Hover,
387    InRange,
388    Indeterminate,
389    Invalid,
390    Lang(Lang),
391    Link,
392    Modal,
393    MozInert,
394    MozMeterOptimum,
395    MozMeterSubOptimum,
396    MozMeterSubSubOptimum,
397    MozTopmostModal,
398    Open,
399    Optional,
400    OutOfRange,
401    PlaceholderShown,
402    PopoverOpen,
403    ReadOnly,
404    ReadWrite,
405    Required,
406    ServoNonZeroBorder,
407    Target,
408    UserInvalid,
409    UserValid,
410    Valid,
411    Visited,
412}
413
414impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
415    #[inline]
416    fn is_active_or_hover(&self) -> bool {
417        matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
418    }
419
420    #[inline]
421    fn is_user_action_state(&self) -> bool {
422        matches!(
423            *self,
424            NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
425        )
426    }
427
428    fn visit<V>(&self, _: &mut V) -> bool {
429        true
430    }
431}
432
433impl ToCss for NonTSPseudoClass {
434    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
435    where
436        W: fmt::Write,
437    {
438        use self::NonTSPseudoClass::*;
439        if let Lang(ref lang) = *self {
440            dest.write_str(":lang(")?;
441            serialize_identifier(lang, dest)?;
442            return dest.write_char(')');
443        }
444
445        dest.write_str(match *self {
446            Self::Active => ":active",
447            Self::AnyLink => ":any-link",
448            Self::Autofill => ":autofill",
449            Self::Checked => ":checked",
450            Self::CustomState(ref state) => {
451                dest.write_str(":state(")?;
452                state.0.to_css(dest)?;
453                return dest.write_char(')');
454            },
455            Self::Default => ":default",
456            Self::Defined => ":defined",
457            Self::Disabled => ":disabled",
458            Self::Enabled => ":enabled",
459            Self::Focus => ":focus",
460            Self::FocusVisible => ":focus-visible",
461            Self::FocusWithin => ":focus-within",
462            Self::Fullscreen => ":fullscreen",
463            Self::Hover => ":hover",
464            Self::InRange => ":in-range",
465            Self::Indeterminate => ":indeterminate",
466            Self::Invalid => ":invalid",
467            Self::Link => ":link",
468            Self::Modal => ":modal",
469            Self::MozInert => ":-moz-inert",
470            Self::MozMeterOptimum => ":-moz-meter-optimum",
471            Self::MozMeterSubOptimum => ":-moz-meter-sub-optimum",
472            Self::MozMeterSubSubOptimum => ":-moz-meter-sub-sub-optimum",
473            Self::MozTopmostModal => ":-moz-topmost-modal",
474            Self::Open => ":open",
475            Self::Optional => ":optional",
476            Self::OutOfRange => ":out-of-range",
477            Self::PlaceholderShown => ":placeholder-shown",
478            Self::PopoverOpen => ":popover-open",
479            Self::ReadOnly => ":read-only",
480            Self::ReadWrite => ":read-write",
481            Self::Required => ":required",
482            Self::ServoNonZeroBorder => ":-servo-nonzero-border",
483            Self::Target => ":target",
484            Self::UserInvalid => ":user-invalid",
485            Self::UserValid => ":user-valid",
486            Self::Valid => ":valid",
487            Self::Visited => ":visited",
488            Self::Lang(_) => unreachable!(),
489        })
490    }
491}
492
493impl NonTSPseudoClass {
494    /// Gets a given state flag for this pseudo-class. This is used to do
495    /// selector matching, and it's set from the DOM.
496    pub fn state_flag(&self) -> ElementState {
497        match *self {
498            Self::Active => ElementState::ACTIVE,
499            Self::AnyLink => ElementState::VISITED_OR_UNVISITED,
500            Self::Autofill => ElementState::AUTOFILL,
501            Self::Checked => ElementState::CHECKED,
502            Self::Default => ElementState::DEFAULT,
503            Self::Defined => ElementState::DEFINED,
504            Self::Disabled => ElementState::DISABLED,
505            Self::Enabled => ElementState::ENABLED,
506            Self::Focus => ElementState::FOCUS,
507            Self::FocusVisible => ElementState::FOCUSRING,
508            Self::FocusWithin => ElementState::FOCUS_WITHIN,
509            Self::Fullscreen => ElementState::FULLSCREEN,
510            Self::Hover => ElementState::HOVER,
511            Self::InRange => ElementState::INRANGE,
512            Self::Indeterminate => ElementState::INDETERMINATE,
513            Self::Invalid => ElementState::INVALID,
514            Self::Link => ElementState::UNVISITED,
515            Self::Modal => ElementState::MODAL,
516            Self::MozInert => ElementState::INERT,
517            Self::MozMeterOptimum => ElementState::OPTIMUM,
518            Self::MozMeterSubOptimum => ElementState::SUB_OPTIMUM,
519            Self::MozMeterSubSubOptimum => ElementState::SUB_SUB_OPTIMUM,
520            Self::MozTopmostModal => ElementState::TOPMOST_MODAL,
521            Self::Open => ElementState::OPEN,
522            Self::Optional => ElementState::OPTIONAL_,
523            Self::OutOfRange => ElementState::OUTOFRANGE,
524            Self::PlaceholderShown => ElementState::PLACEHOLDER_SHOWN,
525            Self::PopoverOpen => ElementState::POPOVER_OPEN,
526            Self::ReadOnly => ElementState::READONLY,
527            Self::ReadWrite => ElementState::READWRITE,
528            Self::Required => ElementState::REQUIRED,
529            Self::Target => ElementState::URLTARGET,
530            Self::UserInvalid => ElementState::USER_INVALID,
531            Self::UserValid => ElementState::USER_VALID,
532            Self::Valid => ElementState::VALID,
533            Self::Visited => ElementState::VISITED,
534            Self::CustomState(_) | Self::Lang(_) | Self::ServoNonZeroBorder => {
535                ElementState::empty()
536            },
537        }
538    }
539
540    /// Get the document state flag associated with a pseudo-class, if any.
541    pub fn document_state_flag(&self) -> DocumentState {
542        DocumentState::empty()
543    }
544
545    /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
546    pub fn needs_cache_revalidation(&self) -> bool {
547        self.state_flag().is_empty()
548    }
549}
550
551/// The abstract struct we implement the selector parser implementation on top
552/// of.
553#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
554pub struct SelectorImpl;
555
556/// A set of extra data to carry along with the matching context, either for
557/// selector-matching or invalidation.
558#[derive(Debug, Default)]
559pub struct ExtraMatchingData<'a> {
560    /// The invalidation data to invalidate doc-state pseudo-classes correctly.
561    pub invalidation_data: InvalidationMatchingData,
562
563    /// The invalidation bits from matching container queries. These are here
564    /// just for convenience mostly.
565    pub cascade_input_flags: ComputedValueFlags,
566
567    /// The style of the originating element in order to evaluate @container
568    /// size queries affecting pseudo-elements.
569    pub originating_element_style: Option<&'a ComputedValues>,
570}
571
572impl ::selectors::SelectorImpl for SelectorImpl {
573    type PseudoElement = PseudoElement;
574    type NonTSPseudoClass = NonTSPseudoClass;
575
576    type ExtraMatchingData<'a> = ExtraMatchingData<'a>;
577    type AttrValue = AtomString;
578    type Identifier = AtomIdent;
579    type LocalName = LocalName;
580    type NamespacePrefix = Prefix;
581    type NamespaceUrl = Namespace;
582    type BorrowedLocalName = web_atoms::LocalName;
583    type BorrowedNamespaceUrl = web_atoms::Namespace;
584}
585
586impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
587    type Impl = SelectorImpl;
588    type Error = StyleParseErrorKind;
589
590    #[inline]
591    fn parse_nth_child_of(&self) -> bool {
592        crate::pref!("layout.css.nth-child-of.enabled")
593    }
594
595    #[inline]
596    fn parse_is_and_where(&self) -> bool {
597        true
598    }
599
600    #[inline]
601    fn parse_has(&self) -> bool {
602        crate::pref!("layout.css.has-selector.enabled")
603    }
604
605    #[inline]
606    fn parse_parent_selector(&self) -> bool {
607        true
608    }
609
610    #[inline]
611    fn parse_part(&self) -> bool {
612        true
613    }
614
615    #[inline]
616    fn allow_forgiving_selectors(&self) -> bool {
617        !self.for_supports_rule
618    }
619
620    fn parse_non_ts_pseudo_class(&self, name: &str) -> Result<NonTSPseudoClass, ParseError> {
621        let pseudo_class = match_ignore_ascii_case! { &name,
622            "active" => NonTSPseudoClass::Active,
623            "any-link" => NonTSPseudoClass::AnyLink,
624            "autofill" => NonTSPseudoClass::Autofill,
625            "checked" => NonTSPseudoClass::Checked,
626            "default" => NonTSPseudoClass::Default,
627            "defined" => NonTSPseudoClass::Defined,
628            "disabled" => NonTSPseudoClass::Disabled,
629            "enabled" => NonTSPseudoClass::Enabled,
630            "focus" => NonTSPseudoClass::Focus,
631            "focus-visible" => NonTSPseudoClass::FocusVisible,
632            "focus-within" => NonTSPseudoClass::FocusWithin,
633            "fullscreen" => NonTSPseudoClass::Fullscreen,
634            "hover" => NonTSPseudoClass::Hover,
635            "indeterminate" => NonTSPseudoClass::Indeterminate,
636            "invalid" => NonTSPseudoClass::Invalid,
637            "link" => NonTSPseudoClass::Link,
638            "modal" => NonTSPseudoClass::Modal,
639            "open" => NonTSPseudoClass::Open,
640            "optional" => NonTSPseudoClass::Optional,
641            "out-of-range" => NonTSPseudoClass::OutOfRange,
642            "placeholder-shown" => NonTSPseudoClass::PlaceholderShown,
643            "popover-open" => NonTSPseudoClass::PopoverOpen,
644            "read-only" => NonTSPseudoClass::ReadOnly,
645            "read-write" => NonTSPseudoClass::ReadWrite,
646            "required" => NonTSPseudoClass::Required,
647            "target" => NonTSPseudoClass::Target,
648            "user-invalid" => NonTSPseudoClass::UserInvalid,
649            "user-valid" => NonTSPseudoClass::UserValid,
650            "valid" => NonTSPseudoClass::Valid,
651            "visited" => NonTSPseudoClass::Visited,
652            "-moz-inert" => {
653                if !self.in_user_agent_stylesheet() {
654                    return Err(ParseError::custom(
655                        SelectorParseErrorKind::UnexpectedIdent
656                    ))
657                }
658                NonTSPseudoClass::MozInert
659            },
660            "-moz-meter-optimum" => NonTSPseudoClass::MozMeterOptimum,
661            "-moz-meter-sub-optimum" => NonTSPseudoClass::MozMeterSubOptimum,
662            "-moz-meter-sub-sub-optimum" => NonTSPseudoClass::MozMeterSubSubOptimum,
663            "-moz-topmost-modal" => {
664                if !self.in_user_agent_stylesheet() {
665                    return Err(ParseError::custom(
666                        SelectorParseErrorKind::UnexpectedIdent
667                    ))
668                }
669                NonTSPseudoClass::MozTopmostModal
670            },
671            "-servo-nonzero-border" => {
672                if !self.in_user_agent_stylesheet() {
673                    return Err(ParseError::custom(
674                        SelectorParseErrorKind::UnexpectedIdent
675                    ))
676                }
677                NonTSPseudoClass::ServoNonZeroBorder
678            },
679            _ => return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
680        };
681
682        Ok(pseudo_class)
683    }
684
685    fn parse_non_ts_functional_pseudo_class(
686        &self,
687        name: &str,
688        parser: &mut CssParser<'i>,
689        after_part: bool,
690    ) -> Result<NonTSPseudoClass, ParseError> {
691        let pseudo_class = match_ignore_ascii_case! { &name,
692            "lang" if !after_part => {
693                NonTSPseudoClass::Lang(parser.expect_ident_or_string()?.as_ref().into())
694            },
695            "state" => {
696                let result = AtomIdent::from(parser.expect_ident()?.as_ref());
697                NonTSPseudoClass::CustomState(CustomState(result))
698            },
699            _ => return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
700        };
701
702        Ok(pseudo_class)
703    }
704
705    fn parse_pseudo_element(&self, name: &str) -> Result<PseudoElement, ParseError> {
706        use self::PseudoElement::*;
707        let pseudo_element = match_ignore_ascii_case! { &name,
708            "before" => Before,
709            "after" => After,
710            "backdrop" => Backdrop,
711            "selection" => Selection,
712            "file-selector-button" => FileSelectorButton,
713            "first-letter" => FirstLetter,
714            "first-line" if crate::pref!("layout.css.first-line.enabled") => FirstLine,
715            "marker" => Marker,
716            "details-content" => DetailsContent,
717            "color-swatch" => ColorSwatch,
718            "placeholder" => Placeholder,
719            "-servo-text-control-inner-container" => {
720                if !self.in_user_agent_stylesheet() {
721                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
722                }
723                ServoTextControlInnerContainer
724            },
725            "-servo-text-control-inner-editor" => {
726                if !self.in_user_agent_stylesheet() {
727                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
728                }
729                ServoTextControlInnerEditor
730            },
731            "slider-fill" => SliderFill,
732            "slider-thumb" => SliderThumb,
733            "slider-track" => SliderTrack,
734            "-moz-progress-bar" => MozProgressBar,
735            "-servo-anonymous-box" => {
736                if !self.in_user_agent_stylesheet() {
737                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
738                }
739                ServoAnonymousBox
740            },
741            "-servo-anonymous-table" => {
742                if !self.in_user_agent_stylesheet() {
743                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
744                }
745                ServoAnonymousTable
746            },
747            "-servo-anonymous-table-row" => {
748                if !self.in_user_agent_stylesheet() {
749                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
750                }
751                ServoAnonymousTableRow
752            },
753            "-servo-anonymous-table-cell" => {
754                if !self.in_user_agent_stylesheet() {
755                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
756                }
757                ServoAnonymousTableCell
758            },
759            "-servo-table-grid" => {
760                if !self.in_user_agent_stylesheet() {
761                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
762                }
763                ServoTableGrid
764            },
765            "-servo-table-wrapper" => {
766                if !self.in_user_agent_stylesheet() {
767                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
768                }
769                ServoTableWrapper
770            },
771            _ => return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
772
773        };
774
775        Ok(pseudo_element)
776    }
777
778    fn default_namespace(&self) -> Option<Namespace> {
779        self.namespaces.default.as_ref().map(|ns| ns.clone())
780    }
781
782    fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
783        self.namespaces.prefixes.get(prefix).cloned()
784    }
785
786    fn parse_host(&self) -> bool {
787        true
788    }
789
790    fn parse_slotted(&self) -> bool {
791        true
792    }
793}
794
795impl SelectorImpl {
796    /// A helper to traverse each eagerly cascaded pseudo-element, executing
797    /// `fun` on it.
798    #[inline]
799    pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
800    where
801        F: FnMut(PseudoElement),
802    {
803        for i in 0..EAGER_PSEUDO_COUNT {
804            fun(PseudoElement::from_eager_index(i));
805        }
806    }
807}
808
809/// A map from elements to snapshots for the Servo style back-end.
810#[derive(Debug)]
811pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
812
813impl SnapshotMap {
814    /// Create a new empty `SnapshotMap`.
815    pub fn new() -> Self {
816        SnapshotMap(FxHashMap::default())
817    }
818
819    /// Get a snapshot given an element.
820    pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
821        self.0.get(&el.as_node().opaque())
822    }
823}
824
825impl Deref for SnapshotMap {
826    type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
827
828    fn deref(&self) -> &Self::Target {
829        &self.0
830    }
831}
832
833impl DerefMut for SnapshotMap {
834    fn deref_mut(&mut self) -> &mut Self::Target {
835        &mut self.0
836    }
837}
838
839/// Servo's version of an element snapshot.
840#[derive(Debug, Default, MallocSizeOf)]
841pub struct ServoElementSnapshot {
842    /// The stored state of the element.
843    pub state: Option<ElementState>,
844    /// The set of stored attributes and its values.
845    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
846    /// The set of changed attributes and its values.
847    pub changed_attrs: Vec<LocalName>,
848    /// Whether the class attribute changed or not.
849    pub class_changed: bool,
850    /// Whether the id attribute changed or not.
851    pub id_changed: bool,
852    /// Whether other attributes other than id or class changed or not.
853    pub other_attributes_changed: bool,
854}
855
856impl ServoElementSnapshot {
857    /// Create an empty element snapshot.
858    pub fn new() -> Self {
859        Self::default()
860    }
861
862    /// Returns whether the id attribute changed or not.
863    pub fn id_changed(&self) -> bool {
864        self.id_changed
865    }
866
867    /// Returns whether the class attribute changed or not.
868    pub fn class_changed(&self) -> bool {
869        self.class_changed
870    }
871
872    /// Returns whether other attributes other than id or class changed or not.
873    pub fn other_attr_changed(&self) -> bool {
874        self.other_attributes_changed
875    }
876
877    fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
878        self.attrs
879            .as_ref()?
880            .iter()
881            .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
882            .map(|&(_, ref v)| v)
883    }
884
885    /// Executes the callback once for each attribute that changed.
886    #[inline]
887    pub fn each_attr_changed<F>(&self, mut callback: F)
888    where
889        F: FnMut(&LocalName),
890    {
891        for name in &self.changed_attrs {
892            callback(name)
893        }
894    }
895
896    fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
897    where
898        F: FnMut(&AttrValue) -> bool,
899    {
900        self.attrs.as_ref().is_some_and(|attrs| {
901            attrs
902                .iter()
903                .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
904        })
905    }
906}
907
908impl ElementSnapshot for ServoElementSnapshot {
909    fn state(&self) -> Option<ElementState> {
910        self.state.clone()
911    }
912
913    fn has_attrs(&self) -> bool {
914        self.attrs.is_some()
915    }
916
917    fn id_attr(&self) -> Option<&Atom> {
918        self.get_attr(&ns!(), &local_name!("id"))
919            .map(|v| v.as_atom())
920    }
921
922    fn is_part(&self, part_name: &AtomIdent) -> bool {
923        self.get_attr(&ns!(), &local_name!("part"))
924            .is_some_and(|v| {
925                v.as_tokens()
926                    .iter()
927                    .any(|atom| CaseSensitivity::CaseSensitive.eq_atom(atom, part_name))
928            })
929    }
930
931    fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
932        None
933    }
934
935    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
936        self.get_attr(&ns!(), &local_name!("class"))
937            .map_or(false, |v| {
938                v.as_tokens()
939                    .iter()
940                    .any(|atom| case_sensitivity.eq_atom(atom, name))
941            })
942    }
943
944    fn each_class<F>(&self, mut callback: F)
945    where
946        F: FnMut(&AtomIdent),
947    {
948        if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
949            for class in v.as_tokens() {
950                callback(AtomIdent::cast(class));
951            }
952        }
953    }
954
955    fn lang_attr(&self) -> Option<SelectorAttrValue> {
956        self.get_attr(&ns!(xml), &local_name!("lang"))
957            .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
958            .map(|v| SelectorAttrValue::from(v as &str))
959    }
960
961    /// Returns true if the snapshot has stored state for custom states
962    #[inline]
963    fn has_custom_states(&self) -> bool {
964        false
965    }
966
967    /// Returns true if the snapshot has a given CustomState
968    #[inline]
969    fn has_custom_state(&self, _state: &AtomIdent) -> bool {
970        false
971    }
972
973    #[inline]
974    fn each_custom_state<F>(&self, mut _callback: F)
975    where
976        F: FnMut(&AtomIdent),
977    {
978    }
979}
980
981impl ServoElementSnapshot {
982    /// selectors::Element::attr_matches
983    pub fn attr_matches(
984        &self,
985        ns: &NamespaceConstraint<&Namespace>,
986        local_name: &LocalName,
987        operation: &AttrSelectorOperation<&AtomString>,
988    ) -> bool {
989        match *ns {
990            NamespaceConstraint::Specific(ref ns) => self
991                .get_attr(ns, local_name)
992                .map_or(false, |value| value.eval_selector(operation)),
993            NamespaceConstraint::Any => {
994                self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
995            },
996        }
997    }
998}
999
1000/// Returns whether the language is matched, as defined by
1001/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2).
1002pub fn extended_filtering(tag: &str, range: &str) -> bool {
1003    range.split(',').any(|lang_range| {
1004        // step 1
1005        let mut range_subtags = lang_range.split('\x2d');
1006        let mut tag_subtags = tag.split('\x2d');
1007
1008        // step 2
1009        // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
1010        if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
1011            if !(range_subtag.eq_ignore_ascii_case(tag_subtag)
1012                || range_subtag.eq_ignore_ascii_case("*"))
1013            {
1014                return false;
1015            }
1016        }
1017
1018        let mut current_tag_subtag = tag_subtags.next();
1019
1020        // step 3
1021        for range_subtag in range_subtags {
1022            // step 3a
1023            if range_subtag == "*" {
1024                continue;
1025            }
1026            match current_tag_subtag.clone() {
1027                Some(tag_subtag) => {
1028                    // step 3c
1029                    if range_subtag.eq_ignore_ascii_case(tag_subtag) {
1030                        current_tag_subtag = tag_subtags.next();
1031                        continue;
1032                    }
1033                    // step 3d
1034                    if tag_subtag.len() == 1 {
1035                        return false;
1036                    }
1037                    // else step 3e - continue with loop
1038                    current_tag_subtag = tag_subtags.next();
1039                    if current_tag_subtag.is_none() {
1040                        return false;
1041                    }
1042                },
1043                // step 3b
1044                None => {
1045                    return false;
1046                },
1047            }
1048        }
1049        // step 4
1050        true
1051    })
1052}