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.
11
use alloc::{string::String, vec::Vec};
12
use core::fmt;
13

            
14
use 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)]
30
pub 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

            
36
impl_option!(
37
    Css,
38
    OptionCss,
39
    copy = false,
40
    [Debug, Clone, PartialEq, PartialOrd]
41
);
42

            
43
impl_vec!(Css, CssVec, CssVecDestructor, CssVecDestructorType, CssVecSlice, OptionCss);
44
impl_vec_mut!(Css, CssVec);
45
impl_vec_debug!(Css, CssVec);
46
impl_vec_partialord!(Css, CssVec);
47
impl_vec_clone!(Css, CssVec, CssVecDestructor);
48
impl_vec_partialeq!(Css, CssVec);
49

            
50
impl_vec!(CssRuleBlock, CssRuleBlockVec, CssRuleBlockVecDestructor, CssRuleBlockVecDestructorType, CssRuleBlockVecSlice, OptionCssRuleBlock);
51
impl_vec_mut!(CssRuleBlock, CssRuleBlockVec);
52
impl_vec_debug!(CssRuleBlock, CssRuleBlockVec);
53
impl_vec_partialord!(CssRuleBlock, CssRuleBlockVec);
54
impl_vec_clone!(CssRuleBlock, CssRuleBlockVec, CssRuleBlockVecDestructor);
55
impl_vec_partialeq!(CssRuleBlock, CssRuleBlockVec);
56

            
57
impl Css {
58
8796
    pub fn is_empty(&self) -> bool {
59
8796
        self.rules.as_ref().is_empty()
60
8796
    }
61

            
62
5461
    pub fn new(rules: Vec<CssRuleBlock>) -> Self {
63
5461
        Self {
64
5461
            rules: rules.into(),
65
5461
        }
66
5461
    }
67

            
68
    #[cfg(feature = "parser")]
69
4224
    pub fn from_string(s: crate::AzString) -> Self {
70
4224
        crate::parser2::new_from_str(s.as_str()).0
71
4224
    }
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
247
    pub fn parse_inline(style: &str) -> Self {
82
        use alloc::string::ToString;
83
247
        let mut wrapped = String::with_capacity(style.len() + 6);
84
247
        wrapped.push_str("* {\n");
85
247
        wrapped.push_str(style);
86
247
        wrapped.push_str("\n}");
87
247
        let (mut css, _warnings) = crate::parser2::new_from_str(&wrapped);
88
247
        for rule in css.rules.as_mut() {
89
247
            rule.priority = rule_priority::INLINE;
90
247
        }
91
247
        css
92
247
    }
93

            
94
    #[cfg(feature = "parser")]
95
14
    pub fn from_string_with_warnings(
96
14
        s: crate::AzString,
97
14
    ) -> (Self, Vec<crate::parser2::CssParseWarnMsgOwned>) {
98
14
        let (css, warnings) = crate::parser2::new_from_str(s.as_str());
99
        (
100
14
            css,
101
14
            warnings
102
14
                .into_iter()
103
14
                .map(|w| crate::parser2::CssParseWarnMsgOwned {
104
                    warning: w.warning.to_contained(),
105
                    location: w.location,
106
                })
107
14
                .collect(),
108
        )
109
14
    }
110
}
111

            
112
impl 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.
123
impl Eq for Css {}
124
impl 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
}
129
impl Eq for CssRuleBlock {}
130
impl 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.
147
impl From<crate::dynamic_selector::CssPropertyWithConditionsVec> for Css {
148
665
    fn from(props: crate::dynamic_selector::CssPropertyWithConditionsVec) -> Self {
149
703
        let rules: Vec<CssRuleBlock> = props.into_library_owned_vec().into_iter().map(|p| {
150
703
            CssRuleBlock {
151
703
                path: CssPath { selectors: Vec::new().into() },
152
703
                declarations: alloc::vec![CssDeclaration::Static(p.property)].into(),
153
703
                conditions: p.apply_if,
154
703
                priority: rule_priority::INLINE,
155
703
            }
156
703
        }).collect();
157
665
        Css { rules: rules.into() }
158
665
    }
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)]
164
pub 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

            
171
impl_option!(
172
    CssDeclaration,
173
    OptionCssDeclaration,
174
    copy = false,
175
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
176
);
177

            
178
impl 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)]
247
pub 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)]
262
pub 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

            
269
impl<T> BoxOrStatic<T> {
270
    /// Allocate `value` on the heap and return a `Boxed` variant.
271
    #[inline]
272
13710
    pub fn heap(value: T) -> Self {
273
13710
        BoxOrStatic::Boxed(Box::into_raw(Box::new(value)))
274
13710
    }
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
107452
    pub fn as_ref(&self) -> &T {
283
107452
        match self {
284
107452
            BoxOrStatic::Boxed(ptr) => unsafe {
285
107452
                debug_assert!(!ptr.is_null(), "BoxOrStatic::Boxed contained a null pointer");
286
107452
                &**ptr
287
            },
288
            BoxOrStatic::Static(ptr) => unsafe {
289
                debug_assert!(!ptr.is_null(), "BoxOrStatic::Static contained a null pointer");
290
                &**ptr
291
            },
292
        }
293
107452
    }
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

            
315
impl<T> Drop for BoxOrStatic<T> {
316
22086
    fn drop(&mut self) {
317
22086
        if let BoxOrStatic::Boxed(ptr) = self {
318
22086
            if !ptr.is_null() {
319
22086
                unsafe { drop(Box::from_raw(*ptr)); }
320
22086
                *ptr = core::ptr::null_mut();
321
22086
            }
322
        }
323
22086
    }
324
}
325

            
326
impl<T: Clone> Clone for BoxOrStatic<T> {
327
672
    fn clone(&self) -> Self {
328
672
        match self {
329
672
            BoxOrStatic::Boxed(ptr) => {
330
672
                let val = unsafe { &**ptr }.clone();
331
672
                BoxOrStatic::Boxed(Box::into_raw(Box::new(val)))
332
            }
333
            BoxOrStatic::Static(ptr) => BoxOrStatic::Static(*ptr),
334
        }
335
672
    }
336
}
337

            
338
impl<T: core::fmt::Debug> core::fmt::Debug for BoxOrStatic<T> {
339
13771
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
340
13771
        self.as_ref().fmt(f)
341
13771
    }
342
}
343

            
344
impl<T: core::fmt::Display> core::fmt::Display for BoxOrStatic<T> {
345
1
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
346
1
        self.as_ref().fmt(f)
347
1
    }
