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(usize)]
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        assert!(i < EAGER_PSEUDO_COUNT);
155        let result: PseudoElement = unsafe { mem::transmute(i) };
156        debug_assert!(result.is_eager());
157        result
158    }
159
160    /// Whether the current pseudo element is ::before or ::after.
161    #[inline]
162    pub fn is_before_or_after(&self) -> bool {
163        self.is_before() || self.is_after()
164    }
165
166    /// Whether this is an unknown ::-webkit- pseudo-element.
167    #[inline]
168    pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
169        false
170    }
171
172    /// Whether this pseudo-element is the ::marker pseudo.
173    #[inline]
174    pub fn is_marker(&self) -> bool {
175        *self == PseudoElement::Marker
176    }
177
178    /// Whether this pseudo-element is the ::selection pseudo.
179    #[inline]
180    pub fn is_selection(&self) -> bool {
181        *self == PseudoElement::Selection
182    }
183
184    /// Whether this pseudo-element is the ::before pseudo.
185    #[inline]
186    pub fn is_before(&self) -> bool {
187        *self == PseudoElement::Before
188    }
189
190    /// Whether this pseudo-element is the ::after pseudo.
191    #[inline]
192    pub fn is_after(&self) -> bool {
193        *self == PseudoElement::After
194    }
195
196    /// Whether the current pseudo element is :first-letter
197    #[inline]
198    pub fn is_first_letter(&self) -> bool {
199        *self == PseudoElement::FirstLetter
200    }
201
202    /// Whether the current pseudo element is :first-line
203    #[inline]
204    pub fn is_first_line(&self) -> bool {
205        false
206    }
207
208    /// Whether this pseudo-element is representing the color swatch
209    /// inside an `<input>` element.
210    #[inline]
211    pub fn is_color_swatch(&self) -> bool {
212        *self == PseudoElement::ColorSwatch
213    }
214
215    /// Whether this pseudo-element is eagerly-cascaded.
216    #[inline]
217    pub fn is_eager(&self) -> bool {
218        self.cascade_type() == PseudoElementCascadeType::Eager
219    }
220
221    /// Whether this pseudo-element is lazily-cascaded.
222    #[inline]
223    pub fn is_lazy(&self) -> bool {
224        self.cascade_type() == PseudoElementCascadeType::Lazy
225    }
226
227    /// Whether this pseudo-element is for an anonymous box.
228    pub fn is_anon_box(&self) -> bool {
229        self.is_precomputed()
230    }
231
232    /// Whether this pseudo-element skips flex/grid container display-based
233    /// fixup.
234    #[inline]
235    pub fn skip_item_display_fixup(&self) -> bool {
236        !self.is_before_or_after()
237    }
238
239    /// Whether this pseudo-element is precomputed.
240    #[inline]
241    pub fn is_precomputed(&self) -> bool {
242        self.cascade_type() == PseudoElementCascadeType::Precomputed
243    }
244
245    /// Returns which kind of cascade type has this pseudo.
246    ///
247    /// See the documentation for `PseudoElementCascadeType` for how we choose
248    /// which cascade type to use.
249    ///
250    /// Note: Keep eager pseudos in sync with `EAGER_PSEUDO_COUNT` and
251    /// `EMPTY_PSEUDO_ARRAY` in `style/data.rs`
252    #[inline]
253    pub fn cascade_type(&self) -> PseudoElementCascadeType {
254        match *self {
255            PseudoElement::After
256            | PseudoElement::Before
257            | PseudoElement::FirstLetter
258            | PseudoElement::Selection => PseudoElementCascadeType::Eager,
259            PseudoElement::Backdrop
260            | PseudoElement::ColorSwatch
261            | PseudoElement::FileSelectorButton
262            | PseudoElement::Marker
263            | PseudoElement::Placeholder
264            | PseudoElement::DetailsContent
265            | PseudoElement::SliderFill
266            | PseudoElement::SliderThumb
267            | PseudoElement::SliderTrack
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 static_prefs::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 is the ::target-text pseudo.
323    #[inline]
324    pub fn is_target_text(&self) -> bool {
325        false
326    }
327
328    /// Whether this is a highlight pseudo-element that is styled lazily during
329    /// painting rather than during the restyle traversal. These pseudos need
330    /// explicit repaint triggering when their styles change.
331    #[inline]
332    pub fn is_lazy_painted_highlight_pseudo(&self) -> bool {
333        self.is_selection() || self.is_highlight() || self.is_target_text()
334    }
335
336    /// Whether this pseudo-element is "element-backed", which means that it inherits from its regular
337    /// flat tree parent, which might not be the originating element.
338    #[inline]
339    pub fn is_element_backed(&self) -> bool {
340        use selectors::parser::PseudoElement;
341        self.parses_as_element_backed()
342            || matches!(
343                self,
344                Self::Placeholder
345                    | Self::ColorSwatch
346                    | Self::FileSelectorButton
347                    | Self::SliderFill
348                    | Self::SliderThumb
349                    | Self::SliderTrack
350                    | Self::ServoTextControlInnerContainer
351                    | Self::ServoTextControlInnerEditor,
352            )
353    }
354}
355
356/// The type used for storing `:lang` arguments.
357pub type Lang = Box<str>;
358
359/// The type used to store the state argument to the `:state` pseudo-class.
360#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)]
361pub struct CustomState(pub AtomIdent);
362
363/// A non tree-structural pseudo-class.
364/// See https://drafts.csswg.org/selectors-4/#structural-pseudos
365#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
366#[allow(missing_docs)]
367pub enum NonTSPseudoClass {
368    Active,
369    AnyLink,
370    Autofill,
371    Checked,
372    /// The :state` pseudo-class.
373    CustomState(CustomState),
374    Default,
375    Defined,
376    Disabled,
377    Enabled,
378    Focus,
379    FocusWithin,
380    FocusVisible,
381    Fullscreen,
382    Hover,
383    InRange,
384    Indeterminate,
385    Invalid,
386    Lang(Lang),
387    Link,
388    Modal,
389    MozMeterOptimum,
390    MozMeterSubOptimum,
391    MozMeterSubSubOptimum,
392    Open,
393    Optional,
394    OutOfRange,
395    PlaceholderShown,
396    PopoverOpen,
397    ReadOnly,
398    ReadWrite,
399    Required,
400    ServoNonZeroBorder,
401    Target,
402    UserInvalid,
403    UserValid,
404    Valid,
405    Visited,
406}
407
408impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
409    type Impl = SelectorImpl;
410
411    #[inline]
412    fn is_active_or_hover(&self) -> bool {
413        matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
414    }
415
416    #[inline]
417    fn is_user_action_state(&self) -> bool {
418        matches!(
419            *self,
420            NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
421        )
422    }
423
424    fn visit<V>(&self, _: &mut V) -> bool
425    where
426        V: SelectorVisitor<Impl = Self::Impl>,
427    {
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::MozMeterOptimum => ":-moz-meter-optimum",
469            Self::MozMeterSubOptimum => ":-moz-meter-sub-optimum",
470            Self::MozMeterSubSubOptimum => ":-moz-meter-sub-sub-optimum",
471            Self::Open => ":open",
472            Self::Optional => ":optional",
473            Self::OutOfRange => ":out-of-range",
474            Self::PlaceholderShown => ":placeholder-shown",
475            Self::PopoverOpen => ":popover-open",
476            Self::ReadOnly => ":read-only",
477            Self::ReadWrite => ":read-write",
478            Self::Required => ":required",
479            Self::ServoNonZeroBorder => ":-servo-nonzero-border",
480            Self::Target => ":target",
481            Self::UserInvalid => ":user-invalid",
482            Self::UserValid => ":user-valid",
483            Self::Valid => ":valid",
484            Self::Visited => ":visited",
485            Self::Lang(_) => unreachable!(),
486        })
487    }
488}
489
490impl NonTSPseudoClass {
491    /// Gets a given state flag for this pseudo-class. This is used to do
492    /// selector matching, and it's set from the DOM.
493    pub fn state_flag(&self) -> ElementState {
494        match *self {
495            Self::Active => ElementState::ACTIVE,
496            Self::AnyLink => ElementState::VISITED_OR_UNVISITED,
497            Self::Autofill => ElementState::AUTOFILL,
498            Self::Checked => ElementState::CHECKED,
499            Self::Default => ElementState::DEFAULT,
500            Self::Defined => ElementState::DEFINED,
501            Self::Disabled => ElementState::DISABLED,
502            Self::Enabled => ElementState::ENABLED,
503            Self::Focus => ElementState::FOCUS,
504            Self::FocusVisible => ElementState::FOCUSRING,
505            Self::FocusWithin => ElementState::FOCUS_WITHIN,
506            Self::Fullscreen => ElementState::FULLSCREEN,
507            Self::Hover => ElementState::HOVER,
508            Self::InRange => ElementState::INRANGE,
509            Self::Indeterminate => ElementState::INDETERMINATE,
510            Self::Invalid => ElementState::INVALID,
511            Self::Link => ElementState::UNVISITED,
512            Self::Modal => ElementState::MODAL,
513            Self::MozMeterOptimum => ElementState::OPTIMUM,
514            Self::MozMeterSubOptimum => ElementState::SUB_OPTIMUM,
515            Self::MozMeterSubSubOptimum => ElementState::SUB_SUB_OPTIMUM,
516            Self::Open => ElementState::OPEN,
517            Self::Optional => ElementState::OPTIONAL_,
518            Self::OutOfRange => ElementState::OUTOFRANGE,
519            Self::PlaceholderShown => ElementState::PLACEHOLDER_SHOWN,
520            Self::PopoverOpen => ElementState::POPOVER_OPEN,
521            Self::ReadOnly => ElementState::READONLY,
522            Self::ReadWrite => ElementState::READWRITE,
523            Self::Required => ElementState::REQUIRED,
524            Self::Target => ElementState::URLTARGET,
525            Self::UserInvalid => ElementState::USER_INVALID,
526            Self::UserValid => ElementState::USER_VALID,
527            Self::Valid => ElementState::VALID,
528            Self::Visited => ElementState::VISITED,
529            Self::CustomState(_) | Self::Lang(_) | Self::ServoNonZeroBorder => {
530                ElementState::empty()
531            },
532        }
533    }
534
535    /// Get the document state flag associated with a pseudo-class, if any.
536    pub fn document_state_flag(&self) -> DocumentState {
537        DocumentState::empty()
538    }
539
540    /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
541    pub fn needs_cache_revalidation(&self) -> bool {
542        self.state_flag().is_empty()
543    }
544}
545
546/// The abstract struct we implement the selector parser implementation on top
547/// of.
548#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
549pub struct SelectorImpl;
550
551/// A set of extra data to carry along with the matching context, either for
552/// selector-matching or invalidation.
553#[derive(Debug, Default)]
554pub struct ExtraMatchingData<'a> {
555    /// The invalidation data to invalidate doc-state pseudo-classes correctly.
556    pub invalidation_data: InvalidationMatchingData,
557
558    /// The invalidation bits from matching container queries. These are here
559    /// just for convenience mostly.
560    pub cascade_input_flags: ComputedValueFlags,
561
562    /// The style of the originating element in order to evaluate @container
563    /// size queries affecting pseudo-elements.
564    pub originating_element_style: Option<&'a ComputedValues>,
565}
566
567impl ::selectors::SelectorImpl for SelectorImpl {
568    type PseudoElement = PseudoElement;
569    type NonTSPseudoClass = NonTSPseudoClass;
570
571    type ExtraMatchingData<'a> = ExtraMatchingData<'a>;
572    type AttrValue = AtomString;
573    type Identifier = AtomIdent;
574    type LocalName = LocalName;
575    type NamespacePrefix = Prefix;
576    type NamespaceUrl = Namespace;
577    type BorrowedLocalName = web_atoms::LocalName;
578    type BorrowedNamespaceUrl = web_atoms::Namespace;
579}
580
581impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
582    type Impl = SelectorImpl;
583    type Error = StyleParseErrorKind<'i>;
584
585    #[inline]
586    fn parse_nth_child_of(&self) -> bool {
587        false
588    }
589
590    #[inline]
591    fn parse_is_and_where(&self) -> bool {
592        true
593    }
594
595    #[inline]
596    fn parse_has(&self) -> bool {
597        false
598    }
599
600    #[inline]
601    fn parse_parent_selector(&self) -> bool {
602        true
603    }
604
605    #[inline]
606    fn parse_part(&self) -> bool {
607        true
608    }
609
610    #[inline]
611    fn allow_forgiving_selectors(&self) -> bool {
612        !self.for_supports_rule
613    }
614
615    fn parse_non_ts_pseudo_class(
616        &self,
617        location: SourceLocation,
618        name: CowRcStr<'i>,
619    ) -> Result<NonTSPseudoClass, ParseError<'i>> {
620        let pseudo_class = match_ignore_ascii_case! { &name,
621            "active" => NonTSPseudoClass::Active,
622            "any-link" => NonTSPseudoClass::AnyLink,
623            "autofill" => NonTSPseudoClass::Autofill,
624            "checked" => NonTSPseudoClass::Checked,
625            "default" => NonTSPseudoClass::Default,
626            "defined" => NonTSPseudoClass::Defined,
627            "disabled" => NonTSPseudoClass::Disabled,
628            "enabled" => NonTSPseudoClass::Enabled,
629            "focus" => NonTSPseudoClass::Focus,
630            "focus-visible" => NonTSPseudoClass::FocusVisible,
631            "focus-within" => NonTSPseudoClass::FocusWithin,
632            "fullscreen" => NonTSPseudoClass::Fullscreen,
633            "hover" => NonTSPseudoClass::Hover,
634            "indeterminate" => NonTSPseudoClass::Indeterminate,
635            "invalid" => NonTSPseudoClass::Invalid,
636            "link" => NonTSPseudoClass::Link,
637            "modal" => NonTSPseudoClass::Modal,
638            "open" => NonTSPseudoClass::Open,
639            "optional" => NonTSPseudoClass::Optional,
640            "out-of-range" => NonTSPseudoClass::OutOfRange,
641            "placeholder-shown" => NonTSPseudoClass::PlaceholderShown,
642            "popover-open" => NonTSPseudoClass::PopoverOpen,
643            "read-only" => NonTSPseudoClass::ReadOnly,
644            "read-write" => NonTSPseudoClass::ReadWrite,
645            "required" => NonTSPseudoClass::Required,
646            "target" => NonTSPseudoClass::Target,
647            "user-invalid" => NonTSPseudoClass::UserInvalid,
648            "user-valid" => NonTSPseudoClass::UserValid,
649            "valid" => NonTSPseudoClass::Valid,
650            "visited" => NonTSPseudoClass::Visited,
651            "-moz-meter-optimum" => NonTSPseudoClass::MozMeterOptimum,
652            "-moz-meter-sub-optimum" => NonTSPseudoClass::MozMeterSubOptimum,
653            "-moz-meter-sub-sub-optimum" => NonTSPseudoClass::MozMeterSubSubOptimum,
654            "-servo-nonzero-border" => {
655                if !self.in_user_agent_stylesheet() {
656                    return Err(location.new_custom_error(
657                        SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into())
658                    ))
659                }
660                NonTSPseudoClass::ServoNonZeroBorder
661            },
662            _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
663        };
664
665        Ok(pseudo_class)
666    }
667
668    fn parse_non_ts_functional_pseudo_class<'t>(
669        &self,
670        name: CowRcStr<'i>,
671        parser: &mut CssParser<'i, 't>,
672        after_part: bool,
673    ) -> Result<NonTSPseudoClass, ParseError<'i>> {
674        let pseudo_class = match_ignore_ascii_case! { &name,
675            "lang" if !after_part => {
676                NonTSPseudoClass::Lang(parser.expect_ident_or_string()?.as_ref().into())
677            },
678            "state" => {
679                let result = AtomIdent::from(parser.expect_ident()?.as_ref());
680                NonTSPseudoClass::CustomState(CustomState(result))
681            },
682            _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
683        };
684
685        Ok(pseudo_class)
686    }
687
688    fn parse_pseudo_element(
689        &self,
690        location: SourceLocation,
691        name: CowRcStr<'i>,
692    ) -> Result<PseudoElement, ParseError<'i>> {
693        use self::PseudoElement::*;
694        let pseudo_element = match_ignore_ascii_case! { &name,
695            "before" => Before,
696            "after" => After,
697            "backdrop" => Backdrop,
698            "selection" => Selection,
699            "file-selector-button" => FileSelectorButton,
700            "first-letter" => FirstLetter,
701            "marker" => Marker,
702            "details-content" => DetailsContent,
703            "color-swatch" => ColorSwatch,
704            "placeholder" => Placeholder,
705            "-servo-text-control-inner-container" => {
706                if !self.in_user_agent_stylesheet() {
707                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
708                }
709                ServoTextControlInnerContainer
710            },
711            "-servo-text-control-inner-editor" => {
712                if !self.in_user_agent_stylesheet() {
713                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
714                }
715                ServoTextControlInnerEditor
716            },
717            "slider-fill" => SliderFill,
718            "slider-thumb" => SliderThumb,
719            "slider-track" => SliderTrack,
720            "-servo-anonymous-box" => {
721                if !self.in_user_agent_stylesheet() {
722                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
723                }
724                ServoAnonymousBox
725            },
726            "-servo-anonymous-table" => {
727                if !self.in_user_agent_stylesheet() {
728                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
729                }
730                ServoAnonymousTable
731            },
732            "-servo-anonymous-table-row" => {
733                if !self.in_user_agent_stylesheet() {
734                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
735                }
736                ServoAnonymousTableRow
737            },
738            "-servo-anonymous-table-cell" => {
739                if !self.in_user_agent_stylesheet() {
740                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
741                }
742                ServoAnonymousTableCell
743            },
744            "-servo-table-grid" => {
745                if !self.in_user_agent_stylesheet() {
746                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
747                }
748                ServoTableGrid
749            },
750            "-servo-table-wrapper" => {
751                if !self.in_user_agent_stylesheet() {
752                    return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
753                }
754                ServoTableWrapper
755            },
756            _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
757
758        };
759
760        Ok(pseudo_element)
761    }
762
763    fn default_namespace(&self) -> Option<Namespace> {
764        self.namespaces.default.as_ref().map(|ns| ns.clone())
765    }
766
767    fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
768        self.namespaces.prefixes.get(prefix).cloned()
769    }
770
771    fn parse_host(&self) -> bool {
772        true
773    }
774
775    fn parse_slotted(&self) -> bool {
776        true
777    }
778}
779
780impl SelectorImpl {
781    /// A helper to traverse each eagerly cascaded pseudo-element, executing
782    /// `fun` on it.
783    #[inline]
784    pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
785    where
786        F: FnMut(PseudoElement),
787    {
788        for i in 0..EAGER_PSEUDO_COUNT {
789            fun(PseudoElement::from_eager_index(i));
790        }
791    }
792}
793
794/// A map from elements to snapshots for the Servo style back-end.
795#[derive(Debug)]
796pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
797
798impl SnapshotMap {
799    /// Create a new empty `SnapshotMap`.
800    pub fn new() -> Self {
801        SnapshotMap(FxHashMap::default())
802    }
803
804    /// Get a snapshot given an element.
805    pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
806        self.0.get(&el.as_node().opaque())
807    }
808}
809
810impl Deref for SnapshotMap {
811    type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
812
813    fn deref(&self) -> &Self::Target {
814        &self.0
815    }
816}
817
818impl DerefMut for SnapshotMap {
819    fn deref_mut(&mut self) -> &mut Self::Target {
820        &mut self.0
821    }
822}
823
824/// Servo's version of an element snapshot.
825#[derive(Debug, Default, MallocSizeOf)]
826pub struct ServoElementSnapshot {
827    /// The stored state of the element.
828    pub state: Option<ElementState>,
829    /// The set of stored attributes and its values.
830    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
831    /// The set of changed attributes and its values.
832    pub changed_attrs: Vec<LocalName>,
833    /// Whether the class attribute changed or not.
834    pub class_changed: bool,
835    /// Whether the id attribute changed or not.
836    pub id_changed: bool,
837    /// Whether other attributes other than id or class changed or not.
838    pub other_attributes_changed: bool,
839}
840
841impl ServoElementSnapshot {
842    /// Create an empty element snapshot.
843    pub fn new() -> Self {
844        Self::default()
845    }
846
847    /// Returns whether the id attribute changed or not.
848    pub fn id_changed(&self) -> bool {
849        self.id_changed
850    }
851
852    /// Returns whether the class attribute changed or not.
853    pub fn class_changed(&self) -> bool {
854        self.class_changed
855    }
856
857    /// Returns whether other attributes other than id or class changed or not.
858    pub fn other_attr_changed(&self) -> bool {
859        self.other_attributes_changed
860    }
861
862    fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
863        self.attrs
864            .as_ref()
865            .unwrap()
866            .iter()
867            .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
868            .map(|&(_, ref v)| v)
869    }
870
871    /// Executes the callback once for each attribute that changed.
872    #[inline]
873    pub fn each_attr_changed<F>(&self, mut callback: F)
874    where
875        F: FnMut(&LocalName),
876    {
877        for name in &self.changed_attrs {
878            callback(name)
879        }
880    }
881
882    fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
883    where
884        F: FnMut(&AttrValue) -> bool,
885    {
886        self.attrs
887            .as_ref()
888            .unwrap()
889            .iter()
890            .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
891    }
892}
893
894impl ElementSnapshot for ServoElementSnapshot {
895    fn state(&self) -> Option<ElementState> {
896        self.state.clone()
897    }
898
899    fn has_attrs(&self) -> bool {
900        self.attrs.is_some()
901    }
902
903    fn id_attr(&self) -> Option<&Atom> {
904        self.get_attr(&ns!(), &local_name!("id"))
905            .map(|v| v.as_atom())
906    }
907
908    fn is_part(&self, part_name: &AtomIdent) -> bool {
909        self.get_attr(&ns!(), &local_name!("part"))
910            .is_some_and(|v| {
911                v.as_tokens()
912                    .iter()
913                    .any(|atom| CaseSensitivity::CaseSensitive.eq_atom(atom, part_name))
914            })
915    }
916
917    fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
918        None
919    }
920
921    fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
922        self.get_attr(&ns!(), &local_name!("class"))
923            .map_or(false, |v| {
924                v.as_tokens()
925                    .iter()
926                    .any(|atom| case_sensitivity.eq_atom(atom, name))
927            })
928    }
929
930    fn each_class<F>(&self, mut callback: F)
931    where
932        F: FnMut(&AtomIdent),
933    {
934        if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
935            for class in v.as_tokens() {
936                callback(AtomIdent::cast(class));
937            }
938        }
939    }
940
941    fn lang_attr(&self) -> Option<SelectorAttrValue> {
942        self.get_attr(&ns!(xml), &local_name!("lang"))
943            .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
944            .map(|v| SelectorAttrValue::from(v as &str))
945    }
946
947    /// Returns true if the snapshot has stored state for custom states
948    #[inline]
949    fn has_custom_states(&self) -> bool {
950        false
951    }
952
953    /// Returns true if the snapshot has a given CustomState
954    #[inline]
955    fn has_custom_state(&self, _state: &AtomIdent) -> bool {
956        false
957    }
958
959    #[inline]
960    fn each_custom_state<F>(&self, mut _callback: F)
961    where
962        F: FnMut(&AtomIdent),
963    {
964    }
965}
966
967impl ServoElementSnapshot {
968    /// selectors::Element::attr_matches
969    pub fn attr_matches(
970        &self,
971        ns: &NamespaceConstraint<&Namespace>,
972        local_name: &LocalName,
973        operation: &AttrSelectorOperation<&AtomString>,
974    ) -> bool {
975        match *ns {
976            NamespaceConstraint::Specific(ref ns) => self
977                .get_attr(ns, local_name)
978                .map_or(false, |value| value.eval_selector(operation)),
979            NamespaceConstraint::Any => {
980                self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
981            },
982        }
983    }
984}
985
986/// Returns whether the language is matched, as defined by
987/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2).
988pub fn extended_filtering(tag: &str, range: &str) -> bool {
989    range.split(',').any(|lang_range| {
990        // step 1
991        let mut range_subtags = lang_range.split('\x2d');
992        let mut tag_subtags = tag.split('\x2d');
993
994        // step 2
995        // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
996        if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
997            if !(range_subtag.eq_ignore_ascii_case(tag_subtag)
998                || range_subtag.eq_ignore_ascii_case("*"))
999            {
1000                return false;
1001            }
1002        }
1003
1004        let mut current_tag_subtag = tag_subtags.next();
1005
1006        // step 3
1007        for range_subtag in range_subtags {
1008            // step 3a
1009            if range_subtag == "*" {
1010                continue;
1011            }
1012            match current_tag_subtag.clone() {
1013                Some(tag_subtag) => {
1014                    // step 3c
1015                    if range_subtag.eq_ignore_ascii_case(tag_subtag) {
1016                        current_tag_subtag = tag_subtags.next();
1017                        continue;
1018                    }
1019                    // step 3d
1020                    if tag_subtag.len() == 1 {
1021                        return false;
1022                    }
1023                    // else step 3e - continue with loop
1024                    current_tag_subtag = tag_subtags.next();
1025                    if current_tag_subtag.is_none() {
1026                        return false;
1027                    }
1028                },
1029                // step 3b
1030                None => {
1031                    return false;
1032                },
1033            }
1034        }
1035        // step 4
1036        true
1037    })
1038}