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