Skip to main content

style/servo/
selector_parser.rs

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