348
}
349

            
350
impl<T: PartialEq> PartialEq for BoxOrStatic<T> {
351
    fn eq(&self, other: &Self) -> bool {
352
        self.as_ref() == other.as_ref()
353
    }
354
}
355

            
356
impl<T: Eq> Eq for BoxOrStatic<T> {}
357

            
358
impl<T: core::hash::Hash> core::hash::Hash for BoxOrStatic<T> {
359
12135
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
360
12135
        self.as_ref().hash(state)
361
12135
    }
362
}
363

            
364
impl<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

            
370
impl<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

            
376
impl<T> core::ops::Deref for BoxOrStatic<T> {
377
    type Target = T;
378
    #[inline]
379
81544
    fn deref(&self) -> &T {
380
81544
        self.as_ref()
381
81544
    }
382
}
383

            
384
impl<T: Default> Default for BoxOrStatic<T> {
385
    fn default() -> Self {
386
        BoxOrStatic::heap(T::default())
387
    }
388
}
389

            
390
impl<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
397
unsafe impl<T: Send + 'static> Send for BoxOrStatic<T> {}
398
// Safety: BoxOrStatic<T> is Sync if T is Sync
399
unsafe impl<T: Sync + 'static> Sync for BoxOrStatic<T> {}
400

            
401
/// Type alias: `BoxOrStatic<StyleBoxShadow>` — used by codegen for FFI monomorphization.
402
pub type BoxOrStaticStyleBoxShadow = BoxOrStatic<crate::props::style::box_shadow::StyleBoxShadow>;
403

            
404
/// Type alias: `BoxOrStatic<AzString>` — used by NodeType::Text and NodeType::Icon.
405
pub 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
411
pub 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.
422
pub trait PrintAsCssValue {
423
    fn print_as_css_value(&self) -> String;
424
}
425

            
426
impl<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

            
440
impl<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

            
455
impl<T> From<T> for CssPropertyValue<T> {
456
62463
    fn from(c: T) -> Self {
457
62463
        CssPropertyValue::Exact(c)
458
62463
    }
459
}
460

            
461
impl<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
287524
    pub fn get_property(&self) -> Option<&T> {
479
287524
        match self {
480
287524
            CssPropertyValue::Exact(c) => Some(c),
481
            _ => None,
482
        }
483
287524
    }
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
176
    pub fn is_auto(&self) -> bool {
495
176
        matches!(self, CssPropertyValue::Auto)
496
176
    }
497

            
498
    #[inline]
499
1
    pub fn is_none(&self) -> bool {
500
1
        matches!(self, CssPropertyValue::None)
501
1
    }
502

            
503
    #[inline]
504
2
    pub fn is_initial(&self) -> bool {
505
2
        matches!(self, CssPropertyValue::Initial)
506
2
    }
507

            
508
    #[inline]
509
3
    pub fn is_inherit(&self) -> bool {
510
3
        matches!(self, CssPropertyValue::Inherit)
511
3
    }
512

            
513
    #[inline]
514
2
    pub fn is_revert(&self) -> bool {
515
2
        matches!(self, CssPropertyValue::Revert)
516
2
    }
517

            
518
    #[inline]
519
4
    pub fn is_unset(&self) -> bool {
520
4
        matches!(self, CssPropertyValue::Unset)
521
4
    }
522
}
523

            
524
impl<T: Default> CssPropertyValue<T> {
525
    #[inline]
526
2305
    pub fn get_property_or_default(self) -> Option<T> {
527
2305
        match self {
528
            CssPropertyValue::Auto | CssPropertyValue::Initial => Some(T::default()),
529
2305
            CssPropertyValue::Exact(c) => Some(c),
530
            CssPropertyValue::None
531
            | CssPropertyValue::Inherit
532
            | CssPropertyValue::Revert
533
            | CssPropertyValue::Unset => None,
534
        }
535
2305
    }
536
}
537

            
538
impl<T: Default> Default for CssPropertyValue<T> {
539
    #[inline]
540
    fn default() -> Self {
541
        CssPropertyValue::Exact(T::default())
542
    }
543
}
544

            
545
impl 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.
565
pub 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)]
602
pub 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

            
617
impl_option!(
618
    CssRuleBlock,
619
    OptionCssRuleBlock,
620
    copy = false,
621
    [Debug, Clone, PartialEq, PartialOrd]
622
);
623

            
624
impl 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

            
634
impl_vec!(CssDeclaration, CssDeclarationVec, CssDeclarationVecDestructor, CssDeclarationVecDestructorType, CssDeclarationVecSlice, OptionCssDeclaration);
635
impl_vec_mut!(CssDeclaration, CssDeclarationVec);
636
impl_vec_debug!(CssDeclaration, CssDeclarationVec);
637
impl_vec_partialord!(CssDeclaration, CssDeclarationVec);
638
impl_vec_ord!(CssDeclaration, CssDeclarationVec);
639
impl_vec_clone!(
640
    CssDeclaration,
641
    CssDeclarationVec,
642
    CssDeclarationVecDestructor
643
);
644
impl_vec_partialeq!(CssDeclaration, CssDeclarationVec);
645
impl_vec_eq!(CssDeclaration, CssDeclarationVec);
646
impl_vec_hash!(CssDeclaration, CssDeclarationVec);
647

            
648
impl CssRuleBlock {
649
2
    pub fn new(path: CssPath, declarations: Vec<CssDeclaration>) -> Self {
650
2
        Self {
651
2
            path,
652
2
            declarations: declarations.into(),
653
2
            conditions: DynamicSelectorVec::from_const_slice(&[]),
654
2
            priority: rule_priority::AUTHOR,
655
2
        }
656
2
    }
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.
673
pub 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)]
678
pub 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)]
969
pub enum NodeTypeTagParseError<'a> {
970
    Invalid(&'a str),
971
}
972

            
973
impl<'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)]
984
pub enum NodeTypeTagParseErrorOwned {
985
    Invalid(AzString),
986
}
987

            
988
impl<'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

            
996
impl NodeTypeTagParseErrorOwned {
997
    pub fn to_shared<'a>(&'a self) -> NodeTypeTagParseError<'a> {
