Skip to main content

azul_css/
css.rs

1//! Types and methods used to describe the style of an application.
2//!
3//! This module defines the core CSS data model:
4//!
5//! - [`Css`] contains one or more [`Stylesheet`]s, each holding [`CssRuleBlock`]s.
6//! - A [`CssRuleBlock`] pairs a [`CssPath`] (selector) with [`CssDeclaration`]s (properties).
7//! - [`CssPropertyValue<T>`] wraps individual property values with CSS keywords
8//!   (`auto`, `inherit`, `initial`, etc.).
9//! - [`BoxOrStatic<T>`] is a smart-pointer enum for heap-allocated or static CSS values.
10//! - [`NodeTypeTag`] enumerates all recognized HTML/SVG element types for selector matching.
11use alloc::{string::String, vec::Vec};
12use core::fmt;
13
14use crate::{
15    corety::OptionString,
16    dynamic_selector::DynamicSelectorVec,
17    props::property::{CssProperty, CssPropertyType},
18    AzString,
19};
20
21/// Css stylesheet - contains a parsed CSS stylesheet in "rule blocks",
22/// i.e. blocks of key-value pairs associated with a selector path.
23///
24/// Layer separation (UA / system / author / inline / runtime) is encoded
25/// per-rule via `CssRuleBlock::priority`; see [`rule_priority`] for the
26/// slot allocation. There is no separate `Stylesheet` wrapper — to merge
27/// two CSS sources, concatenate their `rules` and re-sort.
28#[derive(Debug, Default, PartialEq, PartialOrd, Clone)]
29#[repr(C)]
30pub struct Css {
31    /// All rule blocks, in source order. Sort by `(priority, specificity)`
32    /// via `sort_by_specificity` to put them in cascade order.
33    pub rules: CssRuleBlockVec,
34}
35
36impl_option!(
37    Css,
38    OptionCss,
39    copy = false,
40    [Debug, Clone, PartialEq, PartialOrd]
41);
42
43impl_vec!(Css, CssVec, CssVecDestructor, CssVecDestructorType, CssVecSlice, OptionCss);
44impl_vec_mut!(Css, CssVec);
45impl_vec_debug!(Css, CssVec);
46impl_vec_partialord!(Css, CssVec);
47impl_vec_clone!(Css, CssVec, CssVecDestructor);
48impl_vec_partialeq!(Css, CssVec);
49
50impl_vec!(CssRuleBlock, CssRuleBlockVec, CssRuleBlockVecDestructor, CssRuleBlockVecDestructorType, CssRuleBlockVecSlice, OptionCssRuleBlock);
51impl_vec_mut!(CssRuleBlock, CssRuleBlockVec);
52impl_vec_debug!(CssRuleBlock, CssRuleBlockVec);
53impl_vec_partialord!(CssRuleBlock, CssRuleBlockVec);
54impl_vec_clone!(CssRuleBlock, CssRuleBlockVec, CssRuleBlockVecDestructor);
55impl_vec_partialeq!(CssRuleBlock, CssRuleBlockVec);
56
57impl Css {
58    pub fn is_empty(&self) -> bool {
59        self.rules.as_ref().is_empty()
60    }
61
62    pub fn new(rules: Vec<CssRuleBlock>) -> Self {
63        Self {
64            rules: rules.into(),
65        }
66    }
67
68    #[cfg(feature = "parser")]
69    pub fn from_string(s: crate::AzString) -> Self {
70        crate::parser2::new_from_str(s.as_str()).0
71    }
72
73    /// Parse inline-style CSS (bare properties, pseudo blocks, @-rule blocks)
74    /// and return a `Css` whose rules carry `rule_priority::INLINE`.
75    ///
76    /// Wraps the input in `* { ... }` so the main CSS parser can handle bare
77    /// properties at the top level. Pseudo and at-rule blocks like
78    /// `:hover { color: red; }` or `@os(linux) { font-size: 14px; }` work
79    /// directly via CSS nesting.
80    #[cfg(feature = "parser")]
81    pub fn parse_inline(style: &str) -> Self {
82        use alloc::string::ToString;
83        let mut wrapped = String::with_capacity(style.len() + 6);
84        wrapped.push_str("* {\n");
85        wrapped.push_str(style);
86        wrapped.push_str("\n}");
87        let (mut css, _warnings) = crate::parser2::new_from_str(&wrapped);
88        for rule in css.rules.as_mut() {
89            rule.priority = rule_priority::INLINE;
90        }
91        css
92    }
93
94    #[cfg(feature = "parser")]
95    pub fn from_string_with_warnings(
96        s: crate::AzString,
97    ) -> (Self, Vec<crate::parser2::CssParseWarnMsgOwned>) {
98        let (css, warnings) = crate::parser2::new_from_str(s.as_str());
99        (
100            css,
101            warnings
102                .into_iter()
103                .map(|w| crate::parser2::CssParseWarnMsgOwned {
104                    warning: w.warning.to_contained(),
105                    location: w.location,
106                })
107                .collect(),
108        )
109    }
110}
111
112impl From<Vec<CssRuleBlock>> for Css {
113    fn from(rules: Vec<CssRuleBlock>) -> Self {
114        Self {
115            rules: rules.into(),
116        }
117    }
118}
119
120// NodeData derives Eq + Ord and carries `Css` as its inline style. Provide
121// length-based ordering so the derives keep working — the same pattern the
122// previous `CssPropertyWithConditionsVec` used.
123impl Eq for Css {}
124impl Ord for Css {
125    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
126        self.rules.as_ref().len().cmp(&other.rules.as_ref().len())
127    }
128}
129impl Eq for CssRuleBlock {}
130impl Ord for CssRuleBlock {
131    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
132        // Match the existing PartialOrd: path first, then declarations.
133        // Priority is intentionally not in the sort key — it's a layer label,
134        // not a comparison primitive for callers.
135        self.path.cmp(&other.path).then_with(|| self.declarations.cmp(&other.declarations))
136    }
137}
138
139/// Convert a flat list of `CssPropertyWithConditions` (the legacy inline-CSS form)
140/// into a `Css`. Each property becomes a single-declaration `CssRuleBlock` with
141/// `priority = INLINE`, an empty path (implicitly `:scope` — applies to the node it
142/// lives on), and the original conditions intact.
143///
144/// This bridge lets widget code that built `&[CssPropertyWithConditions]` arrays
145/// keep working through `.into()` while the storage on `NodeData` is the unified
146/// `Css` type.
147impl From<crate::dynamic_selector::CssPropertyWithConditionsVec> for Css {
148    fn from(props: crate::dynamic_selector::CssPropertyWithConditionsVec) -> Self {
149        let rules: Vec<CssRuleBlock> = props.into_library_owned_vec().into_iter().map(|p| {
150            CssRuleBlock {
151                path: CssPath { selectors: Vec::new().into() },
152                declarations: alloc::vec![CssDeclaration::Static(p.property)].into(),
153                conditions: p.apply_if,
154                priority: rule_priority::INLINE,
155            }
156        }).collect();
157        Css { rules: rules.into() }
158    }
159}
160
161/// Contains one parsed `key: value` pair, static or dynamic
162#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
163#[repr(C, u8)]
164pub enum CssDeclaration {
165    /// Static key-value pair, such as `width: 500px`
166    Static(CssProperty),
167    /// Dynamic key-value pair with default value, such as `width: [[ my_id | 500px ]]`
168    Dynamic(DynamicCssProperty),
169}
170
171impl_option!(
172    CssDeclaration,
173    OptionCssDeclaration,
174    copy = false,
175    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
176);
177
178impl CssDeclaration {
179    pub const fn new_static(prop: CssProperty) -> Self {
180        CssDeclaration::Static(prop)
181    }
182
183    pub const fn new_dynamic(prop: DynamicCssProperty) -> Self {
184        CssDeclaration::Dynamic(prop)
185    }
186
187    /// Returns the type of the property (i.e. the CSS key as a typed enum)
188    pub fn get_type(&self) -> CssPropertyType {
189        use self::CssDeclaration::*;
190        match self {
191            Static(s) => s.get_type(),
192            Dynamic(d) => d.default_value.get_type(),
193        }
194    }
195
196    /// Determines if the property will be inherited (applied to the children)
197    /// during the recursive application of the style on the DOM tree
198    pub fn is_inheritable(&self) -> bool {
199        use self::CssDeclaration::*;
200        match self {
201            Static(s) => s.get_type().is_inheritable(),
202            Dynamic(d) => d.is_inheritable(),
203        }
204    }
205
206    /// Returns whether this rule affects only styling properties or layout
207    /// properties (that could trigger a re-layout)
208    pub fn can_trigger_relayout(&self) -> bool {
209        use self::CssDeclaration::*;
210        match self {
211            Static(s) => s.get_type().can_trigger_relayout(),
212            Dynamic(d) => d.can_trigger_relayout(),
213        }
214    }
215
216    pub fn to_str(&self) -> String {
217        use self::CssDeclaration::*;
218        match self {
219            Static(s) => format!("{:?}", s),
220            Dynamic(d) => format!("var(--{}, {:?})", d.dynamic_id, d.default_value),
221        }
222    }
223}
224
225/// A `DynamicCssProperty` is a type of css property that can be changed on possibly
226/// every frame by the Rust code - for example to implement an `On::Hover` behaviour.
227///
228/// The syntax for such a property looks like this:
229///
230/// ```no_run,ignore
231/// #my_div {
232///    padding: var(--my_dynamic_property_id, 400px);
233/// }
234/// ```
235///
236/// Azul will register a dynamic property with the key "my_dynamic_property_id"
237/// and the default value of 400px. If the property gets overridden during one frame,
238/// the overridden property takes precedence.
239///
240/// At runtime the style is immutable (which is a performance optimization - if we
241/// can assume that the property never changes at runtime), we can do some optimizations on it.
242/// Dynamic style properties can also be used for animations and conditional styles
243/// (i.e. `hover`, `focus`, etc.), thereby leading to cleaner code, since all of these
244/// special cases now use one single API.
245#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
246#[repr(C)]
247pub struct DynamicCssProperty {
248    /// The stringified ID of this property, i.e. the `"my_id"` in `width: var(--my_id, 500px)`.
249    pub dynamic_id: AzString,
250    /// Default values for this properties - one single value can control multiple properties!
251    pub default_value: CssProperty,
252}
253
254/// A value that is either heap-allocated (parsed at runtime) or a compile-time
255/// static reference. Used to reduce enum size for large CSS property payloads
256/// by storing them behind a pointer instead of inline.
257///
258/// - Size: 1 (tag) + 7 (padding) + 8 (pointer) = **16 bytes** on 64-bit
259/// - `Static` variant: no allocation, just a `*const T` pointer to static data
260/// - `Boxed` variant: heap-allocated via `Box::into_raw`, freed on Drop
261#[repr(C, u8)]
262pub enum BoxOrStatic<T> {
263    /// Heap-allocated (parsed at runtime). Owned — freed on Drop.
264    Boxed(*mut T),
265    /// Compile-time constant (e.g. from `const` CSS defaults). Not freed.
266    Static(*const T),
267}
268
269impl<T> BoxOrStatic<T> {
270    /// Allocate `value` on the heap and return a `Boxed` variant.
271    #[inline]
272    pub fn heap(value: T) -> Self {
273        BoxOrStatic::Boxed(Box::into_raw(Box::new(value)))
274    }
275
276    /// Return a reference to the inner value.
277    ///
278    /// # Safety invariant
279    /// The inner pointer must be non-null. This is guaranteed by [`heap`](Self::heap)
280    /// and the `Static` constructor (which should always point to valid data).
281    #[inline]
282    pub fn as_ref(&self) -> &T {
283        match self {
284            BoxOrStatic::Boxed(ptr) => unsafe {
285                debug_assert!(!ptr.is_null(), "BoxOrStatic::Boxed contained a null pointer");
286                &**ptr
287            },
288            BoxOrStatic::Static(ptr) => unsafe {
289                debug_assert!(!ptr.is_null(), "BoxOrStatic::Static contained a null pointer");
290                &**ptr
291            },
292        }
293    }
294
295    /// Return a mutable reference to the inner value (only for Boxed).
296    /// Panics if called on Static.
297    #[inline]
298    pub fn as_mut(&mut self) -> &mut T {
299        match self {
300            BoxOrStatic::Boxed(ptr) => unsafe { &mut **ptr },
301            BoxOrStatic::Static(_) => panic!("Cannot mutate a static BoxOrStatic value"),
302        }
303    }
304
305    /// Consume self and return the inner value.
306    #[inline]
307    pub fn into_inner(self) -> T where T: Clone {
308        let val = self.as_ref().clone();
309        // Don't double-free: std::mem::forget prevents Drop from running
310        core::mem::forget(self);
311        val
312    }
313}
314
315impl<T> Drop for BoxOrStatic<T> {
316    fn drop(&mut self) {
317        if let BoxOrStatic::Boxed(ptr) = self {
318            if !ptr.is_null() {
319                unsafe { drop(Box::from_raw(*ptr)); }
320                *ptr = core::ptr::null_mut();
321            }
322        }
323    }
324}
325
326impl<T: Clone> Clone for BoxOrStatic<T> {
327    fn clone(&self) -> Self {
328        match self {
329            BoxOrStatic::Boxed(ptr) => {
330                let val = unsafe { &**ptr }.clone();
331                BoxOrStatic::Boxed(Box::into_raw(Box::new(val)))
332            }
333            BoxOrStatic::Static(ptr) => BoxOrStatic::Static(*ptr),
334        }
335    }
336}
337
338impl<T: core::fmt::Debug> core::fmt::Debug for BoxOrStatic<T> {
339    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
340        self.as_ref().fmt(f)
341    }
342}
343
344impl<T: core::fmt::Display> core::fmt::Display for BoxOrStatic<T> {
345    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
346        self.as_ref().fmt(f)
347    }
348}
349
350impl<T: PartialEq> PartialEq for BoxOrStatic<T> {
351    fn eq(&self, other: &Self) -> bool {
352        self.as_ref() == other.as_ref()
353    }
354}
355
356impl<T: Eq> Eq for BoxOrStatic<T> {}
357
358impl<T: core::hash::Hash> core::hash::Hash for BoxOrStatic<T> {
359    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
360        self.as_ref().hash(state)
361    }
362}
363
364impl<T: PartialOrd> PartialOrd for BoxOrStatic<T> {
365    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
366        self.as_ref().partial_cmp(other.as_ref())
367    }
368}
369
370impl<T: Ord> Ord for BoxOrStatic<T> {
371    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
372        self.as_ref().cmp(other.as_ref())
373    }
374}
375
376impl<T> core::ops::Deref for BoxOrStatic<T> {
377    type Target = T;
378    #[inline]
379    fn deref(&self) -> &T {
380        self.as_ref()
381    }
382}
383
384impl<T: Default> Default for BoxOrStatic<T> {
385    fn default() -> Self {
386        BoxOrStatic::heap(T::default())
387    }
388}
389
390impl<T: PrintAsCssValue> PrintAsCssValue for BoxOrStatic<T> {
391    fn print_as_css_value(&self) -> String {
392        self.as_ref().print_as_css_value()
393    }
394}
395
396// Safety: BoxOrStatic<T> is Send if T is Send
397unsafe impl<T: Send + 'static> Send for BoxOrStatic<T> {}
398// Safety: BoxOrStatic<T> is Sync if T is Sync
399unsafe impl<T: Sync + 'static> Sync for BoxOrStatic<T> {}
400
401/// Type alias: `BoxOrStatic<StyleBoxShadow>` — used by codegen for FFI monomorphization.
402pub type BoxOrStaticStyleBoxShadow = BoxOrStatic<crate::props::style::box_shadow::StyleBoxShadow>;
403
404/// Type alias: `BoxOrStatic<AzString>` — used by NodeType::Text and NodeType::Icon.
405pub type BoxOrStaticString = BoxOrStatic<crate::AzString>;
406
407/// A CSS property value that may be an explicit value or a CSS-wide keyword
408/// (`auto`, `none`, `initial`, `inherit`, `revert`, `unset`).
409#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
410#[repr(C, u8)] // necessary for ABI stability
411pub enum CssPropertyValue<T> {
412    Auto,
413    None,
414    Initial,
415    Inherit,
416    Revert,
417    Unset,
418    Exact(T),
419}
420
421/// Trait for types that can format themselves as a CSS property value string.
422pub trait PrintAsCssValue {
423    fn print_as_css_value(&self) -> String;
424}
425
426impl<T: PrintAsCssValue> CssPropertyValue<T> {
427    pub fn get_css_value_fmt(&self) -> String {
428        match self {
429            CssPropertyValue::Auto => "auto".to_string(),
430            CssPropertyValue::None => "none".to_string(),
431            CssPropertyValue::Initial => "initial".to_string(),
432            CssPropertyValue::Inherit => "inherit".to_string(),
433            CssPropertyValue::Revert => "revert".to_string(),
434            CssPropertyValue::Unset => "unset".to_string(),
435            CssPropertyValue::Exact(e) => e.print_as_css_value(),
436        }
437    }
438}
439
440impl<T: fmt::Display> fmt::Display for CssPropertyValue<T> {
441    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
442        use self::CssPropertyValue::*;
443        match self {
444            Auto => write!(f, "auto"),
445            None => write!(f, "none"),
446            Initial => write!(f, "initial"),
447            Inherit => write!(f, "inherit"),
448            Revert => write!(f, "revert"),
449            Unset => write!(f, "unset"),
450            Exact(e) => write!(f, "{}", e),
451        }
452    }
453}
454
455impl<T> From<T> for CssPropertyValue<T> {
456    fn from(c: T) -> Self {
457        CssPropertyValue::Exact(c)
458    }
459}
460
461impl<T> CssPropertyValue<T> {
462    /// Transforms a `CssPropertyValue<T>` into a `CssPropertyValue<U>` by applying a mapping
463    /// function
464    #[inline]
465    pub fn map_property<F: Fn(T) -> U, U>(self, map_fn: F) -> CssPropertyValue<U> {
466        match self {
467            CssPropertyValue::Exact(c) => CssPropertyValue::Exact(map_fn(c)),
468            CssPropertyValue::Auto => CssPropertyValue::Auto,
469            CssPropertyValue::None => CssPropertyValue::None,
470            CssPropertyValue::Initial => CssPropertyValue::Initial,
471            CssPropertyValue::Inherit => CssPropertyValue::Inherit,
472            CssPropertyValue::Revert => CssPropertyValue::Revert,
473            CssPropertyValue::Unset => CssPropertyValue::Unset,
474        }
475    }
476
477    #[inline]
478    pub fn get_property(&self) -> Option<&T> {
479        match self {
480            CssPropertyValue::Exact(c) => Some(c),
481            _ => None,
482        }
483    }
484
485    #[inline]
486    pub fn get_property_owned(self) -> Option<T> {
487        match self {
488            CssPropertyValue::Exact(c) => Some(c),
489            _ => None,
490        }
491    }
492
493    #[inline]
494    pub fn is_auto(&self) -> bool {
495        matches!(self, CssPropertyValue::Auto)
496    }
497
498    #[inline]
499    pub fn is_none(&self) -> bool {
500        matches!(self, CssPropertyValue::None)
501    }
502
503    #[inline]
504    pub fn is_initial(&self) -> bool {
505        matches!(self, CssPropertyValue::Initial)
506    }
507
508    #[inline]
509    pub fn is_inherit(&self) -> bool {
510        matches!(self, CssPropertyValue::Inherit)
511    }
512
513    #[inline]
514    pub fn is_revert(&self) -> bool {
515        matches!(self, CssPropertyValue::Revert)
516    }
517
518    #[inline]
519    pub fn is_unset(&self) -> bool {
520        matches!(self, CssPropertyValue::Unset)
521    }
522}
523
524impl<T: Default> CssPropertyValue<T> {
525    #[inline]
526    pub fn get_property_or_default(self) -> Option<T> {
527        match self {
528            CssPropertyValue::Auto | CssPropertyValue::Initial => Some(T::default()),
529            CssPropertyValue::Exact(c) => Some(c),
530            CssPropertyValue::None
531            | CssPropertyValue::Inherit
532            | CssPropertyValue::Revert
533            | CssPropertyValue::Unset => None,
534        }
535    }
536}
537
538impl<T: Default> Default for CssPropertyValue<T> {
539    #[inline]
540    fn default() -> Self {
541        CssPropertyValue::Exact(T::default())
542    }
543}
544
545impl DynamicCssProperty {
546    pub fn is_inheritable(&self) -> bool {
547        // Dynamic style properties should not be inheritable,
548        // since that could lead to bugs - you set a property in Rust, suddenly
549        // the wrong UI component starts to react because it was inherited.
550        false
551    }
552
553    pub fn can_trigger_relayout(&self) -> bool {
554        self.default_value.get_type().can_trigger_relayout()
555    }
556}
557
558/// Layer priority for `CssRuleBlock`. Lower numbers cascade first;
559/// higher numbers override earlier layers at the same specificity.
560///
561/// `u8` leaves 256 slots, so a new layer can be inserted between any
562/// two existing slots without renumbering consumers. The gaps between
563/// named slots are intentional — fill them with custom intermediate
564/// layers if/when `@layer` lands.
565pub mod rule_priority {
566    /// User-Agent / framework defaults. Widget code that emits its
567    /// own default CSS uses this. Lowest priority — anything else
568    /// overrides it.
569    pub const UA: u8 = 0;
570
571    /// Stylesheets the host system reports (system fonts, theme CSS
572    /// derived from `SystemStyle`). One step above UA so they win
573    /// against framework defaults but lose against anything the app
574    /// author writes.
575    pub const SYSTEM: u8 = 10;
576
577    /// Default for parser-produced rules: the app author's CSS.
578    /// Everything coming out of `Css::from_string` lives here.
579    pub const AUTHOR: u8 = 20;
580
581    /// Inline `style="..."` / `NodeData::set_css(...)` rules — used
582    /// once the inline-vs-component unification (separate plan) folds
583    /// inline storage into the same Vec.
584    pub const INLINE: u8 = 30;
585
586    /// Reserved for direct-rule runtime overrides. Today the
587    /// prop_cache handles runtime overrides via
588    /// `user_overridden_properties`; this slot is reserved so a
589    /// future "push a CssRuleBlock at runtime" path stays above
590    /// inline. Used only when a callback writes a full rule, not a
591    /// single property.
592    pub const RUNTIME: u8 = 50;
593}
594
595/// One block of rules that applies a bunch of rules to a "path" in the style, i.e.
596/// `div#myid.myclass -> { ("justify-content", "center") }`
597///
598/// The `conditions` field contains @media/@lang/etc. conditions that must ALL be
599/// satisfied for this rule block to apply (from enclosing @-rule blocks).
600#[derive(Debug, Default, Clone, PartialEq)]
601#[repr(C)]
602pub struct CssRuleBlock {
603    /// The css path (full selector) of the style ruleset
604    pub path: CssPath,
605    /// `"justify-content: center"` =>
606    /// `CssDeclaration::Static(CssProperty::JustifyContent(LayoutJustifyContent::Center))`
607    pub declarations: CssDeclarationVec,
608    /// Conditions from enclosing @-rules (@media, @lang, etc.) that must ALL be
609    /// satisfied for this rule block to apply. Empty = unconditional.
610    pub conditions: DynamicSelectorVec,
611    /// Layer priority. See [`rule_priority`] for slot allocation.
612    /// `0` = UA / framework, `20` = author CSS (default), higher = wins.
613    /// Sort key combined with selector specificity in `sort_by_specificity`.
614    pub priority: u8,
615}
616
617impl_option!(
618    CssRuleBlock,
619    OptionCssRuleBlock,
620    copy = false,
621    [Debug, Clone, PartialEq, PartialOrd]
622);
623
624impl PartialOrd for CssRuleBlock {
625    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
626        // Compare by path and declarations only, conditions are not ordered
627        match self.path.partial_cmp(&other.path) {
628            Some(core::cmp::Ordering::Equal) => self.declarations.partial_cmp(&other.declarations),
629            ord => ord,
630        }
631    }
632}
633
634impl_vec!(CssDeclaration, CssDeclarationVec, CssDeclarationVecDestructor, CssDeclarationVecDestructorType, CssDeclarationVecSlice, OptionCssDeclaration);
635impl_vec_mut!(CssDeclaration, CssDeclarationVec);
636impl_vec_debug!(CssDeclaration, CssDeclarationVec);
637impl_vec_partialord!(CssDeclaration, CssDeclarationVec);
638impl_vec_ord!(CssDeclaration, CssDeclarationVec);
639impl_vec_clone!(
640    CssDeclaration,
641    CssDeclarationVec,
642    CssDeclarationVecDestructor
643);
644impl_vec_partialeq!(CssDeclaration, CssDeclarationVec);
645impl_vec_eq!(CssDeclaration, CssDeclarationVec);
646impl_vec_hash!(CssDeclaration, CssDeclarationVec);
647
648impl CssRuleBlock {
649    pub fn new(path: CssPath, declarations: Vec<CssDeclaration>) -> Self {
650        Self {
651            path,
652            declarations: declarations.into(),
653            conditions: DynamicSelectorVec::from_const_slice(&[]),
654            priority: rule_priority::AUTHOR,
655        }
656    }
657
658    pub fn with_conditions(
659        path: CssPath,
660        declarations: Vec<CssDeclaration>,
661        conditions: Vec<crate::dynamic_selector::DynamicSelector>,
662    ) -> Self {
663        Self {
664            path,
665            declarations: declarations.into(),
666            conditions: conditions.into(),
667            priority: rule_priority::AUTHOR,
668        }
669    }
670}
671
672/// A group of CSS path selectors, used during selector matching.
673pub type CssContentGroup<'a> = Vec<&'a CssPathSelector>;
674
675/// Signifies the type of a DOM node without carrying any associated data
676#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
677#[repr(C)]
678pub enum NodeTypeTag {
679    // Document structure
680    Html,
681    Head,
682    Body,
683
684    // Block-level elements
685    Div,
686    P,
687    Article,
688    Section,
689    Nav,
690    Aside,
691    Header,
692    Footer,
693    Main,
694    Figure,
695    FigCaption,
696
697    // Headings
698    H1,
699    H2,
700    H3,
701    H4,
702    H5,
703    H6,
704
705    // Inline text
706    Br,
707    Hr,
708    Pre,
709    BlockQuote,
710    Address,
711    Details,
712    Summary,
713    Dialog,
714
715    // Lists
716    Ul,
717    Ol,
718    Li,
719    Dl,
720    Dt,
721    Dd,
722    Menu,
723    MenuItem,
724    Dir,
725
726    // Tables
727    Table,
728    Caption,
729    THead,
730    TBody,
731    TFoot,
732    Tr,
733    Th,
734    Td,
735    ColGroup,
736    Col,
737
738    // Forms
739    Form,
740    FieldSet,
741    Legend,
742    Label,
743    Input,
744    Button,
745    Select,
746    OptGroup,
747    SelectOption,
748    TextArea,
749    Output,
750    Progress,
751    Meter,
752    DataList,
753
754    // Inline elements
755    Span,
756    A,
757    Em,
758    Strong,
759    B,
760    I,
761    U,
762    S,
763    Mark,
764    Del,
765    Ins,
766    Code,
767    Samp,
768    Kbd,
769    Var,
770    Cite,
771    Dfn,
772    Abbr,
773    Acronym,
774    Q,
775    Time,
776    Sub,
777    Sup,
778    Small,
779    Big,
780    Bdo,
781    Bdi,
782    Wbr,
783    Ruby,
784    Rt,
785    Rtc,
786    Rp,
787    Data,
788
789    // Embedded content
790    Canvas,
791    Object,
792    Param,
793    Embed,
794    Audio,
795    Video,
796    Source,
797    Track,
798    Map,
799    Area,
800    Svg,
801    /// SVG `<path>` element.
802    SvgPath,
803    /// SVG `<circle>` element.
804    SvgCircle,
805    /// SVG `<rect>` element.
806    SvgRect,
807    /// SVG `<ellipse>` element.
808    SvgEllipse,
809    /// SVG `<line>` element.
810    SvgLine,
811    /// SVG `<polygon>` element.
812    SvgPolygon,
813    /// SVG `<polyline>` element.
814    SvgPolyline,
815    /// SVG `<g>` group element.
816    SvgG,
817
818    // SVG container elements
819    /// SVG `<defs>` element.
820    SvgDefs,
821    /// SVG `<symbol>` element.
822    SvgSymbol,
823    /// SVG `<use>` element.
824    SvgUse,
825    /// SVG `<switch>` element.
826    SvgSwitch,
827
828    // SVG text elements
829    /// SVG `<text>` element.
830    SvgText,
831    /// SVG `<tspan>` element.
832    SvgTspan,
833    /// SVG `<textPath>` element.
834    SvgTextPath,
835
836    // SVG paint server elements
837    /// SVG `<linearGradient>` element.
838    SvgLinearGradient,
839    /// SVG `<radialGradient>` element.
840    SvgRadialGradient,
841    /// SVG `<stop>` element.
842    SvgStop,
843    /// SVG `<pattern>` element.
844    SvgPattern,
845
846    // SVG clipping/masking elements
847    /// SVG `<clipPath>` element.
848    SvgClipPathElement,
849    /// SVG `<mask>` element.
850    SvgMask,
851
852    // SVG filter elements
853    /// SVG `<filter>` element.
854    SvgFilter,
855    /// SVG `<feBlend>` element.
856    SvgFeBlend,
857    /// SVG `<feColorMatrix>` element.
858    SvgFeColorMatrix,
859    /// SVG `<feComponentTransfer>` element.
860    SvgFeComponentTransfer,
861    /// SVG `<feComposite>` element.
862    SvgFeComposite,
863    /// SVG `<feConvolveMatrix>` element.
864    SvgFeConvolveMatrix,
865    /// SVG `<feDiffuseLighting>` element.
866    SvgFeDiffuseLighting,
867    /// SVG `<feDisplacementMap>` element.
868    SvgFeDisplacementMap,
869    /// SVG `<feDistantLight>` element.
870    SvgFeDistantLight,
871    /// SVG `<feDropShadow>` element.
872    SvgFeDropShadow,
873    /// SVG `<feFlood>` element.
874    SvgFeFlood,
875    /// SVG `<feFuncR>` element.
876    SvgFeFuncR,
877    /// SVG `<feFuncG>` element.
878    SvgFeFuncG,
879    /// SVG `<feFuncB>` element.
880    SvgFeFuncB,
881    /// SVG `<feFuncA>` element.
882    SvgFeFuncA,
883    /// SVG `<feGaussianBlur>` element.
884    SvgFeGaussianBlur,
885    /// SVG `<feImage>` element.
886    SvgFeImage,
887    /// SVG `<feMerge>` element.
888    SvgFeMerge,
889    /// SVG `<feMergeNode>` element.
890    SvgFeMergeNode,
891    /// SVG `<feMorphology>` element.
892    SvgFeMorphology,
893    /// SVG `<feOffset>` element.
894    SvgFeOffset,
895    /// SVG `<fePointLight>` element.
896    SvgFePointLight,
897    /// SVG `<feSpecularLighting>` element.
898    SvgFeSpecularLighting,
899    /// SVG `<feSpotLight>` element.
900    SvgFeSpotLight,
901    /// SVG `<feTile>` element.
902    SvgFeTile,
903    /// SVG `<feTurbulence>` element.
904    SvgFeTurbulence,
905
906    // SVG marker/image elements
907    /// SVG `<marker>` element.
908    SvgMarker,
909    /// SVG `<image>` element.
910    SvgImage,
911    /// SVG `<foreignObject>` element.
912    SvgForeignObject,
913
914    // SVG descriptive elements
915    /// SVG `<title>` element.
916    SvgTitle,
917    /// SVG `<desc>` element.
918    SvgDesc,
919    /// SVG `<metadata>` element.
920    SvgMetadata,
921    /// SVG `<a>` element.
922    SvgA,
923    /// SVG `<view>` element.
924    SvgView,
925    /// SVG `<style>` element.
926    SvgStyle,
927    /// SVG `<script>` element.
928    SvgScript,
929
930    // SVG animation elements
931    /// SVG `<animate>` element.
932    SvgAnimate,
933    /// SVG `<animateMotion>` element.
934    SvgAnimateMotion,
935    /// SVG `<animateTransform>` element.
936    SvgAnimateTransform,
937    /// SVG `<set>` element.
938    SvgSet,
939    /// SVG `<mpath>` element.
940    SvgMpath,
941
942    // Metadata
943    Title,
944    Meta,
945    Link,
946    Script,
947    Style,
948    Base,
949
950    // Special
951    Text,
952    Img,
953    VirtualView,
954    /// Icon element - resolved to actual content by IconProvider
955    Icon,
956    /// Invisible probe — `NodeType::GeolocationProbe`. Zero-size in
957    /// layout, skipped in the display list. CSS tag: `geolocation-probe`.
958    GeolocationProbe,
959
960    // Pseudo-elements
961    Before,
962    After,
963    Marker,
964    Placeholder,
965}
966
967/// Error returned when a CSS tag name string cannot be mapped to a [`NodeTypeTag`].
968#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
969pub enum NodeTypeTagParseError<'a> {
970    Invalid(&'a str),
971}
972
973impl<'a> fmt::Display for NodeTypeTagParseError<'a> {
974    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
975        match &self {
976            NodeTypeTagParseError::Invalid(e) => write!(f, "Invalid node type: {}", e),
977        }
978    }
979}
980
981/// Owned version of [`NodeTypeTagParseError`] for storage across lifetime boundaries.
982#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
983#[repr(C, u8)]
984pub enum NodeTypeTagParseErrorOwned {
985    Invalid(AzString),
986}
987
988impl<'a> NodeTypeTagParseError<'a> {
989    pub fn to_contained(&self) -> NodeTypeTagParseErrorOwned {
990        match self {
991            NodeTypeTagParseError::Invalid(s) => NodeTypeTagParseErrorOwned::Invalid(s.to_string().into()),
992        }
993    }
994}
995
996impl NodeTypeTagParseErrorOwned {
997    pub fn to_shared<'a>(&'a self) -> NodeTypeTagParseError<'a> {
998        match self {
999            NodeTypeTagParseErrorOwned::Invalid(s) => NodeTypeTagParseError::Invalid(s),
1000        }
1001    }
1002}
1003
1004/// Parses the node type from a CSS string such as `"div"` => `NodeTypeTag::Div`
1005impl NodeTypeTag {
1006    pub fn from_str(css_key: &str) -> Result<Self, NodeTypeTagParseError<'_>> {
1007        match css_key {
1008            // Document structure
1009            "html" => Ok(NodeTypeTag::Html),
1010            "head" => Ok(NodeTypeTag::Head),
1011            "body" => Ok(NodeTypeTag::Body),
1012
1013            // Block-level elements
1014            "div" => Ok(NodeTypeTag::Div),
1015            "p" => Ok(NodeTypeTag::P),
1016            "article" => Ok(NodeTypeTag::Article),
1017            "section" => Ok(NodeTypeTag::Section),
1018            "nav" => Ok(NodeTypeTag::Nav),
1019            "aside" => Ok(NodeTypeTag::Aside),
1020            "header" => Ok(NodeTypeTag::Header),
1021            "footer" => Ok(NodeTypeTag::Footer),
1022            "main" => Ok(NodeTypeTag::Main),
1023            "figure" => Ok(NodeTypeTag::Figure),
1024            "figcaption" => Ok(NodeTypeTag::FigCaption),
1025
1026            // Headings
1027            "h1" => Ok(NodeTypeTag::H1),
1028            "h2" => Ok(NodeTypeTag::H2),
1029            "h3" => Ok(NodeTypeTag::H3),
1030            "h4" => Ok(NodeTypeTag::H4),
1031            "h5" => Ok(NodeTypeTag::H5),
1032            "h6" => Ok(NodeTypeTag::H6),
1033
1034            // Inline text
1035            "br" => Ok(NodeTypeTag::Br),
1036            "hr" => Ok(NodeTypeTag::Hr),
1037            "pre" => Ok(NodeTypeTag::Pre),
1038            "blockquote" => Ok(NodeTypeTag::BlockQuote),
1039            "address" => Ok(NodeTypeTag::Address),
1040            "details" => Ok(NodeTypeTag::Details),
1041            "summary" => Ok(NodeTypeTag::Summary),
1042            "dialog" => Ok(NodeTypeTag::Dialog),
1043
1044            // Lists
1045            "ul" => Ok(NodeTypeTag::Ul),
1046            "ol" => Ok(NodeTypeTag::Ol),
1047            "li" => Ok(NodeTypeTag::Li),
1048            "dl" => Ok(NodeTypeTag::Dl),
1049            "dt" => Ok(NodeTypeTag::Dt),
1050            "dd" => Ok(NodeTypeTag::Dd),
1051            "menu" => Ok(NodeTypeTag::Menu),
1052            "menuitem" => Ok(NodeTypeTag::MenuItem),
1053            "dir" => Ok(NodeTypeTag::Dir),
1054
1055            // Tables
1056            "table" => Ok(NodeTypeTag::Table),
1057            "caption" => Ok(NodeTypeTag::Caption),
1058            "thead" => Ok(NodeTypeTag::THead),
1059            "tbody" => Ok(NodeTypeTag::TBody),
1060            "tfoot" => Ok(NodeTypeTag::TFoot),
1061            "tr" => Ok(NodeTypeTag::Tr),
1062            "th" => Ok(NodeTypeTag::Th),
1063            "td" => Ok(NodeTypeTag::Td),
1064            "colgroup" => Ok(NodeTypeTag::ColGroup),
1065            "col" => Ok(NodeTypeTag::Col),
1066
1067            // Forms
1068            "form" => Ok(NodeTypeTag::Form),
1069            "fieldset" => Ok(NodeTypeTag::FieldSet),
1070            "legend" => Ok(NodeTypeTag::Legend),
1071            "label" => Ok(NodeTypeTag::Label),
1072            "input" => Ok(NodeTypeTag::Input),
1073            "button" => Ok(NodeTypeTag::Button),
1074            "select" => Ok(NodeTypeTag::Select),
1075            "optgroup" => Ok(NodeTypeTag::OptGroup),
1076            "option" => Ok(NodeTypeTag::SelectOption),
1077            "textarea" => Ok(NodeTypeTag::TextArea),
1078            "output" => Ok(NodeTypeTag::Output),
1079            "progress" => Ok(NodeTypeTag::Progress),
1080            "meter" => Ok(NodeTypeTag::Meter),
1081            "datalist" => Ok(NodeTypeTag::DataList),
1082
1083            // Inline elements
1084            "span" => Ok(NodeTypeTag::Span),
1085            "a" => Ok(NodeTypeTag::A),
1086            "em" => Ok(NodeTypeTag::Em),
1087            "strong" => Ok(NodeTypeTag::Strong),
1088            "b" => Ok(NodeTypeTag::B),
1089            "i" => Ok(NodeTypeTag::I),
1090            "u" => Ok(NodeTypeTag::U),
1091            "s" => Ok(NodeTypeTag::S),
1092            "mark" => Ok(NodeTypeTag::Mark),
1093            "del" => Ok(NodeTypeTag::Del),
1094            "ins" => Ok(NodeTypeTag::Ins),
1095            "code" => Ok(NodeTypeTag::Code),
1096            "samp" => Ok(NodeTypeTag::Samp),
1097            "kbd" => Ok(NodeTypeTag::Kbd),
1098            "var" => Ok(NodeTypeTag::Var),
1099            "cite" => Ok(NodeTypeTag::Cite),
1100            "dfn" => Ok(NodeTypeTag::Dfn),
1101            "abbr" => Ok(NodeTypeTag::Abbr),
1102            "acronym" => Ok(NodeTypeTag::Acronym),
1103            "q" => Ok(NodeTypeTag::Q),
1104            "time" => Ok(NodeTypeTag::Time),
1105            "sub" => Ok(NodeTypeTag::Sub),
1106            "sup" => Ok(NodeTypeTag::Sup),
1107            "small" => Ok(NodeTypeTag::Small),
1108            "big" => Ok(NodeTypeTag::Big),
1109            "bdo" => Ok(NodeTypeTag::Bdo),
1110            "bdi" => Ok(NodeTypeTag::Bdi),
1111            "wbr" => Ok(NodeTypeTag::Wbr),
1112            "ruby" => Ok(NodeTypeTag::Ruby),
1113            "rt" => Ok(NodeTypeTag::Rt),
1114            "rtc" => Ok(NodeTypeTag::Rtc),
1115            "rp" => Ok(NodeTypeTag::Rp),
1116            "data" => Ok(NodeTypeTag::Data),
1117
1118            // Embedded content
1119            "canvas" => Ok(NodeTypeTag::Canvas),
1120            "object" => Ok(NodeTypeTag::Object),
1121            "param" => Ok(NodeTypeTag::Param),
1122            "embed" => Ok(NodeTypeTag::Embed),
1123            "audio" => Ok(NodeTypeTag::Audio),
1124            "video" => Ok(NodeTypeTag::Video),
1125            "source" => Ok(NodeTypeTag::Source),
1126            "track" => Ok(NodeTypeTag::Track),
1127            "map" => Ok(NodeTypeTag::Map),
1128            "area" => Ok(NodeTypeTag::Area),
1129            "svg" => Ok(NodeTypeTag::Svg),
1130
1131            // SVG shape elements
1132            "path" => Ok(NodeTypeTag::SvgPath),
1133            "circle" => Ok(NodeTypeTag::SvgCircle),
1134            "rect" => Ok(NodeTypeTag::SvgRect),
1135            "ellipse" => Ok(NodeTypeTag::SvgEllipse),
1136            "line" => Ok(NodeTypeTag::SvgLine),
1137            "polygon" => Ok(NodeTypeTag::SvgPolygon),
1138            "polyline" => Ok(NodeTypeTag::SvgPolyline),
1139            "g" => Ok(NodeTypeTag::SvgG),
1140
1141            // SVG container elements
1142            "defs" => Ok(NodeTypeTag::SvgDefs),
1143            "symbol" => Ok(NodeTypeTag::SvgSymbol),
1144            "use" => Ok(NodeTypeTag::SvgUse),
1145            "switch" => Ok(NodeTypeTag::SvgSwitch),
1146
1147            // SVG text elements
1148            "svg:text" => Ok(NodeTypeTag::SvgText),
1149            "tspan" => Ok(NodeTypeTag::SvgTspan),
1150            "textpath" => Ok(NodeTypeTag::SvgTextPath),
1151
1152            // SVG paint server elements
1153            "lineargradient" => Ok(NodeTypeTag::SvgLinearGradient),
1154            "radialgradient" => Ok(NodeTypeTag::SvgRadialGradient),
1155            "stop" => Ok(NodeTypeTag::SvgStop),
1156            "pattern" => Ok(NodeTypeTag::SvgPattern),
1157
1158            // SVG clipping/masking elements
1159            "clippath" => Ok(NodeTypeTag::SvgClipPathElement),
1160            "mask" => Ok(NodeTypeTag::SvgMask),
1161
1162            // SVG filter elements
1163            "filter" => Ok(NodeTypeTag::SvgFilter),
1164            "feblend" => Ok(NodeTypeTag::SvgFeBlend),
1165            "fecolormatrix" => Ok(NodeTypeTag::SvgFeColorMatrix),
1166            "fecomponenttransfer" => Ok(NodeTypeTag::SvgFeComponentTransfer),
1167            "fecomposite" => Ok(NodeTypeTag::SvgFeComposite),
1168            "feconvolvematrix" => Ok(NodeTypeTag::SvgFeConvolveMatrix),
1169            "fediffuselighting" => Ok(NodeTypeTag::SvgFeDiffuseLighting),
1170            "fedisplacementmap" => Ok(NodeTypeTag::SvgFeDisplacementMap),
1171            "fedistantlight" => Ok(NodeTypeTag::SvgFeDistantLight),
1172            "fedropshadow" => Ok(NodeTypeTag::SvgFeDropShadow),
1173            "feflood" => Ok(NodeTypeTag::SvgFeFlood),
1174            "fefuncr" => Ok(NodeTypeTag::SvgFeFuncR),
1175            "fefuncg" => Ok(NodeTypeTag::SvgFeFuncG),
1176            "fefuncb" => Ok(NodeTypeTag::SvgFeFuncB),
1177            "fefunca" => Ok(NodeTypeTag::SvgFeFuncA),
1178            "fegaussianblur" => Ok(NodeTypeTag::SvgFeGaussianBlur),
1179            "feimage" => Ok(NodeTypeTag::SvgFeImage),
1180            "femerge" => Ok(NodeTypeTag::SvgFeMerge),
1181            "femergenode" => Ok(NodeTypeTag::SvgFeMergeNode),
1182            "femorphology" => Ok(NodeTypeTag::SvgFeMorphology),
1183            "feoffset" => Ok(NodeTypeTag::SvgFeOffset),
1184            "fepointlight" => Ok(NodeTypeTag::SvgFePointLight),
1185            "fespecularlighting" => Ok(NodeTypeTag::SvgFeSpecularLighting),
1186            "fespotlight" => Ok(NodeTypeTag::SvgFeSpotLight),
1187            "fetile" => Ok(NodeTypeTag::SvgFeTile),
1188            "feturbulence" => Ok(NodeTypeTag::SvgFeTurbulence),
1189
1190            // SVG marker/image elements
1191            "image" | "svg:image" => Ok(NodeTypeTag::SvgImage),
1192            "svg:marker" => Ok(NodeTypeTag::SvgMarker),
1193            "foreignobject" => Ok(NodeTypeTag::SvgForeignObject),
1194
1195            // SVG descriptive elements
1196            "svg:title" => Ok(NodeTypeTag::SvgTitle),
1197            "svg:a" => Ok(NodeTypeTag::SvgA),
1198            "svg:style" => Ok(NodeTypeTag::SvgStyle),
1199            "svg:script" => Ok(NodeTypeTag::SvgScript),
1200            "desc" => Ok(NodeTypeTag::SvgDesc),
1201            "metadata" => Ok(NodeTypeTag::SvgMetadata),
1202            "view" => Ok(NodeTypeTag::SvgView),
1203
1204            // SVG animation elements
1205            "animate" => Ok(NodeTypeTag::SvgAnimate),
1206            "animatemotion" => Ok(NodeTypeTag::SvgAnimateMotion),
1207            "animatetransform" => Ok(NodeTypeTag::SvgAnimateTransform),
1208            "set" => Ok(NodeTypeTag::SvgSet),
1209            "mpath" => Ok(NodeTypeTag::SvgMpath),
1210
1211            // Metadata
1212            "title" => Ok(NodeTypeTag::Title),
1213            "meta" => Ok(NodeTypeTag::Meta),
1214            "link" => Ok(NodeTypeTag::Link),
1215            "script" => Ok(NodeTypeTag::Script),
1216            "style" => Ok(NodeTypeTag::Style),
1217            "base" => Ok(NodeTypeTag::Base),
1218
1219            // Special
1220            "img" => Ok(NodeTypeTag::Img),
1221            "virtual-view" | "iframe" => Ok(NodeTypeTag::VirtualView),
1222            "icon" => Ok(NodeTypeTag::Icon),
1223            "geolocation-probe" => Ok(NodeTypeTag::GeolocationProbe),
1224
1225            // Pseudo-elements (usually prefixed with ::)
1226            "before" | "::before" => Ok(NodeTypeTag::Before),
1227            "after" | "::after" => Ok(NodeTypeTag::After),
1228            "marker" | "::marker" => Ok(NodeTypeTag::Marker),
1229            "placeholder" | "::placeholder" => Ok(NodeTypeTag::Placeholder),
1230
1231            other => Err(NodeTypeTagParseError::Invalid(other)),
1232        }
1233    }
1234}
1235
1236impl fmt::Display for NodeTypeTag {
1237    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1238        match self {
1239            // Document structure
1240            NodeTypeTag::Html => write!(f, "html"),
1241            NodeTypeTag::Head => write!(f, "head"),
1242            NodeTypeTag::Body => write!(f, "body"),
1243
1244            // Block elements
1245            NodeTypeTag::Div => write!(f, "div"),
1246            NodeTypeTag::P => write!(f, "p"),
1247            NodeTypeTag::Article => write!(f, "article"),
1248            NodeTypeTag::Section => write!(f, "section"),
1249            NodeTypeTag::Nav => write!(f, "nav"),
1250            NodeTypeTag::Aside => write!(f, "aside"),
1251            NodeTypeTag::Header => write!(f, "header"),
1252            NodeTypeTag::Footer => write!(f, "footer"),
1253            NodeTypeTag::Main => write!(f, "main"),
1254            NodeTypeTag::Figure => write!(f, "figure"),
1255            NodeTypeTag::FigCaption => write!(f, "figcaption"),
1256
1257            // Headings
1258            NodeTypeTag::H1 => write!(f, "h1"),
1259            NodeTypeTag::H2 => write!(f, "h2"),
1260            NodeTypeTag::H3 => write!(f, "h3"),
1261            NodeTypeTag::H4 => write!(f, "h4"),
1262            NodeTypeTag::H5 => write!(f, "h5"),
1263            NodeTypeTag::H6 => write!(f, "h6"),
1264
1265            // Text formatting
1266            NodeTypeTag::Br => write!(f, "br"),
1267            NodeTypeTag::Hr => write!(f, "hr"),
1268            NodeTypeTag::Pre => write!(f, "pre"),
1269            NodeTypeTag::BlockQuote => write!(f, "blockquote"),
1270            NodeTypeTag::Address => write!(f, "address"),
1271            NodeTypeTag::Details => write!(f, "details"),
1272            NodeTypeTag::Summary => write!(f, "summary"),
1273            NodeTypeTag::Dialog => write!(f, "dialog"),
1274
1275            // List elements
1276            NodeTypeTag::Ul => write!(f, "ul"),
1277            NodeTypeTag::Ol => write!(f, "ol"),
1278            NodeTypeTag::Li => write!(f, "li"),
1279            NodeTypeTag::Dl => write!(f, "dl"),
1280            NodeTypeTag::Dt => write!(f, "dt"),
1281            NodeTypeTag::Dd => write!(f, "dd"),
1282            NodeTypeTag::Menu => write!(f, "menu"),
1283            NodeTypeTag::MenuItem => write!(f, "menuitem"),
1284            NodeTypeTag::Dir => write!(f, "dir"),
1285
1286            // Table elements
1287            NodeTypeTag::Table => write!(f, "table"),
1288            NodeTypeTag::Caption => write!(f, "caption"),
1289            NodeTypeTag::THead => write!(f, "thead"),
1290            NodeTypeTag::TBody => write!(f, "tbody"),
1291            NodeTypeTag::TFoot => write!(f, "tfoot"),
1292            NodeTypeTag::Tr => write!(f, "tr"),
1293            NodeTypeTag::Th => write!(f, "th"),
1294            NodeTypeTag::Td => write!(f, "td"),
1295            NodeTypeTag::ColGroup => write!(f, "colgroup"),
1296            NodeTypeTag::Col => write!(f, "col"),
1297
1298            // Form elements
1299            NodeTypeTag::Form => write!(f, "form"),
1300            NodeTypeTag::FieldSet => write!(f, "fieldset"),
1301            NodeTypeTag::Legend => write!(f, "legend"),
1302            NodeTypeTag::Label => write!(f, "label"),
1303            NodeTypeTag::Input => write!(f, "input"),
1304            NodeTypeTag::Button => write!(f, "button"),
1305            NodeTypeTag::Select => write!(f, "select"),
1306            NodeTypeTag::OptGroup => write!(f, "optgroup"),
1307            NodeTypeTag::SelectOption => write!(f, "option"),
1308            NodeTypeTag::TextArea => write!(f, "textarea"),
1309            NodeTypeTag::Output => write!(f, "output"),
1310            NodeTypeTag::Progress => write!(f, "progress"),
1311            NodeTypeTag::Meter => write!(f, "meter"),
1312            NodeTypeTag::DataList => write!(f, "datalist"),
1313
1314            // Inline elements
1315            NodeTypeTag::Span => write!(f, "span"),
1316            NodeTypeTag::A => write!(f, "a"),
1317            NodeTypeTag::Em => write!(f, "em"),
1318            NodeTypeTag::Strong => write!(f, "strong"),
1319            NodeTypeTag::B => write!(f, "b"),
1320            NodeTypeTag::I => write!(f, "i"),
1321            NodeTypeTag::U => write!(f, "u"),
1322            NodeTypeTag::S => write!(f, "s"),
1323            NodeTypeTag::Mark => write!(f, "mark"),
1324            NodeTypeTag::Del => write!(f, "del"),
1325            NodeTypeTag::Ins => write!(f, "ins"),
1326            NodeTypeTag::Code => write!(f, "code"),
1327            NodeTypeTag::Samp => write!(f, "samp"),
1328            NodeTypeTag::Kbd => write!(f, "kbd"),
1329            NodeTypeTag::Var => write!(f, "var"),
1330            NodeTypeTag::Cite => write!(f, "cite"),
1331            NodeTypeTag::Dfn => write!(f, "dfn"),
1332            NodeTypeTag::Abbr => write!(f, "abbr"),
1333            NodeTypeTag::Acronym => write!(f, "acronym"),
1334            NodeTypeTag::Q => write!(f, "q"),
1335            NodeTypeTag::Time => write!(f, "time"),
1336            NodeTypeTag::Sub => write!(f, "sub"),
1337            NodeTypeTag::Sup => write!(f, "sup"),
1338            NodeTypeTag::Small => write!(f, "small"),
1339            NodeTypeTag::Big => write!(f, "big"),
1340            NodeTypeTag::Bdo => write!(f, "bdo"),
1341            NodeTypeTag::Bdi => write!(f, "bdi"),
1342            NodeTypeTag::Wbr => write!(f, "wbr"),
1343            NodeTypeTag::Ruby => write!(f, "ruby"),
1344            NodeTypeTag::Rt => write!(f, "rt"),
1345            NodeTypeTag::Rtc => write!(f, "rtc"),
1346            NodeTypeTag::Rp => write!(f, "rp"),
1347            NodeTypeTag::Data => write!(f, "data"),
1348
1349            // Embedded content
1350            NodeTypeTag::Canvas => write!(f, "canvas"),
1351            NodeTypeTag::Object => write!(f, "object"),
1352            NodeTypeTag::Param => write!(f, "param"),
1353            NodeTypeTag::Embed => write!(f, "embed"),
1354            NodeTypeTag::Audio => write!(f, "audio"),
1355            NodeTypeTag::Video => write!(f, "video"),
1356            NodeTypeTag::Source => write!(f, "source"),
1357            NodeTypeTag::Track => write!(f, "track"),
1358            NodeTypeTag::Map => write!(f, "map"),
1359            NodeTypeTag::Area => write!(f, "area"),
1360            NodeTypeTag::Svg => write!(f, "svg"),
1361            NodeTypeTag::SvgPath => write!(f, "path"),
1362            NodeTypeTag::SvgCircle => write!(f, "circle"),
1363            NodeTypeTag::SvgRect => write!(f, "rect"),
1364            NodeTypeTag::SvgEllipse => write!(f, "ellipse"),
1365            NodeTypeTag::SvgLine => write!(f, "line"),
1366            NodeTypeTag::SvgPolygon => write!(f, "polygon"),
1367            NodeTypeTag::SvgPolyline => write!(f, "polyline"),
1368            NodeTypeTag::SvgG => write!(f, "g"),
1369
1370            // SVG container elements
1371            NodeTypeTag::SvgDefs => write!(f, "defs"),
1372            NodeTypeTag::SvgSymbol => write!(f, "symbol"),
1373            NodeTypeTag::SvgUse => write!(f, "use"),
1374            NodeTypeTag::SvgSwitch => write!(f, "switch"),
1375
1376            // SVG text elements
1377            NodeTypeTag::SvgText => write!(f, "svg:text"),
1378            NodeTypeTag::SvgTspan => write!(f, "tspan"),
1379            NodeTypeTag::SvgTextPath => write!(f, "textpath"),
1380
1381            // SVG paint server elements
1382            NodeTypeTag::SvgLinearGradient => write!(f, "lineargradient"),
1383            NodeTypeTag::SvgRadialGradient => write!(f, "radialgradient"),
1384            NodeTypeTag::SvgStop => write!(f, "stop"),
1385            NodeTypeTag::SvgPattern => write!(f, "pattern"),
1386
1387            // SVG clipping/masking elements
1388            NodeTypeTag::SvgClipPathElement => write!(f, "clippath"),
1389            NodeTypeTag::SvgMask => write!(f, "mask"),
1390
1391            // SVG filter elements
1392            NodeTypeTag::SvgFilter => write!(f, "filter"),
1393            NodeTypeTag::SvgFeBlend => write!(f, "feblend"),
1394            NodeTypeTag::SvgFeColorMatrix => write!(f, "fecolormatrix"),
1395            NodeTypeTag::SvgFeComponentTransfer => write!(f, "fecomponenttransfer"),
1396            NodeTypeTag::SvgFeComposite => write!(f, "fecomposite"),
1397            NodeTypeTag::SvgFeConvolveMatrix => write!(f, "feconvolvematrix"),
1398            NodeTypeTag::SvgFeDiffuseLighting => write!(f, "fediffuselighting"),
1399            NodeTypeTag::SvgFeDisplacementMap => write!(f, "fedisplacementmap"),
1400            NodeTypeTag::SvgFeDistantLight => write!(f, "fedistantlight"),
1401            NodeTypeTag::SvgFeDropShadow => write!(f, "fedropshadow"),
1402            NodeTypeTag::SvgFeFlood => write!(f, "feflood"),
1403            NodeTypeTag::SvgFeFuncR => write!(f, "fefuncr"),
1404            NodeTypeTag::SvgFeFuncG => write!(f, "fefuncg"),
1405            NodeTypeTag::SvgFeFuncB => write!(f, "fefuncb"),
1406            NodeTypeTag::SvgFeFuncA => write!(f, "fefunca"),
1407            NodeTypeTag::SvgFeGaussianBlur => write!(f, "fegaussianblur"),
1408            NodeTypeTag::SvgFeImage => write!(f, "feimage"),
1409            NodeTypeTag::SvgFeMerge => write!(f, "femerge"),
1410            NodeTypeTag::SvgFeMergeNode => write!(f, "femergenode"),
1411            NodeTypeTag::SvgFeMorphology => write!(f, "femorphology"),
1412            NodeTypeTag::SvgFeOffset => write!(f, "feoffset"),
1413            NodeTypeTag::SvgFePointLight => write!(f, "fepointlight"),
1414            NodeTypeTag::SvgFeSpecularLighting => write!(f, "fespecularlighting"),
1415            NodeTypeTag::SvgFeSpotLight => write!(f, "fespotlight"),
1416            NodeTypeTag::SvgFeTile => write!(f, "fetile"),
1417            NodeTypeTag::SvgFeTurbulence => write!(f, "feturbulence"),
1418
1419            // SVG marker/image elements
1420            NodeTypeTag::SvgMarker => write!(f, "svg:marker"),
1421            NodeTypeTag::SvgImage => write!(f, "svg:image"),
1422            NodeTypeTag::SvgForeignObject => write!(f, "foreignobject"),
1423
1424            // SVG descriptive elements
1425            NodeTypeTag::SvgTitle => write!(f, "svg:title"),
1426            NodeTypeTag::SvgDesc => write!(f, "desc"),
1427            NodeTypeTag::SvgMetadata => write!(f, "metadata"),
1428            NodeTypeTag::SvgA => write!(f, "svg:a"),
1429            NodeTypeTag::SvgView => write!(f, "view"),
1430            NodeTypeTag::SvgStyle => write!(f, "svg:style"),
1431            NodeTypeTag::SvgScript => write!(f, "svg:script"),
1432
1433            // SVG animation elements
1434            NodeTypeTag::SvgAnimate => write!(f, "animate"),
1435            NodeTypeTag::SvgAnimateMotion => write!(f, "animatemotion"),
1436            NodeTypeTag::SvgAnimateTransform => write!(f, "animatetransform"),
1437            NodeTypeTag::SvgSet => write!(f, "set"),
1438            NodeTypeTag::SvgMpath => write!(f, "mpath"),
1439
1440            // Metadata
1441            NodeTypeTag::Title => write!(f, "title"),
1442            NodeTypeTag::Meta => write!(f, "meta"),
1443            NodeTypeTag::Link => write!(f, "link"),
1444            NodeTypeTag::Script => write!(f, "script"),
1445            NodeTypeTag::Style => write!(f, "style"),
1446            NodeTypeTag::Base => write!(f, "base"),
1447
1448            // Content elements
1449            NodeTypeTag::Text => write!(f, "text"),
1450            NodeTypeTag::Img => write!(f, "img"),
1451            NodeTypeTag::VirtualView => write!(f, "virtual-view"),
1452            NodeTypeTag::Icon => write!(f, "icon"),
1453            NodeTypeTag::GeolocationProbe => write!(f, "geolocation-probe"),
1454
1455            // Pseudo-elements
1456            NodeTypeTag::Before => write!(f, "::before"),
1457            NodeTypeTag::After => write!(f, "::after"),
1458            NodeTypeTag::Marker => write!(f, "::marker"),
1459            NodeTypeTag::Placeholder => write!(f, "::placeholder"),
1460        }
1461    }
1462}
1463
1464/// Represents a full CSS path (i.e. the "div#id.class" selector belonging to
1465///  a CSS "content group" (the following key-value block)).
1466///
1467/// ```no_run,ignore
1468/// "#div > .my_class:focus" ==
1469/// [
1470///   CssPathSelector::Type(NodeTypeTag::Div),
1471///   CssPathSelector::PseudoSelector(CssPathPseudoSelector::LimitChildren),
1472///   CssPathSelector::Class("my_class"),
1473///   CssPathSelector::PseudoSelector(CssPathPseudoSelector::Focus),
1474/// ]
1475#[derive(Clone, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
1476#[repr(C)]
1477pub struct CssPath {
1478    pub selectors: CssPathSelectorVec,
1479}
1480
1481impl_vec!(CssPathSelector, CssPathSelectorVec, CssPathSelectorVecDestructor, CssPathSelectorVecDestructorType, CssPathSelectorVecSlice, OptionCssPathSelector);
1482impl_vec_debug!(CssPathSelector, CssPathSelectorVec);
1483impl_vec_partialord!(CssPathSelector, CssPathSelectorVec);
1484impl_vec_ord!(CssPathSelector, CssPathSelectorVec);
1485impl_vec_clone!(
1486    CssPathSelector,
1487    CssPathSelectorVec,
1488    CssPathSelectorVecDestructor
1489);
1490impl_vec_partialeq!(CssPathSelector, CssPathSelectorVec);
1491impl_vec_eq!(CssPathSelector, CssPathSelectorVec);
1492impl_vec_hash!(CssPathSelector, CssPathSelectorVec);
1493
1494impl CssPath {
1495    pub fn new(selectors: Vec<CssPathSelector>) -> Self {
1496        Self {
1497            selectors: selectors.into(),
1498        }
1499    }
1500}
1501
1502impl fmt::Display for CssPath {
1503    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1504        for selector in self.selectors.as_ref() {
1505            write!(f, "{}", selector)?;
1506        }
1507        Ok(())
1508    }
1509}
1510
1511impl fmt::Debug for CssPath {
1512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1513        write!(f, "{}", self)
1514    }
1515}
1516
1517#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1518#[repr(C, u8)]
1519#[derive(Default)]
1520pub enum CssPathSelector {
1521    /// Represents the `*` selector
1522    #[default]
1523    Global,
1524    /// `div`, `p`, etc.
1525    Type(NodeTypeTag),
1526    /// `.something`
1527    Class(AzString),
1528    /// `#something`
1529    Id(AzString),
1530    /// `:something`
1531    PseudoSelector(CssPathPseudoSelector),
1532    /// `[attr]`, `[attr="value"]`, `[attr~="value"]`, etc.
1533    Attribute(CssAttributeSelector),
1534    /// Represents the `>` selector (direct child)
1535    DirectChildren,
1536    /// Represents the ` ` selector (descendant)
1537    Children,
1538    /// Represents the `+` selector (adjacent sibling)
1539    AdjacentSibling,
1540    /// Represents the `~` selector (general sibling)
1541    GeneralSibling,
1542}
1543
1544/// Attribute selector (`[attr]`, `[attr="value"]`, ...).
1545#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1546#[repr(C)]
1547pub struct CssAttributeSelector {
1548    pub name: AzString,
1549    pub op: AttributeMatchOp,
1550    pub value: OptionString,
1551}
1552
1553impl Default for CssAttributeSelector {
1554    fn default() -> Self {
1555        Self {
1556            name: AzString::default(),
1557            op: AttributeMatchOp::Exists,
1558            value: OptionString::None,
1559        }
1560    }
1561}
1562
1563/// Operator that compares an attribute value against a target string.
1564#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1565#[repr(C)]
1566pub enum AttributeMatchOp {
1567    /// `[attr]` — attribute is present (any value).
1568    Exists,
1569    /// `[attr="value"]` — attribute equals value exactly.
1570    Eq,
1571    /// `[attr~="value"]` — value is one of the whitespace-separated words.
1572    Includes,
1573    /// `[attr|="value"]` — value equals exactly OR begins with value followed by `-`.
1574    DashMatch,
1575    /// `[attr^="value"]` — value starts with the given prefix.
1576    Prefix,
1577    /// `[attr$="value"]` — value ends with the given suffix.
1578    Suffix,
1579    /// `[attr*="value"]` — value contains the given substring.
1580    Substring,
1581}
1582
1583impl Default for AttributeMatchOp {
1584    fn default() -> Self {
1585        AttributeMatchOp::Exists
1586    }
1587}
1588
1589impl_option!(
1590    CssPathSelector,
1591    OptionCssPathSelector,
1592    copy = false,
1593    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1594);
1595
1596
1597impl fmt::Display for CssPathSelector {
1598    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1599        use self::CssPathSelector::*;
1600        match &self {
1601            Global => write!(f, "*"),
1602            Type(n) => write!(f, "{}", n),
1603            Class(c) => write!(f, ".{}", c),
1604            Id(i) => write!(f, "#{}", i),
1605            PseudoSelector(p) => write!(f, ":{}", p),
1606            Attribute(a) => write!(f, "{}", a),
1607            DirectChildren => write!(f, ">"),
1608            Children => write!(f, " "),
1609            AdjacentSibling => write!(f, "+"),
1610            GeneralSibling => write!(f, "~"),
1611        }
1612    }
1613}
1614
1615impl fmt::Display for CssAttributeSelector {
1616    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1617        match (&self.op, self.value.as_ref()) {
1618            (AttributeMatchOp::Exists, _) => write!(f, "[{}]", self.name),
1619            (op, Some(v)) => write!(f, "[{}{}=\"{}\"]", self.name, op.symbol_prefix(), v),
1620            (op, None) => write!(f, "[{}{}=\"\"]", self.name, op.symbol_prefix()),
1621        }
1622    }
1623}
1624
1625impl AttributeMatchOp {
1626    /// Returns the prefix character for the `=` operator (e.g. `~` for `~=`).
1627    /// `Eq` returns `""`, `Exists` is unused (no `=` printed at all).
1628    pub fn symbol_prefix(&self) -> &'static str {
1629        match self {
1630            AttributeMatchOp::Exists => "",
1631            AttributeMatchOp::Eq => "",
1632            AttributeMatchOp::Includes => "~",
1633            AttributeMatchOp::DashMatch => "|",
1634            AttributeMatchOp::Prefix => "^",
1635            AttributeMatchOp::Suffix => "$",
1636            AttributeMatchOp::Substring => "*",
1637        }
1638    }
1639}
1640
1641#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1642#[repr(C, u8)]
1643pub enum CssPathPseudoSelector {
1644    /// `:first`
1645    First,
1646    /// `:last`
1647    Last,
1648    /// `:nth-child`
1649    NthChild(CssNthChildSelector),
1650    /// `:hover` - mouse is over element
1651    Hover,
1652    /// `:active` - mouse is pressed and over element
1653    Active,
1654    /// `:focus` - element has received focus
1655    Focus,
1656    /// `:lang(de)` - element matches language
1657    Lang(AzString),
1658    /// `:backdrop` - window is not focused (GTK compatibility)
1659    Backdrop,
1660    /// `:dragging` - element is currently being dragged
1661    Dragging,
1662    /// `:drag-over` - a dragged element is over this drop target
1663    DragOver,
1664}
1665
1666/// Selector for the `:nth-child()` CSS pseudo-class.
1667#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1668#[repr(C, u8)]
1669pub enum CssNthChildSelector {
1670    Number(u32),
1671    Even,
1672    Odd,
1673    Pattern(CssNthChildPattern),
1674}
1675
1676/// Pattern for `:nth-child(An+B)` selectors, where `pattern_repeat` is A and `offset` is B.
1677#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1678#[repr(C)]
1679pub struct CssNthChildPattern {
1680    pub pattern_repeat: u32,
1681    pub offset: u32,
1682}
1683
1684impl fmt::Display for CssNthChildSelector {
1685    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1686        use self::CssNthChildSelector::*;
1687        match &self {
1688            Number(u) => write!(f, "{}", u),
1689            Even => write!(f, "even"),
1690            Odd => write!(f, "odd"),
1691            Pattern(p) => write!(f, "{}n + {}", p.pattern_repeat, p.offset),
1692        }
1693    }
1694}
1695
1696impl fmt::Display for CssPathPseudoSelector {
1697    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1698        use self::CssPathPseudoSelector::*;
1699        match &self {
1700            First => write!(f, "first"),
1701            Last => write!(f, "last"),
1702            NthChild(u) => write!(f, "nth-child({})", u),
1703            Hover => write!(f, "hover"),
1704            Active => write!(f, "active"),
1705            Focus => write!(f, "focus"),
1706            Lang(lang) => write!(f, "lang({})", lang.as_str()),
1707            Backdrop => write!(f, "backdrop"),
1708            Dragging => write!(f, "dragging"),
1709            DragOver => write!(f, "drag-over"),
1710        }
1711    }
1712}
1713
1714impl Css {
1715    /// Creates a new, empty CSS.
1716    pub fn empty() -> Self {
1717        Default::default()
1718    }
1719
1720    /// Sort the rules by `(priority, specificity)` so they apply in cascade order.
1721    /// Lower-priority rules sort first; ties break by selector specificity.
1722    /// This preserves layer identity (UA / SYSTEM / AUTHOR / INLINE / RUNTIME)
1723    /// without needing a separate `Stylesheet` boundary.
1724    pub fn sort_by_specificity(&mut self) {
1725        self.rules.as_mut().sort_by(|a, b| {
1726            a.priority.cmp(&b.priority)
1727                .then_with(|| get_specificity(&a.path).cmp(&get_specificity(&b.path)))
1728        });
1729    }
1730
1731    pub fn rules<'a>(&'a self) -> core::slice::Iter<'a, CssRuleBlock> {
1732        self.rules.as_ref().iter()
1733    }
1734
1735    /// Iterate `(property, conditions)` pairs as if this were a flat list of
1736    /// `CssPropertyWithConditions`. Each `Static` declaration yields one item,
1737    /// sharing the conditions of its enclosing rule. `Dynamic` declarations
1738    /// are skipped (matching the previous inline-CSS behaviour).
1739    ///
1740    /// Used by cascade and diff code that walks per-property to keep the
1741    /// flat-iteration shape after the inline-vs-component unification.
1742    pub fn iter_inline_properties<'a>(
1743        &'a self,
1744    ) -> impl Iterator<
1745        Item = (
1746            &'a crate::props::property::CssProperty,
1747            &'a DynamicSelectorVec,
1748        ),
1749    > + 'a {
1750        self.rules.as_ref().iter().flat_map(|r| {
1751            r.declarations.as_ref().iter().filter_map(move |d| match d {
1752                CssDeclaration::Static(p) => Some((p, &r.conditions)),
1753                CssDeclaration::Dynamic(_) => None,
1754            })
1755        })
1756    }
1757}
1758
1759#[cfg(test)]
1760mod priority_sort_tests {
1761    use super::*;
1762    use crate::css::rule_priority;
1763
1764    fn rule_with(priority: u8, selectors: Vec<CssPathSelector>) -> CssRuleBlock {
1765        CssRuleBlock {
1766            path: CssPath { selectors: selectors.into() },
1767            declarations: Vec::new().into(),
1768            conditions: DynamicSelectorVec::from_const_slice(&[]),
1769            priority,
1770        }
1771    }
1772
1773    /// Pin the (priority, specificity) sort order. Lower priority sorts first;
1774    /// ties break by specificity.
1775    #[test]
1776    fn sort_by_priority_then_specificity() {
1777        let mut css = Css::new(vec![
1778            // Author rule, no specificity.
1779            rule_with(rule_priority::AUTHOR, vec![CssPathSelector::Global]),
1780            // UA rule with high specificity — must still come BEFORE any author rule.
1781            rule_with(rule_priority::UA, vec![
1782                CssPathSelector::Id("ua-id".to_string().into()),
1783                CssPathSelector::Class("ua-class".to_string().into()),
1784            ]),
1785            // Author rule with high specificity.
1786            rule_with(rule_priority::AUTHOR, vec![
1787                CssPathSelector::Id("a-id".to_string().into()),
1788            ]),
1789            // System rule with no specificity — must sit between UA and author.
1790            rule_with(rule_priority::SYSTEM, vec![CssPathSelector::Global]),
1791        ]);
1792        css.sort_by_specificity();
1793        let priorities: Vec<u8> = css.rules.as_ref().iter().map(|r| r.priority).collect();
1794        assert_eq!(
1795            priorities,
1796            vec![rule_priority::UA, rule_priority::SYSTEM, rule_priority::AUTHOR, rule_priority::AUTHOR],
1797            "rules must sort by layer first; specificity only breaks ties within a layer"
1798        );
1799        // Within author, the high-specificity #a-id comes after the * rule.
1800        let last_two_specificity: Vec<_> = css.rules.as_ref().iter()
1801            .filter(|r| r.priority == rule_priority::AUTHOR)
1802            .map(|r| get_specificity(&r.path))
1803            .collect();
1804        assert!(last_two_specificity[0] < last_two_specificity[1]);
1805    }
1806}
1807
1808/// Returns specificity of the given css path. Further information can be found on
1809/// [the w3 website](http://www.w3.org/TR/selectors/#specificity).
1810pub fn get_specificity(path: &CssPath) -> (usize, usize, usize, usize) {
1811    let id_count = path
1812        .selectors
1813        .iter()
1814        .filter(|x| matches!(x, CssPathSelector::Id(_)))
1815        .count();
1816    let class_count = path
1817        .selectors
1818        .iter()
1819        .filter(|x| {
1820            matches!(
1821                x,
1822                CssPathSelector::Class(_)
1823                    | CssPathSelector::PseudoSelector(_)
1824                    | CssPathSelector::Attribute(_)
1825            )
1826        })
1827        .count();
1828    let div_count = path
1829        .selectors
1830        .iter()
1831        .filter(|x| matches!(x, CssPathSelector::Type(_)))
1832        .count();
1833    (id_count, class_count, div_count, path.selectors.len())
1834}