Skip to main content

bun_css/selectors/
parser.rs

1//! CSS selector parser — ported from `src/css/selectors/parser.zig`.
2//! Originally derived from servo/lightningcss selector parsing.
3
4use crate::ArenaVec;
5use core::fmt;
6
7use bun_alloc::{Arena as Bump, ArenaPtr};
8use bun_core::strings;
9use bun_css as css;
10use bun_css::css_values::ident::{CustomIdent, Ident};
11use bun_css::selector::serialize;
12use bun_css::{
13    CSSStringFns, IdentFns, Parser as CssParser, ParserOptions, PrintErr, Printer, SmallList,
14    Token, TokenList,
15};
16use bun_wyhash::Wyhash;
17
18use super::impl_;
19
20use super::builder::SelectorBuilder;
21
22pub use bun_css::Printer as PrinterRe; // re-export parity (Printer/PrintErr were `pub const` aliases)
23
24/// `css::Result<T>` — the CSS parser result type (`Ok(T)` / `Err(css::ParseError)`).
25type CResult<T> = css::Result<T>;
26
27// TODO(port): arena lifetimes. The Zig code threads `parser.arena` / `input.arena()`
28// (a bump arena) through every allocation. The Rust port uses `Vec`/`Box` and a `Str` alias for
29// source-borrowed byte slices; re-thread `'bump` and switch to
30// `bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators (AST crates).
31// PERF(port): was arena bulk-free — profile if it shows up on a hot path.
32//
33// NOTE: `Str` is `&'static [u8]` here (not `crate::Str = *const [u8]`) to match
34// `crate::Token`'s payload shape (`Token::Ident(&'static [u8])` etc.) — every
35// `Str` in this module originates from a token slice. The `'static` is a
36// placeholder for the tokenizer source lifetime; it widens to `&'bump
37// [u8]` once `Parser<'bump>` threads the arena lifetime.
38type Str = &'static [u8]; // arena-backed `[]const u8` source slice
39
40// ─── Protocol traits ─────────────────────────────────────────────────────────
41// Zig's `implementEql` / `implementHash` / `implementDeepClone` are comptime
42// field/variant reflection over `@typeInfo(T)` — in Rust this is the body of
43// `#[derive(CssEql, CssHash, DeepClone)]` (`bun_css_derive`). Non-generic
44// grammar types below carry the derive directly; the `<Impl: SelectorImpl>`-
45// generic types hand-write bodies (the derive's `where Impl: CssEql` bound is
46// useless — equality recurses on `Impl::Assoc`, not `Impl`).
47//
48// `deep_clone` on the grammar types drops the `&Arena` parameter: `GenericSelector.components`
49// is `ArenaVec<_>` and clones into the *source* allocator (intra-arena only).
50use css::generics::{CssEql, CssHash};
51// Dual-mode plain vec (stable api2 mirror) for CssEql/CssHash-derived fields.
52use bun_alloc::core_alloc::AllocVec as _PVec;
53use bun_alloc::core_alloc::Global as _PG;
54type PlainVec2<T> = _PVec<T, _PG>;
55
56/// Drain a `SmallList<T, N>` into a `Box<[T]>`. `SmallList` has no `into_vec`;
57/// this bitwise-moves each element out and `set_len(0)`s the source so its
58/// `Drop` doesn't double-free. Mirrors Zig `toOwnedSlice`.
59fn small_list_into_box<T, const N: usize>(mut sl: SmallList<T, N>) -> Box<[T]> {
60    let len = sl.len() as usize;
61    let mut v: Vec<T> = Vec::with_capacity(len);
62    {
63        let src = sl.slice();
64        for i in 0..len {
65            // SAFETY: each index is read exactly once; `set_len(0)` below
66            // prevents `SmallList::drop` from re-dropping the moved elements.
67            unsafe { v.push(core::ptr::read(src.get_unchecked(i))) };
68        }
69    }
70    sl.set_len(0);
71    v.into_boxed_slice()
72}
73
74/// Allocate an ASCII-lowercased copy of `name` in the parse-session bump arena.
75/// Zig used `parser.arena().alloc(u8, n)` (the bump arena owns the buffer
76/// for the parse session and frees it on arena reset). Returns a raw arena
77/// pointer (`*const [u8]`) — `Ident.v`'s field type — so we don't fabricate a
78/// `'static` lifetime (PORTING.md §Forbidden: never `Box::leak` to satisfy
79/// `&'static`). Re-threading `&'bump Bump` would widen `Ident.v` to
80/// `&'bump [u8]`.
81// PERF(port): was arena alloc — profile if it shows up on a hot path.
82#[inline]
83fn arena_lowercase(bump: &Bump, name: &[u8]) -> *const [u8] {
84    let buf = bump.alloc_slice_fill_copy(name.len(), 0u8);
85    let _ = strings::copy_lowercase(name, buf);
86    std::ptr::from_ref::<[u8]>(buf)
87}
88
89// ─── selector-slice protocol helpers ─────────────────────────────────────────
90// `Box<[GenericSelector<Impl>]>` appears in `Component::{Negation,Where,Is,
91// Any,Has}`, `NthOfSelectorData`, and (via `SelectorList.v`) at the top level.
92// Hoisted as free fns so the hand-written `eql`/`hash`/`deep_clone` bodies
93// below stay small.
94#[inline]
95fn eql_selector_slice<Impl: BunSelectorImpl>(
96    a: &[GenericSelector<Impl>],
97    b: &[GenericSelector<Impl>],
98) -> bool {
99    a.len() == b.len() && a.iter().zip(b).all(|(l, r)| l.eql(r))
100}
101#[inline]
102fn hash_selector_slice<Impl: BunSelectorImpl>(s: &[GenericSelector<Impl>], hasher: &mut Wyhash) {
103    for sel in s {
104        sel.hash(hasher);
105    }
106}
107#[inline]
108fn deep_clone_selector_slice<Impl: BunSelectorImpl>(
109    s: &[GenericSelector<Impl>],
110) -> Box<[GenericSelector<Impl>]> {
111    s.iter().map(|sel| sel.deep_clone()).collect()
112}
113
114/// Instantiation of generic selector structs using our implementation of the `SelectorImpl` trait.
115pub type Component = GenericComponent<impl_::Selectors>;
116pub type Selector = GenericSelector<impl_::Selectors>;
117pub type SelectorList = GenericSelectorList<impl_::Selectors>;
118
119#[derive(Clone, Copy, PartialEq, Eq)]
120pub enum ToCssCtx {
121    Lightning,
122    Servo,
123}
124
125/// The definition of whitespace per CSS Selectors Level 3 § 4.
126pub const SELECTOR_WHITESPACE: &[u8] = &[b' ', b'\t', b'\n', b'\r', 0x0C];
127
128/// Compile-time check that `T` satisfies the `SelectorImpl` trait shape.
129/// In Rust this is expressed as a trait bound; this fn is kept for diff parity.
130pub fn valid_selector_impl<T: SelectorImpl>() {
131    // Zig used `_ = T.SelectorImpl.X;` to force decl resolution; in Rust the trait
132    // bound `T: SelectorImpl` is the check.
133}
134
135/// The `SelectorImpl` shape (Zig validated via `ValidSelectorImpl`). Implemented
136/// by `impl_::Selectors` in `bun_css::selector::impl_`.
137// PORT NOTE: `PartialEq + Clone` bounds dropped — the concrete assoc types
138// (`values::ident::{Ident,IdentOrRef}`, `*const [u8]`) implement structural
139// equality via the `CssEql` protocol (`generics::implement_eql`), not
140// `core::cmp::PartialEq`. Every `eql`/`deep_clone`/`hash` callsite in this
141// module forwards through `css::implement_*` which bound on `CssEql`/
142// `DeepClone`/`CssHash`, so the std bounds were never load-bearing.
143pub trait SelectorImpl: Sized {
144    type ExtraMatchingData;
145    type AttrValue: Clone;
146    type Identifier: Clone;
147    type LocalIdentifier: Clone;
148    type LocalName: Clone;
149    type NamespaceUrl: Clone;
150    type NamespacePrefix: Clone;
151    type BorrowedNamespaceUrl;
152    type BorrowedLocalName;
153    type NonTSPseudoClass: Clone;
154    type VendorPrefix: Clone;
155    type PseudoElement: Clone;
156}
157
158/// Constrained `SelectorImpl` with the concrete assoc-type bundle Bun uses.
159///
160/// PORT NOTE: in Zig the `parse_*` functions were `comptime Impl: type` generics
161/// but every body assumed the concrete `selector.impl.Selectors` shapes (it was
162/// the only instantiation). Rust can't see through the open `Impl::LocalName`
163/// to `Ident`, so the parse functions bound on this sub-trait instead — the
164/// associated-type equality clauses make `Impl::LocalName == Ident` etc.
165/// visible to the body without monomorphizing the signature.
166pub trait BunSelectorImpl:
167    SelectorImpl<
168        AttrValue = css::CSSString,
169        Identifier = Ident,
170        LocalIdentifier = css::css_values::ident::IdentOrRef,
171        LocalName = Ident,
172        NamespaceUrl = Str,
173        NamespacePrefix = Ident,
174        NonTSPseudoClass = PseudoClass,
175        PseudoElement = PseudoElement,
176        VendorPrefix = css::VendorPrefix,
177    >
178{
179}
180impl BunSelectorImpl for impl_::Selectors {}
181
182// ─────────────────────────────────────────────────────────────────────────────
183// attrs
184// ─────────────────────────────────────────────────────────────────────────────
185pub mod attrs {
186    use super::*;
187
188    #[derive(Clone)]
189    pub struct NamespaceUrl<Impl: SelectorImpl> {
190        pub prefix: Impl::NamespacePrefix,
191        pub url: Impl::NamespaceUrl,
192    }
193
194    impl<Impl: BunSelectorImpl> NamespaceUrl<Impl> {
195        pub fn eql(&self, rhs: &Self) -> bool {
196            // `BunSelectorImpl` fixes `NamespacePrefix = Ident`, `NamespaceUrl = Str`.
197            self.prefix.eql(&rhs.prefix) && strings::eql(self.url, rhs.url)
198        }
199        pub fn deep_clone(&self) -> Self {
200            // `NamespaceUrl = &'static [u8]` (arena-backed) — identity copy.
201            Self {
202                prefix: self.prefix,
203                url: self.url,
204            }
205        }
206        pub fn hash(&self, hasher: &mut Wyhash) {
207            self.prefix.hash(hasher);
208            hasher.update(self.url);
209        }
210    }
211
212    #[derive(Clone)]
213    pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> {
214        pub namespace: Option<NamespaceConstraint<NamespaceUrl<Impl>>>,
215        pub local_name: Impl::LocalName,
216        pub local_name_lower: Impl::LocalName,
217        pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
218        pub never_matches: bool,
219    }
220
221    impl<Impl: BunSelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
222        pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
223            dest.write_char(b'[')?;
224            if let Some(nsp) = &self.namespace {
225                match nsp {
226                    NamespaceConstraint::Specific(v) => {
227                        IdentFns::to_css(&v.prefix, dest)?;
228                        dest.write_char(b'|')?;
229                    }
230                    NamespaceConstraint::Any => {
231                        dest.write_str("*|")?;
232                    }
233                }
234            }
235            IdentFns::to_css(&self.local_name, dest)?;
236            match &self.operation {
237                ParsedAttrSelectorOperation::Exists => {}
238                ParsedAttrSelectorOperation::WithValue {
239                    operator,
240                    case_sensitivity,
241                    expected_value,
242                } => {
243                    operator.to_css(dest)?;
244                    // try v.expected_value.toCss(dest);
245                    CSSStringFns::to_css(expected_value, dest)?;
246                    match case_sensitivity {
247                        ParsedCaseSensitivity::CaseSensitive
248                        | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}
249                        ParsedCaseSensitivity::AsciiCaseInsensitive => {
250                            dest.write_str(" i")?;
251                        }
252                        ParsedCaseSensitivity::ExplicitCaseSensitive => {
253                            dest.write_str(" s")?;
254                        }
255                    }
256                }
257            }
258            dest.write_char(b']')
259        }
260
261        pub fn eql(&self, rhs: &Self) -> bool {
262            match (&self.namespace, &rhs.namespace) {
263                (None, None) => true,
264                (Some(a), Some(b)) => a.eql(b),
265                _ => return false,
266            }
267            .then_some(())
268            .is_some()
269                && self.local_name.eql(&rhs.local_name)
270                && self.local_name_lower.eql(&rhs.local_name_lower)
271                && self.operation.eql(&rhs.operation)
272                && self.never_matches == rhs.never_matches
273        }
274        pub fn deep_clone(&self) -> Self {
275            Self {
276                namespace: self.namespace.as_ref().map(|n| n.deep_clone()),
277                local_name: self.local_name,
278                local_name_lower: self.local_name_lower,
279                operation: self.operation.deep_clone(),
280                never_matches: self.never_matches,
281            }
282        }
283        pub fn hash(&self, hasher: &mut Wyhash) {
284            if let Some(ns) = &self.namespace {
285                ns.hash(hasher);
286            }
287            self.local_name.hash(hasher);
288            self.local_name_lower.hash(hasher);
289            self.operation.hash(hasher);
290            hasher.update(&[self.never_matches as u8]);
291        }
292    }
293
294    #[derive(Clone, PartialEq, Eq)]
295    pub enum NamespaceConstraint<NamespaceUrl> {
296        Any,
297        /// Empty string for no namespace
298        Specific(NamespaceUrl),
299    }
300
301    impl<Impl: BunSelectorImpl> NamespaceConstraint<NamespaceUrl<Impl>> {
302        pub fn eql(&self, rhs: &Self) -> bool {
303            match (self, rhs) {
304                (Self::Any, Self::Any) => true,
305                (Self::Specific(a), Self::Specific(b)) => a.eql(b),
306                _ => false,
307            }
308        }
309        pub fn hash(&self, hasher: &mut Wyhash) {
310            match self {
311                Self::Any => hasher.update(&0u32.to_ne_bytes()),
312                Self::Specific(n) => {
313                    hasher.update(&1u32.to_ne_bytes());
314                    n.hash(hasher);
315                }
316            }
317        }
318        pub fn deep_clone(&self) -> Self {
319            match self {
320                Self::Any => Self::Any,
321                Self::Specific(n) => Self::Specific(n.deep_clone()),
322            }
323        }
324    }
325
326    #[derive(Clone, PartialEq, Eq)]
327    pub enum ParsedAttrSelectorOperation<AttrValue> {
328        Exists,
329        WithValue {
330            operator: AttrSelectorOperator,
331            case_sensitivity: ParsedCaseSensitivity,
332            expected_value: AttrValue,
333        },
334    }
335
336    // PORT NOTE: implemented for the concrete `AttrValue = css::CSSString`
337    // (= `*const [u8]`) only — the sole `BunSelectorImpl` instantiation.
338    impl ParsedAttrSelectorOperation<css::CSSString> {
339        pub fn deep_clone(&self) -> Self {
340            self.clone()
341        }
342        pub fn eql(&self, rhs: &Self) -> bool {
343            match (self, rhs) {
344                (Self::Exists, Self::Exists) => true,
345                (
346                    Self::WithValue {
347                        operator: ao,
348                        case_sensitivity: ac,
349                        expected_value: av,
350                    },
351                    Self::WithValue {
352                        operator: bo,
353                        case_sensitivity: bc,
354                        expected_value: bv,
355                    },
356                ) => {
357                    ao == bo
358                        && ac == bc
359                        // SAFETY: arena-owned slices live for the parse session.
360                        && unsafe { strings::eql(&**av, &**bv) }
361                }
362                _ => false,
363            }
364        }
365        pub fn hash(&self, hasher: &mut Wyhash) {
366            match self {
367                Self::Exists => hasher.update(&0u32.to_ne_bytes()),
368                Self::WithValue {
369                    operator,
370                    case_sensitivity,
371                    expected_value,
372                } => {
373                    hasher.update(&1u32.to_ne_bytes());
374                    operator.hash(hasher);
375                    case_sensitivity.hash(hasher);
376                    // SAFETY: arena-owned slice.
377                    hasher.update(unsafe { crate::arena_str(*expected_value) });
378                }
379            }
380        }
381    }
382
383    #[derive(Clone, Copy, PartialEq, Eq, Hash, CssEql, CssHash, css::generics::DeepClone)]
384    pub enum AttrSelectorOperator {
385        Equal,
386        Includes,
387        DashMatch,
388        Prefix,
389        Substring,
390        Suffix,
391    }
392
393    impl AttrSelectorOperator {
394        pub fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> {
395            // https://drafts.csswg.org/cssom/#serializing-selectors
396            // See "attribute selector".
397            dest.write_str(match self {
398                Self::Equal => "=",
399                Self::Includes => "~=",
400                Self::DashMatch => "|=",
401                Self::Prefix => "^=",
402                Self::Substring => "*=",
403                Self::Suffix => "$=",
404            })
405        }
406    }
407
408    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
409    pub enum AttrSelectorOperation {
410        Equal,
411        Includes,
412        DashMatch,
413        Prefix,
414        Substring,
415        Suffix,
416    }
417
418    #[derive(Clone, Copy, PartialEq, Eq, Hash, CssEql, CssHash, css::generics::DeepClone)]
419    pub enum ParsedCaseSensitivity {
420        // 's' was specified.
421        ExplicitCaseSensitive,
422        // 'i' was specified.
423        AsciiCaseInsensitive,
424        // No flags were specified and HTML says this is a case-sensitive attribute.
425        CaseSensitive,
426        // No flags were specified and HTML says this is a case-insensitive attribute.
427        AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
428    }
429}
430
431// ─────────────────────────────────────────────────────────────────────────────
432// Specificity
433// ─────────────────────────────────────────────────────────────────────────────
434
435#[derive(Clone, Copy, Default)]
436pub struct Specificity {
437    pub id_selectors: u32,
438    pub class_like_selectors: u32,
439    pub element_selectors: u32,
440}
441
442impl Specificity {
443    const MAX_10BIT: u32 = (1 << 10) - 1;
444
445    pub fn to_u32(self) -> u32 {
446        (self.id_selectors.min(Self::MAX_10BIT) << 20)
447            | (self.class_like_selectors.min(Self::MAX_10BIT) << 10)
448            | self.element_selectors.min(Self::MAX_10BIT)
449    }
450
451    pub fn from_u32(value: u32) -> Specificity {
452        debug_assert!(value <= (Self::MAX_10BIT << 20 | Self::MAX_10BIT << 10 | Self::MAX_10BIT));
453        Specificity {
454            id_selectors: value >> 20,
455            class_like_selectors: (value >> 10) & Self::MAX_10BIT,
456            element_selectors: value & Self::MAX_10BIT,
457        }
458    }
459
460    pub fn add(&mut self, rhs: Specificity) {
461        self.id_selectors += rhs.id_selectors;
462        self.element_selectors += rhs.element_selectors;
463        self.class_like_selectors += rhs.class_like_selectors;
464    }
465}
466
467pub fn compute_specificity<Impl: BunSelectorImpl>(iter: &[GenericComponent<Impl>]) -> u32 {
468    let spec = compute_complex_selector_specificity::<Impl>(iter);
469    spec.to_u32()
470}
471
472fn compute_complex_selector_specificity<Impl: BunSelectorImpl>(
473    iter: &[GenericComponent<Impl>],
474) -> Specificity {
475    let mut specificity = Specificity::default();
476    for simple_selector in iter {
477        compute_simple_selector_specificity::<Impl>(simple_selector, &mut specificity);
478    }
479    specificity
480}
481
482fn compute_simple_selector_specificity<Impl: BunSelectorImpl>(
483    simple_selector: &GenericComponent<Impl>,
484    specificity: &mut Specificity,
485) {
486    use GenericComponent as C;
487    match simple_selector {
488        C::Combinator(_) => {
489            unreachable!("Found combinator in simple selectors vector?");
490        }
491        C::Part(_) | C::PseudoElement(_) | C::LocalName(_) => {
492            specificity.element_selectors += 1;
493        }
494        C::Slotted(selector) => {
495            specificity.element_selectors += 1;
496            // Note that due to the way ::slotted works we only compete with
497            // other ::slotted rules, so the above rule doesn't really
498            // matter, but we do it still for consistency with other
499            // pseudo-elements.
500            //
501            // See: https://github.com/w3c/csswg-drafts/issues/1915
502            specificity.add(Specificity::from_u32(selector.specificity()));
503        }
504        C::Host(maybe_selector) => {
505            specificity.class_like_selectors += 1;
506            if let Some(selector) = maybe_selector {
507                // See: https://github.com/w3c/csswg-drafts/issues/1915
508                specificity.add(Specificity::from_u32(selector.specificity()));
509            }
510        }
511        C::Id(_) => {
512            specificity.id_selectors += 1;
513        }
514        C::Class(_)
515        | C::AttributeInNoNamespace { .. }
516        | C::AttributeInNoNamespaceExists { .. }
517        | C::AttributeOther(_)
518        | C::Root
519        | C::Empty
520        | C::Scope
521        | C::Nth(_)
522        | C::NonTsPseudoClass(_) => {
523            specificity.class_like_selectors += 1;
524        }
525        C::NthOf(nth_of_data) => {
526            // https://drafts.csswg.org/selectors/#specificity-rules:
527            //
528            //     The specificity of the :nth-last-child() pseudo-class,
529            //     like the :nth-child() pseudo-class, combines the
530            //     specificity of a regular pseudo-class with that of its
531            //     selector argument S.
532            specificity.class_like_selectors += 1;
533            let mut max: u32 = 0;
534            for selector in nth_of_data.selectors.iter() {
535                max = selector.specificity().max(max);
536            }
537            specificity.add(Specificity::from_u32(max));
538        }
539        C::Negation(_) | C::Is(_) | C::Any { .. } => {
540            // https://drafts.csswg.org/selectors/#specificity-rules:
541            //
542            //     The specificity of an :is() pseudo-class is replaced by the
543            //     specificity of the most specific complex selector in its
544            //     selector list argument.
545            let list: &[GenericSelector<Impl>] = match simple_selector {
546                C::Negation(list) => list,
547                C::Is(list) => list,
548                C::Any { selectors, .. } => selectors,
549                _ => unreachable!(),
550            };
551            let mut max: u32 = 0;
552            for selector in list {
553                max = selector.specificity().max(max);
554            }
555            specificity.add(Specificity::from_u32(max));
556        }
557        C::Where(_)
558        | C::Has(_)
559        | C::ExplicitUniversalType
560        | C::ExplicitAnyNamespace
561        | C::ExplicitNoNamespace
562        | C::DefaultNamespace(_)
563        | C::Namespace { .. } => {
564            // Does not affect specificity
565        }
566        C::Nesting => {
567            // TODO
568        }
569    }
570}
571
572/// Build up a Selector.
573/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
574///
575/// `Err` means invalid selector.
576fn parse_selector<Impl: BunSelectorImpl>(
577    parser: &mut SelectorParser,
578    input: &mut CssParser,
579    state: &mut SelectorParsingState,
580    nesting_requirement: NestingRequirement,
581) -> CResult<GenericSelector<Impl>> {
582    if nesting_requirement == NestingRequirement::Prefixed {
583        let parser_state = input.state();
584        if !input.expect_delim(b'&').is_ok() {
585            return Err(input.new_custom_error(
586                SelectorParseErrorKind::MissingNestingPrefix.into_default_parser_error(),
587            ));
588        }
589        input.reset(&parser_state);
590    }
591
592    let mut builder = SelectorBuilder::<Impl>::init_in(ArenaPtr::new(input.arena()));
593
594    'outer_loop: loop {
595        // Parse a sequence of simple selectors.
596        let empty = parse_compound_selector::<Impl>(parser, state, input, &mut builder)?;
597        if empty {
598            let kind: SelectorParseErrorKind = if builder.has_combinators() {
599                SelectorParseErrorKind::DanglingCombinator
600            } else {
601                SelectorParseErrorKind::EmptySelector
602            };
603            return Err(input.new_custom_error(kind.into_default_parser_error()));
604        }
605
606        if state.after_any_pseudo() {
607            let source_location = input.current_source_location();
608            if let Ok(next) = input.next() {
609                return Err(source_location.new_custom_error(
610                    SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next.clone())
611                        .into_default_parser_error(),
612                ));
613            }
614            break;
615        }
616
617        // Parse a combinator
618        let combinator: Combinator;
619        let mut any_whitespace = false;
620        loop {
621            let before_this_token = input.state();
622            let tok: &Token = match input.next_including_whitespace() {
623                Ok(vv) => vv,
624                Err(_) => break 'outer_loop,
625            };
626            match tok {
627                Token::Whitespace(_) => {
628                    any_whitespace = true;
629                    continue;
630                }
631                // PORT NOTE: `Token::Delim` carries `u32` codepoint; cast to
632                // `u8` for ASCII match (all CSS combinator delims are ASCII).
633                Token::Delim(d) => match u8::try_from(*d).ok() {
634                    Some(b'>') => {
635                        if parser.deep_combinator_enabled()
636                            && input
637                                .try_parse(|i: &mut CssParser| -> CResult<()> {
638                                    i.expect_delim(b'>')?;
639                                    i.expect_delim(b'>')
640                                })
641                                .is_ok()
642                        {
643                            combinator = Combinator::DeepDescendant;
644                        } else {
645                            combinator = Combinator::Child;
646                        }
647                        break;
648                    }
649                    Some(b'+') => {
650                        combinator = Combinator::NextSibling;
651                        break;
652                    }
653                    Some(b'~') => {
654                        combinator = Combinator::LaterSibling;
655                        break;
656                    }
657                    Some(b'/') => {
658                        if parser.deep_combinator_enabled() {
659                            if input
660                                .try_parse(|i: &mut CssParser| -> CResult<()> {
661                                    i.expect_ident_matching(b"deep")?;
662                                    i.expect_delim(b'/')
663                                })
664                                .is_ok()
665                            {
666                                combinator = Combinator::Deep;
667                                break;
668                            } else {
669                                break 'outer_loop;
670                            }
671                        }
672                    }
673                    _ => {}
674                },
675                _ => {}
676            }
677
678            input.reset(&before_this_token);
679
680            if any_whitespace {
681                combinator = Combinator::Descendant;
682                break;
683            } else {
684                break 'outer_loop;
685            }
686        }
687
688        if !state.allows_combinators() {
689            return Err(input.new_custom_error(
690                SelectorParseErrorKind::InvalidState.into_default_parser_error(),
691            ));
692        }
693
694        builder.push_combinator(combinator);
695    }
696
697    if !state.contains(SelectorParsingState::AFTER_NESTING) {
698        match nesting_requirement {
699            NestingRequirement::Implicit => {
700                builder.add_nesting_prefix();
701            }
702            NestingRequirement::Contained | NestingRequirement::Prefixed => {
703                return Err(input.new_custom_error(
704                    SelectorParseErrorKind::MissingNestingSelector.into_default_parser_error(),
705                ));
706            }
707            _ => {}
708        }
709    }
710
711    let has_pseudo_element = state.contains(SelectorParsingState::AFTER_PSEUDO_ELEMENT)
712        || state.contains(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT);
713    let slotted = state.contains(SelectorParsingState::AFTER_SLOTTED);
714    let part = state.contains(SelectorParsingState::AFTER_PART);
715    let result = builder.build(has_pseudo_element, slotted, part);
716    Ok(GenericSelector {
717        specificity_and_flags: result.specificity_and_flags,
718        components: result.components,
719    })
720}
721
722/// simple_selector_sequence
723/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
724/// | [ HASH | class | attrib | pseudo | negation ]+
725///
726/// `Err(())` means invalid selector.
727/// `Ok(true)` is an empty selector
728fn parse_compound_selector<Impl: BunSelectorImpl>(
729    parser: &mut SelectorParser,
730    state: &mut SelectorParsingState,
731    input: &mut CssParser,
732    builder: &mut SelectorBuilder<Impl>,
733) -> CResult<bool> {
734    input.skip_whitespace();
735
736    let mut empty: bool = true;
737    if parser.is_nesting_allowed() && input.try_parse(|i| i.expect_delim(b'&')).is_ok() {
738        state.insert(SelectorParsingState::AFTER_NESTING);
739        builder.push_simple_selector(GenericComponent::Nesting);
740        empty = false;
741    }
742
743    if parse_type_selector::<Impl>(parser, input, *state, builder).is_ok() {
744        // Note: Zig `.asValue()` here means "if Ok"; the bool result is unused.
745        // TODO(port): the Zig only sets `empty = false` on Ok(true|false) — but
746        // `asValue()` returns Some on .result regardless of bool value, so this matches.
747        empty = false;
748    }
749
750    loop {
751        let result: SimpleSelectorParseResult<Impl> = {
752            let ret = parse_one_simple_selector::<Impl>(parser, input, state)?;
753            match ret {
754                Some(result) => result,
755                None => break,
756            }
757        };
758
759        if empty {
760            if let Some(url) = parser.default_namespace() {
761                // If there was no explicit type selector, but there is a
762                // default namespace, there is an implicit "<defaultns>|*" type
763                // selector. Except for :host() or :not() / :is() / :where(),
764                // where we ignore it.
765                //
766                // https://drafts.csswg.org/css-scoping/#host-element-in-tree:
767                //
768                //     When considered within its own shadow trees, the shadow
769                //     host is featureless. Only the :host, :host(), and
770                //     :host-context() pseudo-classes are allowed to match it.
771                //
772                // https://drafts.csswg.org/selectors-4/#featureless:
773                //
774                //     A featureless element does not match any selector at all,
775                //     except those it is explicitly defined to match. If a
776                //     given selector is allowed to match a featureless element,
777                //     it must do so while ignoring the default namespace.
778                //
779                // https://drafts.csswg.org/selectors-4/#matches
780                //
781                //     Default namespace declarations do not affect the compound
782                //     selector representing the subject of any selector within
783                //     a :is() pseudo-class, unless that compound selector
784                //     contains an explicit universal selector or type selector.
785                //
786                //     (Similar quotes for :where() / :not())
787                //
788                let ignore_default_ns = state
789                    .contains(SelectorParsingState::SKIP_DEFAULT_NAMESPACE)
790                    || matches!(
791                        result,
792                        SimpleSelectorParseResult::SimpleSelector(GenericComponent::Host(_))
793                    );
794                if !ignore_default_ns {
795                    builder.push_simple_selector(GenericComponent::DefaultNamespace(url));
796                }
797            }
798        }
799
800        empty = false;
801
802        match result {
803            SimpleSelectorParseResult::SimpleSelector(s) => {
804                builder.push_simple_selector(s);
805            }
806            SimpleSelectorParseResult::PartPseudo(selector) => {
807                state.insert(SelectorParsingState::AFTER_PART);
808                builder.push_combinator(Combinator::Part);
809                builder.push_simple_selector(GenericComponent::Part(selector));
810            }
811            SimpleSelectorParseResult::SlottedPseudo(selector) => {
812                state.insert(SelectorParsingState::AFTER_SLOTTED);
813                builder.push_combinator(Combinator::SlotAssignment);
814                builder.push_simple_selector(GenericComponent::Slotted(selector));
815            }
816            SimpleSelectorParseResult::PseudoElement(p) => {
817                if !p.is_unknown() {
818                    state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
819                    builder.push_combinator(Combinator::PseudoElement);
820                } else {
821                    state.insert(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT);
822                }
823
824                if !p.accepts_state_pseudo_classes() {
825                    state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
826                }
827
828                if p.is_webkit_scrollbar() {
829                    state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR);
830                }
831
832                if p.is_view_transition() {
833                    state.insert(SelectorParsingState::AFTER_VIEW_TRANSITION);
834                }
835
836                builder.push_simple_selector(GenericComponent::PseudoElement(p));
837            }
838        }
839    }
840
841    Ok(empty)
842}
843
844fn parse_relative_selector<Impl: BunSelectorImpl>(
845    parser: &mut SelectorParser,
846    input: &mut CssParser,
847    state: &mut SelectorParsingState,
848    nesting_requirement_: NestingRequirement,
849) -> CResult<GenericSelector<Impl>> {
850    // https://www.w3.org/TR/selectors-4/#parse-relative-selector
851    let mut nesting_requirement = nesting_requirement_;
852    let s = input.state();
853
854    let combinator: Option<Combinator> = 'combinator: {
855        let tok = input.next()?;
856        if let Token::Delim(c) = tok {
857            match u8::try_from(*c).ok() {
858                Some(b'>') => break 'combinator Some(Combinator::Child),
859                Some(b'+') => break 'combinator Some(Combinator::NextSibling),
860                Some(b'~') => break 'combinator Some(Combinator::LaterSibling),
861                _ => {}
862            }
863        }
864        input.reset(&s);
865        None
866    };
867
868    let scope: GenericComponent<Impl> = if nesting_requirement == NestingRequirement::Implicit {
869        GenericComponent::Nesting
870    } else {
871        GenericComponent::Scope
872    };
873
874    if combinator.is_some() {
875        nesting_requirement = NestingRequirement::None;
876    }
877
878    let mut selector = parse_selector::<Impl>(parser, input, state, nesting_requirement)?;
879    if let Some(wombo_combo) = combinator {
880        // https://www.w3.org/TR/selectors/#absolutizing
881        selector
882            .components
883            .push(GenericComponent::Combinator(wombo_combo));
884        // PERF(port): was assume_capacity (catch unreachable on arena)
885        selector.components.push(scope);
886    }
887
888    Ok(selector)
889}
890
891/// Compile-time validation of the `SelectorParser` shape. In Rust the methods are
892/// inherent on `SelectorParser`; this is a no-op kept for diff parity.
893pub fn valid_selector_parser<T>() {
894    // Zig: `_ = T.SelectorParser.parseSlotted;` etc. — structural duck-typing check.
895    // In Rust these are inherent methods on `SelectorParser`; nothing to validate at runtime.
896}
897
898/// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.
899// Re-export of the canonical `{ltr, rtl}` enum from `properties::text` — both Zig
900// specs (selectors/parser.zig:700, properties/text.zig:251) define the same
901// `DefineEnumProperty` shape, so the Rust port shares one definition. The
902// `#[derive(DefineEnumProperty)]` on the canonical provides `parse`/`to_css`/
903// `as_str`; `CssEql`/`CssHash`/`DeepClone` come from `generics::inherent_bridge`.
904pub use css::css_properties::text::Direction;
905
906/// A pseudo class.
907// PORT NOTE: `PartialEq` derive dropped — `Local`/`Global` carry
908// `Box<Selector>` and `CustomFunction` carries `TokenList`, neither of which
909// implements `PartialEq`. Equality goes through `eql()` (CssEql protocol).
910#[derive(Clone, CssEql, CssHash)]
911pub enum PseudoClass {
912    /// https://drafts.csswg.org/selectors-4/#linguistic-pseudos
913    /// The [:lang()](https://drafts.csswg.org/selectors-4/#the-lang-pseudo) pseudo class.
914    Lang {
915        /// A list of language codes.
916        languages: PlainVec2<Str>,
917        // PERF(port): was arena ArrayList — profile if it shows up on a hot path.
918    },
919    /// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class.
920    Dir {
921        /// A direction.
922        direction: Direction,
923    },
924
925    // https://drafts.csswg.org/selectors-4/#useraction-pseudos
926    /// The [:hover](https://drafts.csswg.org/selectors-4/#the-hover-pseudo) pseudo class.
927    Hover,
928    /// The [:active](https://drafts.csswg.org/selectors-4/#the-active-pseudo) pseudo class.
929    Active,
930    /// The [:focus](https://drafts.csswg.org/selectors-4/#the-focus-pseudo) pseudo class.
931    Focus,
932    /// The [:focus-visible](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo) pseudo class.
933    FocusVisible,
934    /// The [:focus-within](https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo) pseudo class.
935    FocusWithin,
936
937    /// https://drafts.csswg.org/selectors-4/#time-pseudos
938    /// The [:current](https://drafts.csswg.org/selectors-4/#the-current-pseudo) pseudo class.
939    Current,
940    /// The [:past](https://drafts.csswg.org/selectors-4/#the-past-pseudo) pseudo class.
941    Past,
942    /// The [:future](https://drafts.csswg.org/selectors-4/#the-future-pseudo) pseudo class.
943    Future,
944
945    /// https://drafts.csswg.org/selectors-4/#resource-pseudos
946    /// The [:playing](https://drafts.csswg.org/selectors-4/#selectordef-playing) pseudo class.
947    Playing,
948    /// The [:paused](https://drafts.csswg.org/selectors-4/#selectordef-paused) pseudo class.
949    Paused,
950    /// The [:seeking](https://drafts.csswg.org/selectors-4/#selectordef-seeking) pseudo class.
951    Seeking,
952    /// The [:buffering](https://drafts.csswg.org/selectors-4/#selectordef-buffering) pseudo class.
953    Buffering,
954    /// The [:stalled](https://drafts.csswg.org/selectors-4/#selectordef-stalled) pseudo class.
955    Stalled,
956    /// The [:muted](https://drafts.csswg.org/selectors-4/#selectordef-muted) pseudo class.
957    Muted,
958    /// The [:volume-locked](https://drafts.csswg.org/selectors-4/#selectordef-volume-locked) pseudo class.
959    VolumeLocked,
960
961    /// The [:fullscreen](https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class) pseudo class.
962    Fullscreen(css::VendorPrefix),
963
964    /// https://drafts.csswg.org/selectors/#display-state-pseudos
965    /// The [:open](https://drafts.csswg.org/selectors/#selectordef-open) pseudo class.
966    Open,
967    /// The [:closed](https://drafts.csswg.org/selectors/#selectordef-closed) pseudo class.
968    Closed,
969    /// The [:modal](https://drafts.csswg.org/selectors/#modal-state) pseudo class.
970    Modal,
971    /// The [:picture-in-picture](https://drafts.csswg.org/selectors/#pip-state) pseudo class.
972    PictureInPicture,
973
974    /// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open
975    /// The [:popover-open](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open) pseudo class.
976    PopoverOpen,
977
978    /// The [:defined](https://drafts.csswg.org/selectors-4/#the-defined-pseudo) pseudo class.
979    Defined,
980
981    /// https://drafts.csswg.org/selectors-4/#location
982    /// The [:any-link](https://drafts.csswg.org/selectors-4/#the-any-link-pseudo) pseudo class.
983    AnyLink(css::VendorPrefix),
984    /// The [:link](https://drafts.csswg.org/selectors-4/#link-pseudo) pseudo class.
985    Link,
986    /// The [:local-link](https://drafts.csswg.org/selectors-4/#the-local-link-pseudo) pseudo class.
987    LocalLink,
988    /// The [:target](https://drafts.csswg.org/selectors-4/#the-target-pseudo) pseudo class.
989    Target,
990    /// The [:target-within](https://drafts.csswg.org/selectors-4/#the-target-within-pseudo) pseudo class.
991    TargetWithin,
992    /// The [:visited](https://drafts.csswg.org/selectors-4/#visited-pseudo) pseudo class.
993    Visited,
994
995    /// https://drafts.csswg.org/selectors-4/#input-pseudos
996    /// The [:enabled](https://drafts.csswg.org/selectors-4/#enabled-pseudo) pseudo class.
997    Enabled,
998    /// The [:disabled](https://drafts.csswg.org/selectors-4/#disabled-pseudo) pseudo class.
999    Disabled,
1000    /// The [:read-only](https://drafts.csswg.org/selectors-4/#read-only-pseudo) pseudo class.
1001    ReadOnly(css::VendorPrefix),
1002    /// The [:read-write](https://drafts.csswg.org/selectors-4/#read-write-pseudo) pseudo class.
1003    ReadWrite(css::VendorPrefix),
1004    /// The [:placeholder-shown](https://drafts.csswg.org/selectors-4/#placeholder) pseudo class.
1005    PlaceholderShown(css::VendorPrefix),
1006    /// The [:default](https://drafts.csswg.org/selectors-4/#the-default-pseudo) pseudo class.
1007    Default,
1008    /// The [:checked](https://drafts.csswg.org/selectors-4/#checked) pseudo class.
1009    Checked,
1010    /// The [:indeterminate](https://drafts.csswg.org/selectors-4/#indeterminate) pseudo class.
1011    Indeterminate,
1012    /// The [:blank](https://drafts.csswg.org/selectors-4/#blank) pseudo class.
1013    Blank,
1014    /// The [:valid](https://drafts.csswg.org/selectors-4/#valid-pseudo) pseudo class.
1015    Valid,
1016    /// The [:invalid](https://drafts.csswg.org/selectors-4/#invalid-pseudo) pseudo class.
1017    Invalid,
1018    /// The [:in-range](https://drafts.csswg.org/selectors-4/#in-range-pseudo) pseudo class.
1019    InRange,
1020    /// The [:out-of-range](https://drafts.csswg.org/selectors-4/#out-of-range-pseudo) pseudo class.
1021    OutOfRange,
1022    /// The [:required](https://drafts.csswg.org/selectors-4/#required-pseudo) pseudo class.
1023    Required,
1024    /// The [:optional](https://drafts.csswg.org/selectors-4/#optional-pseudo) pseudo class.
1025    Optional,
1026    /// The [:user-valid](https://drafts.csswg.org/selectors-4/#user-valid-pseudo) pseudo class.
1027    UserValid,
1028    /// The [:used-invalid](https://drafts.csswg.org/selectors-4/#user-invalid-pseudo) pseudo class.
1029    UserInvalid,
1030
1031    /// The [:autofill](https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill) pseudo class.
1032    Autofill(css::VendorPrefix),
1033
1034    // CSS modules
1035    /// The CSS modules :local() pseudo class.
1036    Local {
1037        /// A local selector.
1038        selector: Box<Selector>,
1039    },
1040    /// The CSS modules :global() pseudo class.
1041    Global {
1042        /// A global selector.
1043        selector: Box<Selector>,
1044    },
1045
1046    /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.
1047    // https://webkit.org/blog/363/styling-scrollbars/
1048    WebkitScrollbar(WebKitScrollbarPseudoClass),
1049    /// An unknown pseudo class.
1050    Custom {
1051        /// The pseudo class name.
1052        name: Str,
1053    },
1054    /// An unknown functional pseudo class.
1055    CustomFunction {
1056        /// The pseudo class name.
1057        name: Str,
1058        /// The arguments of the pseudo class function.
1059        arguments: TokenList,
1060    },
1061}
1062
1063impl PseudoClass {
1064    pub fn is_equivalent(&self, other: &PseudoClass) -> bool {
1065        use PseudoClass as P;
1066        if matches!(self, P::Fullscreen(_)) && matches!(other, P::Fullscreen(_)) {
1067            return true;
1068        }
1069        if matches!(self, P::AnyLink(_)) && matches!(other, P::AnyLink(_)) {
1070            return true;
1071        }
1072        if matches!(self, P::ReadOnly(_)) && matches!(other, P::ReadOnly(_)) {
1073            return true;
1074        }
1075        if matches!(self, P::ReadWrite(_)) && matches!(other, P::ReadWrite(_)) {
1076            return true;
1077        }
1078        if matches!(self, P::PlaceholderShown(_)) && matches!(other, P::PlaceholderShown(_)) {
1079            return true;
1080        }
1081        if matches!(self, P::Autofill(_)) && matches!(other, P::Autofill(_)) {
1082            return true;
1083        }
1084        self.eql(other)
1085    }
1086
1087    pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
1088        // PERF(alloc): I don't like making these little allocations
1089        // PORT NOTE: Zig builds a fresh `Printer` over an allocating writer,
1090        // calls `serialize::serializePseudoClass`, then writes the buffer to
1091        // `dest`. The buffered indirection only matters for length-dependent
1092        // minification decisions made by callers (none here), so write
1093        // directly to `dest` until `Printer::new_buffered` lands.
1094        serialize::serialize_pseudo_class(self, dest, None)
1095    }
1096
1097    // eql / hash — provided by `#[derive(CssEql, CssHash)]` (variant-wise; the
1098    // `Box<Selector>` arms recurse via the `CssEql for GenericSelector` impl
1099    // below). `deep_clone` is `Clone` — the selector AST is global-alloc and
1100    // every borrowed payload (`Str`, `Ident.v`) is an arena-static identity copy.
1101    pub fn deep_clone(&self) -> Self {
1102        self.clone()
1103    }
1104
1105    pub fn get_prefix(&self) -> css::VendorPrefix {
1106        use PseudoClass as P;
1107        match self {
1108            P::Fullscreen(p)
1109            | P::AnyLink(p)
1110            | P::ReadOnly(p)
1111            | P::ReadWrite(p)
1112            | P::PlaceholderShown(p)
1113            | P::Autofill(p) => *p,
1114            _ => css::VendorPrefix::empty(),
1115        }
1116    }
1117
1118    pub fn get_necessary_prefixes(&mut self, targets: &css::targets::Targets) -> css::VendorPrefix {
1119        use PseudoClass as P;
1120        use css::prefixes::Feature as F;
1121        let (p, feature): (&mut css::VendorPrefix, F) = match self {
1122            P::Fullscreen(p) => (p, F::PseudoClassFullscreen),
1123            P::AnyLink(p) => (p, F::PseudoClassAnyLink),
1124            P::ReadOnly(p) => (p, F::PseudoClassReadOnly),
1125            P::ReadWrite(p) => (p, F::PseudoClassReadWrite),
1126            P::PlaceholderShown(p) => (p, F::PseudoClassPlaceholderShown),
1127            P::Autofill(p) => (p, F::PseudoClassAutofill),
1128            _ => return css::VendorPrefix::empty(),
1129        };
1130        *p = targets.prefixes(*p, feature);
1131        *p
1132    }
1133
1134    pub fn is_user_action_state(&self) -> bool {
1135        use PseudoClass as P;
1136        matches!(
1137            self,
1138            P::Active | P::Hover | P::Focus | P::FocusWithin | P::FocusVisible
1139        )
1140    }
1141
1142    pub fn is_valid_before_webkit_scrollbar(&self) -> bool {
1143        !matches!(self, PseudoClass::WebkitScrollbar(_))
1144    }
1145
1146    pub fn is_valid_after_webkit_scrollbar(&self) -> bool {
1147        use PseudoClass as P;
1148        matches!(
1149            self,
1150            P::WebkitScrollbar(_) | P::Enabled | P::Disabled | P::Hover | P::Active
1151        )
1152    }
1153}
1154
1155/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo class.
1156#[derive(Clone, Copy, PartialEq, Eq, Hash, CssEql, CssHash, css::generics::DeepClone)]
1157pub enum WebKitScrollbarPseudoClass {
1158    /// :horizontal
1159    Horizontal,
1160    /// :vertical
1161    Vertical,
1162    /// :decrement
1163    Decrement,
1164    /// :increment
1165    Increment,
1166    /// :start
1167    Start,
1168    /// :end
1169    End,
1170    /// :double-button
1171    DoubleButton,
1172    /// :single-button
1173    SingleButton,
1174    /// :no-button
1175    NoButton,
1176    /// :corner-present
1177    CornerPresent,
1178    /// :window-inactive
1179    WindowInactive,
1180}
1181
1182/// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.
1183#[derive(Clone, Copy, PartialEq, Eq, Hash, CssHash)]
1184pub enum WebKitScrollbarPseudoElement {
1185    /// ::-webkit-scrollbar
1186    Scrollbar,
1187    /// ::-webkit-scrollbar-button
1188    Button,
1189    /// ::-webkit-scrollbar-track
1190    Track,
1191    /// ::-webkit-scrollbar-track-piece
1192    TrackPiece,
1193    /// ::-webkit-scrollbar-thumb
1194    Thumb,
1195    /// ::-webkit-scrollbar-corner
1196    Corner,
1197    /// ::-webkit-resizer
1198    Resizer,
1199}
1200
1201impl WebKitScrollbarPseudoElement {
1202    #[inline]
1203    pub fn eql(self, rhs: Self) -> bool {
1204        self == rhs
1205    }
1206    // hash — via `#[derive(CssHash)]`.
1207}
1208
1209// ─────────────────────────────────────────────────────────────────────────────
1210// SelectorParser
1211// ─────────────────────────────────────────────────────────────────────────────
1212
1213pub struct SelectorParser<'a> {
1214    pub is_nesting_allowed: bool,
1215    pub options: &'a ParserOptions<'a>,
1216    // `arena: Allocator` dropped — arena threaded via `input.arena()` in Zig.
1217    // PERF(port): was arena bulk-free — re-thread `&'bump Bump` to restore.
1218}
1219
1220// Zig: `pub const Impl = impl_.Selectors;` lived inside the struct for
1221// `ValidSelectorParser`'s comptime decl-probe. Rust inherent associated types
1222// are unstable (rust#8995); the equivalent contract is the `BunSelectorImpl`
1223// blanket impl above, so expose the alias at module scope instead.
1224pub type SelectorParserImpl = impl_::Selectors;
1225
1226impl<'a> SelectorParser<'a> {
1227    pub fn new_local_identifier(
1228        &mut self,
1229        input: &mut CssParser,
1230        tag: css::CssRefTag,
1231        raw: Str,
1232        loc: usize,
1233    ) -> <impl_::Selectors as SelectorImpl>::LocalIdentifier {
1234        // blocked_on: `Parser::add_symbol_for_name` (gated in css_parser.rs on
1235        // ArrayHashMap::entry + SymbolList::push). The CSS-modules branch
1236        // returns the symbol-table ref; until that un-gates, fall through to
1237        // the ident arm so non-modules parsing is correct.
1238
1239        if input.flags.css_modules() {
1240            return <impl_::Selectors as SelectorImpl>::LocalIdentifier::from_ref(
1241                input.add_symbol_for_name(
1242                    raw,
1243                    tag,
1244                    bun_ast::Loc {
1245                        start: i32::try_from(loc).expect("int cast"),
1246                    },
1247                ),
1248                crate::values::ident::debug_ident(raw, input.arena()),
1249            );
1250        }
1251        let _ = (input, tag, loc);
1252        <impl_::Selectors as SelectorImpl>::LocalIdentifier::from_ident(Ident {
1253            v: std::ptr::from_ref::<[u8]>(raw),
1254        })
1255    }
1256
1257    pub fn namespace_for_prefix(&mut self, prefix: Ident) -> Option<Str> {
1258        let _ = self;
1259        // SAFETY: `Ident.v` borrows the parser arena which outlives the parse
1260        // session (`'static` is a placeholder for the arena lifetime).
1261        Some(unsafe { crate::arena_str(prefix.v) })
1262    }
1263
1264    pub fn parse_functional_pseudo_element(
1265        &mut self,
1266        name: Str,
1267        input: &mut CssParser,
1268    ) -> CResult<PseudoElement> {
1269        // Spec parity: parser.zig:1054 uses `ComptimeEnumMap.get(name)` which is
1270        // CASE-SENSITIVE (`ComptimeStringMap.get`, not `getAnyCase`/`getASCIIICaseInsensitive`).
1271        // `::CUE(..)` / `::View-Transition-Group(..)` therefore fall through to
1272        // `CustomFunction` in the spec — match that here by looking up `name`
1273        // verbatim with no case folding.
1274        //
1275        // PERF(port): 6 entries with near-unique lengths (3/10/19/19/21/26) —
1276        // a length-gated `match` rejects the overwhelmingly-common miss path
1277        // (unknown `::-webkit-foo(...)` etc.) on a single `usize` compare,
1278        // versus phf's hash + 2 table loads + slice compare. Only len==19 has
1279        // two candidates, disambiguated by one full slice compare each.
1280        match name.len() {
1281            3 if name == b"cue" => {
1282                return Ok(PseudoElement::CueFunction {
1283                    selector: Box::new(Selector::parse(self, input)?),
1284                });
1285            }
1286            10 if name == b"cue-region" => {
1287                return Ok(PseudoElement::CueRegionFunction {
1288                    selector: Box::new(Selector::parse(self, input)?),
1289                });
1290            }
1291            19 => match name {
1292                b"view-transition-old" => {
1293                    return Ok(PseudoElement::ViewTransitionOld {
1294                        part_name: ViewTransitionPartName::parse(input)?,
1295                    });
1296                }
1297                b"view-transition-new" => {
1298                    return Ok(PseudoElement::ViewTransitionNew {
1299                        part_name: ViewTransitionPartName::parse(input)?,
1300                    });
1301                }
1302                _ => {}
1303            },
1304            21 if name == b"view-transition-group" => {
1305                return Ok(PseudoElement::ViewTransitionGroup {
1306                    part_name: ViewTransitionPartName::parse(input)?,
1307                });
1308            }
1309            26 if name == b"view-transition-image-pair" => {
1310                return Ok(PseudoElement::ViewTransitionImagePair {
1311                    part_name: ViewTransitionPartName::parse(input)?,
1312                });
1313            }
1314            _ => {}
1315        }
1316        if !strings::starts_with(name, b"-") {
1317            self.options.warn(
1318                &input.new_custom_error(
1319                    SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)
1320                        .into_default_parser_error(),
1321                ),
1322            );
1323        }
1324
1325        // blocked_on: properties::custom (TokenList::parse_raw / TokenOrValue) un-gate.
1326        // The stub `properties::custom::TokenList` is a unit struct with no `.v`
1327        // field and no `parse_raw`; consume the function args as opaque tokens
1328        // until the real `custom.rs` un-gates.
1329
1330        {
1331            let mut args: PlainVec2<css::css_properties::custom::TokenOrValue> = PlainVec2::new();
1332            TokenList::parse_raw(input, &mut args, self.options, 0)?;
1333            return Ok(PseudoElement::CustomFunction {
1334                name,
1335                arguments: TokenList { v: args },
1336            });
1337        }
1338    }
1339
1340    fn parse_is_and_where(&self) -> bool {
1341        let _ = self;
1342        true
1343    }
1344
1345    /// Whether the given function name is an alias for the `:is()` function.
1346    fn parse_any_prefix(&self, name: &[u8]) -> Option<css::VendorPrefix> {
1347        crate::match_ignore_ascii_case! { name, {
1348            b"-webkit-any" => Some(css::VendorPrefix::WEBKIT),
1349            b"-moz-any" => Some(css::VendorPrefix::MOZ),
1350            _ => None,
1351        }}
1352    }
1353
1354    pub fn parse_non_ts_pseudo_class(
1355        &mut self,
1356        loc: css::SourceLocation,
1357        name: Str,
1358    ) -> CResult<PseudoClass> {
1359        // @compileError(css.todo_stuff.match_ignore_ascii_case);
1360        let pseudo_class: PseudoClass = 'pseudo_class: {
1361            // TODO(port): phf custom hasher — Zig used `ComptimeStringMap.getAnyCase`
1362            // (ASCII case-insensitive). Generate a case-folded phf or use a
1363            // `match` over the lowercased name.
1364            if let Some(pseudo) = lookup_non_ts_pseudo_class(name) {
1365                break 'pseudo_class pseudo;
1366            }
1367            if strings::starts_with_char(name, b'_') {
1368                self.options.warn(&loc.new_custom_error(
1369                    SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
1370                ));
1371            } else if (self.options.css_modules.is_some()
1372                && strings::eql_case_insensitive_ascii_check_length(name, b"local"))
1373                || strings::eql_case_insensitive_ascii_check_length(name, b"global")
1374            {
1375                return Err(
1376                    loc.new_custom_error(SelectorParseErrorKind::AmbiguousCssModuleClass(name))
1377                );
1378            }
1379            return Ok(PseudoClass::Custom { name });
1380        };
1381
1382        Ok(pseudo_class)
1383    }
1384
1385    pub fn parse_host(&mut self) -> bool {
1386        true
1387    }
1388
1389    pub fn parse_non_ts_functional_pseudo_class(
1390        &mut self,
1391        name: Str,
1392        parser: &mut CssParser,
1393    ) -> CResult<PseudoClass> {
1394        let pseudo_class = crate::match_ignore_ascii_case! { name, {
1395            b"lang" => {
1396                // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]`
1397                // (lifetime-tied to `&mut self`), which can't satisfy
1398                // `parse_comma_separated`'s HRTB. Clone the token to extract
1399                // the underlying `&'static [u8]` payload directly.
1400                let languages = parser.parse_comma_separated(|p| -> CResult<Str> {
1401                    let loc = p.current_source_location();
1402                    let tok = p.next()?.clone();
1403                    match tok {
1404                        Token::Ident(i) | Token::QuotedString(i) => Ok(i),
1405                        t => Err(loc.new_unexpected_token_error(t)),
1406                    }
1407                })?;
1408                return Ok(PseudoClass::Lang { languages });
1409            },
1410            b"dir" => PseudoClass::Dir {
1411                direction: Direction::parse(parser)?,
1412            },
1413            b"local" if self.options.css_modules.is_some() => PseudoClass::Local {
1414                selector: Box::new(Selector::parse(self, parser)?),
1415            },
1416            b"global" if self.options.css_modules.is_some() => PseudoClass::Global {
1417                selector: Box::new(Selector::parse(self, parser)?),
1418            },
1419            _ => {
1420                if !strings::starts_with_char(name, b'-') {
1421                    self.options.warn(
1422                        &parser.new_custom_error(
1423                            SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)
1424                                .into_default_parser_error(),
1425                        ),
1426                    );
1427                }
1428                let mut args: PlainVec2<css::css_properties::custom::TokenOrValue> = PlainVec2::new();
1429                css::TokenListFns::parse_raw(parser, &mut args, self.options, 0)?;
1430                PseudoClass::CustomFunction {
1431                    name,
1432                    arguments: TokenList { v: args },
1433                }
1434            },
1435        }};
1436
1437        Ok(pseudo_class)
1438    }
1439
1440    pub fn is_nesting_allowed(&self) -> bool {
1441        self.is_nesting_allowed
1442    }
1443
1444    pub fn deep_combinator_enabled(&self) -> bool {
1445        self.options
1446            .flags
1447            .contains(css::ParserFlags::DEEP_SELECTOR_COMBINATOR)
1448    }
1449
1450    pub fn default_namespace(&self) -> Option<<impl_::Selectors as SelectorImpl>::NamespaceUrl> {
1451        let _ = self;
1452        None
1453    }
1454
1455    pub fn parse_part(&self) -> bool {
1456        let _ = self;
1457        true
1458    }
1459
1460    pub fn parse_slotted(&self) -> bool {
1461        let _ = self;
1462        true
1463    }
1464
1465    /// The error recovery that selector lists inside :is() and :where() have.
1466    fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {
1467        let _ = self;
1468        ParseErrorRecovery::IgnoreInvalidSelector
1469    }
1470
1471    pub fn parse_pseudo_element(
1472        &mut self,
1473        loc: css::SourceLocation,
1474        name: Str,
1475    ) -> CResult<PseudoElement> {
1476        // TODO(port): phf custom hasher — Zig used `ComptimeStringMap.getCaseInsensitiveWithEql`.
1477        let pseudo_element = lookup_pseudo_element(name).unwrap_or_else(|| {
1478            if !strings::starts_with_char(name, b'-') {
1479                self.options.warn(&loc.new_custom_error(
1480                    SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
1481                ));
1482            }
1483            PseudoElement::Custom { name }
1484        });
1485
1486        Ok(pseudo_element)
1487    }
1488}
1489
1490/// Case-insensitive lookup table for `parse_non_ts_pseudo_class`.
1491/// Mirrors the `ComptimeStringMap` at parser.zig:1120.
1492fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option<PseudoClass> {
1493    use PseudoClass as P;
1494    use WebKitScrollbarPseudoClass as WS;
1495    use css::VendorPrefix as VP;
1496    Some(crate::match_ignore_ascii_case! { name, {
1497        // https://drafts.csswg.org/selectors-4/#useraction-pseudos
1498        b"hover" => P::Hover,
1499        b"active" => P::Active,
1500        b"focus" => P::Focus,
1501        b"focus-visible" => P::FocusVisible,
1502        b"focus-within" => P::FocusWithin,
1503        // https://drafts.csswg.org/selectors-4/#time-pseudos
1504        b"current" => P::Current,
1505        b"past" => P::Past,
1506        b"future" => P::Future,
1507        // https://drafts.csswg.org/selectors-4/#resource-pseudos
1508        b"playing" => P::Playing,
1509        b"paused" => P::Paused,
1510        b"seeking" => P::Seeking,
1511        b"buffering" => P::Buffering,
1512        b"stalled" => P::Stalled,
1513        b"muted" => P::Muted,
1514        b"volume-locked" => P::VolumeLocked,
1515        // https://fullscreen.spec.whatwg.org/#:fullscreen-pseudo-class
1516        b"fullscreen" => P::Fullscreen(VP::NONE),
1517        b"-webkit-full-screen" => P::Fullscreen(VP::WEBKIT),
1518        b"-moz-full-screen" => P::Fullscreen(VP::MOZ),
1519        b"-ms-fullscreen" => P::Fullscreen(VP::MS),
1520        // https://drafts.csswg.org/selectors/#display-state-pseudos
1521        b"open" => P::Open,
1522        b"closed" => P::Closed,
1523        b"modal" => P::Modal,
1524        b"picture-in-picture" => P::PictureInPicture,
1525        // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-popover-open
1526        b"popover-open" => P::PopoverOpen,
1527        // https://drafts.csswg.org/selectors-4/#the-defined-pseudo
1528        b"defined" => P::Defined,
1529        // https://drafts.csswg.org/selectors-4/#location
1530        b"any-link" => P::AnyLink(VP::NONE),
1531        b"-webkit-any-link" => P::AnyLink(VP::WEBKIT),
1532        b"-moz-any-link" => P::AnyLink(VP::MOZ),
1533        b"link" => P::Link,
1534        b"local-link" => P::LocalLink,
1535        b"target" => P::Target,
1536        b"target-within" => P::TargetWithin,
1537        b"visited" => P::Visited,
1538        // https://drafts.csswg.org/selectors-4/#input-pseudos
1539        b"enabled" => P::Enabled,
1540        b"disabled" => P::Disabled,
1541        b"read-only" => P::ReadOnly(VP::NONE),
1542        b"-moz-read-only" => P::ReadOnly(VP::MOZ),
1543        b"read-write" => P::ReadWrite(VP::NONE),
1544        b"-moz-read-write" => P::ReadWrite(VP::MOZ),
1545        b"placeholder-shown" => P::PlaceholderShown(VP::NONE),
1546        b"-moz-placeholder-shown" => P::PlaceholderShown(VP::MOZ),
1547        b"-ms-placeholder-shown" => P::PlaceholderShown(VP::MS),
1548        b"default" => P::Default,
1549        b"checked" => P::Checked,
1550        b"indeterminate" => P::Indeterminate,
1551        b"blank" => P::Blank,
1552        b"valid" => P::Valid,
1553        b"invalid" => P::Invalid,
1554        b"in-range" => P::InRange,
1555        b"out-of-range" => P::OutOfRange,
1556        b"required" => P::Required,
1557        b"optional" => P::Optional,
1558        b"user-valid" => P::UserValid,
1559        b"user-invalid" => P::UserInvalid,
1560        // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-autofill
1561        b"autofill" => P::Autofill(VP::NONE),
1562        b"-webkit-autofill" => P::Autofill(VP::WEBKIT),
1563        b"-o-autofill" => P::Autofill(VP::O),
1564        // https://webkit.org/blog/363/styling-scrollbars/
1565        b"horizontal" => P::WebkitScrollbar(WS::Horizontal),
1566        b"vertical" => P::WebkitScrollbar(WS::Vertical),
1567        b"decrement" => P::WebkitScrollbar(WS::Decrement),
1568        b"increment" => P::WebkitScrollbar(WS::Increment),
1569        b"start" => P::WebkitScrollbar(WS::Start),
1570        b"end" => P::WebkitScrollbar(WS::End),
1571        b"double-button" => P::WebkitScrollbar(WS::DoubleButton),
1572        b"single-button" => P::WebkitScrollbar(WS::SingleButton),
1573        b"no-button" => P::WebkitScrollbar(WS::NoButton),
1574        b"corner-present" => P::WebkitScrollbar(WS::CornerPresent),
1575        b"window-inactive" => P::WebkitScrollbar(WS::WindowInactive),
1576        _ => return None,
1577    } })
1578}
1579
1580/// Case-insensitive lookup table for `parse_pseudo_element`.
1581/// Mirrors the `ComptimeStringMap` at parser.zig:1333.
1582fn lookup_pseudo_element(name: &[u8]) -> Option<PseudoElement> {
1583    use PseudoElement as PE;
1584    use WebKitScrollbarPseudoElement as WS;
1585    use css::VendorPrefix as VP;
1586    Some(crate::match_ignore_ascii_case! { name, {
1587        b"before" => PE::Before,
1588        b"after" => PE::After,
1589        b"first-line" => PE::FirstLine,
1590        b"first-letter" => PE::FirstLetter,
1591        b"cue" => PE::Cue,
1592        b"cue-region" => PE::CueRegion,
1593        b"selection" => PE::Selection(VP::NONE),
1594        b"-moz-selection" => PE::Selection(VP::MOZ),
1595        b"placeholder" => PE::Placeholder(VP::NONE),
1596        b"-webkit-input-placeholder" => PE::Placeholder(VP::WEBKIT),
1597        b"-moz-placeholder" => PE::Placeholder(VP::MOZ),
1598        b"-ms-input-placeholder" => PE::Placeholder(VP::MS),
1599        b"marker" => PE::Marker,
1600        b"backdrop" => PE::Backdrop(VP::NONE),
1601        b"-webkit-backdrop" => PE::Backdrop(VP::WEBKIT),
1602        b"file-selector-button" => PE::FileSelectorButton(VP::NONE),
1603        b"-webkit-file-upload-button" => PE::FileSelectorButton(VP::WEBKIT),
1604        b"-ms-browse" => PE::FileSelectorButton(VP::MS),
1605        b"-webkit-scrollbar" => PE::WebkitScrollbar(WS::Scrollbar),
1606        b"-webkit-scrollbar-button" => PE::WebkitScrollbar(WS::Button),
1607        b"-webkit-scrollbar-track" => PE::WebkitScrollbar(WS::Track),
1608        b"-webkit-scrollbar-track-piece" => PE::WebkitScrollbar(WS::TrackPiece),
1609        b"-webkit-scrollbar-thumb" => PE::WebkitScrollbar(WS::Thumb),
1610        b"-webkit-scrollbar-corner" => PE::WebkitScrollbar(WS::Corner),
1611        b"-webkit-resizer" => PE::WebkitScrollbar(WS::Resizer),
1612        b"view-transition" => PE::ViewTransition,
1613        _ => return None,
1614    } })
1615}
1616
1617// ─────────────────────────────────────────────────────────────────────────────
1618// GenericSelectorList
1619// ─────────────────────────────────────────────────────────────────────────────
1620
1621pub struct GenericSelectorList<Impl: SelectorImpl> {
1622    // PERF: make this equivalent to SmallVec<[Selector; 1]>
1623    pub v: SmallList<GenericSelector<Impl>, 1>,
1624}
1625
1626impl<Impl: SelectorImpl> Default for GenericSelectorList<Impl> {
1627    fn default() -> Self {
1628        Self {
1629            v: SmallList::default(),
1630        }
1631    }
1632}
1633
1634impl<Impl: SelectorImpl> Default for GenericSelector<Impl> {
1635    fn default() -> Self {
1636        Self {
1637            specificity_and_flags: SpecificityAndFlags {
1638                specificity: 0,
1639                flags: SelectorFlags::empty(),
1640            },
1641            components: ArenaVec::new_in(ArenaPtr::global()),
1642        }
1643    }
1644}
1645
1646impl<Impl: SelectorImpl> GenericSelectorList<Impl> {
1647    /// Consume `self.v` and return a heap slice — used by `:is()`/`:where()`/
1648    /// `:has()`/`:not()`/`:nth-*(.. of ..)` which store `Box<[Selector]>` to
1649    /// keep `Component` small. See `small_list_into_box`.
1650    #[inline]
1651    pub fn into_boxed_selectors(self) -> Box<[GenericSelector<Impl>]> {
1652        small_list_into_box(self.v)
1653    }
1654}
1655
1656/// `DebugFmt` wrapper — implements `Display` over a borrowed list (debug builds only).
1657pub struct SelectorListDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelectorList<Impl>);
1658
1659impl<'a, Impl: BunSelectorImpl> fmt::Display for SelectorListDebugFmt<'a, Impl> {
1660    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1661        if !cfg!(debug_assertions) {
1662            return Ok(());
1663        }
1664        writeln!(f, "SelectorList[")?;
1665        let last = (self.0.v.len() as usize).saturating_sub(1);
1666        for (i, sel) in self.0.v.slice().iter().enumerate() {
1667            if i != last {
1668                writeln!(f, " {}", sel.debug())?;
1669            } else {
1670                writeln!(f, " {},", sel.debug())?;
1671            }
1672        }
1673        writeln!(f, "]")
1674    }
1675}
1676
1677impl<Impl: BunSelectorImpl> GenericSelectorList<Impl> {
1678    pub fn debug(&self) -> SelectorListDebugFmt<'_, Impl> {
1679        SelectorListDebugFmt(self)
1680    }
1681
1682    pub fn any_has_pseudo_element(&self) -> bool {
1683        for sel in self.v.slice() {
1684            if sel.has_pseudo_element() {
1685                return true;
1686            }
1687        }
1688        false
1689    }
1690
1691    pub fn specifities_all_equal(&self) -> bool {
1692        if self.v.len() == 0 {
1693            return true;
1694        }
1695        if self.v.len() == 1 {
1696            return true;
1697        }
1698        let value = self.v.at(0).specificity();
1699        for sel in &self.v.slice()[1..] {
1700            if sel.specificity() != value {
1701                return false;
1702            }
1703        }
1704        true
1705    }
1706
1707    /// Do not call this! Use `serializer::serialize_selector_list()` or
1708    /// `tocss_servo::to_css_selector_list()` instead.
1709    #[deprecated = "use serializer::serialize_selector_list()"]
1710    pub fn to_css(&self, _dest: &mut Printer) -> Result<(), PrintErr> {
1711        unreachable!("use serializer::serialize_selector_list()");
1712    }
1713
1714    pub fn parse_with_options(input: &mut CssParser, options: &ParserOptions) -> CResult<Self> {
1715        let mut parser = SelectorParser {
1716            options,
1717            is_nesting_allowed: true,
1718        };
1719        Self::parse(
1720            &mut parser,
1721            input,
1722            ParseErrorRecovery::DiscardList,
1723            NestingRequirement::None,
1724        )
1725    }
1726
1727    pub fn parse(
1728        parser: &mut SelectorParser,
1729        input: &mut CssParser,
1730        error_recovery: ParseErrorRecovery,
1731        nesting_requirement: NestingRequirement,
1732    ) -> CResult<Self> {
1733        let mut state = SelectorParsingState::empty();
1734        Self::parse_with_state(
1735            parser,
1736            input,
1737            &mut state,
1738            error_recovery,
1739            nesting_requirement,
1740        )
1741    }
1742
1743    pub fn parse_relative(
1744        parser: &mut SelectorParser,
1745        input: &mut CssParser,
1746        error_recovery: ParseErrorRecovery,
1747        nesting_requirement: NestingRequirement,
1748    ) -> CResult<Self> {
1749        let mut state = SelectorParsingState::empty();
1750        Self::parse_relative_with_state(
1751            parser,
1752            input,
1753            &mut state,
1754            error_recovery,
1755            nesting_requirement,
1756        )
1757    }
1758
1759    pub fn parse_with_state(
1760        parser: &mut SelectorParser,
1761        input: &mut CssParser,
1762        state: &mut SelectorParsingState,
1763        recovery: ParseErrorRecovery,
1764        nesting_requirement: NestingRequirement,
1765    ) -> CResult<Self> {
1766        let original_state = *state;
1767        // TODO: Think about deinitialization in error cases
1768        let mut values: SmallList<GenericSelector<Impl>, 1> = SmallList::default();
1769
1770        loop {
1771            // PORT NOTE: reshaped for borrowck — Zig used a `Closure` struct capturing
1772            // `&mut state` and `&mut parser`; Rust captures a local `saw_nesting` flag
1773            // and applies it to `state` after the closure returns (no raw `*mut`).
1774            let mut saw_nesting = false;
1775            let selector =
1776                input.parse_until_before(css::Delimiters::COMMA, |input2: &mut CssParser| {
1777                    let mut selector_state = original_state;
1778                    let result = parse_selector::<Impl>(
1779                        parser,
1780                        input2,
1781                        &mut selector_state,
1782                        nesting_requirement,
1783                    );
1784                    if selector_state.contains(SelectorParsingState::AFTER_NESTING) {
1785                        saw_nesting = true;
1786                    }
1787                    result
1788                });
1789            if saw_nesting {
1790                state.insert(SelectorParsingState::AFTER_NESTING);
1791            }
1792
1793            let was_ok = selector.is_ok();
1794            match selector {
1795                Ok(sel) => {
1796                    values.append(sel);
1797                    // PERF(port): was arena append — profile if it shows up on a hot path.
1798                }
1799                Err(e) => match recovery {
1800                    ParseErrorRecovery::DiscardList => return Err(e),
1801                    ParseErrorRecovery::IgnoreInvalidSelector => {}
1802                },
1803            }
1804
1805            if let Ok(tok) = input.next() {
1806                if matches!(tok, Token::Comma) {
1807                    continue;
1808                }
1809                // Shouldn't have got a selector if getting here.
1810                debug_assert!(!was_ok);
1811            }
1812            return Ok(Self { v: values });
1813        }
1814    }
1815
1816    // TODO: this looks exactly the same as `parse_with_state()` except it uses
1817    // `parse_relative_selector()` instead of `parse_selector()`
1818    pub fn parse_relative_with_state(
1819        parser: &mut SelectorParser,
1820        input: &mut CssParser,
1821        state: &mut SelectorParsingState,
1822        recovery: ParseErrorRecovery,
1823        nesting_requirement: NestingRequirement,
1824    ) -> CResult<Self> {
1825        let original_state = *state;
1826        // TODO: Think about deinitialization in error cases
1827        let mut values: SmallList<GenericSelector<Impl>, 1> = SmallList::default();
1828
1829        loop {
1830            // PORT NOTE: reshaped for borrowck — capture a local flag instead of a
1831            // raw `*mut SelectorParsingState`, then fold into `state` after return.
1832            let mut saw_nesting = false;
1833            let selector =
1834                input.parse_until_before(css::Delimiters::COMMA, |input2: &mut CssParser| {
1835                    let mut selector_state = original_state;
1836                    let result = parse_relative_selector::<Impl>(
1837                        parser,
1838                        input2,
1839                        &mut selector_state,
1840                        nesting_requirement,
1841                    );
1842                    if selector_state.contains(SelectorParsingState::AFTER_NESTING) {
1843                        saw_nesting = true;
1844                    }
1845                    result
1846                });
1847            if saw_nesting {
1848                state.insert(SelectorParsingState::AFTER_NESTING);
1849            }
1850
1851            let was_ok = selector.is_ok();
1852            match selector {
1853                Ok(sel) => {
1854                    values.append(sel);
1855                }
1856                Err(e) => match recovery {
1857                    ParseErrorRecovery::DiscardList => return Err(e),
1858                    ParseErrorRecovery::IgnoreInvalidSelector => {}
1859                },
1860            }
1861
1862            if let Ok(tok) = input.next() {
1863                if matches!(tok, Token::Comma) {
1864                    continue;
1865                }
1866                // Shouldn't have got a selector if getting here.
1867                debug_assert!(!was_ok);
1868            }
1869            return Ok(Self { v: values });
1870        }
1871    }
1872
1873    pub fn from_selector(selector: GenericSelector<Impl>) -> Self {
1874        let mut result = Self::default();
1875        result.v.append(selector);
1876        result
1877    }
1878
1879    pub fn deep_clone(&self) -> Self {
1880        let mut v = SmallList::<GenericSelector<Impl>, 1>::init_capacity(self.v.len());
1881        for sel in self.v.slice() {
1882            v.append(sel.deep_clone());
1883        }
1884        Self { v }
1885    }
1886
1887    pub fn eql(&self, rhs: &Self) -> bool {
1888        eql_selector_slice(self.v.slice(), rhs.v.slice())
1889    }
1890
1891    pub fn hash(&self, hasher: &mut Wyhash) {
1892        hash_selector_slice(self.v.slice(), hasher);
1893    }
1894}
1895
1896impl<Impl: BunSelectorImpl> CssEql for GenericSelectorList<Impl> {
1897    #[inline]
1898    fn eql(&self, other: &Self) -> bool {
1899        self.eql(other)
1900    }
1901}
1902impl<Impl: BunSelectorImpl> CssHash for GenericSelectorList<Impl> {
1903    #[inline]
1904    fn hash(&self, hasher: &mut Wyhash) {
1905        self.hash(hasher)
1906    }
1907}
1908
1909// ─────────────────────────────────────────────────────────────────────────────
1910// GenericSelector
1911// ─────────────────────────────────────────────────────────────────────────────
1912
1913/// -- original comment from servo --
1914/// A Selector stores a sequence of simple selectors and combinators. The
1915/// iterator classes allow callers to iterate at either the raw sequence level or
1916/// at the level of sequences of simple selectors separated by combinators. Most
1917/// callers want the higher-level iterator.
1918///
1919/// We store compound selectors internally right-to-left (in matching order).
1920/// Additionally, we invert the order of top-level compound selectors so that
1921/// each one matches left-to-right. This is because matching namespace, local name,
1922/// id, and class are all relatively cheap, whereas matching pseudo-classes might
1923/// be expensive (depending on the pseudo-class). Since authors tend to put the
1924/// pseudo-classes on the right, it's faster to start matching on the left.
1925///
1926/// This reordering doesn't change the semantics of selector matching, and we
1927/// handle it in to_css to make it invisible to serialization.
1928#[derive(Clone)]
1929pub struct GenericSelector<Impl: SelectorImpl> {
1930    pub specificity_and_flags: SpecificityAndFlags,
1931    pub components: ArenaVec<GenericComponent<Impl>>,
1932}
1933
1934pub struct SelectorDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelector<Impl>);
1935
1936impl<'a, Impl: SelectorImpl> fmt::Display for SelectorDebugFmt<'a, Impl> {
1937    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1938        if !cfg!(debug_assertions) {
1939            return Ok(());
1940        }
1941        // TODO(port): the Zig builds a fresh `Printer` and calls
1942        // `tocss_servo::to_css_selector` into a buffer, then writes the buffer.
1943        // blocked_on: `Printer::new_buffered` + `SymbolMap::default` (debug-
1944        // only path; serialization body lives in `selector::tocss_servo`).
1945        write!(f, "Selector(<{} components>)", self.0.components.len())
1946    }
1947}
1948
1949impl<Impl: BunSelectorImpl> GenericSelector<Impl> {
1950    pub fn debug(&self) -> SelectorDebugFmt<'_, Impl> {
1951        SelectorDebugFmt(self)
1952    }
1953
1954    /// Parse a selector, without any pseudo-element.
1955    pub fn parse(parser: &mut SelectorParser, input: &mut CssParser) -> CResult<Self> {
1956        let mut state = SelectorParsingState::empty();
1957        parse_selector::<Impl>(parser, input, &mut state, NestingRequirement::None)
1958    }
1959
1960    /// Do not call this! Use `serializer::serialize_selector()` or
1961    /// `tocss_servo::to_css_selector()` instead.
1962    #[deprecated = "use serializer::serialize_selector()"]
1963    pub fn to_css(&self, _dest: &mut Printer) -> Result<(), PrintErr> {
1964        unreachable!("use serializer::serialize_selector()");
1965    }
1966
1967    pub fn append(&mut self, component: GenericComponent<Impl>) {
1968        let index = 'index: {
1969            for (i, comp) in self.components.iter().enumerate() {
1970                match comp {
1971                    GenericComponent::Combinator(_) | GenericComponent::PseudoElement(_) => {
1972                        break 'index i;
1973                    }
1974                    _ => {}
1975                }
1976            }
1977            self.components.len()
1978        };
1979        self.components.insert(index, component);
1980    }
1981
1982    pub fn deep_clone(&self) -> Self {
1983        let alloc = *self.components.allocator();
1984        let mut components = ArenaVec::with_capacity_in(self.components.len(), alloc);
1985        components.extend(self.components.iter().map(|c| c.deep_clone()));
1986        Self {
1987            specificity_and_flags: self.specificity_and_flags,
1988            components,
1989        }
1990    }
1991
1992    pub fn eql(&self, other: &Self) -> bool {
1993        self.specificity_and_flags.eql(&other.specificity_and_flags)
1994            && self.components.len() == other.components.len()
1995            && self
1996                .components
1997                .iter()
1998                .zip(other.components.iter())
1999                .all(|(a, b)| a.eql(b))
2000    }
2001
2002    pub fn has_combinator(&self) -> bool {
2003        for c in &self.components {
2004            if let GenericComponent::Combinator(comb) = c {
2005                if comb.is_tree_combinator() {
2006                    return true;
2007                }
2008            }
2009        }
2010        false
2011    }
2012
2013    pub fn has_pseudo_element(&self) -> bool {
2014        self.specificity_and_flags.has_pseudo_element()
2015    }
2016
2017    /// Returns count of simple selectors and combinators in the Selector.
2018    pub fn len(&self) -> usize {
2019        self.components.len()
2020    }
2021
2022    pub fn from_component(component: GenericComponent<Impl>) -> Self {
2023        Self::from_component_in(component, ArenaPtr::global())
2024    }
2025
2026    pub fn from_component_in(component: GenericComponent<Impl>, alloc: ArenaPtr) -> Self {
2027        let mut builder = SelectorBuilder::<Impl>::init_in(alloc);
2028        if let Some(combinator) = component.as_combinator() {
2029            builder.push_combinator(combinator);
2030        } else {
2031            builder.push_simple_selector(component);
2032        }
2033        let result = builder.build(false, false, false);
2034        Self {
2035            specificity_and_flags: result.specificity_and_flags,
2036            components: result.components,
2037        }
2038    }
2039
2040    pub fn specificity(&self) -> u32 {
2041        self.specificity_and_flags.specificity
2042    }
2043
2044    pub fn parse_with_options(input: &mut CssParser, options: &ParserOptions) -> CResult<Self> {
2045        let mut selector_parser = SelectorParser {
2046            is_nesting_allowed: true,
2047            options,
2048        };
2049        Self::parse(&mut selector_parser, input)
2050    }
2051
2052    pub fn iter_raw_match_order(&self) -> RawMatchOrderIterator<'_, Impl> {
2053        RawMatchOrderIterator {
2054            slice: &self.components,
2055            i: 0,
2056        }
2057    }
2058
2059    /// Returns an iterator over the sequence of simple selectors and
2060    /// combinators, in parse order (from left to right), starting from
2061    /// `offset`.
2062    pub fn iter_raw_parse_order_from(&self, offset: usize) -> RawParseOrderFromIter<'_, Impl> {
2063        RawParseOrderFromIter {
2064            slice: &self.components[0..self.components.len() - offset],
2065            i: 0,
2066        }
2067    }
2068
2069    pub fn hash(&self, hasher: &mut Wyhash) {
2070        self.specificity_and_flags.hash(hasher);
2071        for c in &self.components {
2072            c.hash(hasher);
2073        }
2074    }
2075}
2076
2077impl<Impl: BunSelectorImpl> CssEql for GenericSelector<Impl> {
2078    #[inline]
2079    fn eql(&self, other: &Self) -> bool {
2080        self.eql(other)
2081    }
2082}
2083impl<Impl: BunSelectorImpl> CssHash for GenericSelector<Impl> {
2084    #[inline]
2085    fn hash(&self, hasher: &mut Wyhash) {
2086        self.hash(hasher)
2087    }
2088}
2089
2090pub struct RawMatchOrderIterator<'a, Impl: SelectorImpl> {
2091    slice: &'a [GenericComponent<Impl>],
2092    i: usize,
2093}
2094
2095impl<'a, Impl: SelectorImpl> Iterator for RawMatchOrderIterator<'a, Impl> {
2096    type Item = &'a GenericComponent<Impl>;
2097    fn next(&mut self) -> Option<Self::Item> {
2098        if self.i >= self.slice.len() {
2099            return None;
2100        }
2101        let result = &self.slice[self.i];
2102        self.i += 1;
2103        Some(result)
2104    }
2105}
2106
2107pub struct RawParseOrderFromIter<'a, Impl: SelectorImpl> {
2108    slice: &'a [GenericComponent<Impl>],
2109    i: usize,
2110}
2111
2112impl<'a, Impl: SelectorImpl> Iterator for RawParseOrderFromIter<'a, Impl> {
2113    type Item = &'a GenericComponent<Impl>;
2114    fn next(&mut self) -> Option<Self::Item> {
2115        if !(self.i < self.slice.len()) {
2116            return None;
2117        }
2118        let result = &self.slice[self.slice.len() - 1 - self.i];
2119        self.i += 1;
2120        Some(result)
2121    }
2122}
2123
2124// ─────────────────────────────────────────────────────────────────────────────
2125// GenericComponent
2126// ─────────────────────────────────────────────────────────────────────────────
2127
2128/// A CSS simple selector or combinator. We store both in the same enum for
2129/// optimal packing and cache performance, see [1].
2130///
2131/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973
2132#[derive(Clone)]
2133pub enum GenericComponent<Impl: SelectorImpl> {
2134    Combinator(Combinator),
2135
2136    ExplicitAnyNamespace,
2137    ExplicitNoNamespace,
2138    DefaultNamespace(Impl::NamespaceUrl),
2139    Namespace {
2140        prefix: Impl::NamespacePrefix,
2141        url: Impl::NamespaceUrl,
2142    },
2143
2144    ExplicitUniversalType,
2145    LocalName(LocalName<Impl>),
2146
2147    Id(Impl::LocalIdentifier),
2148    Class(Impl::LocalIdentifier),
2149
2150    AttributeInNoNamespaceExists {
2151        local_name: Impl::LocalName,
2152        local_name_lower: Impl::LocalName,
2153    },
2154    /// Used only when local_name is already lowercase.
2155    AttributeInNoNamespace {
2156        local_name: Impl::LocalName,
2157        operator: attrs::AttrSelectorOperator,
2158        value: Impl::AttrValue,
2159        case_sensitivity: attrs::ParsedCaseSensitivity,
2160        never_matches: bool,
2161    },
2162    /// Use a Box in the less common cases with more data to keep size_of::<Component>() small.
2163    AttributeOther(Box<attrs::AttrSelectorWithOptionalNamespace<Impl>>),
2164
2165    /// Pseudo-classes
2166    Negation(Box<[GenericSelector<Impl>]>),
2167    Root,
2168    Empty,
2169    Scope,
2170    Nth(NthSelectorData),
2171    NthOf(NthOfSelectorData<Impl>),
2172    NonTsPseudoClass(Impl::NonTSPseudoClass),
2173    /// The ::slotted() pseudo-element:
2174    ///
2175    /// https://drafts.csswg.org/css-scoping/#slotted-pseudo
2176    ///
2177    /// The selector here is a compound selector, that is, no combinators.
2178    ///
2179    /// NOTE(emilio): This should support a list of selectors, but as of this
2180    /// writing no other browser does, and that allows them to put ::slotted()
2181    /// in the rule hash, so we do that too.
2182    ///
2183    /// See https://github.com/w3c/csswg-drafts/issues/2158
2184    Slotted(GenericSelector<Impl>),
2185    /// The `::part` pseudo-element.
2186    ///   https://drafts.csswg.org/css-shadow-parts/#part
2187    Part(Box<[Impl::Identifier]>),
2188    /// The `:host` pseudo-class:
2189    ///
2190    /// https://drafts.csswg.org/css-scoping/#host-selector
2191    ///
2192    /// NOTE(emilio): This should support a list of selectors, but as of this
2193    /// writing no other browser does, and that allows them to put :host()
2194    /// in the rule hash, so we do that too.
2195    ///
2196    /// See https://github.com/w3c/csswg-drafts/issues/2158
2197    Host(Option<GenericSelector<Impl>>),
2198    /// The `:where` pseudo-class.
2199    ///
2200    /// https://drafts.csswg.org/selectors/#zero-matches
2201    ///
2202    /// The inner argument is conceptually a SelectorList, but we move the
2203    /// selectors to the heap to keep Component small.
2204    Where(Box<[GenericSelector<Impl>]>),
2205    /// The `:is` pseudo-class.
2206    ///
2207    /// https://drafts.csswg.org/selectors/#matches-pseudo
2208    ///
2209    /// Same comment as above re. the argument.
2210    Is(Box<[GenericSelector<Impl>]>),
2211    Any {
2212        vendor_prefix: Impl::VendorPrefix,
2213        selectors: Box<[GenericSelector<Impl>]>,
2214    },
2215    /// The `:has` pseudo-class.
2216    ///
2217    /// https://www.w3.org/TR/selectors/#relational
2218    Has(Box<[GenericSelector<Impl>]>),
2219    /// An implementation-dependent pseudo-element selector.
2220    PseudoElement(Impl::PseudoElement),
2221    /// A nesting selector:
2222    ///
2223    /// https://drafts.csswg.org/css-nesting-1/#nest-selector
2224    ///
2225    /// NOTE: This is a lightningcss addition.
2226    Nesting,
2227}
2228
2229impl<Impl: BunSelectorImpl> GenericComponent<Impl> {
2230    /// If css modules is enabled these will be locally scoped
2231    pub fn is_locally_scoped(&self) -> bool {
2232        matches!(self, Self::Id(_) | Self::Class(_))
2233    }
2234
2235    pub fn as_class(&self) -> Option<&Impl::LocalIdentifier> {
2236        match self {
2237            Self::Class(v) => Some(v),
2238            _ => None,
2239        }
2240    }
2241
2242    pub fn deep_clone(&self) -> Self {
2243        // PORT NOTE: hand-written variant-walk (Zig `implementDeepClone`).
2244        // Every borrowed payload (`Str`, `Ident.v`, `IdentOrRef`) is an
2245        // arena-static identity copy; owning containers (`Vec`/`Box`) recurse.
2246        use GenericComponent as C;
2247        match self {
2248            C::Combinator(c) => C::Combinator(*c),
2249            C::ExplicitAnyNamespace => C::ExplicitAnyNamespace,
2250            C::ExplicitNoNamespace => C::ExplicitNoNamespace,
2251            C::DefaultNamespace(u) => C::DefaultNamespace(*u),
2252            C::Namespace { prefix, url } => C::Namespace {
2253                prefix: *prefix,
2254                url: *url,
2255            },
2256            C::ExplicitUniversalType => C::ExplicitUniversalType,
2257            C::LocalName(ln) => C::LocalName(ln.deep_clone()),
2258            C::Id(i) => C::Id(*i),
2259            C::Class(i) => C::Class(*i),
2260            C::AttributeInNoNamespaceExists {
2261                local_name,
2262                local_name_lower,
2263            } => C::AttributeInNoNamespaceExists {
2264                local_name: *local_name,
2265                local_name_lower: *local_name_lower,
2266            },
2267            C::AttributeInNoNamespace {
2268                local_name,
2269                operator,
2270                value,
2271                case_sensitivity,
2272                never_matches,
2273            } => C::AttributeInNoNamespace {
2274                local_name: *local_name,
2275                operator: *operator,
2276                value: *value,
2277                case_sensitivity: *case_sensitivity,
2278                never_matches: *never_matches,
2279            },
2280            C::AttributeOther(a) => C::AttributeOther(Box::new(a.deep_clone())),
2281            C::Negation(s) => C::Negation(deep_clone_selector_slice(s)),
2282            C::Root => C::Root,
2283            C::Empty => C::Empty,
2284            C::Scope => C::Scope,
2285            C::Nth(n) => C::Nth(*n),
2286            C::NthOf(n) => C::NthOf(n.deep_clone()),
2287            C::NonTsPseudoClass(p) => C::NonTsPseudoClass(p.deep_clone()),
2288            C::Slotted(s) => C::Slotted(s.deep_clone()),
2289            C::Part(p) => C::Part(p.iter().cloned().collect()),
2290            C::Host(h) => C::Host(h.as_ref().map(|s| s.deep_clone())),
2291            C::Where(s) => C::Where(deep_clone_selector_slice(s)),
2292            C::Is(s) => C::Is(deep_clone_selector_slice(s)),
2293            C::Any {
2294                vendor_prefix,
2295                selectors,
2296            } => C::Any {
2297                vendor_prefix: *vendor_prefix,
2298                selectors: deep_clone_selector_slice(selectors),
2299            },
2300            C::Has(s) => C::Has(deep_clone_selector_slice(s)),
2301            C::PseudoElement(pe) => C::PseudoElement(pe.deep_clone()),
2302            C::Nesting => C::Nesting,
2303        }
2304    }
2305
2306    pub fn eql(&self, rhs: &Self) -> bool {
2307        use GenericComponent as C;
2308        match (self, rhs) {
2309            (C::Combinator(a), C::Combinator(b)) => a.eql(b),
2310            (C::ExplicitAnyNamespace, C::ExplicitAnyNamespace)
2311            | (C::ExplicitNoNamespace, C::ExplicitNoNamespace)
2312            | (C::ExplicitUniversalType, C::ExplicitUniversalType)
2313            | (C::Root, C::Root)
2314            | (C::Empty, C::Empty)
2315            | (C::Scope, C::Scope)
2316            | (C::Nesting, C::Nesting) => true,
2317            (C::DefaultNamespace(a), C::DefaultNamespace(b)) => strings::eql(a, b),
2318            (
2319                C::Namespace {
2320                    prefix: ap,
2321                    url: au,
2322                },
2323                C::Namespace {
2324                    prefix: bp,
2325                    url: bu,
2326                },
2327            ) => ap.eql(bp) && strings::eql(au, bu),
2328            (C::LocalName(a), C::LocalName(b)) => a.eql(b),
2329            (C::Id(a), C::Id(b)) | (C::Class(a), C::Class(b)) => a.eql(b),
2330            (
2331                C::AttributeInNoNamespaceExists {
2332                    local_name: an,
2333                    local_name_lower: al,
2334                },
2335                C::AttributeInNoNamespaceExists {
2336                    local_name: bn,
2337                    local_name_lower: bl,
2338                },
2339            ) => an.eql(bn) && al.eql(bl),
2340            (
2341                C::AttributeInNoNamespace {
2342                    local_name: an,
2343                    operator: ao,
2344                    value: av,
2345                    case_sensitivity: ac,
2346                    never_matches: am,
2347                },
2348                C::AttributeInNoNamespace {
2349                    local_name: bn,
2350                    operator: bo,
2351                    value: bv,
2352                    case_sensitivity: bc,
2353                    never_matches: bm,
2354                },
2355            ) => {
2356                an.eql(bn)
2357                    && ao == bo
2358                    && ac == bc
2359                    && am == bm
2360                    // SAFETY: arena-owned slices live for the parse session.
2361                    && unsafe { strings::eql(&**av, &**bv) }
2362            }
2363            (C::AttributeOther(a), C::AttributeOther(b)) => a.eql(b),
2364            (C::Negation(a), C::Negation(b))
2365            | (C::Where(a), C::Where(b))
2366            | (C::Is(a), C::Is(b))
2367            | (C::Has(a), C::Has(b)) => eql_selector_slice(a, b),
2368            (C::Nth(a), C::Nth(b)) => a.eql(b),
2369            (C::NthOf(a), C::NthOf(b)) => a.eql(b),
2370            (C::NonTsPseudoClass(a), C::NonTsPseudoClass(b)) => a.eql(b),
2371            (C::Slotted(a), C::Slotted(b)) => a.eql(b),
2372            (C::Part(a), C::Part(b)) => {
2373                a.len() == b.len() && a.iter().zip(b.iter()).all(|(l, r)| l.eql(r))
2374            }
2375            (C::Host(a), C::Host(b)) => match (a, b) {
2376                (None, None) => true,
2377                (Some(a), Some(b)) => a.eql(b),
2378                _ => false,
2379            },
2380            (
2381                C::Any {
2382                    vendor_prefix: ap,
2383                    selectors: asel,
2384                },
2385                C::Any {
2386                    vendor_prefix: bp,
2387                    selectors: bsel,
2388                },
2389            ) => ap == bp && eql_selector_slice(asel, bsel),
2390            (C::PseudoElement(a), C::PseudoElement(b)) => a.eql(b),
2391            _ => false,
2392        }
2393    }
2394
2395    pub fn as_combinator(&self) -> Option<Combinator> {
2396        if let Self::Combinator(c) = self {
2397            Some(*c)
2398        } else {
2399            None
2400        }
2401    }
2402
2403    pub fn convert_helper_is(s: Box<[GenericSelector<Impl>]>) -> Self {
2404        Self::Is(s)
2405    }
2406
2407    pub fn convert_helper_where(s: Box<[GenericSelector<Impl>]>) -> Self {
2408        Self::Where(s)
2409    }
2410
2411    pub fn convert_helper_any(s: Box<[GenericSelector<Impl>]>, prefix: Impl::VendorPrefix) -> Self {
2412        Self::Any {
2413            vendor_prefix: prefix,
2414            selectors: s,
2415        }
2416    }
2417
2418    /// Returns true if this is a combinator.
2419    pub fn is_combinator(&self) -> bool {
2420        matches!(self, Self::Combinator(_))
2421    }
2422
2423    /// Do not call this! Use `serializer::serialize_component()` or
2424    /// `tocss_servo::to_css_component()` instead.
2425    #[deprecated = "use serializer::serialize_component()"]
2426    pub fn to_css(&self, _dest: &mut Printer) -> Result<(), PrintErr> {
2427        unreachable!("use serializer::serialize_component()");
2428    }
2429
2430    pub fn hash(&self, hasher: &mut Wyhash) {
2431        use GenericComponent as C;
2432        // Zig `implementHash`: `bun.writeAnyToHasher(@intFromEnum(this))` then payload.
2433        // SAFETY: `GenericComponent` is `#[repr(Rust)]`; reading the discriminant
2434        // via `core::mem::discriminant` is stable but not byte-hashable. Use a
2435        // per-arm tag instead (CSS hashing is in-process dedup only — self-
2436        // consistency, not Zig-byte-identity, is the contract).
2437        macro_rules! tag {
2438            ($n:expr) => {
2439                hasher.update(&($n as u32).to_ne_bytes())
2440            };
2441        }
2442        match self {
2443            C::Combinator(c) => {
2444                tag!(0);
2445                CssHash::hash(c, hasher);
2446            }
2447            C::ExplicitAnyNamespace => tag!(1),
2448            C::ExplicitNoNamespace => tag!(2),
2449            C::DefaultNamespace(u) => {
2450                tag!(3);
2451                hasher.update(u);
2452            }
2453            C::Namespace { prefix, url } => {
2454                tag!(4);
2455                prefix.hash(hasher);
2456                hasher.update(url);
2457            }
2458            C::ExplicitUniversalType => tag!(5),
2459            C::LocalName(ln) => {
2460                tag!(6);
2461                ln.hash(hasher);
2462            }
2463            C::Id(i) => {
2464                tag!(7);
2465                i.hash(hasher);
2466            }
2467            C::Class(i) => {
2468                tag!(8);
2469                i.hash(hasher);
2470            }
2471            C::AttributeInNoNamespaceExists {
2472                local_name,
2473                local_name_lower,
2474            } => {
2475                tag!(9);
2476                local_name.hash(hasher);
2477                local_name_lower.hash(hasher);
2478            }
2479            C::AttributeInNoNamespace {
2480                local_name,
2481                operator,
2482                value,
2483                case_sensitivity,
2484                never_matches,
2485            } => {
2486                tag!(10);
2487                local_name.hash(hasher);
2488                CssHash::hash(operator, hasher);
2489                // SAFETY: arena-owned slice.
2490                hasher.update(unsafe { crate::arena_str(*value) });
2491                CssHash::hash(case_sensitivity, hasher);
2492                hasher.update(&[*never_matches as u8]);
2493            }
2494            C::AttributeOther(a) => {
2495                tag!(11);
2496                a.hash(hasher);
2497            }
2498            C::Negation(s) => {
2499                tag!(12);
2500                hash_selector_slice(s, hasher);
2501            }
2502            C::Root => tag!(13),
2503            C::Empty => tag!(14),
2504            C::Scope => tag!(15),
2505            C::Nth(n) => {
2506                tag!(16);
2507                n.hash(hasher);
2508            }
2509            C::NthOf(n) => {
2510                tag!(17);
2511                n.hash(hasher);
2512            }
2513            C::NonTsPseudoClass(p) => {
2514                tag!(18);
2515                CssHash::hash(p, hasher);
2516            }
2517            C::Slotted(s) => {
2518                tag!(19);
2519                s.hash(hasher);
2520            }
2521            C::Part(p) => {
2522                tag!(20);
2523                for id in p.iter() {
2524                    id.hash(hasher);
2525                }
2526            }
2527            C::Host(h) => {
2528                tag!(21);
2529                if let Some(s) = h {
2530                    s.hash(hasher);
2531                }
2532            }
2533            C::Where(s) => {
2534                tag!(22);
2535                hash_selector_slice(s, hasher);
2536            }
2537            C::Is(s) => {
2538                tag!(23);
2539                hash_selector_slice(s, hasher);
2540            }
2541            C::Any {
2542                vendor_prefix,
2543                selectors,
2544            } => {
2545                tag!(24);
2546                CssHash::hash(vendor_prefix, hasher);
2547                hash_selector_slice(selectors, hasher);
2548            }
2549            C::Has(s) => {
2550                tag!(25);
2551                hash_selector_slice(s, hasher);
2552            }
2553            C::PseudoElement(pe) => {
2554                tag!(26);
2555                CssHash::hash(pe, hasher);
2556            }
2557            C::Nesting => tag!(27),
2558        }
2559    }
2560}
2561
2562impl<Impl: BunSelectorImpl> CssEql for GenericComponent<Impl> {
2563    #[inline]
2564    fn eql(&self, other: &Self) -> bool {
2565        self.eql(other)
2566    }
2567}
2568impl<Impl: BunSelectorImpl> CssHash for GenericComponent<Impl> {
2569    #[inline]
2570    fn hash(&self, hasher: &mut Wyhash) {
2571        self.hash(hasher)
2572    }
2573}
2574
2575impl<Impl: BunSelectorImpl> fmt::Display for GenericComponent<Impl> {
2576    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2577        // TODO(port): Zig matches on a few variants and falls through to `@tagName`.
2578        // Rust enums need `strum::IntoStaticStr` for the tag name.
2579        match self {
2580            Self::LocalName(ln) => write!(f, "local_name={}", bstr::BStr::new(ln.name.v())),
2581            Self::Combinator(c) => write!(f, "combinator='{}'", c),
2582            Self::PseudoElement(_) => write!(f, "pseudo_element=<..>"),
2583            Self::Class(_) => write!(f, "class=<..>"),
2584            _ => write!(f, "<component>"),
2585        }
2586    }
2587}
2588
2589// ─────────────────────────────────────────────────────────────────────────────
2590// NthSelectorData / NthOfSelectorData
2591// ─────────────────────────────────────────────────────────────────────────────
2592
2593/// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g.,
2594/// nth-child(An+B)).
2595/// https://www.w3.org/TR/selectors-3/#nth-child-pseudo
2596#[derive(Clone, Copy, PartialEq, Eq)]
2597pub struct NthSelectorData {
2598    pub ty: NthType,
2599    pub is_function: bool,
2600    pub a: i32,
2601    pub b: i32,
2602}
2603
2604impl NthSelectorData {
2605    /// Returns selector data for :only-{child,of-type}
2606    pub fn only(of_type: bool) -> NthSelectorData {
2607        NthSelectorData {
2608            ty: if of_type {
2609                NthType::OnlyOfType
2610            } else {
2611                NthType::OnlyChild
2612            },
2613            is_function: false,
2614            a: 0,
2615            b: 1,
2616        }
2617    }
2618
2619    /// Returns selector data for :first-{child,of-type}
2620    pub fn first(of_type: bool) -> NthSelectorData {
2621        NthSelectorData {
2622            ty: if of_type {
2623                NthType::OfType
2624            } else {
2625                NthType::Child
2626            },
2627            is_function: false,
2628            a: 0,
2629            b: 1,
2630        }
2631    }
2632
2633    /// Returns selector data for :last-{child,of-type}
2634    pub fn last(of_type: bool) -> NthSelectorData {
2635        NthSelectorData {
2636            ty: if of_type {
2637                NthType::LastOfType
2638            } else {
2639                NthType::LastChild
2640            },
2641            is_function: false,
2642            a: 0,
2643            b: 1,
2644        }
2645    }
2646
2647    pub fn write_start(&self, dest: &mut Printer, is_function: bool) -> Result<(), PrintErr> {
2648        dest.write_str(match self.ty {
2649            NthType::Child => {
2650                if is_function {
2651                    ":nth-child("
2652                } else {
2653                    ":first-child"
2654                }
2655            }
2656            NthType::LastChild => {
2657                if is_function {
2658                    ":nth-last-child("
2659                } else {
2660                    ":last-child"
2661                }
2662            }
2663            NthType::OfType => {
2664                if is_function {
2665                    ":nth-of-type("
2666                } else {
2667                    ":first-of-type"
2668                }
2669            }
2670            NthType::LastOfType => {
2671                if is_function {
2672                    ":nth-last-of-type("
2673                } else {
2674                    ":last-of-type"
2675                }
2676            }
2677            NthType::OnlyChild => ":only-child",
2678            NthType::OnlyOfType => ":only-of-type",
2679            NthType::Col => ":nth-col(",
2680            NthType::LastCol => ":nth-last-col(",
2681        })
2682    }
2683
2684    pub fn is_function_(&self) -> bool {
2685        self.a != 0 || self.b != 1
2686    }
2687
2688    fn number_sign(num: i32) -> &'static str {
2689        if num >= 0 { "+" } else { "" }
2690    }
2691
2692    pub fn write_affine(&self, dest: &mut Printer) -> Result<(), PrintErr> {
2693        // PERF: this could be made faster
2694        if self.a == 0 && self.b == 0 {
2695            dest.write_char(b'0')
2696        } else if self.a == 1 && self.b == 0 {
2697            dest.write_char(b'n')
2698        } else if self.a == -1 && self.b == 0 {
2699            dest.write_str("-n")
2700        } else if self.b == 0 {
2701            dest.write_fmt(format_args!("{}n", self.a))
2702        } else if self.a == 2 && self.b == 1 {
2703            dest.write_str("odd")
2704        } else if self.a == 0 {
2705            dest.write_fmt(format_args!("{}", self.b))
2706        } else if self.a == 1 {
2707            dest.write_fmt(format_args!("n{}{}", Self::number_sign(self.b), self.b))
2708        } else if self.a == -1 {
2709            dest.write_fmt(format_args!("-n{}{}", Self::number_sign(self.b), self.b))
2710        } else {
2711            dest.write_fmt(format_args!(
2712                "{}n{}{}",
2713                self.a,
2714                Self::number_sign(self.b),
2715                self.b
2716            ))
2717        }
2718    }
2719
2720    pub fn hash(&self, hasher: &mut Wyhash) {
2721        hasher.update(&(self.ty as u32).to_ne_bytes());
2722        hasher.update(&[self.is_function as u8]);
2723        hasher.update(&self.a.to_ne_bytes());
2724        hasher.update(&self.b.to_ne_bytes());
2725    }
2726
2727    #[inline]
2728    pub fn deep_clone(&self) -> Self {
2729        *self
2730    }
2731}
2732
2733/// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g.,
2734/// nth-child(An+B [of S]?)).
2735/// https://www.w3.org/TR/selectors-4/#nth-child-pseudo
2736#[derive(Clone)]
2737pub struct NthOfSelectorData<Impl: SelectorImpl> {
2738    pub data: NthSelectorData,
2739    pub selectors: Box<[GenericSelector<Impl>]>,
2740}
2741
2742impl<Impl: BunSelectorImpl> NthOfSelectorData<Impl> {
2743    pub fn eql(&self, rhs: &Self) -> bool {
2744        self.data.eql(&rhs.data) && eql_selector_slice(&self.selectors, &rhs.selectors)
2745    }
2746    pub fn hash(&self, hasher: &mut Wyhash) {
2747        self.data.hash(hasher);
2748        hash_selector_slice(&self.selectors, hasher);
2749    }
2750    pub fn deep_clone(&self) -> Self {
2751        Self {
2752            data: self.data,
2753            selectors: deep_clone_selector_slice(&self.selectors),
2754        }
2755    }
2756    pub fn nth_data(&self) -> NthSelectorData {
2757        self.data
2758    }
2759}
2760
2761// ─────────────────────────────────────────────────────────────────────────────
2762// SelectorParsingState (packed struct(u16) — all-bool → bitflags!)
2763// ─────────────────────────────────────────────────────────────────────────────
2764
2765bitflags::bitflags! {
2766    #[derive(Clone, Copy, PartialEq, Eq)]
2767    pub struct SelectorParsingState: u16 {
2768        /// Whether we should avoid adding default namespaces to selectors that
2769        /// aren't type or universal selectors.
2770        const SKIP_DEFAULT_NAMESPACE = 1 << 0;
2771
2772        /// Whether we've parsed a ::slotted() pseudo-element already.
2773        ///
2774        /// If so, then we can only parse a subset of pseudo-elements, and
2775        /// whatever comes after them if so.
2776        const AFTER_SLOTTED = 1 << 1;
2777
2778        /// Whether we've parsed a ::part() pseudo-element already.
2779        ///
2780        /// If so, then we can only parse a subset of pseudo-elements, and
2781        /// whatever comes after them if so.
2782        const AFTER_PART = 1 << 2;
2783
2784        /// Whether we've parsed a pseudo-element (as in, an
2785        /// `Impl::PseudoElement` thus not accounting for `::slotted` or
2786        /// `::part`) already.
2787        ///
2788        /// If so, then other pseudo-elements and most other selectors are
2789        /// disallowed.
2790        const AFTER_PSEUDO_ELEMENT = 1 << 3;
2791
2792        /// Whether we've parsed a non-stateful pseudo-element (again, as-in
2793        /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are
2794        /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
2795        /// as well.
2796        const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
2797
2798        /// Whether we explicitly disallow combinators.
2799        const DISALLOW_COMBINATORS = 1 << 5;
2800
2801        /// Whether we explicitly disallow pseudo-element-like things.
2802        const DISALLOW_PSEUDOS = 1 << 6;
2803
2804        /// Whether we have seen a nesting selector.
2805        const AFTER_NESTING = 1 << 7;
2806
2807        const AFTER_WEBKIT_SCROLLBAR = 1 << 8;
2808        const AFTER_VIEW_TRANSITION = 1 << 9;
2809        const AFTER_UNKNOWN_PSEUDO_ELEMENT = 1 << 10;
2810    }
2811}
2812
2813impl SelectorParsingState {
2814    /// Whether we are after any of the pseudo-like things.
2815    pub fn after_any_pseudo(self) -> bool {
2816        self.intersects(Self::AFTER_PART | Self::AFTER_SLOTTED | Self::AFTER_PSEUDO_ELEMENT)
2817    }
2818
2819    pub fn allows_pseudos(self) -> bool {
2820        !self.contains(Self::AFTER_PSEUDO_ELEMENT) && !self.contains(Self::DISALLOW_PSEUDOS)
2821    }
2822
2823    pub fn allows_part(self) -> bool {
2824        !self.contains(Self::DISALLOW_PSEUDOS) && !self.after_any_pseudo()
2825    }
2826
2827    pub fn allows_slotted(self) -> bool {
2828        self.allows_part()
2829    }
2830
2831    pub fn allows_tree_structural_pseudo_classes(self) -> bool {
2832        !self.after_any_pseudo()
2833    }
2834
2835    pub fn allows_non_functional_pseudo_classes(self) -> bool {
2836        !self.contains(Self::AFTER_SLOTTED)
2837            && !self.contains(Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
2838    }
2839
2840    pub fn allows_combinators(self) -> bool {
2841        !self.contains(Self::DISALLOW_COMBINATORS)
2842    }
2843
2844    pub fn allows_custom_functional_pseudo_classes(self) -> bool {
2845        !self.after_any_pseudo()
2846    }
2847}
2848
2849// ─────────────────────────────────────────────────────────────────────────────
2850
2851#[derive(Clone, Copy, PartialEq, Eq)]
2852pub struct SpecificityAndFlags {
2853    /// There are two free bits here, since we use ten bits for each specificity
2854    /// kind (id, class, element).
2855    pub specificity: u32,
2856    /// There's padding after this field due to the size of the flags.
2857    pub flags: SelectorFlags,
2858}
2859
2860impl SpecificityAndFlags {
2861    pub fn has_pseudo_element(self) -> bool {
2862        self.flags.contains(SelectorFlags::HAS_PSEUDO)
2863    }
2864    pub fn hash(self, hasher: &mut Wyhash) {
2865        hasher.update(&self.specificity.to_ne_bytes());
2866        hasher.update(&[self.flags.bits()]);
2867    }
2868    pub fn deep_clone(self) -> Self {
2869        self
2870    }
2871}
2872
2873bitflags::bitflags! {
2874    #[derive(Clone, Copy, PartialEq, Eq)]
2875    pub struct SelectorFlags: u8 {
2876        const HAS_PSEUDO = 1 << 0;
2877        const HAS_SLOTTED = 1 << 1;
2878        const HAS_PART = 1 << 2;
2879    }
2880}
2881
2882/// How to treat invalid selectors in a selector list.
2883#[derive(Clone, Copy, PartialEq, Eq)]
2884pub enum ParseErrorRecovery {
2885    /// Discard the entire selector list, this is the default behavior for
2886    /// almost all of CSS.
2887    DiscardList,
2888    /// Ignore invalid selectors, potentially creating an empty selector list.
2889    ///
2890    /// This is the error recovery mode of :is() and :where()
2891    IgnoreInvalidSelector,
2892}
2893
2894#[derive(Clone, Copy, PartialEq, Eq)]
2895pub enum NestingRequirement {
2896    None,
2897    Prefixed,
2898    Contained,
2899    Implicit,
2900}
2901
2902#[derive(Clone, Copy, PartialEq, Eq, Hash, strum::IntoStaticStr, CssHash)]
2903pub enum Combinator {
2904    Child,        // >
2905    Descendant,   // space
2906    NextSibling,  // +
2907    LaterSibling, // ~
2908    /// A dummy combinator we use to the left of pseudo-elements.
2909    ///
2910    /// It serializes as the empty string, and acts effectively as a child
2911    /// combinator in most cases.  If we ever actually start using a child
2912    /// combinator for this, we will need to fix up the way hashes are computed
2913    /// for revalidation selectors.
2914    PseudoElement,
2915    /// Another combinator used for ::slotted(), which represent the jump from
2916    /// a node to its assigned slot.
2917    SlotAssignment,
2918
2919    /// Another combinator used for `::part()`, which represents the jump from
2920    /// the part to the containing shadow host.
2921    Part,
2922
2923    /// Non-standard Vue >>> combinator.
2924    /// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors
2925    DeepDescendant,
2926    /// Non-standard /deep/ combinator.
2927    /// Appeared in early versions of the css-scoping-1 specification:
2928    /// https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#deep-combinator
2929    /// And still supported as an alias for >>> by Vue.
2930    Deep,
2931}
2932
2933impl Combinator {
2934    // hash — via `#[derive(CssHash)]`.
2935
2936    /// Do not call this! Use `serializer::serialize_combinator()` or
2937    /// `tocss_servo::to_css_combinator()` instead.
2938    #[deprecated = "use serializer::serialize_combinator()"]
2939    pub fn to_css(self, _dest: &mut Printer) -> Result<(), PrintErr> {
2940        unreachable!("use serializer::serialize_combinator()");
2941    }
2942
2943    pub fn is_tree_combinator(self) -> bool {
2944        matches!(
2945            self,
2946            Self::Child | Self::Descendant | Self::NextSibling | Self::LaterSibling
2947        )
2948    }
2949}
2950
2951impl fmt::Display for Combinator {
2952    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2953        match self {
2954            Self::Child => write!(f, ">"),
2955            Self::Descendant => write!(f, "`descendant` (space)"),
2956            Self::NextSibling => write!(f, "+"),
2957            Self::LaterSibling => write!(f, "~"),
2958            other => write!(f, "{}", <&'static str>::from(*other)),
2959        }
2960    }
2961}
2962
2963#[derive(Clone)]
2964pub enum SelectorParseErrorKind {
2965    InvalidState,
2966    ClassNeedsIdent(Token),
2967    PseudoElementExpectedIdent(Token),
2968    UnsupportedPseudoClassOrElement(Str),
2969    NoQualifiedNameInAttributeSelector(Token),
2970    UnexpectedTokenInAttributeSelector(Token),
2971    UnexpectedSelectorAfterPseudoElement(Token),
2972    InvalidQualNameInAttr(Token),
2973    ExpectedBarInAttr(Token),
2974    EmptySelector,
2975    DanglingCombinator,
2976    InvalidPseudoClassBeforeWebkitScrollbar,
2977    InvalidPseudoClassAfterWebkitScrollbar,
2978    InvalidPseudoClassAfterPseudoElement,
2979    MissingNestingSelector,
2980    MissingNestingPrefix,
2981    ExpectedNamespace(Str),
2982    BadValueInAttr(Token),
2983    ExplicitNamespaceUnexpectedToken(Token),
2984    UnexpectedIdent(Str),
2985    AmbiguousCssModuleClass(Str),
2986}
2987
2988impl SelectorParseErrorKind {
2989    pub fn into_default_parser_error(self) -> css::ParserError {
2990        css::ParserError::selector_error(self.into_selector_error())
2991    }
2992
2993    pub fn into_selector_error(self) -> css::SelectorError {
2994        // PORT NOTE: `error.rs::SelectorError` variants are snake_case
2995        // (`#[allow(non_camel_case_types)]` Zig-tagName parity).
2996        use SelectorParseErrorKind as K;
2997        use css::SelectorError as S;
2998        match self {
2999            K::InvalidState => S::invalid_state,
3000            K::ClassNeedsIdent(token) => S::class_needs_ident(token),
3001            K::PseudoElementExpectedIdent(token) => S::pseudo_element_expected_ident(token),
3002            K::UnsupportedPseudoClassOrElement(name) => {
3003                S::unsupported_pseudo_class_or_element(name)
3004            }
3005            K::NoQualifiedNameInAttributeSelector(token) => {
3006                S::no_qualified_name_in_attribute_selector(token)
3007            }
3008            K::UnexpectedTokenInAttributeSelector(token) => {
3009                S::unexpected_token_in_attribute_selector(token)
3010            }
3011            K::InvalidQualNameInAttr(token) => S::invalid_qual_name_in_attr(token),
3012            K::ExpectedBarInAttr(token) => S::expected_bar_in_attr(token),
3013            K::EmptySelector => S::empty_selector,
3014            K::DanglingCombinator => S::dangling_combinator,
3015            K::InvalidPseudoClassBeforeWebkitScrollbar => {
3016                S::invalid_pseudo_class_before_webkit_scrollbar
3017            }
3018            K::InvalidPseudoClassAfterWebkitScrollbar => {
3019                S::invalid_pseudo_class_after_webkit_scrollbar
3020            }
3021            K::InvalidPseudoClassAfterPseudoElement => S::invalid_pseudo_class_after_pseudo_element,
3022            K::MissingNestingSelector => S::missing_nesting_selector,
3023            K::MissingNestingPrefix => S::missing_nesting_prefix,
3024            K::ExpectedNamespace(name) => S::expected_namespace(name),
3025            K::BadValueInAttr(token) => S::bad_value_in_attr(token),
3026            K::ExplicitNamespaceUnexpectedToken(token) => {
3027                S::explicit_namespace_unexpected_token(token)
3028            }
3029            K::UnexpectedIdent(ident) => S::unexpected_ident(ident),
3030            K::UnexpectedSelectorAfterPseudoElement(tok) => {
3031                S::unexpected_selector_after_pseudo_element(tok)
3032            }
3033            K::AmbiguousCssModuleClass(name) => S::ambiguous_css_module_class(name),
3034        }
3035    }
3036}
3037
3038// `SourceLocation::new_custom_error` accepts `impl IntoParserError`.
3039impl css::IntoParserError for SelectorParseErrorKind {
3040    fn into_parser_error(self) -> css::ParserError {
3041        self.into_default_parser_error()
3042    }
3043}
3044
3045pub enum SimpleSelectorParseResult<Impl: SelectorImpl> {
3046    SimpleSelector(GenericComponent<Impl>),
3047    PseudoElement(Impl::PseudoElement),
3048    SlottedPseudo(GenericSelector<Impl>),
3049    // todo_stuff.think_mem_mgmt
3050    PartPseudo(Box<[Impl::Identifier]>),
3051}
3052
3053/// A pseudo element.
3054// PORT NOTE: see PseudoClass — `PartialEq` derive dropped (Box<Selector>/TokenList).
3055#[derive(Clone, CssEql, CssHash)]
3056pub enum PseudoElement {
3057    /// The [::after](https://drafts.csswg.org/css-pseudo-4/#selectordef-after) pseudo element.
3058    After,
3059    /// The [::before](https://drafts.csswg.org/css-pseudo-4/#selectordef-before) pseudo element.
3060    Before,
3061    /// The [::first-line](https://drafts.csswg.org/css-pseudo-4/#first-line-pseudo) pseudo element.
3062    FirstLine,
3063    /// The [::first-letter](https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo) pseudo element.
3064    FirstLetter,
3065    /// The [::selection](https://drafts.csswg.org/css-pseudo-4/#selectordef-selection) pseudo element.
3066    Selection(css::VendorPrefix),
3067    /// The [::placeholder](https://drafts.csswg.org/css-pseudo-4/#placeholder-pseudo) pseudo element.
3068    Placeholder(css::VendorPrefix),
3069    /// The [::marker](https://drafts.csswg.org/css-pseudo-4/#marker-pseudo) pseudo element.
3070    Marker,
3071    /// The [::backdrop](https://fullscreen.spec.whatwg.org/#::backdrop-pseudo-element) pseudo element.
3072    Backdrop(css::VendorPrefix),
3073    /// The [::file-selector-button](https://drafts.csswg.org/css-pseudo-4/#file-selector-button-pseudo) pseudo element.
3074    FileSelectorButton(css::VendorPrefix),
3075    /// A [webkit scrollbar](https://webkit.org/blog/363/styling-scrollbars/) pseudo element.
3076    WebkitScrollbar(WebKitScrollbarPseudoElement),
3077    /// The [::cue](https://w3c.github.io/webvtt/#the-cue-pseudo-element) pseudo element.
3078    Cue,
3079    /// The [::cue-region](https://w3c.github.io/webvtt/#the-cue-region-pseudo-element) pseudo element.
3080    CueRegion,
3081    /// The [::cue()](https://w3c.github.io/webvtt/#cue-selector) functional pseudo element.
3082    CueFunction {
3083        /// The selector argument.
3084        selector: Box<Selector>,
3085    },
3086    /// The [::cue-region()](https://w3c.github.io/webvtt/#cue-region-selector) functional pseudo element.
3087    CueRegionFunction {
3088        /// The selector argument.
3089        selector: Box<Selector>,
3090    },
3091    /// The [::view-transition](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition) pseudo element.
3092    ViewTransition,
3093    /// The [::view-transition-group()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-group-pt-name-selector) functional pseudo element.
3094    ViewTransitionGroup {
3095        /// A part name selector.
3096        part_name: ViewTransitionPartName,
3097    },
3098    /// The [::view-transition-image-pair()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-image-pair-pt-name-selector) functional pseudo element.
3099    ViewTransitionImagePair {
3100        /// A part name selector.
3101        part_name: ViewTransitionPartName,
3102    },
3103    /// The [::view-transition-old()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-old-pt-name-selector) functional pseudo element.
3104    ViewTransitionOld {
3105        /// A part name selector.
3106        part_name: ViewTransitionPartName,
3107    },
3108    /// The [::view-transition-new()](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#view-transition-new-pt-name-selector) functional pseudo element.
3109    ViewTransitionNew {
3110        /// A part name selector.
3111        part_name: ViewTransitionPartName,
3112    },
3113    /// An unknown pseudo element.
3114    Custom {
3115        /// The name of the pseudo element.
3116        name: Str,
3117    },
3118    /// An unknown functional pseudo element.
3119    CustomFunction {
3120        /// The name of the pseudo element.
3121        name: Str,
3122        /// The arguments of the pseudo element function.
3123        arguments: TokenList,
3124    },
3125}
3126
3127impl PseudoElement {
3128    pub fn is_equivalent(&self, other: &PseudoElement) -> bool {
3129        use PseudoElement as PE;
3130        if matches!(self, PE::Selection(_)) && matches!(other, PE::Selection(_)) {
3131            return true;
3132        }
3133        if matches!(self, PE::Placeholder(_)) && matches!(other, PE::Placeholder(_)) {
3134            return true;
3135        }
3136        if matches!(self, PE::Backdrop(_)) && matches!(other, PE::Backdrop(_)) {
3137            return true;
3138        }
3139        if matches!(self, PE::FileSelectorButton(_)) && matches!(other, PE::FileSelectorButton(_)) {
3140            return true;
3141        }
3142        self.eql(other)
3143    }
3144
3145    // eql / hash — provided by `#[derive(CssEql, CssHash)]`.
3146    pub fn deep_clone(&self) -> Self {
3147        self.clone()
3148    }
3149
3150    pub fn get_necessary_prefixes(&mut self, targets: &css::targets::Targets) -> css::VendorPrefix {
3151        use PseudoElement as PE;
3152        use css::prefixes::Feature as F;
3153        let (p, feature): (&mut css::VendorPrefix, F) = match self {
3154            PE::Selection(p) => (p, F::PseudoElementSelection),
3155            PE::Placeholder(p) => (p, F::PseudoElementPlaceholder),
3156            PE::Backdrop(p) => (p, F::PseudoElementBackdrop),
3157            PE::FileSelectorButton(p) => (p, F::PseudoElementFileSelectorButton),
3158            _ => return css::VendorPrefix::empty(),
3159        };
3160        *p = targets.prefixes(*p, feature);
3161        *p
3162    }
3163
3164    pub fn get_prefix(&self) -> css::VendorPrefix {
3165        use PseudoElement as PE;
3166        match self {
3167            PE::Selection(p) | PE::Placeholder(p) | PE::Backdrop(p) | PE::FileSelectorButton(p) => {
3168                *p
3169            }
3170            _ => css::VendorPrefix::empty(),
3171        }
3172    }
3173
3174    pub fn valid_after_slotted(&self) -> bool {
3175        use PseudoElement as PE;
3176        matches!(
3177            self,
3178            PE::Before | PE::After | PE::Marker | PE::Placeholder(_) | PE::FileSelectorButton(_)
3179        )
3180    }
3181
3182    pub fn is_unknown(&self) -> bool {
3183        matches!(
3184            self,
3185            PseudoElement::Custom { .. } | PseudoElement::CustomFunction { .. }
3186        )
3187    }
3188
3189    pub fn accepts_state_pseudo_classes(&self) -> bool {
3190        let _ = self;
3191        // Be lienient.
3192        true
3193    }
3194
3195    pub fn is_webkit_scrollbar(&self) -> bool {
3196        matches!(self, PseudoElement::WebkitScrollbar(_))
3197    }
3198
3199    pub fn is_view_transition(&self) -> bool {
3200        use PseudoElement as PE;
3201        matches!(
3202            self,
3203            PE::ViewTransitionGroup { .. }
3204                | PE::ViewTransitionImagePair { .. }
3205                | PE::ViewTransitionNew { .. }
3206                | PE::ViewTransitionOld { .. }
3207        )
3208    }
3209
3210    pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
3211        // PERF(alloc): I don't like making small allocations here for the string.
3212        // PORT NOTE: see PseudoClass::to_css — write directly until
3213        // `Printer::new_buffered` lands.
3214        serialize::serialize_pseudo_element(self, dest, None)
3215    }
3216}
3217
3218impl fmt::Display for PseudoElement {
3219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3220        // TODO(port): @tagName — needs strum::IntoStaticStr.
3221        write!(f, "<pseudo_element>")
3222    }
3223}
3224
3225/// An enum for the different types of :nth- pseudoclasses
3226#[derive(Clone, Copy, PartialEq, Eq, Hash)]
3227pub enum NthType {
3228    Child,
3229    LastChild,
3230    OnlyChild,
3231    OfType,
3232    LastOfType,
3233    OnlyOfType,
3234    Col,
3235    LastCol,
3236}
3237
3238impl NthType {
3239    pub fn is_only(self) -> bool {
3240        self == NthType::OnlyChild || self == NthType::OnlyOfType
3241    }
3242
3243    pub fn is_of_type(self) -> bool {
3244        self == NthType::OfType || self == NthType::LastOfType || self == NthType::OnlyOfType
3245    }
3246
3247    pub fn is_from_end(self) -> bool {
3248        self == NthType::LastChild || self == NthType::LastOfType || self == NthType::LastCol
3249    }
3250
3251    pub fn allows_of_selector(self) -> bool {
3252        self == NthType::Child || self == NthType::LastChild
3253    }
3254}
3255
3256// ─────────────────────────────────────────────────────────────────────────────
3257// parse_type_selector / parse_one_simple_selector / parse_attribute_selector
3258// ─────────────────────────────────────────────────────────────────────────────
3259
3260/// * `Err(())`: Invalid selector, abort
3261/// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed.
3262/// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
3263pub fn parse_type_selector<Impl: BunSelectorImpl>(
3264    parser: &mut SelectorParser,
3265    input: &mut CssParser,
3266    state: SelectorParsingState,
3267    sink: &mut SelectorBuilder<Impl>,
3268) -> CResult<bool> {
3269    let result = match parse_qualified_name::<Impl>(parser, input, false) {
3270        Ok(v) => v,
3271        Err(e) => {
3272            if matches!(
3273                e.kind,
3274                css::ParseErrorKind::basic(css::BasicParseErrorKind::end_of_input)
3275            ) {
3276                return Ok(false);
3277            }
3278            return Err(e);
3279        }
3280    };
3281
3282    let (namespace, local_name) = match result {
3283        OptionalQName::None(_) => return Ok(false),
3284        OptionalQName::Some(ns, ln) => (ns, ln),
3285    };
3286
3287    if state.after_any_pseudo() {
3288        return Err(input
3289            .new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
3290    }
3291
3292    match namespace {
3293        QNamePrefix::ImplicitAnyNamespace => {}
3294        QNamePrefix::ImplicitDefaultNamespace(url) => {
3295            sink.push_simple_selector(GenericComponent::DefaultNamespace(url));
3296        }
3297        QNamePrefix::ExplicitNamespace(prefix, url) => {
3298            let component: GenericComponent<Impl> = 'component: {
3299                if let Some(default_url) = parser.default_namespace() {
3300                    if url == default_url {
3301                        break 'component GenericComponent::DefaultNamespace(url);
3302                    }
3303                }
3304                GenericComponent::Namespace { prefix, url }
3305            };
3306            sink.push_simple_selector(component);
3307        }
3308        QNamePrefix::ExplicitNoNamespace => {
3309            sink.push_simple_selector(GenericComponent::ExplicitNoNamespace);
3310        }
3311        QNamePrefix::ExplicitAnyNamespace => {
3312            // Element type selectors that have no namespace
3313            // component (no namespace separator) represent elements
3314            // without regard to the element's namespace (equivalent
3315            // to "*|") unless a default namespace has been declared
3316            // for namespaced selectors (e.g. in CSS, in the style
3317            // sheet). If a default namespace has been declared,
3318            // such selectors will represent only elements in the
3319            // default namespace.
3320            // -- Selectors § 6.1.1
3321            // So we'll have this act the same as the
3322            // QNamePrefix::ImplicitAnyNamespace case.
3323            // For lightning css this logic was removed, should be handled when matching.
3324            sink.push_simple_selector(GenericComponent::ExplicitAnyNamespace);
3325        }
3326        QNamePrefix::ImplicitNoNamespace => {
3327            unreachable!("Should not be returned with in_attr_selector = false");
3328        }
3329    }
3330
3331    if let Some(name) = local_name {
3332        sink.push_simple_selector(GenericComponent::LocalName(LocalName {
3333            lower_name: {
3334                // PERF: check if it's already lowercase
3335                // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`).
3336                Ident {
3337                    v: arena_lowercase(input.arena(), name),
3338                }
3339            },
3340            name: Ident {
3341                v: std::ptr::from_ref::<[u8]>(name),
3342            },
3343        }));
3344    } else {
3345        sink.push_simple_selector(GenericComponent::ExplicitUniversalType);
3346    }
3347
3348    Ok(true)
3349}
3350
3351/// Parse a simple selector other than a type selector.
3352///
3353/// * `Err(())`: Invalid selector, abort
3354/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed.
3355/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element
3356pub fn parse_one_simple_selector<Impl: BunSelectorImpl>(
3357    parser: &mut SelectorParser,
3358    input: &mut CssParser,
3359    state: &mut SelectorParsingState,
3360) -> CResult<Option<SimpleSelectorParseResult<Impl>>> {
3361    type S<Impl> = SimpleSelectorParseResult<Impl>;
3362
3363    let start = input.state();
3364    let token_location = input.current_source_location();
3365    let token_loc = input.position();
3366    let token = match input.next_including_whitespace() {
3367        Ok(v) => v.clone(),
3368        Err(_) => {
3369            input.reset(&start);
3370            return Ok(None);
3371        }
3372    };
3373
3374    match token {
3375        Token::IdHash(id) => {
3376            if state.after_any_pseudo() {
3377                return Err(token_location.new_custom_error(
3378                    SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IdHash(id))
3379                        .into_default_parser_error(),
3380                ));
3381            }
3382            let component = GenericComponent::Id(parser.new_local_identifier(
3383                input,
3384                css::CssRefTag::ID,
3385                id,
3386                token_loc,
3387            ));
3388            return Ok(Some(S::SimpleSelector(component)));
3389        }
3390        Token::OpenSquare => {
3391            if state.after_any_pseudo() {
3392                return Err(token_location.new_custom_error(
3393                    SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::OpenSquare)
3394                        .into_default_parser_error(),
3395                ));
3396            }
3397            let attr = input.parse_nested_block(|input2: &mut CssParser| {
3398                parse_attribute_selector::<Impl>(parser, input2)
3399            })?;
3400            return Ok(Some(S::SimpleSelector(attr)));
3401        }
3402        Token::Colon => {
3403            let location = input.current_source_location();
3404            let (is_single_colon, next_token): (bool, Token) =
3405                match input.next_including_whitespace()?.clone() {
3406                    Token::Colon => (false, input.next_including_whitespace()?.clone()),
3407                    t => (true, t),
3408                };
3409            let (name, is_functional): (Str, bool) = match next_token {
3410                Token::Ident(name) => (name, false),
3411                Token::Function(name) => (name, true),
3412                t => {
3413                    let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t);
3414                    return Err(input.new_custom_error(e.into_default_parser_error()));
3415                }
3416            };
3417            let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(name);
3418            if is_pseudo_element {
3419                if !state.allows_pseudos() {
3420                    return Err(input.new_custom_error(
3421                        SelectorParseErrorKind::InvalidState.into_default_parser_error(),
3422                    ));
3423                }
3424                let pseudo_element: Impl::PseudoElement = if is_functional {
3425                    if parser.parse_part()
3426                        && strings::eql_case_insensitive_ascii_check_length(name, b"part")
3427                    {
3428                        if !state.allows_part() {
3429                            return Err(input.new_custom_error(
3430                                SelectorParseErrorKind::InvalidState.into_default_parser_error(),
3431                            ));
3432                        }
3433
3434                        let names = input.parse_nested_block(
3435                            |input2: &mut CssParser| -> CResult<Box<[Impl::Identifier]>> {
3436                                // todo_stuff.think_about_mem_mgmt
3437                                // PERF(port): was arena ArrayList with capacity 1 — profile if hot.
3438                                let mut result: Vec<Impl::Identifier> = Vec::with_capacity(1);
3439
3440                                result.push(Ident {
3441                                    v: input2.expect_ident()?,
3442                                });
3443
3444                                while !input2.is_exhausted() {
3445                                    result.push(Ident {
3446                                        v: input2.expect_ident()?,
3447                                    });
3448                                }
3449
3450                                Ok(result.into_boxed_slice())
3451                            },
3452                        )?;
3453
3454                        return Ok(Some(S::PartPseudo(names)));
3455                    }
3456
3457                    if parser.parse_slotted()
3458                        && strings::eql_case_insensitive_ascii_check_length(name, b"slotted")
3459                    {
3460                        if !state.allows_slotted() {
3461                            return Err(input.new_custom_error(
3462                                SelectorParseErrorKind::InvalidState.into_default_parser_error(),
3463                            ));
3464                        }
3465                        let selector = input.parse_nested_block(|input2: &mut CssParser| {
3466                            parse_inner_compound_selector::<Impl>(parser, input2, state)
3467                        })?;
3468                        return Ok(Some(S::SlottedPseudo(selector)));
3469                    }
3470
3471                    input.parse_nested_block(|i: &mut CssParser| {
3472                        parser.parse_functional_pseudo_element(name, i)
3473                    })?
3474                    // TODO(port): `Impl::PseudoElement` is `PseudoElement` for the concrete
3475                    // `impl_::Selectors`; the generic path would need a `From`/trait bound.
3476                } else {
3477                    parser.parse_pseudo_element(location, name)?
3478                };
3479
3480                if state.contains(SelectorParsingState::AFTER_SLOTTED)
3481                    && pseudo_element.valid_after_slotted()
3482                {
3483                    return Ok(Some(S::PseudoElement(pseudo_element)));
3484                }
3485
3486                return Ok(Some(S::PseudoElement(pseudo_element)));
3487            } else {
3488                let pseudo_class: GenericComponent<Impl> = if is_functional {
3489                    input.parse_nested_block(|input2: &mut CssParser| {
3490                        parse_functional_pseudo_class::<Impl>(parser, input2, name, state)
3491                    })?
3492                } else {
3493                    parse_simple_pseudo_class::<Impl>(parser, location, name, *state)?
3494                };
3495                return Ok(Some(S::SimpleSelector(pseudo_class)));
3496            }
3497        }
3498        Token::Delim(d) => match u8::try_from(d).ok() {
3499            Some(b'.') => {
3500                if state.after_any_pseudo() {
3501                    return Err(token_location.new_custom_error(
3502                        SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim(
3503                            b'.' as u32,
3504                        ))
3505                        .into_default_parser_error(),
3506                    ));
3507                }
3508                let location = input.current_source_location();
3509                let class = match input.next_including_whitespace()?.clone() {
3510                    Token::Ident(class) => class,
3511                    t => {
3512                        let e = SelectorParseErrorKind::ClassNeedsIdent(t);
3513                        return Err(location.new_custom_error(e.into_default_parser_error()));
3514                    }
3515                };
3516                return Ok(Some(S::SimpleSelector(GenericComponent::Class(
3517                    parser.new_local_identifier(input, css::CssRefTag::CLASS, class, token_loc),
3518                ))));
3519            }
3520            Some(b'&') => {
3521                if parser.is_nesting_allowed() {
3522                    state.insert(SelectorParsingState::AFTER_NESTING);
3523                    return Ok(Some(S::SimpleSelector(GenericComponent::Nesting)));
3524                }
3525            }
3526            _ => {}
3527        },
3528        _ => {}
3529    }
3530
3531    input.reset(&start);
3532    Ok(None)
3533}
3534
3535pub fn parse_attribute_selector<Impl: BunSelectorImpl>(
3536    parser: &mut SelectorParser,
3537    input: &mut CssParser,
3538) -> CResult<GenericComponent<Impl>> {
3539    type N<Impl> = attrs::NamespaceConstraint<attrs::NamespaceUrl<Impl>>;
3540
3541    let (namespace, local_name): (Option<N<Impl>>, Str) = 'brk: {
3542        input.skip_whitespace();
3543
3544        let qname = parse_qualified_name::<Impl>(parser, input, true)?;
3545        match qname {
3546            OptionalQName::None(t) => {
3547                return Err(input.new_custom_error(
3548                    SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t)
3549                        .into_default_parser_error(),
3550                ));
3551            }
3552            OptionalQName::Some(ns, ln) => {
3553                let ln = ln.unwrap_or_else(|| unreachable!());
3554                break 'brk (
3555                    match ns {
3556                        QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None,
3557                        QNamePrefix::ExplicitNamespace(prefix, url) => {
3558                            Some(attrs::NamespaceConstraint::Specific(attrs::NamespaceUrl {
3559                                prefix,
3560                                url,
3561                            }))
3562                        }
3563                        QNamePrefix::ExplicitAnyNamespace => Some(attrs::NamespaceConstraint::Any),
3564                        QNamePrefix::ImplicitAnyNamespace
3565                        | QNamePrefix::ImplicitDefaultNamespace(_) => {
3566                            unreachable!("Not returned with in_attr_selector = true");
3567                        }
3568                    },
3569                    ln,
3570                );
3571            }
3572        }
3573    };
3574
3575    let location = input.current_source_location();
3576    let operator: attrs::AttrSelectorOperator = 'operator: {
3577        let tok = match input.next() {
3578            Ok(v) => v.clone(),
3579            Err(_) => {
3580                // [foo]
3581                // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`).
3582                let local_name_lower: *const [u8] = arena_lowercase(input.arena(), local_name);
3583                if let Some(ns) = namespace {
3584                    let x = attrs::AttrSelectorWithOptionalNamespace::<Impl> {
3585                        namespace: Some(ns),
3586                        local_name: Ident { v: local_name },
3587                        local_name_lower: Ident {
3588                            v: local_name_lower,
3589                        },
3590                        never_matches: false,
3591                        operation: attrs::ParsedAttrSelectorOperation::Exists,
3592                    };
3593                    return Ok(GenericComponent::AttributeOther(Box::new(x)));
3594                } else {
3595                    return Ok(GenericComponent::AttributeInNoNamespaceExists {
3596                        local_name: Ident { v: local_name },
3597                        local_name_lower: Ident {
3598                            v: local_name_lower,
3599                        },
3600                    });
3601                }
3602            }
3603        };
3604
3605        match tok {
3606            // [foo=bar]
3607            Token::Delim(d) if d == b'=' as u32 => {
3608                break 'operator attrs::AttrSelectorOperator::Equal;
3609            }
3610            // [foo~=bar]
3611            Token::IncludeMatch => break 'operator attrs::AttrSelectorOperator::Includes,
3612            // [foo|=bar]
3613            Token::DashMatch => break 'operator attrs::AttrSelectorOperator::DashMatch,
3614            // [foo^=bar]
3615            Token::PrefixMatch => break 'operator attrs::AttrSelectorOperator::Prefix,
3616            // [foo*=bar]
3617            Token::SubstringMatch => break 'operator attrs::AttrSelectorOperator::Substring,
3618            // [foo$=bar]
3619            Token::SuffixMatch => break 'operator attrs::AttrSelectorOperator::Suffix,
3620            _ => {}
3621        }
3622        return Err(location.new_custom_error(
3623            SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(tok)
3624                .into_default_parser_error(),
3625        ));
3626    };
3627
3628    // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]` (lifetime-tied to
3629    // `&mut *input`); `parse_attribute_flags(input)` below needs `input` again.
3630    // Clone the token so the borrow is released before we re-borrow.
3631    let value_str: Str = {
3632        let value_loc = input.current_source_location();
3633        let tok = input.next()?.clone();
3634        match tok {
3635            Token::Ident(v) | Token::QuotedString(v) => v,
3636            t => {
3637                return Err(value_loc.new_custom_error(
3638                    SelectorParseErrorKind::BadValueInAttr(t).into_default_parser_error(),
3639                ));
3640            }
3641        }
3642    };
3643    let never_matches = match operator {
3644        attrs::AttrSelectorOperator::Equal | attrs::AttrSelectorOperator::DashMatch => false,
3645        attrs::AttrSelectorOperator::Includes => {
3646            value_str.is_empty() || strings::index_of_any(value_str, SELECTOR_WHITESPACE).is_some()
3647        }
3648        attrs::AttrSelectorOperator::Prefix
3649        | attrs::AttrSelectorOperator::Substring
3650        | attrs::AttrSelectorOperator::Suffix => value_str.is_empty(),
3651    };
3652
3653    let attribute_flags = parse_attribute_flags(input)?;
3654
3655    let value: Impl::AttrValue = std::ptr::from_ref::<[u8]>(value_str);
3656    let (local_name_lower, local_name_is_ascii_lowercase): (Impl::LocalName, bool) = 'brk: {
3657        let first_uppercase = 'a: {
3658            for (i, &b) in local_name.iter().enumerate() {
3659                if b >= b'A' && b <= b'Z' {
3660                    break 'a Some(i);
3661                }
3662            }
3663            None
3664        };
3665        if let Some(first_uppercase) = first_uppercase {
3666            let str_ = &local_name[first_uppercase..];
3667            // PERF(port): was arena alloc — profile if hot (see `arena_lowercase`).
3668            let lowered: *const [u8] = arena_lowercase(input.arena(), str_);
3669            break 'brk (Ident { v: lowered }, false);
3670        } else {
3671            break 'brk (
3672                Ident {
3673                    v: std::ptr::from_ref::<[u8]>(local_name),
3674                },
3675                true,
3676            );
3677        }
3678    };
3679    let case_sensitivity: attrs::ParsedCaseSensitivity =
3680        attribute_flags.to_case_sensitivity(local_name_lower.v(), namespace.is_some());
3681    if namespace.is_some() && !local_name_is_ascii_lowercase {
3682        Ok(GenericComponent::AttributeOther(Box::new(
3683            attrs::AttrSelectorWithOptionalNamespace::<Impl> {
3684                namespace,
3685                local_name: Ident {
3686                    v: std::ptr::from_ref::<[u8]>(local_name),
3687                },
3688                local_name_lower,
3689                never_matches,
3690                operation: attrs::ParsedAttrSelectorOperation::WithValue {
3691                    operator,
3692                    case_sensitivity,
3693                    expected_value: value,
3694                },
3695            },
3696        )))
3697    } else {
3698        Ok(GenericComponent::AttributeInNoNamespace {
3699            local_name: Ident { v: local_name },
3700            operator,
3701            value,
3702            case_sensitivity,
3703            never_matches,
3704        })
3705    }
3706}
3707
3708/// Returns whether the name corresponds to a CSS2 pseudo-element that
3709/// can be specified with the single colon syntax (in addition to the
3710/// double-colon syntax, which can be used for all pseudo-elements).
3711pub fn is_css2_pseudo_element(name: &[u8]) -> bool {
3712    // ** Do not add to this list! **
3713    crate::match_ignore_ascii_case! { name, {
3714        b"before" | b"after" | b"first-line" | b"first-letter" => true,
3715        _ => false,
3716    }}
3717}
3718
3719/// Parses one compound selector suitable for nested stuff like :-moz-any, etc.
3720pub fn parse_inner_compound_selector<Impl: BunSelectorImpl>(
3721    parser: &mut SelectorParser,
3722    input: &mut CssParser,
3723    state: &mut SelectorParsingState,
3724) -> CResult<GenericSelector<Impl>> {
3725    let mut child_state = {
3726        let mut child_state = *state;
3727        child_state.insert(SelectorParsingState::DISALLOW_PSEUDOS);
3728        child_state.insert(SelectorParsingState::DISALLOW_COMBINATORS);
3729        child_state
3730    };
3731    let result = parse_selector::<Impl>(parser, input, &mut child_state, NestingRequirement::None)?;
3732    if child_state.contains(SelectorParsingState::AFTER_NESTING) {
3733        state.insert(SelectorParsingState::AFTER_NESTING);
3734    }
3735    Ok(result)
3736}
3737
3738pub fn parse_functional_pseudo_class<Impl: BunSelectorImpl>(
3739    parser: &mut SelectorParser,
3740    input: &mut CssParser,
3741    name: Str,
3742    state: &mut SelectorParsingState,
3743) -> CResult<GenericComponent<Impl>> {
3744    crate::match_ignore_ascii_case! { name, {
3745        b"nth-child" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::Child),
3746        b"nth-of-type" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::OfType),
3747        b"nth-last-child" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::LastChild),
3748        b"nth-last-of-type" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::LastOfType),
3749        b"nth-col" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::Col),
3750        b"nth-last-col" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::LastCol),
3751        b"is" => if parser.parse_is_and_where() {
3752            return parse_is_or_where::<Impl, _>(parser, input, state, |s| GenericComponent::convert_helper_is(s));
3753        },
3754        b"where" => if parser.parse_is_and_where() {
3755            return parse_is_or_where::<Impl, _>(parser, input, state, |s| GenericComponent::convert_helper_where(s));
3756        },
3757        b"has" => return parse_has::<Impl>(parser, input, state),
3758        b"host" => {
3759            if !state.allows_tree_structural_pseudo_classes() {
3760                return Err(input.new_custom_error(
3761                    SelectorParseErrorKind::InvalidState.into_default_parser_error(),
3762                ));
3763            }
3764            return Ok(GenericComponent::Host(Some(
3765                parse_inner_compound_selector::<Impl>(parser, input, state)?,
3766            )));
3767        },
3768        b"not" => return parse_negation::<Impl>(parser, input, state),
3769        _ => {},
3770    } }
3771
3772    if let Some(prefix) = parser.parse_any_prefix(name) {
3773        return parse_is_or_where::<Impl, _>(parser, input, state, move |s| {
3774            GenericComponent::convert_helper_any(s, prefix)
3775        });
3776    }
3777
3778    if !state.allows_custom_functional_pseudo_classes() {
3779        return Err(input
3780            .new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
3781    }
3782
3783    let result = parser.parse_non_ts_functional_pseudo_class(name, input)?;
3784
3785    Ok(GenericComponent::NonTsPseudoClass(result))
3786    // TODO(port): `Impl::NonTSPseudoClass` is `PseudoClass` for the concrete impl;
3787    // generic path would need a `From` bound.
3788}
3789
3790pub fn parse_simple_pseudo_class<Impl: BunSelectorImpl>(
3791    parser: &mut SelectorParser,
3792    location: css::SourceLocation,
3793    name: Str,
3794    state: SelectorParsingState,
3795) -> CResult<GenericComponent<Impl>> {
3796    if !state.allows_non_functional_pseudo_classes() {
3797        return Err(location
3798            .new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
3799    }
3800
3801    if state.allows_tree_structural_pseudo_classes() {
3802        crate::match_ignore_ascii_case! { name, {
3803            b"first-child" => return Ok(GenericComponent::Nth(NthSelectorData::first(false))),
3804            b"last-child" => return Ok(GenericComponent::Nth(NthSelectorData::last(false))),
3805            b"only-child" => return Ok(GenericComponent::Nth(NthSelectorData::only(false))),
3806            b"root" => return Ok(GenericComponent::Root),
3807            b"empty" => return Ok(GenericComponent::Empty),
3808            b"scope" => return Ok(GenericComponent::Scope),
3809            b"host" => if parser.parse_host() {
3810                return Ok(GenericComponent::Host(None));
3811            },
3812            b"first-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::first(true))),
3813            b"last-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::last(true))),
3814            b"only-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::only(true))),
3815            _ => {},
3816        } }
3817    }
3818
3819    // The view-transition pseudo elements accept the :only-child pseudo class.
3820    // https://w3c.github.io/csswg-drafts/css-view-transitions-1/#pseudo-root
3821    if state.contains(SelectorParsingState::AFTER_VIEW_TRANSITION) {
3822        if strings::eql_case_insensitive_ascii_check_length(name, b"only-child") {
3823            return Ok(GenericComponent::Nth(NthSelectorData::only(false)));
3824        }
3825    }
3826
3827    let pseudo_class = parser.parse_non_ts_pseudo_class(location, name)?;
3828    if state.contains(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) {
3829        if !pseudo_class.is_valid_after_webkit_scrollbar() {
3830            return Err(location.new_custom_error(
3831                SelectorParseErrorKind::InvalidPseudoClassAfterWebkitScrollbar
3832                    .into_default_parser_error(),
3833            ));
3834        }
3835    } else if state.contains(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {
3836        if !pseudo_class.is_user_action_state() {
3837            return Err(location.new_custom_error(
3838                SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement
3839                    .into_default_parser_error(),
3840            ));
3841        }
3842    } else if !pseudo_class.is_valid_before_webkit_scrollbar() {
3843        return Err(location.new_custom_error(
3844            SelectorParseErrorKind::InvalidPseudoClassBeforeWebkitScrollbar
3845                .into_default_parser_error(),
3846        ));
3847    }
3848
3849    Ok(GenericComponent::NonTsPseudoClass(pseudo_class))
3850}
3851
3852pub fn parse_nth_pseudo_class<Impl: BunSelectorImpl>(
3853    parser: &mut SelectorParser,
3854    input: &mut CssParser,
3855    state: SelectorParsingState,
3856    ty: NthType,
3857) -> CResult<GenericComponent<Impl>> {
3858    if !state.allows_tree_structural_pseudo_classes() {
3859        return Err(input
3860            .new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
3861    }
3862
3863    let (a, b) = css::nth::parse_nth(input)?;
3864    let nth_data = NthSelectorData {
3865        ty,
3866        is_function: true,
3867        a,
3868        b,
3869    };
3870
3871    if !ty.allows_of_selector() {
3872        return Ok(GenericComponent::Nth(nth_data));
3873    }
3874
3875    // Try to parse "of <selector-list>".
3876    if input.try_parse(|i| i.expect_ident_matching(b"of")).is_err() {
3877        return Ok(GenericComponent::Nth(nth_data));
3878    }
3879
3880    // Whitespace between "of" and the selector list is optional
3881    // https://github.com/w3c/csswg-drafts/issues/8285
3882    let mut child_state = {
3883        let mut s = state;
3884        s.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE);
3885        s.insert(SelectorParsingState::DISALLOW_PSEUDOS);
3886        s
3887    };
3888
3889    let selectors = GenericSelectorList::<Impl>::parse_with_state(
3890        parser,
3891        input,
3892        &mut child_state,
3893        ParseErrorRecovery::IgnoreInvalidSelector,
3894        NestingRequirement::None,
3895    )?;
3896
3897    Ok(GenericComponent::NthOf(NthOfSelectorData {
3898        data: nth_data,
3899        selectors: selectors.into_boxed_selectors(),
3900    }))
3901}
3902
3903/// `func` must take `Box<[GenericSelector<Impl>]>` (plus any captured extras) and
3904/// return a `GenericComponent<Impl>`.
3905pub fn parse_is_or_where<Impl: BunSelectorImpl, F>(
3906    parser: &mut SelectorParser,
3907    input: &mut CssParser,
3908    state: &mut SelectorParsingState,
3909    func: F,
3910) -> CResult<GenericComponent<Impl>>
3911where
3912    F: FnOnce(Box<[GenericSelector<Impl>]>) -> GenericComponent<Impl>,
3913{
3914    debug_assert!(parser.parse_is_and_where());
3915    // https://drafts.csswg.org/selectors/#matches-pseudo:
3916    //
3917    //     Pseudo-elements cannot be represented by the matches-any
3918    //     pseudo-class; they are not valid within :is().
3919    //
3920    let mut child_state = {
3921        let mut child_state = *state;
3922        child_state.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE);
3923        child_state.insert(SelectorParsingState::DISALLOW_PSEUDOS);
3924        child_state
3925    };
3926
3927    let inner = GenericSelectorList::<Impl>::parse_with_state(
3928        parser,
3929        input,
3930        &mut child_state,
3931        parser.is_and_where_error_recovery(),
3932        NestingRequirement::None,
3933    )?;
3934    if child_state.contains(SelectorParsingState::AFTER_NESTING) {
3935        state.insert(SelectorParsingState::AFTER_NESTING);
3936    }
3937
3938    let selector_slice = inner.into_boxed_selectors();
3939
3940    // PORT NOTE: Zig threaded extra `args_` through an ArgsTuple to `func`; in Rust
3941    // the closure captures extras directly (e.g. `prefix` for `:any()`).
3942    let result = func(selector_slice);
3943
3944    Ok(result)
3945}
3946
3947pub fn parse_has<Impl: BunSelectorImpl>(
3948    parser: &mut SelectorParser,
3949    input: &mut CssParser,
3950    state: &mut SelectorParsingState,
3951) -> CResult<GenericComponent<Impl>> {
3952    let mut child_state = *state;
3953    let inner = GenericSelectorList::<Impl>::parse_relative_with_state(
3954        parser,
3955        input,
3956        &mut child_state,
3957        parser.is_and_where_error_recovery(),
3958        NestingRequirement::None,
3959    )?;
3960
3961    if child_state.contains(SelectorParsingState::AFTER_NESTING) {
3962        state.insert(SelectorParsingState::AFTER_NESTING);
3963    }
3964    Ok(GenericComponent::Has(inner.into_boxed_selectors()))
3965}
3966
3967/// Level 3: Parse **one** simple_selector.  (Though we might insert a second
3968/// implied "<defaultns>|*" type selector.)
3969pub fn parse_negation<Impl: BunSelectorImpl>(
3970    parser: &mut SelectorParser,
3971    input: &mut CssParser,
3972    state: &mut SelectorParsingState,
3973) -> CResult<GenericComponent<Impl>> {
3974    let mut child_state = *state;
3975    child_state.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE);
3976    child_state.insert(SelectorParsingState::DISALLOW_PSEUDOS);
3977
3978    let list = GenericSelectorList::<Impl>::parse_with_state(
3979        parser,
3980        input,
3981        &mut child_state,
3982        ParseErrorRecovery::DiscardList,
3983        NestingRequirement::None,
3984    )?;
3985
3986    if child_state.contains(SelectorParsingState::AFTER_NESTING) {
3987        state.insert(SelectorParsingState::AFTER_NESTING);
3988    }
3989
3990    Ok(GenericComponent::Negation(list.into_boxed_selectors()))
3991}
3992
3993pub enum OptionalQName<Impl: SelectorImpl> {
3994    Some(QNamePrefix<Impl>, Option<Str>),
3995    None(Token),
3996}
3997
3998pub enum QNamePrefix<Impl: SelectorImpl> {
3999    ImplicitNoNamespace,                          // `foo` in attr selectors
4000    ImplicitAnyNamespace,                         // `foo` in type selectors, without a default ns
4001    ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns
4002    ExplicitNoNamespace,                          // `|foo`
4003    ExplicitAnyNamespace,                         // `*|foo`
4004    ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo`
4005}
4006
4007/// * `Err(())`: Invalid selector, abort
4008/// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed,
4009///                      but the token is still returned.
4010/// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector
4011pub fn parse_qualified_name<Impl: BunSelectorImpl>(
4012    parser: &mut SelectorParser,
4013    input: &mut CssParser,
4014    in_attr_selector: bool,
4015) -> CResult<OptionalQName<Impl>> {
4016    let start = input.state();
4017
4018    let tok = match input.next_including_whitespace() {
4019        Ok(v) => v.clone(),
4020        Err(e) => {
4021            input.reset(&start);
4022            return Err(e);
4023        }
4024    };
4025    match &tok {
4026        Token::Ident(value) => {
4027            let value = *value;
4028            let after_ident = input.state();
4029            let n = if let Ok(t) = input.next_including_whitespace() {
4030                matches!(t, Token::Delim(d) if *d == b'|' as u32)
4031            } else {
4032                false
4033            };
4034            if n {
4035                let prefix: Impl::NamespacePrefix = Ident { v: value };
4036                let result: Option<Impl::NamespaceUrl> =
4037                    parser.namespace_for_prefix(Ident { v: value });
4038                let url: Impl::NamespaceUrl = match result {
4039                    Some(url) => url,
4040                    None => {
4041                        return Err(input.new_custom_error(
4042                            SelectorParseErrorKind::UnsupportedPseudoClassOrElement(value)
4043                                .into_default_parser_error(),
4044                        ));
4045                    }
4046                };
4047                return parse_qualified_name_eplicit_namespace_helper::<Impl>(
4048                    input,
4049                    QNamePrefix::ExplicitNamespace(prefix, url),
4050                    in_attr_selector,
4051                );
4052            } else {
4053                input.reset(&after_ident);
4054                if in_attr_selector {
4055                    return Ok(OptionalQName::Some(
4056                        QNamePrefix::ImplicitNoNamespace,
4057                        Some(value),
4058                    ));
4059                }
4060                return Ok(parse_qualified_name_default_namespace_helper::<Impl>(
4061                    parser,
4062                    Some(value),
4063                ));
4064            }
4065        }
4066        Token::Delim(c) => match u8::try_from(*c).ok() {
4067            Some(b'*') => {
4068                let after_star = input.state();
4069                let result = input.next_including_whitespace();
4070                if let Ok(t) = &result {
4071                    if matches!(t, Token::Delim(d) if *d == b'|' as u32) {
4072                        return parse_qualified_name_eplicit_namespace_helper::<Impl>(
4073                            input,
4074                            QNamePrefix::ExplicitAnyNamespace,
4075                            in_attr_selector,
4076                        );
4077                    }
4078                }
4079                // PORT NOTE: reshaped for borrowck — clone token before reset.
4080                let result_cloned = result.cloned();
4081                input.reset(&after_star);
4082                if in_attr_selector {
4083                    let t = result_cloned?;
4084                    return Err(after_star
4085                        .source_location()
4086                        .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)));
4087                } else {
4088                    return Ok(parse_qualified_name_default_namespace_helper::<Impl>(
4089                        parser, None,
4090                    ));
4091                }
4092            }
4093            Some(b'|') => {
4094                return parse_qualified_name_eplicit_namespace_helper::<Impl>(
4095                    input,
4096                    QNamePrefix::ExplicitNoNamespace,
4097                    in_attr_selector,
4098                );
4099            }
4100            _ => {}
4101        },
4102        _ => {}
4103    }
4104    input.reset(&start);
4105    Ok(OptionalQName::None(tok))
4106}
4107
4108fn parse_qualified_name_default_namespace_helper<Impl: BunSelectorImpl>(
4109    parser: &mut SelectorParser,
4110    local_name: Option<Str>,
4111) -> OptionalQName<Impl> {
4112    let namespace: QNamePrefix<Impl> = if let Some(url) = parser.default_namespace() {
4113        QNamePrefix::ImplicitDefaultNamespace(url)
4114    } else {
4115        QNamePrefix::ImplicitAnyNamespace
4116    };
4117    OptionalQName::Some(namespace, local_name)
4118}
4119
4120fn parse_qualified_name_eplicit_namespace_helper<Impl: BunSelectorImpl>(
4121    input: &mut CssParser,
4122    namespace: QNamePrefix<Impl>,
4123    in_attr_selector: bool,
4124) -> CResult<OptionalQName<Impl>> {
4125    let location = input.current_source_location();
4126    let t = input.next_including_whitespace()?.clone();
4127    match &t {
4128        Token::Ident(local_name) => return Ok(OptionalQName::Some(namespace, Some(*local_name))),
4129        // `*` is only a valid local name outside of attribute selectors;
4130        // `[ns|*]` must fall through to the `InvalidQualNameInAttr` error below.
4131        Token::Delim(c) if *c == b'*' as u32 && !in_attr_selector => {
4132            return Ok(OptionalQName::Some(namespace, None));
4133        }
4134        _ => {}
4135    }
4136    if in_attr_selector {
4137        let e = SelectorParseErrorKind::InvalidQualNameInAttr(t);
4138        return Err(location.new_custom_error(e));
4139    }
4140    Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t)))
4141}
4142
4143#[derive(Clone, PartialEq)]
4144pub struct LocalName<Impl: SelectorImpl> {
4145    pub name: Impl::LocalName,
4146    pub lower_name: Impl::LocalName,
4147}
4148
4149impl<Impl: BunSelectorImpl> LocalName<Impl> {
4150    pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
4151        IdentFns::to_css(&self.name, dest)
4152    }
4153    pub fn eql(&self, rhs: &Self) -> bool {
4154        self.name.eql(&rhs.name) && self.lower_name.eql(&rhs.lower_name)
4155    }
4156    pub fn hash(&self, hasher: &mut Wyhash) {
4157        self.name.hash(hasher);
4158        self.lower_name.hash(hasher);
4159    }
4160    pub fn deep_clone(&self) -> Self {
4161        Self {
4162            name: self.name,
4163            lower_name: self.lower_name,
4164        }
4165    }
4166}
4167
4168/// An attribute selector can have 's' or 'i' as flags, or no flags at all.
4169#[derive(Clone, Copy, PartialEq, Eq)]
4170pub enum AttributeFlags {
4171    // Matching should be case-sensitive ('s' flag).
4172    CaseSensitive,
4173    // Matching should be case-insensitive ('i' flag).
4174    AsciiCaseInsensitive,
4175    // No flags.  Matching behavior depends on the name of the attribute.
4176    CaseSensitivityDependsOnName,
4177}
4178
4179impl AttributeFlags {
4180    pub fn to_case_sensitivity(
4181        self,
4182        local_name: &[u8],
4183        have_namespace: bool,
4184    ) -> attrs::ParsedCaseSensitivity {
4185        match self {
4186            AttributeFlags::CaseSensitive => attrs::ParsedCaseSensitivity::ExplicitCaseSensitive,
4187            AttributeFlags::AsciiCaseInsensitive => {
4188                attrs::ParsedCaseSensitivity::AsciiCaseInsensitive
4189            }
4190            AttributeFlags::CaseSensitivityDependsOnName => {
4191                // <https://html.spec.whatwg.org/multipage/#selectors>
4192                if !have_namespace && is_html_case_insensitive_attribute(local_name) {
4193                    return attrs::ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument;
4194                }
4195                attrs::ParsedCaseSensitivity::CaseSensitive
4196            }
4197        }
4198    }
4199}
4200
4201/// HTML attributes whose value is matched ASCII-case-insensitively when no
4202/// explicit `s`/`i` flag is given on the attribute selector.
4203/// <https://html.spec.whatwg.org/multipage/#selectors>
4204///
4205/// PERF(port): Zig used `ComptimeEnumMap.has` (zero-cost membership at
4206/// comptime). An earlier `phf::Set` port paid, on every
4207/// `[attr=val]` selector, a 32-bit FNV-ish hash over the name plus a
4208/// bounds check, indirect load, and full key compare — measurable in CSS
4209/// bundling profiles where the dominant inputs (`class`, `href`, `data-*`,
4210/// `aria-*`) are *misses*. A 2-level open-coded dispatch (length →
4211/// first-byte → exact bytes) rejects those misses in ≤2 scalar compares and
4212/// resolves hits in ≤3 short slice compares; the 46-entry table is small
4213/// enough that LLVM unrolls each leaf into a single word/SIMD compare.
4214#[inline]
4215fn is_html_case_insensitive_attribute(name: &[u8]) -> bool {
4216    // 46 entries, lengths 3..=14. Buckets at len 5/7/8 are dense (11/7/8
4217    // entries) so a flat `matches!` per length would degrade to a linear
4218    // scan there; the inner first-byte gate keeps every leaf at ≤3 candidates.
4219    match name.len() {
4220        3 => match name[0] {
4221            b'd' => name == b"dir",
4222            b'r' => matches!(name, b"rel" | b"rev"),
4223            _ => false,
4224        },
4225        4 => match name[0] {
4226            b'a' => name == b"axis",
4227            b'f' => name == b"face",
4228            b'l' => matches!(name, b"lang" | b"link"),
4229            b't' => matches!(name, b"text" | b"type"),
4230            _ => false,
4231        },
4232        5 => match name[0] {
4233            b'a' => matches!(name, b"align" | b"alink"),
4234            b'c' => matches!(name, b"clear" | b"color"),
4235            b'd' => name == b"defer",
4236            b'f' => name == b"frame",
4237            b'm' => name == b"media",
4238            b'r' => name == b"rules",
4239            b's' => matches!(name, b"scope" | b"shape"),
4240            b'v' => name == b"vlink",
4241            _ => false,
4242        },
4243        6 => match name[0] {
4244            b'a' => name == b"accept",
4245            b'm' => name == b"method",
4246            b'n' => matches!(name, b"nohref" | b"nowrap"),
4247            b't' => name == b"target",
4248            b'v' => name == b"valign",
4249            _ => false,
4250        },
4251        7 => match name[0] {
4252            b'b' => name == b"bgcolor",
4253            b'c' => matches!(name, b"charset" | b"checked" | b"compact"),
4254            b'd' => name == b"declare",
4255            b'e' => name == b"enctype",
4256            b'n' => name == b"noshade",
4257            _ => false,
4258        },
4259        8 => match name[0] {
4260            // All 8 entries have distinct first bytes — single compare each.
4261            b'c' => name == b"codetype",
4262            b'd' => name == b"disabled",
4263            b'h' => name == b"hreflang",
4264            b'l' => name == b"language",
4265            b'm' => name == b"multiple",
4266            b'n' => name == b"noresize",
4267            b'r' => name == b"readonly",
4268            b's' => name == b"selected",
4269            _ => false,
4270        },
4271        9 => match name[0] {
4272            b'd' => name == b"direction",
4273            b's' => name == b"scrolling",
4274            b'v' => name == b"valuetype",
4275            _ => false,
4276        },
4277        10 => name == b"http_equiv",
4278        14 => name == b"accept_charset",
4279        _ => false,
4280    }
4281}
4282
4283/// A [view transition part name](https://w3c.github.io/csswg-drafts/css-view-transitions-1/#typedef-pt-name-selector).
4284#[derive(Clone)]
4285pub enum ViewTransitionPartName {
4286    /// *
4287    All,
4288    /// <custom-ident>
4289    Name(CustomIdent),
4290    /// .<custom-ident>
4291    Class(CustomIdent),
4292}
4293
4294impl ViewTransitionPartName {
4295    pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
4296        // PORT NOTE: `CustomIdentFns::to_css` is ``-gated on
4297        // `Printer::{css_module,write_ident}`; inline the
4298        // `write_ident(v, false)` body (CSS-modules custom-ident scoping is a
4299        // serializer concern, not a grammar concern — the gated impl just
4300        // toggles the second arg).
4301        let write_ci = |name: &CustomIdent, dest: &mut Printer| -> Result<(), PrintErr> {
4302            dest.serialize_identifier(name.v())
4303        };
4304        match self {
4305            Self::All => dest.write_str("*"),
4306            Self::Name(name) => write_ci(name, dest),
4307            Self::Class(name) => {
4308                dest.write_char(b'.')?;
4309                write_ci(name, dest)
4310            }
4311        }
4312    }
4313
4314    pub fn parse(input: &mut CssParser) -> CResult<ViewTransitionPartName> {
4315        if input.try_parse(|i| i.expect_delim(b'*')).is_ok() {
4316            return Ok(Self::All);
4317        }
4318
4319        // Try to parse a class selector (.<custom-ident>)
4320        if input.try_parse(|i| i.expect_delim(b'.')).is_ok() {
4321            return Ok(Self::Class(CustomIdent::parse(input)?));
4322        }
4323
4324        Ok(Self::Name(CustomIdent::parse(input)?))
4325    }
4326
4327    pub fn eql(&self, rhs: &Self) -> bool {
4328        match (self, rhs) {
4329            (Self::All, Self::All) => true,
4330            (Self::Name(a), Self::Name(b)) | (Self::Class(a), Self::Class(b)) => a.eql(b),
4331            _ => false,
4332        }
4333    }
4334
4335    pub fn hash(&self, hasher: &mut Wyhash) {
4336        match self {
4337            Self::All => hasher.update(&0u32.to_ne_bytes()),
4338            Self::Name(n) => {
4339                hasher.update(&1u32.to_ne_bytes());
4340                n.hash(hasher);
4341            }
4342            Self::Class(n) => {
4343                hasher.update(&2u32.to_ne_bytes());
4344                n.hash(hasher);
4345            }
4346        }
4347    }
4348
4349    pub fn deep_clone(&self) -> Self {
4350        self.clone()
4351    }
4352}
4353
4354pub fn parse_attribute_flags(input: &mut CssParser) -> CResult<AttributeFlags> {
4355    let location = input.current_source_location();
4356    let token = match input.next() {
4357        Ok(v) => v.clone(),
4358        Err(_) => {
4359            // Selectors spec says language-defined; HTML says it depends on the
4360            // exact attribute name.
4361            return Ok(AttributeFlags::CaseSensitivityDependsOnName);
4362        }
4363    };
4364
4365    let ident = if let Token::Ident(ident) = &token {
4366        *ident
4367    } else {
4368        return Err(location.new_basic_unexpected_token_error(token));
4369    };
4370
4371    if strings::eql_case_insensitive_ascii_check_length(ident, b"i") {
4372        Ok(AttributeFlags::AsciiCaseInsensitive)
4373    } else if strings::eql_case_insensitive_ascii_check_length(ident, b"s") {
4374        Ok(AttributeFlags::CaseSensitive)
4375    } else {
4376        Err(location.new_basic_unexpected_token_error(token))
4377    }
4378}
4379
4380crate::css_eql_partialeq!(NthSelectorData, SpecificityAndFlags, Combinator);
4381
4382// ported from: src/css/selectors/parser.zig