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