998
        match self {
999
            NodeTypeTagParseErrorOwned::Invalid(s) => NodeTypeTagParseError::Invalid(s),
        }
    }
}
/// Parses the node type from a CSS string such as `"div"` => `NodeTypeTag::Div`
impl NodeTypeTag {
5822
    pub fn from_str(css_key: &str) -> Result<Self, NodeTypeTagParseError<'_>> {
5822
        match css_key {
            // Document structure
5822
            "html" => Ok(NodeTypeTag::Html),
5754
            "head" => Ok(NodeTypeTag::Head),
5747
            "body" => Ok(NodeTypeTag::Body),
            // Block-level elements
3603
            "div" => Ok(NodeTypeTag::Div),
2103
            "p" => Ok(NodeTypeTag::P),
1260
            "article" => Ok(NodeTypeTag::Article),
1253
            "section" => Ok(NodeTypeTag::Section),
1246
            "nav" => Ok(NodeTypeTag::Nav),
1239
            "aside" => Ok(NodeTypeTag::Aside),
1232
            "header" => Ok(NodeTypeTag::Header),
1225
            "footer" => Ok(NodeTypeTag::Footer),
1218
            "main" => Ok(NodeTypeTag::Main),
1211
            "figure" => Ok(NodeTypeTag::Figure),
1204
            "figcaption" => Ok(NodeTypeTag::FigCaption),
            // Headings
1197
            "h1" => Ok(NodeTypeTag::H1),
1183
            "h2" => Ok(NodeTypeTag::H2),
1169
            "h3" => Ok(NodeTypeTag::H3),
1155
            "h4" => Ok(NodeTypeTag::H4),
1148
            "h5" => Ok(NodeTypeTag::H5),
1141
            "h6" => Ok(NodeTypeTag::H6),
            // Inline text
1134
            "br" => Ok(NodeTypeTag::Br),
1127
            "hr" => Ok(NodeTypeTag::Hr),
1120
            "pre" => Ok(NodeTypeTag::Pre),
987
            "blockquote" => Ok(NodeTypeTag::BlockQuote),
980
            "address" => Ok(NodeTypeTag::Address),
973
            "details" => Ok(NodeTypeTag::Details),
966
            "summary" => Ok(NodeTypeTag::Summary),
959
            "dialog" => Ok(NodeTypeTag::Dialog),
            // Lists
952
            "ul" => Ok(NodeTypeTag::Ul),
938
            "ol" => Ok(NodeTypeTag::Ol),
924
            "li" => Ok(NodeTypeTag::Li),
910
            "dl" => Ok(NodeTypeTag::Dl),
903
            "dt" => Ok(NodeTypeTag::Dt),
896
            "dd" => Ok(NodeTypeTag::Dd),
889
            "menu" => Ok(NodeTypeTag::Menu),
882
            "menuitem" => Ok(NodeTypeTag::MenuItem),
875
            "dir" => Ok(NodeTypeTag::Dir),
            // Tables
868
            "table" => Ok(NodeTypeTag::Table),
854
            "caption" => Ok(NodeTypeTag::Caption),
847
            "thead" => Ok(NodeTypeTag::THead),
833
            "tbody" => Ok(NodeTypeTag::TBody),
819
            "tfoot" => Ok(NodeTypeTag::TFoot),
812
            "tr" => Ok(NodeTypeTag::Tr),
798
            "th" => Ok(NodeTypeTag::Th),
784
            "td" => Ok(NodeTypeTag::Td),
770
            "colgroup" => Ok(NodeTypeTag::ColGroup),
763
            "col" => Ok(NodeTypeTag::Col),
            // Forms
756
            "form" => Ok(NodeTypeTag::Form),
742
            "fieldset" => Ok(NodeTypeTag::FieldSet),
735
            "legend" => Ok(NodeTypeTag::Legend),
728
            "label" => Ok(NodeTypeTag::Label),
721
            "input" => Ok(NodeTypeTag::Input),
700
            "button" => Ok(NodeTypeTag::Button),
686
            "select" => Ok(NodeTypeTag::Select),
679
            "optgroup" => Ok(NodeTypeTag::OptGroup),
672
            "option" => Ok(NodeTypeTag::SelectOption),
665
            "textarea" => Ok(NodeTypeTag::TextArea),
658
            "output" => Ok(NodeTypeTag::Output),
651
            "progress" => Ok(NodeTypeTag::Progress),
644
            "meter" => Ok(NodeTypeTag::Meter),
637
            "datalist" => Ok(NodeTypeTag::DataList),
            // Inline elements
630
            "span" => Ok(NodeTypeTag::Span),
518
            "a" => Ok(NodeTypeTag::A),
469
            "em" => Ok(NodeTypeTag::Em),
462
            "strong" => Ok(NodeTypeTag::Strong),
455
            "b" => Ok(NodeTypeTag::B),
448
            "i" => Ok(NodeTypeTag::I),
441
            "u" => Ok(NodeTypeTag::U),
434
            "s" => Ok(NodeTypeTag::S),
427
            "mark" => Ok(NodeTypeTag::Mark),
420
            "del" => Ok(NodeTypeTag::Del),
413
            "ins" => Ok(NodeTypeTag::Ins),
406
            "code" => Ok(NodeTypeTag::Code),
399
            "samp" => Ok(NodeTypeTag::Samp),
392
            "kbd" => Ok(NodeTypeTag::Kbd),
385
            "var" => Ok(NodeTypeTag::Var),
378
            "cite" => Ok(NodeTypeTag::Cite),
371
            "dfn" => Ok(NodeTypeTag::Dfn),
364
            "abbr" => Ok(NodeTypeTag::Abbr),
357
            "acronym" => Ok(NodeTypeTag::Acronym),
350
            "q" => Ok(NodeTypeTag::Q),
343
            "time" => Ok(NodeTypeTag::Time),
336
            "sub" => Ok(NodeTypeTag::Sub),
329
            "sup" => Ok(NodeTypeTag::Sup),
322
            "small" => Ok(NodeTypeTag::Small),
315
            "big" => Ok(NodeTypeTag::Big),
308
            "bdo" => Ok(NodeTypeTag::Bdo),
301
            "bdi" => Ok(NodeTypeTag::Bdi),
294
            "wbr" => Ok(NodeTypeTag::Wbr),
287
            "ruby" => Ok(NodeTypeTag::Ruby),
280
            "rt" => Ok(NodeTypeTag::Rt),
273
            "rtc" => Ok(NodeTypeTag::Rtc),
266
            "rp" => Ok(NodeTypeTag::Rp),
259
            "data" => Ok(NodeTypeTag::Data),
            // Embedded content
252
            "canvas" => Ok(NodeTypeTag::Canvas),
245
            "object" => Ok(NodeTypeTag::Object),
238
            "param" => Ok(NodeTypeTag::Param),
231
            "embed" => Ok(NodeTypeTag::Embed),
224
            "audio" => Ok(NodeTypeTag::Audio),
217
            "video" => Ok(NodeTypeTag::Video),
210
            "source" => Ok(NodeTypeTag::Source),
203
            "track" => Ok(NodeTypeTag::Track),
196
            "map" => Ok(NodeTypeTag::Map),
189
            "area" => Ok(NodeTypeTag::Area),
182
            "svg" => Ok(NodeTypeTag::Svg),
            // SVG shape elements
175
            "path" => Ok(NodeTypeTag::SvgPath),
175
            "circle" => Ok(NodeTypeTag::SvgCircle),
175
            "rect" => Ok(NodeTypeTag::SvgRect),
175
            "ellipse" => Ok(NodeTypeTag::SvgEllipse),
175
            "line" => Ok(NodeTypeTag::SvgLine),
175
            "polygon" => Ok(NodeTypeTag::SvgPolygon),
175
            "polyline" => Ok(NodeTypeTag::SvgPolyline),
175
            "g" => Ok(NodeTypeTag::SvgG),
            // SVG container elements
175
            "defs" => Ok(NodeTypeTag::SvgDefs),
175
            "symbol" => Ok(NodeTypeTag::SvgSymbol),
175
            "use" => Ok(NodeTypeTag::SvgUse),
175
            "switch" => Ok(NodeTypeTag::SvgSwitch),
            // SVG text elements
175
            "svg:text" => Ok(NodeTypeTag::SvgText),
175
            "tspan" => Ok(NodeTypeTag::SvgTspan),
175
            "textpath" => Ok(NodeTypeTag::SvgTextPath),
            // SVG paint server elements
175
            "lineargradient" => Ok(NodeTypeTag::SvgLinearGradient),
175
            "radialgradient" => Ok(NodeTypeTag::SvgRadialGradient),
175
            "stop" => Ok(NodeTypeTag::SvgStop),
175
            "pattern" => Ok(NodeTypeTag::SvgPattern),
            // SVG clipping/masking elements
175
            "clippath" => Ok(NodeTypeTag::SvgClipPathElement),
175
            "mask" => Ok(NodeTypeTag::SvgMask),
            // SVG filter elements
175
            "filter" => Ok(NodeTypeTag::SvgFilter),
175
            "feblend" => Ok(NodeTypeTag::SvgFeBlend),
175
            "fecolormatrix" => Ok(NodeTypeTag::SvgFeColorMatrix),
175
            "fecomponenttransfer" => Ok(NodeTypeTag::SvgFeComponentTransfer),
175
            "fecomposite" => Ok(NodeTypeTag::SvgFeComposite),
175
            "feconvolvematrix" => Ok(NodeTypeTag::SvgFeConvolveMatrix),
175
            "fediffuselighting" => Ok(NodeTypeTag::SvgFeDiffuseLighting),
175
            "fedisplacementmap" => Ok(NodeTypeTag::SvgFeDisplacementMap),
175
            "fedistantlight" => Ok(NodeTypeTag::SvgFeDistantLight),
175
            "fedropshadow" => Ok(NodeTypeTag::SvgFeDropShadow),
175
            "feflood" => Ok(NodeTypeTag::SvgFeFlood),
175
            "fefuncr" => Ok(NodeTypeTag::SvgFeFuncR),
175
            "fefuncg" => Ok(NodeTypeTag::SvgFeFuncG),
175
            "fefuncb" => Ok(NodeTypeTag::SvgFeFuncB),
175
            "fefunca" => Ok(NodeTypeTag::SvgFeFuncA),
175
            "fegaussianblur" => Ok(NodeTypeTag::SvgFeGaussianBlur),
175
            "feimage" => Ok(NodeTypeTag::SvgFeImage),
175
            "femerge" => Ok(NodeTypeTag::SvgFeMerge),
175
            "femergenode" => Ok(NodeTypeTag::SvgFeMergeNode),
175
            "femorphology" => Ok(NodeTypeTag::SvgFeMorphology),
175
            "feoffset" => Ok(NodeTypeTag::SvgFeOffset),
175
            "fepointlight" => Ok(NodeTypeTag::SvgFePointLight),
175
            "fespecularlighting" => Ok(NodeTypeTag::SvgFeSpecularLighting),
175
            "fespotlight" => Ok(NodeTypeTag::SvgFeSpotLight),
175
            "fetile" => Ok(NodeTypeTag::SvgFeTile),
175
            "feturbulence" => Ok(NodeTypeTag::SvgFeTurbulence),
            // SVG marker/image elements
175
            "image" | "svg:image" => Ok(NodeTypeTag::SvgImage),
175
            "svg:marker" => Ok(NodeTypeTag::SvgMarker),
175
            "foreignobject" => Ok(NodeTypeTag::SvgForeignObject),
            // SVG descriptive elements
175
            "svg:title" => Ok(NodeTypeTag::SvgTitle),
175
            "svg:a" => Ok(NodeTypeTag::SvgA),
175
            "svg:style" => Ok(NodeTypeTag::SvgStyle),
175
            "svg:script" => Ok(NodeTypeTag::SvgScript),
175
            "desc" => Ok(NodeTypeTag::SvgDesc),
175
            "metadata" => Ok(NodeTypeTag::SvgMetadata),
175
            "view" => Ok(NodeTypeTag::SvgView),
            // SVG animation elements
175
            "animate" => Ok(NodeTypeTag::SvgAnimate),
175
            "animatemotion" => Ok(NodeTypeTag::SvgAnimateMotion),
175
            "animatetransform" => Ok(NodeTypeTag::SvgAnimateTransform),
175
            "set" => Ok(NodeTypeTag::SvgSet),
175
            "mpath" => Ok(NodeTypeTag::SvgMpath),
            // Metadata
175
            "title" => Ok(NodeTypeTag::Title),
168
            "meta" => Ok(NodeTypeTag::Meta),
161
            "link" => Ok(NodeTypeTag::Link),
154
            "script" => Ok(NodeTypeTag::Script),
147
            "style" => Ok(NodeTypeTag::Style),
140
            "base" => Ok(NodeTypeTag::Base),
            // Special
133
            "img" => Ok(NodeTypeTag::Img),
119
            "virtual-view" | "iframe" => Ok(NodeTypeTag::VirtualView),
112
            "icon" => Ok(NodeTypeTag::Icon),
112
            "geolocation-probe" => Ok(NodeTypeTag::GeolocationProbe),
            // Pseudo-elements (usually prefixed with ::)
112
            "before" | "::before" => Ok(NodeTypeTag::Before),
98
            "after" | "::after" => Ok(NodeTypeTag::After),
84
            "marker" | "::marker" => Ok(NodeTypeTag::Marker),
70
            "placeholder" | "::placeholder" => Ok(NodeTypeTag::Placeholder),
56
            other => Err(NodeTypeTagParseError::Invalid(other)),
        }
5822
    }
}
impl fmt::Display for NodeTypeTag {
19138
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19138
        match self {
            // Document structure
7
            NodeTypeTag::Html => write!(f, "html"),
7
            NodeTypeTag::Head => write!(f, "head"),
1225
            NodeTypeTag::Body => write!(f, "body"),
            // Block elements
2149
            NodeTypeTag::Div => write!(f, "div"),
49
            NodeTypeTag::P => write!(f, "p"),
7
            NodeTypeTag::Article => write!(f, "article"),
7
            NodeTypeTag::Section => write!(f, "section"),
7
            NodeTypeTag::Nav => write!(f, "nav"),
7
            NodeTypeTag::Aside => write!(f, "aside"),
7
            NodeTypeTag::Header => write!(f, "header"),
7
            NodeTypeTag::Footer => write!(f, "footer"),
7
            NodeTypeTag::Main => write!(f, "main"),
7
            NodeTypeTag::Figure => write!(f, "figure"),
7
            NodeTypeTag::FigCaption => write!(f, "figcaption"),
            // Headings
49
            NodeTypeTag::H1 => write!(f, "h1"),
7
            NodeTypeTag::H2 => write!(f, "h2"),
7
            NodeTypeTag::H3 => write!(f, "h3"),
7
            NodeTypeTag::H4 => write!(f, "h4"),
7
            NodeTypeTag::H5 => write!(f, "h5"),
7
            NodeTypeTag::H6 => write!(f, "h6"),
            // Text formatting
7
            NodeTypeTag::Br => write!(f, "br"),
7
            NodeTypeTag::Hr => write!(f, "hr"),
7
            NodeTypeTag::Pre => write!(f, "pre"),
7
            NodeTypeTag::BlockQuote => write!(f, "blockquote"),
7
            NodeTypeTag::Address => write!(f, "address"),
7
            NodeTypeTag::Details => write!(f, "details"),
7
            NodeTypeTag::Summary => write!(f, "summary"),
7
            NodeTypeTag::Dialog => write!(f, "dialog"),
            // List elements
7
            NodeTypeTag::Ul => write!(f, "ul"),
7
            NodeTypeTag::Ol => write!(f, "ol"),
7
            NodeTypeTag::Li => write!(f, "li"),
7
            NodeTypeTag::Dl => write!(f, "dl"),
7
            NodeTypeTag::Dt => write!(f, "dt"),
7
            NodeTypeTag::Dd => write!(f, "dd"),
7
            NodeTypeTag::Menu => write!(f, "menu"),
7
            NodeTypeTag::MenuItem => write!(f, "menuitem"),
7
            NodeTypeTag::Dir => write!(f, "dir"),
            // Table elements
1351
            NodeTypeTag::Table => write!(f, "table"),
7
            NodeTypeTag::Caption => write!(f, "caption"),
7
            NodeTypeTag::THead => write!(f, "thead"),
7
            NodeTypeTag::TBody => write!(f, "tbody"),
7
            NodeTypeTag::TFoot => write!(f, "tfoot"),
1393
            NodeTypeTag::Tr => write!(f, "tr"),
7
            NodeTypeTag::Th => write!(f, "th"),
3493
            NodeTypeTag::Td => write!(f, "td"),
7
            NodeTypeTag::ColGroup => write!(f, "colgroup"),
7
            NodeTypeTag::Col => write!(f, "col"),
            // Form elements
7
            NodeTypeTag::Form => write!(f, "form"),
7
            NodeTypeTag::FieldSet => write!(f, "fieldset"),
7
            NodeTypeTag::Legend => write!(f, "legend"),
7
            NodeTypeTag::Label => write!(f, "label"),
7
            NodeTypeTag::Input => write!(f, "input"),
7
            NodeTypeTag::Button => write!(f, "button"),
7
            NodeTypeTag::Select => write!(f, "select"),
7
            NodeTypeTag::OptGroup => write!(f, "optgroup"),
7
            NodeTypeTag::SelectOption => write!(f, "option"),
7
            NodeTypeTag::TextArea => write!(f, "textarea"),
7
            NodeTypeTag::Output => write!(f, "output"),
7
            NodeTypeTag::Progress => write!(f, "progress"),
7
            NodeTypeTag::Meter => write!(f, "meter"),
7
            NodeTypeTag::DataList => write!(f, "datalist"),
            // Inline elements
2191
            NodeTypeTag::Span => write!(f, "span"),
2065
            NodeTypeTag::A => write!(f, "a"),
7
            NodeTypeTag::Em => write!(f, "em"),
7
            NodeTypeTag::Strong => write!(f, "strong"),
7
            NodeTypeTag::B => write!(f, "b"),
7
            NodeTypeTag::I => write!(f, "i"),
7
            NodeTypeTag::U => write!(f, "u"),
7
            NodeTypeTag::S => write!(f, "s"),
7
            NodeTypeTag::Mark => write!(f, "mark"),
7
            NodeTypeTag::Del => write!(f, "del"),
7
            NodeTypeTag::Ins => write!(f, "ins"),
7
            NodeTypeTag::Code => write!(f, "code"),
7
            NodeTypeTag::Samp => write!(f, "samp"),
7
            NodeTypeTag::Kbd => write!(f, "kbd"),
7
            NodeTypeTag::Var => write!(f, "var"),
7
            NodeTypeTag::Cite => write!(f, "cite"),
7
            NodeTypeTag::Dfn => write!(f, "dfn"),
7
            NodeTypeTag::Abbr => write!(f, "abbr"),
7
            NodeTypeTag::Acronym => write!(f, "acronym"),
7
            NodeTypeTag::Q => write!(f, "q"),
7
            NodeTypeTag::Time => write!(f, "time"),
7
            NodeTypeTag::Sub => write!(f, "sub"),
7
            NodeTypeTag::Sup => write!(f, "sup"),
7
            NodeTypeTag::Small => write!(f, "small"),
7
            NodeTypeTag::Big => write!(f, "big"),
7
            NodeTypeTag::Bdo => write!(f, "bdo"),
7
            NodeTypeTag::Bdi => write!(f, "bdi"),
7
            NodeTypeTag::Wbr => write!(f, "wbr"),
7
            NodeTypeTag::Ruby => write!(f, "ruby"),
7
            NodeTypeTag::Rt => write!(f, "rt"),
7
            NodeTypeTag::Rtc => write!(f, "rtc"),
7
            NodeTypeTag::Rp => write!(f, "rp"),
7
            NodeTypeTag::Data => write!(f, "data"),
            // Embedded content
7
            NodeTypeTag::Canvas => write!(f, "canvas"),
7
            NodeTypeTag::Object => write!(f, "object"),
7
            NodeTypeTag::Param => write!(f, "param"),
7
            NodeTypeTag::Embed => write!(f, "embed"),
7
            NodeTypeTag::Audio => write!(f, "audio"),
7
            NodeTypeTag::Video => write!(f, "video"),
7
            NodeTypeTag::Source => write!(f, "source"),
7
            NodeTypeTag::Track => write!(f, "track"),
7
            NodeTypeTag::Map => write!(f, "map"),
7
            NodeTypeTag::Area => write!(f, "area"),
7
            NodeTypeTag::Svg => write!(f, "svg"),
            NodeTypeTag::SvgPath => write!(f, "path"),
            NodeTypeTag::SvgCircle => write!(f, "circle"),
            NodeTypeTag::SvgRect => write!(f, "rect"),
            NodeTypeTag::SvgEllipse => write!(f, "ellipse"),
            NodeTypeTag::SvgLine => write!(f, "line"),
            NodeTypeTag::SvgPolygon => write!(f, "polygon"),
            NodeTypeTag::SvgPolyline => write!(f, "polyline"),
            NodeTypeTag::SvgG => write!(f, "g"),
            // SVG container elements
            NodeTypeTag::SvgDefs => write!(f, "defs"),
            NodeTypeTag::SvgSymbol => write!(f, "symbol"),
            NodeTypeTag::SvgUse => write!(f, "use"),
            NodeTypeTag::SvgSwitch => write!(f, "switch"),
            // SVG text elements
            NodeTypeTag::SvgText => write!(f, "svg:text"),
            NodeTypeTag::SvgTspan => write!(f, "tspan"),
            NodeTypeTag::SvgTextPath => write!(f, "textpath"),
            // SVG paint server elements
            NodeTypeTag::SvgLinearGradient => write!(f, "lineargradient"),
            NodeTypeTag::SvgRadialGradient => write!(f, "radialgradient"),
            NodeTypeTag::SvgStop => write!(f, "stop"),
            NodeTypeTag::SvgPattern => write!(f, "pattern"),
            // SVG clipping/masking elements
            NodeTypeTag::SvgClipPathElement => write!(f, "clippath"),
            NodeTypeTag::SvgMask => write!(f, "mask"),
            // SVG filter elements
            NodeTypeTag::SvgFilter => write!(f, "filter"),
            NodeTypeTag::SvgFeBlend => write!(f, "feblend"),
            NodeTypeTag::SvgFeColorMatrix => write!(f, "fecolormatrix"),
            NodeTypeTag::SvgFeComponentTransfer => write!(f, "fecomponenttransfer"),
            NodeTypeTag::SvgFeComposite => write!(f, "fecomposite"),
            NodeTypeTag::SvgFeConvolveMatrix => write!(f, "feconvolvematrix"),
            NodeTypeTag::SvgFeDiffuseLighting => write!(f, "fediffuselighting"),
            NodeTypeTag::SvgFeDisplacementMap => write!(f, "fedisplacementmap"),
            NodeTypeTag::SvgFeDistantLight => write!(f, "fedistantlight"),
            NodeTypeTag::SvgFeDropShadow => write!(f, "fedropshadow"),
            NodeTypeTag::SvgFeFlood => write!(f, "feflood"),
            NodeTypeTag::SvgFeFuncR => write!(f, "fefuncr"),
            NodeTypeTag::SvgFeFuncG => write!(f, "fefuncg"),
            NodeTypeTag::SvgFeFuncB => write!(f, "fefuncb"),
            NodeTypeTag::SvgFeFuncA => write!(f, "fefunca"),
            NodeTypeTag::SvgFeGaussianBlur => write!(f, "fegaussianblur"),
            NodeTypeTag::SvgFeImage => write!(f, "feimage"),
            NodeTypeTag::SvgFeMerge => write!(f, "femerge"),
            NodeTypeTag::SvgFeMergeNode => write!(f, "femergenode"),
            NodeTypeTag::SvgFeMorphology => write!(f, "femorphology"),
            NodeTypeTag::SvgFeOffset => write!(f, "feoffset"),
            NodeTypeTag::SvgFePointLight => write!(f, "fepointlight"),
            NodeTypeTag::SvgFeSpecularLighting => write!(f, "fespecularlighting"),
            NodeTypeTag::SvgFeSpotLight => write!(f, "fespotlight"),
            NodeTypeTag::SvgFeTile => write!(f, "fetile"),
            NodeTypeTag::SvgFeTurbulence => write!(f, "feturbulence"),
            // SVG marker/image elements
            NodeTypeTag::SvgMarker => write!(f, "svg:marker"),
            NodeTypeTag::SvgImage => write!(f, "svg:image"),
            NodeTypeTag::SvgForeignObject => write!(f, "foreignobject"),
            // SVG descriptive elements
            NodeTypeTag::SvgTitle => write!(f, "svg:title"),
            NodeTypeTag::SvgDesc => write!(f, "desc"),
            NodeTypeTag::SvgMetadata => write!(f, "metadata"),
            NodeTypeTag::SvgA => write!(f, "svg:a"),
            NodeTypeTag::SvgView => write!(f, "view"),
            NodeTypeTag::SvgStyle => write!(f, "svg:style"),
            NodeTypeTag::SvgScript => write!(f, "svg:script"),
            // SVG animation elements
            NodeTypeTag::SvgAnimate => write!(f, "animate"),
            NodeTypeTag::SvgAnimateMotion => write!(f, "animatemotion"),
            NodeTypeTag::SvgAnimateTransform => write!(f, "animatetransform"),
            NodeTypeTag::SvgSet => write!(f, "set"),
            NodeTypeTag::SvgMpath => write!(f, "mpath"),
            // Metadata
7
            NodeTypeTag::Title => write!(f, "title"),
7
            NodeTypeTag::Meta => write!(f, "meta"),
7
            NodeTypeTag::Link => write!(f, "link"),
7
            NodeTypeTag::Script => write!(f, "script"),
7
            NodeTypeTag::Style => write!(f, "style"),
7
            NodeTypeTag::Base => write!(f, "base"),
            // Content elements
4417
            NodeTypeTag::Text => write!(f, "text"),
7
            NodeTypeTag::Img => write!(f, "img"),
7
            NodeTypeTag::VirtualView => write!(f, "virtual-view"),
            NodeTypeTag::Icon => write!(f, "icon"),
            NodeTypeTag::GeolocationProbe => write!(f, "geolocation-probe"),
            // Pseudo-elements
7
            NodeTypeTag::Before => write!(f, "::before"),
7
            NodeTypeTag::After => write!(f, "::after"),
7
            NodeTypeTag::Marker => write!(f, "::marker"),
7
            NodeTypeTag::Placeholder => write!(f, "::placeholder"),
        }
19138
    }
}
/// Represents a full CSS path (i.e. the "div#id.class" selector belonging to
///  a CSS "content group" (the following key-value block)).
///
/// ```no_run,ignore
/// "#div > .my_class:focus" ==
/// [
///   CssPathSelector::Type(NodeTypeTag::Div),
///   CssPathSelector::PseudoSelector(CssPathPseudoSelector::LimitChildren),
///   CssPathSelector::Class("my_class"),
///   CssPathSelector::PseudoSelector(CssPathPseudoSelector::Focus),
/// ]
#[derive(Clone, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub struct CssPath {
    pub selectors: CssPathSelectorVec,
}
impl_vec!(CssPathSelector, CssPathSelectorVec, CssPathSelectorVecDestructor, CssPathSelectorVecDestructorType, CssPathSelectorVecSlice, OptionCssPathSelector);
impl_vec_debug!(CssPathSelector, CssPathSelectorVec);
impl_vec_partialord!(CssPathSelector, CssPathSelectorVec);
impl_vec_ord!(CssPathSelector, CssPathSelectorVec);
impl_vec_clone!(
    CssPathSelector,
    CssPathSelectorVec,
    CssPathSelectorVecDestructor
);
impl_vec_partialeq!(CssPathSelector, CssPathSelectorVec);
impl_vec_eq!(CssPathSelector, CssPathSelectorVec);
impl_vec_hash!(CssPathSelector, CssPathSelectorVec);
impl CssPath {
2
    pub fn new(selectors: Vec<CssPathSelector>) -> Self {
2
        Self {
2
            selectors: selectors.into(),
2
        }
2
    }
}
impl fmt::Display for CssPath {
42
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42
        for selector in self.selectors.as_ref() {
42
            write!(f, "{}", selector)?;
        }
42
        Ok(())
42
    }
}
impl fmt::Debug for CssPath {
14
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14
        write!(f, "{}", self)
14
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
#[derive(Default)]
pub enum CssPathSelector {
    /// Represents the `*` selector
    #[default]
    Global,
    /// `div`, `p`, etc.
    Type(NodeTypeTag),
    /// `.something`
    Class(AzString),
    /// `#something`
    Id(AzString),
    /// `:something`
    PseudoSelector(CssPathPseudoSelector),
    /// `[attr]`, `[attr="value"]`, `[attr~="value"]`, etc.
    Attribute(CssAttributeSelector),
    /// Represents the `>` selector (direct child)
    DirectChildren,
    /// Represents the ` ` selector (descendant)
    Children,
    /// Represents the `+` selector (adjacent sibling)
    AdjacentSibling,
    /// Represents the `~` selector (general sibling)
    GeneralSibling,
}
/// Attribute selector (`[attr]`, `[attr="value"]`, ...).
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct CssAttributeSelector {
    pub name: AzString,
    pub op: AttributeMatchOp,
    pub value: OptionString,
}
impl Default for CssAttributeSelector {
    fn default() -> Self {
        Self {
            name: AzString::default(),
            op: AttributeMatchOp::Exists,
            value: OptionString::None,
        }
    }
}
/// Operator that compares an attribute value against a target string.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum AttributeMatchOp {
    /// `[attr]` — attribute is present (any value).
    Exists,
    /// `[attr="value"]` — attribute equals value exactly.
    Eq,
    /// `[attr~="value"]` — value is one of the whitespace-separated words.
    Includes,
    /// `[attr|="value"]` — value equals exactly OR begins with value followed by `-`.
    DashMatch,
    /// `[attr^="value"]` — value starts with the given prefix.
    Prefix,
    /// `[attr$="value"]` — value ends with the given suffix.
    Suffix,
    /// `[attr*="value"]` — value contains the given substring.
    Substring,
}
impl Default for AttributeMatchOp {
    fn default() -> Self {
        AttributeMatchOp::Exists
    }
}
impl_option!(
    CssPathSelector,
    OptionCssPathSelector,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl fmt::Display for CssPathSelector {
42
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::CssPathSelector::*;
42
        match &self {
            Global => write!(f, "*"),
            Type(n) => write!(f, "{}", n),
42
            Class(c) => write!(f, ".{}", c),
            Id(i) => write!(f, "#{}", i),
            PseudoSelector(p) => write!(f, ":{}", p),
            Attribute(a) => write!(f, "{}", a),
            DirectChildren => write!(f, ">"),
            Children => write!(f, " "),
            AdjacentSibling => write!(f, "+"),
            GeneralSibling => write!(f, "~"),
        }
42
    }
}
impl fmt::Display for CssAttributeSelector {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match (&self.op, self.value.as_ref()) {
            (AttributeMatchOp::Exists, _) => write!(f, "[{}]", self.name),
            (op, Some(v)) => write!(f, "[{}{}=\"{}\"]", self.name, op.symbol_prefix(), v),
            (op, None) => write!(f, "[{}{}=\"\"]", self.name, op.symbol_prefix()),
        }
    }
}
impl AttributeMatchOp {
    /// Returns the prefix character for the `=` operator (e.g. `~` for `~=`).
    /// `Eq` returns `""`, `Exists` is unused (no `=` printed at all).
    pub fn symbol_prefix(&self) -> &'static str {
        match self {
            AttributeMatchOp::Exists => "",
            AttributeMatchOp::Eq => "",
            AttributeMatchOp::Includes => "~",
            AttributeMatchOp::DashMatch => "|",
            AttributeMatchOp::Prefix => "^",
            AttributeMatchOp::Suffix => "$",
            AttributeMatchOp::Substring => "*",
        }
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum CssPathPseudoSelector {
    /// `:first`
    First,
    /// `:last`
    Last,
    /// `:nth-child`
    NthChild(CssNthChildSelector),
    /// `:hover` - mouse is over element
    Hover,
    /// `:active` - mouse is pressed and over element
    Active,
    /// `:focus` - element has received focus
    Focus,
    /// `:lang(de)` - element matches language
    Lang(AzString),
    /// `:backdrop` - window is not focused (GTK compatibility)
    Backdrop,
    /// `:dragging` - element is currently being dragged
    Dragging,
    /// `:drag-over` - a dragged element is over this drop target
    DragOver,
}
/// Selector for the `:nth-child()` CSS pseudo-class.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum CssNthChildSelector {
    Number(u32),
    Even,
    Odd,
    Pattern(CssNthChildPattern),
}
/// Pattern for `:nth-child(An+B)` selectors, where `pattern_repeat` is A and `offset` is B.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct CssNthChildPattern {
    pub pattern_repeat: u32,
    pub offset: u32,
}
impl fmt::Display for CssNthChildSelector {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::CssNthChildSelector::*;
        match &self {
            Number(u) => write!(f, "{}", u),
            Even => write!(f, "even"),
            Odd => write!(f, "odd"),
            Pattern(p) => write!(f, "{}n + {}", p.pattern_repeat, p.offset),
        }
    }
}
impl fmt::Display for CssPathPseudoSelector {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::CssPathPseudoSelector::*;
        match &self {
            First => write!(f, "first"),
            Last => write!(f, "last"),
            NthChild(u) => write!(f, "nth-child({})", u),
            Hover => write!(f, "hover"),
            Active => write!(f, "active"),
            Focus => write!(f, "focus"),
            Lang(lang) => write!(f, "lang({})", lang.as_str()),
            Backdrop => write!(f, "backdrop"),
            Dragging => write!(f, "dragging"),
            DragOver => write!(f, "drag-over"),
        }
    }
}
impl Css {
    /// Creates a new, empty CSS.
5221
    pub fn empty() -> Self {
5221
        Default::default()
5221
    }
    /// Sort the rules by `(priority, specificity)` so they apply in cascade order.
    /// Lower-priority rules sort first; ties break by selector specificity.
    /// This preserves layer identity (UA / SYSTEM / AUTHOR / INLINE / RUNTIME)
    /// without needing a separate `Stylesheet` boundary.
5561
    pub fn sort_by_specificity(&mut self) {
8505
        self.rules.as_mut().sort_by(|a, b| {
7954
            a.priority.cmp(&b.priority)
7954
                .then_with(|| get_specificity(&a.path).cmp(&get_specificity(&b.path)))
7954
        });
5561
    }
5770
    pub fn rules<'a>(&'a self) -> core::slice::Iter<'a, CssRuleBlock> {
