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