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