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    Open,
398    Optional,
399    OutOfRange,
400    PlaceholderShown,
401    PopoverOpen,
402    ReadOnly,
403    ReadWrite,
404    Required,
405    ServoNonZeroBorder,
406    Target,
407    UserInvalid,
408    UserValid,
409    Valid,
410    Visited,
411}
412
413impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
414    #[inline]
415    fn is_active_or_hover(&self) -> bool {
416        matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
417    }
418
419    #[inline]
420    fn is_user_action_state(&self) -> bool {
421        matches!(
422            *self,
423            NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
424        )
425    }
426
427    fn visit<V>(&self, _: &mut V) -> bool {
428        true
429    }
430}
431
432impl ToCss for NonTSPseudoClass {
433    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
434    where
435        W: fmt::Write,
436    {
437        use self::NonTSPseudoClass::*;
438        if let Lang(ref lang) = *self {
439            dest.write_str(":lang(")?;
440            serialize_identifier(lang, dest)?;
441            return dest.write_char(')');
442        }
443
444        dest.write_str(match *self {
445            Self::Active => ":active",
446            Self::AnyLink => ":any-link",
447            Self::Autofill => ":autofill",
448            Self::Checked => ":checked",
449            Self::CustomState(ref state) => {
450                dest.write_str(":state(")?;
451                state.0.to_css(dest)?;
452                return dest.write_char(')');
453            },
454            Self::Default => ":default",
455            Self::Defined => ":defined",
456            Self::Disabled => ":disabled",
457            Self::Enabled => ":enabled",
458            Self::Focus => ":focus",
459            Self::FocusVisible => ":focus-visible",
460            Self::FocusWithin => ":focus-within",
461            Self::Fullscreen => ":fullscreen",
462            Self::Hover => ":hover",
463            Self::InRange => ":in-range",
464            Self::Indeterminate => ":indeterminate",
465            Self::Invalid => ":invalid",
466            Self::Link => ":link",
467            Self::Modal => ":modal",
468            Self::MozInert => ":-moz-inert",
469            Self::MozMeterOptimum => ":-moz-meter-optimum",
470            Self::MozMeterSubOptimum => ":-moz-meter-sub-optimum",
471            Self::MozMeterSubSubOptimum => ":-moz-meter-sub-sub-optimum",
472            Self::Open => ":open",
473            Self::Optional => ":optional",
474            Self::OutOfRange => ":out-of-range",
475            Self::PlaceholderShown => ":placeholder-shown",
476            Self::PopoverOpen => ":popover-open",
477            Self::ReadOnly => ":read-only",
478            Self::ReadWrite => ":read-write",
479            Self::Required => ":required",
480            Self::ServoNonZeroBorder => ":-servo-nonzero-border",
481            Self::Target => ":target",
482            Self::UserInvalid => ":user-invalid",
483            Self::UserValid => ":user-valid",
484            Self::Valid => ":valid",
485            Self::Visited => ":visited",
486            Self::Lang(_) => unreachable!(),
487        })
488    }
489}
490
491impl NonTSPseudoClass {
492    /// Gets a given state flag for this pseudo-class. This is used to do
493    /// selector matching, and it's set from the DOM.
494    pub fn state_flag(&self) -> ElementState {
495        match *self {
496            Self::Active => ElementState::ACTIVE,
497            Self::AnyLink => ElementState::VISITED_OR_UNVISITED,
498            Self::Autofill => ElementState::AUTOFILL,
499            Self::Checked => ElementState::CHECKED,
500            Self::Default => ElementState::DEFAULT,
501            Self::Defined => ElementState::DEFINED,
502            Self::Disabled => ElementState::DISABLED,
503            Self::Enabled => ElementState::ENABLED,
504            Self::Focus => ElementState::FOCUS,
505            Self::FocusVisible => ElementState::FOCUSRING,
506            Self::FocusWithin => ElementState::FOCUS_WITHIN,
507            Self::Fullscreen => ElementState::FULLSCREEN,
508            Self::Hover => ElementState::HOVER,
509            Self::InRange => ElementState::INRANGE,
510            Self::Indeterminate => ElementState::INDETERMINATE,
511            Self::Invalid => ElementState::INVALID,
512            Self::Link => ElementState::UNVISITED,
513            Self::Modal => ElementState::MODAL,
514            Self::MozInert => ElementState::INERT,
515            Self::MozMeterOptimum => ElementState::OPTIMUM,
516            Self::MozMeterSubOptimum => ElementState::SUB_OPTIMUM,
517            Self::MozMeterSubSubOptimum => ElementState::SUB_SUB_OPTIMUM,
518            Self::Open => ElementState::OPEN,
519            Self::Optional => ElementState::OPTIONAL_,
520            Self::OutOfRange => ElementState::OUTOFRANGE,
521            Self::PlaceholderShown => ElementState::PLACEHOLDER_SHOWN,
522            Self::PopoverOpen => ElementState::POPOVER_OPEN,
523            Self::ReadOnly => ElementState::READONLY,
524            Self::ReadWrite => ElementState::READWRITE,
525            Self::Required => ElementState::REQUIRED,
526            Self::Target => ElementState::URLTARGET,
527            Self::UserInvalid => ElementState::USER_INVALID,
528            Self::UserValid => ElementState::USER_VALID,
529            Self::Valid => ElementState::VALID,
530            Self::Visited => ElementState::VISITED,
531            Self::CustomState(_) | Self::Lang(_) | Self::ServoNonZeroBorder => {
532                ElementState::empty()
533            },
534        }
535    }
536
537    /// Get the document state flag associated with a pseudo-class, if any.
538    pub fn document_state_flag(&self) -> DocumentState {
539        DocumentState::empty()
540    }
541
542    /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
543    pub fn needs_cache_revalidation(&self) -> bool {
544        self.state_flag().is_empty()
545    }
546}
547
548/// The abstract struct we implement the selector parser implementation on top
549/// of.
550#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
551pub struct SelectorImpl;
552
553/// A set of extra data to carry along with the matching context, either for
554/// selector-matching or invalidation.
555#[derive(Debug, Default)]
556pub struct ExtraMatchingData<'a> {
557    /// The invalidation data to invalidate doc-state pseudo-classes correctly.
558    pub invalidation_data: InvalidationMatchingData,
559
560    /// The invalidation bits from matching container queries. These are here
561    /// just for convenience mostly.
562    pub cascade_input_flags: ComputedValueFlags,
563
564    /// The style of the originating element in order to evaluate @container
565    /// size queries affecting pseudo-elements.
566    pub originating_element_style: Option<&'a ComputedValues>,
567}
568
569impl ::selectors::SelectorImpl for SelectorImpl {
570    type PseudoElement = PseudoElement;
571    type NonTSPseudoClass = NonTSPseudoClass;
572
573    type ExtraMatchingData<'a> = ExtraMatchingData<'a>;
574    type AttrValue = AtomString;
575    type Identifier = AtomIdent;
576    type LocalName = LocalName;
577    type NamespacePrefix = Prefix;
578    type NamespaceUrl = Namespace;
579    type BorrowedLocalName = web_atoms::LocalName;
580    type BorrowedNamespaceUrl = web_atoms::Namespace;
581}
582
583impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
584    type Impl = SelectorImpl;
585    type Error = StyleParseErrorKind;
586
587    #[inline]
588    fn parse_nth_child_of(&self) -> bool {
589        crate::pref!("layout.css.nth-child-of.enabled")
590    }
591
592    #[inline]
593    fn parse_is_and_where(&self) -> bool {
594        true
595    }
596
597    #[inline]
598    fn parse_has(&self) -> bool {
599        crate::pref!("layout.css.has-selector.enabled")
600    }
601
602    #[inline]
603    fn parse_parent_selector(&self) -> bool {
604        true
605    }
606
607    #[inline]
608    fn parse_part(&self) -> bool {
609        true
610    }
611
612    #[inline]
613    fn allow_forgiving_selectors(&self) -> bool {
614        !self.for_supports_rule
615    }
616
617    fn parse_non_ts_pseudo_class(&self, name: &str) -> Result<NonTSPseudoClass, ParseError> {
618        let pseudo_class = match_ignore_ascii_case! { &name,
619            "active" => NonTSPseudoClass::Active,
620            "any-link" => NonTSPseudoClass::AnyLink,
621            "autofill" => NonTSPseudoClass::Autofill,
622            "checked" => NonTSPseudoClass::Checked,
623            "default" => NonTSPseudoClass::Default,
624            "defined" => NonTSPseudoClass::Defined,
625            "disabled" => NonTSPseudoClass::Disabled,
626            "enabled" => NonTSPseudoClass::Enabled,
627            "focus" => NonTSPseudoClass::Focus,
628            "focus-visible" => NonTSPseudoClass::FocusVisible,
629            "focus-within" => NonTSPseudoClass::FocusWithin,
630            "fullscreen" => NonTSPseudoClass::Fullscreen,
631            "hover" => NonTSPseudoClass::Hover,
632            "indeterminate" => NonTSPseudoClass::Indeterminate,
633            "invalid" => NonTSPseudoClass::Invalid,
634            "link" => NonTSPseudoClass::Link,
635            "modal" => NonTSPseudoClass::Modal,
636            "open" => NonTSPseudoClass::Open,
637            "optional" => NonTSPseudoClass::Optional,
638            "out-of-range" => NonTSPseudoClass::OutOfRange,
639            "placeholder-shown" => NonTSPseudoClass::PlaceholderShown,
640            "popover-open" => NonTSPseudoClass::PopoverOpen,
641            "read-only" => NonTSPseudoClass::ReadOnly,
642            "read-write" => NonTSPseudoClass::ReadWrite,
643            "required" => NonTSPseudoClass::Required,
644            "target" => NonTSPseudoClass::Target,
645            "user-invalid" => NonTSPseudoClass::UserInvalid,
646            "user-valid" => NonTSPseudoClass::UserValid,
647            "valid" => NonTSPseudoClass::Valid,
648            "visited" => NonTSPseudoClass::Visited,
649            "-moz-inert" => {
650                if !self.in_user_agent_stylesheet() {
651                    return Err(ParseError::custom(
652                        SelectorParseErrorKind::UnexpectedIdent
653                    ))
654                }
655                NonTSPseudoClass::MozInert
656            },
657            "-moz-meter-optimum" => NonTSPseudoClass::MozMeterOptimum,
658            "-moz-meter-sub-optimum" => NonTSPseudoClass::MozMeterSubOptimum,
659            "-moz-meter-sub-sub-optimum" => NonTSPseudoClass::MozMeterSubSubOptimum,
660            "-servo-nonzero-border" => {
661                if !self.in_user_agent_stylesheet() {
662                    return Err(ParseError::custom(
663                        SelectorParseErrorKind::UnexpectedIdent
664                    ))
665                }
666                NonTSPseudoClass::ServoNonZeroBorder
667            },
668            _ => return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
669        };
670
671        Ok(pseudo_class)
672    }
673
674    fn parse_non_ts_functional_pseudo_class(
675        &self,
676        name: &str,
677        parser: &mut CssParser<'i>,
678        after_part: bool,
679    ) -> Result<NonTSPseudoClass, ParseError> {
680        let pseudo_class = match_ignore_ascii_case! { &name,
681            "lang" if !after_part => {
682                NonTSPseudoClass::Lang(parser.expect_ident_or_string()?.as_ref().into())
683            },
684            "state" => {
685                let result = AtomIdent::from(parser.expect_ident()?.as_ref());
686                NonTSPseudoClass::CustomState(CustomState(result))
687            },
688            _ => return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
689        };
690
691        Ok(pseudo_class)
692    }
693
694    fn parse_pseudo_element(&self, name: &str) -> Result<PseudoElement, ParseError> {
695        use self::PseudoElement::*;
696        let pseudo_element = match_ignore_ascii_case! { &name,
697            "before" => Before,
698            "after" => After,
699            "backdrop" => Backdrop,
700            "selection" => Selection,
701            "file-selector-button" => FileSelectorButton,
702            "first-letter" => FirstLetter,
703            "first-line" if crate::pref!("layout.css.first-line.enabled") => FirstLine,
704            "marker" => Marker,
705            "details-content" => DetailsContent,
706            "color-swatch" => ColorSwatch,
707            "placeholder" => Placeholder,
708            "-servo-text-control-inner-container" => {
709                if !self.in_user_agent_stylesheet() {
710                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
711                }
712                ServoTextControlInnerContainer
713            },
714            "-servo-text-control-inner-editor" => {
715                if !self.in_user_agent_stylesheet() {
716                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
717                }
718                ServoTextControlInnerEditor
719            },
720            "slider-fill" => SliderFill,
721            "slider-thumb" => SliderThumb,
722            "slider-track" => SliderTrack,
723            "-moz-progress-bar" => MozProgressBar,
724            "-servo-anonymous-box" => {
725                if !self.in_user_agent_stylesheet() {
726                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
727                }
728                ServoAnonymousBox
729            },
730            "-servo-anonymous-table" => {
731                if !self.in_user_agent_stylesheet() {
732                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
733                }
734                ServoAnonymousTable
735            },
736            "-servo-anonymous-table-row" => {
737                if !self.in_user_agent_stylesheet() {
738                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
739                }
740                ServoAnonymousTableRow
741            },
742            "-servo-anonymous-table-cell" => {
743                if !self.in_user_agent_stylesheet() {
744                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
745                }
746                ServoAnonymousTableCell
747            },
748            "-servo-table-grid" => {
749                if !self.in_user_agent_stylesheet() {
750                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
751                }
752                ServoTableGrid
753            },
754            "-servo-table-wrapper" => {
755                if !self.in_user_agent_stylesheet() {
756                    return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
757                }
758                ServoTableWrapper
759            },
760            _ => return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
761
762        };
763
764        Ok(pseudo_element)
765    }
766
767    fn default_namespace(&self) -> Option<Namespace> {
768        self.namespaces.default.as_ref().map(|ns| ns.clone())
769    }
770
771    fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
772        self.namespaces.prefixes.get(prefix).cloned()
773    }
774
775    fn parse_host(&self) -> bool {
776        true
777    }
778
779    fn parse_slotted(&self) -> bool {
780        true
781    }
782}
783
784impl SelectorImpl {
785    /// A helper to traverse each eagerly cascaded pseudo-element, executing
786    /// `fun` on it.
787    #[inline]
788    pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
789    where
790        F: FnMut(PseudoElement),
791    {
792        for i in 0..EAGER_PSEUDO_COUNT {
793            fun(PseudoElement::from_eager_index(i));
794        }
795    }
796}
797
798/// A map from elements to snapshots for the Servo style back-end.
799#[derive(Debug)]
800pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
801
802impl SnapshotMap {
803    /// Create a new empty `SnapshotMap`.
804    pub fn new() -> Self {
805        SnapshotMap(FxHashMap::default())
806    }
807
808    /// Get a snapshot given an element.
809    pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
810        self.0.get(&el.as_node().opaque())
811    }
812}
813
814impl Deref for SnapshotMap {
815    type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
816
817    fn deref(&self) -> &Self::Target {
818        &self.0
819    }
820}
821
822impl DerefMut for SnapshotMap {
823    fn deref_mut(&mut self) -> &mut Self::Target {
824        &mut self.0
825    }
826}
827
828/// Servo's version of an element snapshot.
829#[derive(Debug, Default, MallocSizeOf)]
830pub struct ServoElementSnapshot {
831    /// The stored state of the element.
832    pub state: Option<ElementState>,
833    /// The set of stored attributes and its values.
834    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
835    /// The set of changed attributes and its values.
836    pub changed_attrs: Vec<LocalName>,
837    /// Whether the class attribute changed or not.
838    pub class_changed: bool,
839    /// Whether the id attribute changed or not.
840    pub id_changed: bool,
841    /// Whether other attributes other than id or class changed or not.
842    pub other_attributes_changed: bool,
843}
844
845impl ServoElementSnapshot {
846    /// Create an empty element snapshot.
847    pub fn new() -> Self {
848        Self::default()
849    }
850
851    /// Returns whether the id attribute changed or not.
852    pub fn id_changed(&self) -> bool {
853        self.id_changed
854    }
855
856    /// Returns whether the class attribute changed or not.
857    pub fn class_changed(&self) -> bool {
858        self.class_changed
859    }
860
861    /// Returns whether other attributes other than id or class changed or not.
862    pub fn other_attr_changed(&self) -> bool {
863        self.other_attributes_changed
864    }
865
866    fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
867        self.attrs
868            .as_ref()?
869            .iter()
870            .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
871            .map(|&(_, ref v)| v)
872    }
873
874    /// Executes the callback once for each attribute that changed.
875    #[inline]
876    pub fn each_attr_changed<F>(&self, mut callback: F)
877    where
878        F: FnMut(&LocalName),
879    {
880        for name in &self.changed_attrs {
881            callback(name)
882        }
883    }
884
885    fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
886    where
887        F: FnMut(&AttrValue) -> bool,
888    {
889        self.attrs.as_ref().is_some_and(|attrs| {
890            attrs
891                .iter()
892                .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
893        })
894    }
895}
896
897impl ElementSnapshot for ServoElementSnapshot {
898    fn state(&self) -> Option<ElementState> {
899        self.state.clone()
900    }
901
902    fn has_attrs(&self) -> bool {
903        self.attrs.is_some()
904    }
905
906    fn id_attr(&self) -> Option<&Atom> {
907        self.get_attr(&ns!(), &local_name!("id"))
908            .map(|v| v.as_atom())
909    }
910
911    fn is_part(&self, part_name: &AtomIdent) -> bool {
912        self.get_attr(&ns!(), &local_name!("part"))
913            .is_some_and(|v| {
914                v.as_tokens()
915                    .iter()
916                    .any(|atom| CaseSensitivity::CaseSensitive.eq_atom(atom, part_name))
917            })
918    }
919
920    fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
921        None
922    }
923
924    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
925        self.get_attr(&ns!(), &local_name!("class"))
926            .map_or(false, |v| {
927                v.as_tokens()
928                    .iter()
929                    .any(|atom| case_sensitivity.eq_atom(atom, name))
930            })
931    }
932
933    fn each_class<F>(&self, mut callback: F)
934    where
935        F: FnMut(&AtomIdent),
936    {
937        if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
938            for class in v.as_tokens() {
939                callback(AtomIdent::cast(class));
940            }
941        }
942    }
943
944    fn lang_attr(&self) -> Option<SelectorAttrValue> {
945        self.get_attr(&ns!(xml), &local_name!("lang"))
946            .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
947            .map(|v| SelectorAttrValue::from(v as &str))
948    }
949
950    /// Returns true if the snapshot has stored state for custom states
951    #[inline]
952    fn has_custom_states(&self) -> bool {
953        false
954    }
955
956    /// Returns true if the snapshot has a given CustomState
957    #[inline]
958    fn has_custom_state(&self, _state: &AtomIdent) -> bool {
959        false
960    }
961
962    #[inline]
963    fn each_custom_state<F>(&self, mut _callback: F)
964    where
965        F: FnMut(&AtomIdent),
966    {
967    }
968}
969
970impl ServoElementSnapshot {
971    /// selectors::Element::attr_matches
972    pub fn attr_matches(
973        &self,
974        ns: &NamespaceConstraint<&Namespace>,
975        local_name: &LocalName,
976        operation: &AttrSelectorOperation<&AtomString>,
977    ) -> bool {
978        match *ns {
979            NamespaceConstraint::Specific(ref ns) => self
980                .get_attr(ns, local_name)
981                .map_or(false, |value| value.eval_selector(operation)),
982            NamespaceConstraint::Any => {
983                self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
984            },
985        }
986    }
987}
988
989/// Returns whether the language is matched, as defined by
990/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2).
991pub fn extended_filtering(tag: &str, range: &str) -> bool {
992    range.split(',').any(|lang_range| {
993        // step 1
994        let mut range_subtags = lang_range.split('\x2d');
995        let mut tag_subtags = tag.split('\x2d');
996
997        // step 2
998        // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
999        if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
1000            if !(range_subtag.eq_ignore_ascii_case(tag_subtag)
1001                || range_subtag.eq_ignore_ascii_case("*"))
1002            {
1003                return false;
1004            }
1005        }
1006
1007        let mut current_tag_subtag = tag_subtags.next();
1008
1009        // step 3
1010        for range_subtag in range_subtags {
1011            // step 3a
1012            if range_subtag == "*" {
1013                continue;
1014            }
1015            match current_tag_subtag.clone() {
1016                Some(tag_subtag) => {
1017                    // step 3c
1018                    if range_subtag.eq_ignore_ascii_case(tag_subtag) {
1019                        current_tag_subtag = tag_subtags.next();
1020                        continue;
1021                    }
1022                    // step 3d
1023                    if tag_subtag.len() == 1 {
1024                        return false;
1025                    }
1026                    // else step 3e - continue with loop
1027                    current_tag_subtag = tag_subtags.next();
1028                    if current_tag_subtag.is_none() {
1029                        return false;
1030                    }
1031                },
1032                // step 3b
1033                None => {
1034                    return false;
1035                },
1036            }
1037        }
1038        // step 4
1039        true
1040    })
1041}