5770
        self.rules.as_ref().iter()
5770
    }
    /// Iterate `(property, conditions)` pairs as if this were a flat list of
    /// `CssPropertyWithConditions`. Each `Static` declaration yields one item,
    /// sharing the conditions of its enclosing rule. `Dynamic` declarations
    /// are skipped (matching the previous inline-CSS behaviour).
    ///
    /// Used by cascade and diff code that walks per-property to keep the
    /// flat-iteration shape after the inline-vs-component unification.
1806012
    pub fn iter_inline_properties<'a>(
1806012
        &'a self,
1806012
    ) -> impl Iterator<
1806012
        Item = (
1806012
            &'a crate::props::property::CssProperty,
1806012
            &'a DynamicSelectorVec,
1806012
        ),
1806012
    > + 'a {
1806012
        self.rules.as_ref().iter().flat_map(|r| {
32453
            r.declarations.as_ref().iter().filter_map(move |d| match d {
32453
                CssDeclaration::Static(p) => Some((p, &r.conditions)),
                CssDeclaration::Dynamic(_) => None,
32453
            })
32453
        })
1806012
    }
}
#[cfg(test)]
mod priority_sort_tests {
    use super::*;
    use crate::css::rule_priority;
4
    fn rule_with(priority: u8, selectors: Vec<CssPathSelector>) -> CssRuleBlock {
4
        CssRuleBlock {
4
            path: CssPath { selectors: selectors.into() },
4
            declarations: Vec::new().into(),
4
            conditions: DynamicSelectorVec::from_const_slice(&[]),
4
            priority,
4
        }
4
    }
    /// Pin the (priority, specificity) sort order. Lower priority sorts first;
    /// ties break by specificity.
    #[test]
1
    fn sort_by_priority_then_specificity() {
1
        let mut css = Css::new(vec![
            // Author rule, no specificity.
1
            rule_with(rule_priority::AUTHOR, vec![CssPathSelector::Global]),
            // UA rule with high specificity — must still come BEFORE any author rule.
1
            rule_with(rule_priority::UA, vec![
1
                CssPathSelector::Id("ua-id".to_string().into()),
1
                CssPathSelector::Class("ua-class".to_string().into()),
            ]),
            // Author rule with high specificity.
1
            rule_with(rule_priority::AUTHOR, vec![
1
                CssPathSelector::Id("a-id".to_string().into()),
            ]),
            // System rule with no specificity — must sit between UA and author.
1
            rule_with(rule_priority::SYSTEM, vec![CssPathSelector::Global]),
        ]);
1
        css.sort_by_specificity();
1
        let priorities: Vec<u8> = css.rules.as_ref().iter().map(|r| r.priority).collect();
1
        assert_eq!(
            priorities,
1
            vec![rule_priority::UA, rule_priority::SYSTEM, rule_priority::AUTHOR, rule_priority::AUTHOR],
            "rules must sort by layer first; specificity only breaks ties within a layer"
        );
        // Within author, the high-specificity #a-id comes after the * rule.
1
        let last_two_specificity: Vec<_> = css.rules.as_ref().iter()
4
            .filter(|r| r.priority == rule_priority::AUTHOR)
2
            .map(|r| get_specificity(&r.path))
1
            .collect();
1
        assert!(last_two_specificity[0] < last_two_specificity[1]);
1
    }
}
/// Returns specificity of the given css path. Further information can be found on
/// [the w3 website](http://www.w3.org/TR/selectors/#specificity).
15902
pub fn get_specificity(path: &CssPath) -> (usize, usize, usize, usize) {
15902
    let id_count = path
15902
        .selectors
15902
        .iter()
15902
        .filter(|x| matches!(x, CssPathSelector::Id(_)))
15902
        .count();
15902
    let class_count = path
15902
        .selectors
15902
        .iter()
15902
        .filter(|x| {
6296
            matches!(
15902
                x,
                CssPathSelector::Class(_)
                    | CssPathSelector::PseudoSelector(_)
                    | CssPathSelector::Attribute(_)
            )
15902
        })
15902
        .count();
15902
    let div_count = path
15902
        .selectors
15902
        .iter()
15902
        .filter(|x| matches!(x, CssPathSelector::Type(_)))
15902
        .count();
15902
    (id_count, class_count, div_count, path.selectors.len())
15902
}