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
12158
    pub fn is_empty(&self) -> bool {
59
12158
        self.rules.as_ref().is_empty()
60
12158
    }
61

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

            
68
    #[cfg(feature = "parser")]
69
5118
    pub fn from_string(s: crate::AzString) -> Self {
70
5118
        crate::parser2::new_from_str(s.as_str()).0
71
5118
    }
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
18514
    pub fn parse_inline(style: &str) -> Self {
82
        use alloc::string::ToString;
83
18514
        let mut wrapped = String::with_capacity(style.len() + 6);
84
18514
        wrapped.push_str("* {\n");
85
18514
        wrapped.push_str(style);
86
18514
        wrapped.push_str("\n}");
87
18514
        let (mut css, _warnings) = crate::parser2::new_from_str(&wrapped);
88
19585
        for rule in css.rules.as_mut() {
89
19585
            rule.priority = rule_priority::INLINE;
90
19585
        }
91
18514
        css
92
18514
    }
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
1194
    fn from(props: crate::dynamic_selector::CssPropertyWithConditionsVec) -> Self {
149
        // Build via an explicit push loop rather than `.into_iter().map(|p| CssRuleBlock {
150
        // declarations: vec![...], ... }).collect()`. On the web/remill lift, constructing a
151
        // complex struct with nested Vecs *inside* a mapped+collected closure drops every
152
        // element (AzButton's inline container style came back with 0 rules even though the
153
        // source Vec had props), whereas the identical construction in a plain loop body lifts
154
        // correctly — same pattern `NodeData::add_css_property` already relies on. Native
155
        // behavior is byte-identical.
156
1194
        let owned = props.into_library_owned_vec();
157
1194
        let mut rules: Vec<CssRuleBlock> = Vec::with_capacity(owned.len());
158
9315
        for p in owned {
159
8121
            rules.push(CssRuleBlock {
160
8121
                path: CssPath { selectors: Vec::new().into() },
161
8121
                declarations: alloc::vec![CssDeclaration::Static(p.property)].into(),
162
8121
                conditions: p.apply_if,
163
8121
                priority: rule_priority::INLINE,
164
8121
            });
165
8121
        }
166
1194
        Css { rules: rules.into() }
167
1194
    }
168
}
169

            
170
/// Contains one parsed `key: value` pair, static or dynamic
171
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
172
#[repr(C, u8)]
173
pub enum CssDeclaration {
174
    /// Static key-value pair, such as `width: 500px`
175
    Static(CssProperty),
176
    /// Dynamic key-value pair with default value, such as `width: [[ my_id | 500px ]]`
177
    Dynamic(DynamicCssProperty),
178
}
179

            
180
impl_option!(
181
    CssDeclaration,
182
    OptionCssDeclaration,
183
    copy = false,
184
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
185
);
186

            
187
impl CssDeclaration {
188
    pub const fn new_static(prop: CssProperty) -> Self {
189
        CssDeclaration::Static(prop)
190
    }
191

            
192
    pub const fn new_dynamic(prop: DynamicCssProperty) -> Self {
193
        CssDeclaration::Dynamic(prop)
194
    }
195

            
196
    /// Returns the type of the property (i.e. the CSS key as a typed enum)
197
    pub fn get_type(&self) -> CssPropertyType {
198
        use self::CssDeclaration::*;
199
        match self {
200
            Static(s) => s.get_type(),
201
            Dynamic(d) => d.default_value.get_type(),
202
        }
203
    }
204

            
205
    /// Determines if the property will be inherited (applied to the children)
206
    /// during the recursive application of the style on the DOM tree
207
    pub fn is_inheritable(&self) -> bool {
208
        use self::CssDeclaration::*;
209
        match self {
210
            Static(s) => s.get_type().is_inheritable(),
211
            Dynamic(d) => d.is_inheritable(),
212
        }
213
    }
214

            
215
    /// Returns whether this rule affects only styling properties or layout
216
    /// properties (that could trigger a re-layout)
217
    pub fn can_trigger_relayout(&self) -> bool {
218
        use self::CssDeclaration::*;
219
        match self {
220
            Static(s) => s.get_type().can_trigger_relayout(),
221
            Dynamic(d) => d.can_trigger_relayout(),
222
        }
223
    }
224

            
225
    pub fn to_str(&self) -> String {
226
        use self::CssDeclaration::*;
227
        match self {
228
            Static(s) => format!("{:?}", s),
229
            Dynamic(d) => format!("var(--{}, {:?})", d.dynamic_id, d.default_value),
230
        }
231
    }
232
}
233

            
234
/// A `DynamicCssProperty` is a type of css property that can be changed on possibly
235
/// every frame by the Rust code - for example to implement an `On::Hover` behaviour.
236
///
237
/// The syntax for such a property looks like this:
238
///
239
/// ```no_run,ignore
240
/// #my_div {
241
///    padding: var(--my_dynamic_property_id, 400px);
242
/// }
243
/// ```
244
///
245
/// Azul will register a dynamic property with the key "my_dynamic_property_id"
246
/// and the default value of 400px. If the property gets overridden during one frame,
247
/// the overridden property takes precedence.
248
///
249
/// At runtime the style is immutable (which is a performance optimization - if we
250
/// can assume that the property never changes at runtime), we can do some optimizations on it.
251
/// Dynamic style properties can also be used for animations and conditional styles
252
/// (i.e. `hover`, `focus`, etc.), thereby leading to cleaner code, since all of these
253
/// special cases now use one single API.
254
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
255
#[repr(C)]
256
pub struct DynamicCssProperty {
257
    /// The stringified ID of this property, i.e. the `"my_id"` in `width: var(--my_id, 500px)`.
258
    pub dynamic_id: AzString,
259
    /// Default values for this properties - one single value can control multiple properties!
260
    pub default_value: CssProperty,
261
}
262

            
263
/// A value that is either heap-allocated (parsed at runtime) or a compile-time
264
/// static reference. Used to reduce enum size for large CSS property payloads
265
/// by storing them behind a pointer instead of inline.
266
///
267
/// - Size: 1 (tag) + 7 (padding) + 8 (pointer) = **16 bytes** on 64-bit
268
/// - `Static` variant: no allocation, just a `*const T` pointer to static data
269
/// - `Boxed` variant: heap-allocated via `Box::into_raw`, freed on Drop
270
#[repr(C, u8)]
271
pub enum BoxOrStatic<T> {
272
    /// Heap-allocated (parsed at runtime). Owned — freed on Drop.
273
    Boxed(*mut T),
274
    /// Compile-time constant (e.g. from `const` CSS defaults). Not freed.
275
    Static(*const T),
276
}
277

            
278
impl<T> BoxOrStatic<T> {
279
    /// Allocate `value` on the heap and return a `Boxed` variant.
280
    #[inline]
281
16918
    pub fn heap(value: T) -> Self {
282
16918
        BoxOrStatic::Boxed(Box::into_raw(Box::new(value)))
283
16918
    }
284

            
285
    /// Return a reference to the inner value.
286
    ///
287
    /// # Safety invariant
288
    /// The inner pointer must be non-null. This is guaranteed by [`heap`](Self::heap)
289
    /// and the `Static` constructor (which should always point to valid data).
290
    #[inline]
291
308418
    pub fn as_ref(&self) -> &T {
292
308418
        match self {
293
308418
            BoxOrStatic::Boxed(ptr) => unsafe {
294
308418
                debug_assert!(!ptr.is_null(), "BoxOrStatic::Boxed contained a null pointer");
295
308418
                &**ptr
296
            },
297
            BoxOrStatic::Static(ptr) => unsafe {
298
                debug_assert!(!ptr.is_null(), "BoxOrStatic::Static contained a null pointer");
299
                &**ptr
300
            },
301
        }
302
308418
    }
303

            
304
    /// Return a mutable reference to the inner value (only for Boxed).
305
    /// Panics if called on Static.
306
    #[inline]
307
    pub fn as_mut(&mut self) -> &mut T {
308
        match self {
309
            BoxOrStatic::Boxed(ptr) => unsafe { &mut **ptr },
310
            BoxOrStatic::Static(_) => panic!("Cannot mutate a static BoxOrStatic value"),
311
        }
312
    }
313

            
314
    /// Consume self and return the inner value.
315
    #[inline]
316
    pub fn into_inner(self) -> T where T: Clone {
317
        let val = self.as_ref().clone();
318
        // Don't double-free: std::mem::forget prevents Drop from running
319
        core::mem::forget(self);
320
        val
321
    }
322
}
323

            
324
impl<T> Drop for BoxOrStatic<T> {
325
32371
    fn drop(&mut self) {
326
32371
        if let BoxOrStatic::Boxed(ptr) = self {
327
32371
            if !ptr.is_null() {
328
32371
                unsafe { drop(Box::from_raw(*ptr)); }
329
32371
                *ptr = core::ptr::null_mut();
330
32371
            }
331
        }
332
32371
    }
333
}
334

            
335
impl<T: Clone> Clone for BoxOrStatic<T> {
336
612
    fn clone(&self) -> Self {
337
612
        match self {
338
612
            BoxOrStatic::Boxed(ptr) => {
339
612
                let val = unsafe { &**ptr }.clone();
340
612
                BoxOrStatic::Boxed(Box::into_raw(Box::new(val)))
341
            }
342
            BoxOrStatic::Static(ptr) => BoxOrStatic::Static(*ptr),
343
        }
344
612
    }
345
}
346

            
347
impl<T: core::fmt::Debug> core::fmt::Debug for BoxOrStatic<T> {
348
52432
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
349
52432
        self.as_ref().fmt(f)
350
52432
    }
351
}
352

            
353
impl<T: core::fmt::Display> core::fmt::Display for BoxOrStatic<T> {
354
1
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
355
1
        self.as_ref().fmt(f)
356
1
    }
357
}
358

            
359
impl<T: PartialEq> PartialEq for BoxOrStatic<T> {
360
    fn eq(&self, other: &Self) -> bool {
361
        self.as_ref() == other.as_ref()
362
    }
363
}
364

            
365
impl<T: Eq> Eq for BoxOrStatic<T> {}
366

            
367
impl<T: core::hash::Hash> core::hash::Hash for BoxOrStatic<T> {
368
39186
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
369
39186
        self.as_ref().hash(state)
370
39186
    }
371
}
372

            
373
impl<T: PartialOrd> PartialOrd for BoxOrStatic<T> {
374
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
375
        self.as_ref().partial_cmp(other.as_ref())
376
    }
377
}
378

            
379
impl<T: Ord> Ord for BoxOrStatic<T> {
380
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
381
        self.as_ref().cmp(other.as_ref())
382
    }
383
}
384

            
385
impl<T> core::ops::Deref for BoxOrStatic<T> {
386
    type Target = T;
387
    #[inline]
388
216578
    fn deref(&self) -> &T {
389
216578
        self.as_ref()
390
216578
    }
391
}
392

            
393
impl<T: Default> Default for BoxOrStatic<T> {
394
    fn default() -> Self {
395
        BoxOrStatic::heap(T::default())
396
    }
397
}
398

            
399
impl<T: PrintAsCssValue> PrintAsCssValue for BoxOrStatic<T> {
400
    fn print_as_css_value(&self) -> String {
401
        self.as_ref().print_as_css_value()
402
    }
403
}
404

            
405
// Safety: BoxOrStatic<T> is Send if T is Send
406
unsafe impl<T: Send + 'static> Send for BoxOrStatic<T> {}
407
// Safety: BoxOrStatic<T> is Sync if T is Sync
408
unsafe impl<T: Sync + 'static> Sync for BoxOrStatic<T> {}
409

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

            
413
/// Type alias: `BoxOrStatic<AzString>` — used by NodeType::Text and NodeType::Icon.
414
pub type BoxOrStaticString = BoxOrStatic<crate::AzString>;
415

            
416
/// A CSS property value that may be an explicit value or a CSS-wide keyword
417
/// (`auto`, `none`, `initial`, `inherit`, `revert`, `unset`).
418
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
419
#[repr(C, u8)] // necessary for ABI stability
420
pub enum CssPropertyValue<T> {
421
    Auto,
422
    None,
423
    Initial,
424
    Inherit,
425
    Revert,
426
    Unset,
427
    Exact(T),
428
}
429

            
430
/// Trait for types that can format themselves as a CSS property value string.
431
pub trait PrintAsCssValue {
432
    fn print_as_css_value(&self) -> String;
433
}
434

            
435
impl<T: PrintAsCssValue> CssPropertyValue<T> {
436
    pub fn get_css_value_fmt(&self) -> String {
437
        match self {
438
            CssPropertyValue::Auto => "auto".to_string(),
439
            CssPropertyValue::None => "none".to_string(),
440
            CssPropertyValue::Initial => "initial".to_string(),
441
            CssPropertyValue::Inherit => "inherit".to_string(),
442
            CssPropertyValue::Revert => "revert".to_string(),
443
            CssPropertyValue::Unset => "unset".to_string(),
444
            CssPropertyValue::Exact(e) => e.print_as_css_value(),
445
        }
446
    }
447
}
448

            
449
impl<T: fmt::Display> fmt::Display for CssPropertyValue<T> {
450
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
451
        use self::CssPropertyValue::*;
452
        match self {
453
            Auto => write!(f, "auto"),
454
            None => write!(f, "none"),
455
            Initial => write!(f, "initial"),
456
            Inherit => write!(f, "inherit"),
457
            Revert => write!(f, "revert"),
458
            Unset => write!(f, "unset"),
459
            Exact(e) => write!(f, "{}", e),
460
        }
461
    }
462
}
463

            
464
impl<T> From<T> for CssPropertyValue<T> {
465
274975
    fn from(c: T) -> Self {
466
274975
        CssPropertyValue::Exact(c)
467
274975
    }
468
}
469

            
470
impl<T> CssPropertyValue<T> {
471
    /// Transforms a `CssPropertyValue<T>` into a `CssPropertyValue<U>` by applying a mapping
472
    /// function
473
    #[inline]
474
    pub fn map_property<F: Fn(T) -> U, U>(self, map_fn: F) -> CssPropertyValue<U> {
475
        match self {
476
            CssPropertyValue::Exact(c) => CssPropertyValue::Exact(map_fn(c)),
477
            CssPropertyValue::Auto => CssPropertyValue::Auto,
478
            CssPropertyValue::None => CssPropertyValue::None,
479
            CssPropertyValue::Initial => CssPropertyValue::Initial,
480
            CssPropertyValue::Inherit => CssPropertyValue::Inherit,
481
            CssPropertyValue::Revert => CssPropertyValue::Revert,
482
            CssPropertyValue::Unset => CssPropertyValue::Unset,
483
        }
484
    }
485

            
486
    #[inline]
487
1004004
    pub fn get_property(&self) -> Option<&T> {
488
1004004
        match self {
489
1004004
            CssPropertyValue::Exact(c) => Some(c),
490
            _ => None,
491
        }
492
1004004
    }
493

            
494
    #[inline]
495
    pub fn get_property_owned(self) -> Option<T> {
496
        match self {
497
            CssPropertyValue::Exact(c) => Some(c),
498
            _ => None,
499
        }
500
    }
501

            
502
    #[inline]
503
397
    pub fn is_auto(&self) -> bool {
504
397
        matches!(self, CssPropertyValue::Auto)
505
397
    }
506

            
507
    #[inline]
508
1
    pub fn is_none(&self) -> bool {
509
1
        matches!(self, CssPropertyValue::None)
510
1
    }
511

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

            
517
    #[inline]
518
3
    pub fn is_inherit(&self) -> bool {
519
3
        matches!(self, CssPropertyValue::Inherit)
520
3
    }
521

            
522
    #[inline]
523
2
    pub fn is_revert(&self) -> bool {
524
2
        matches!(self, CssPropertyValue::Revert)
525
2
    }
526

            
527
    #[inline]
528
4
    pub fn is_unset(&self) -> bool {
529
4
        matches!(self, CssPropertyValue::Unset)
530
4
    }
531
}
532

            
533
impl<T: Default> CssPropertyValue<T> {
534
    #[inline]
535
33780
    pub fn get_property_or_default(self) -> Option<T> {
536
33780
        match self {
537
            CssPropertyValue::Auto | CssPropertyValue::Initial => Some(T::default()),
538
33780
            CssPropertyValue::Exact(c) => Some(c),
539
            CssPropertyValue::None
540
            | CssPropertyValue::Inherit
541
            | CssPropertyValue::Revert
542
            | CssPropertyValue::Unset => None,
543
        }
544
33780
    }
545
}
546

            
547
impl<T: Default> Default for CssPropertyValue<T> {
548
    #[inline]
549
    fn default() -> Self {
550
        CssPropertyValue::Exact(T::default())
551
    }
552
}
553

            
554
impl DynamicCssProperty {
555
    pub fn is_inheritable(&self) -> bool {
556
        // Dynamic style properties should not be inheritable,
557
        // since that could lead to bugs - you set a property in Rust, suddenly
558
        // the wrong UI component starts to react because it was inherited.
559
        false
560
    }
561

            
562
    pub fn can_trigger_relayout(&self) -> bool {
563
        self.default_value.get_type().can_trigger_relayout()
564
    }
565
}
566

            
567
/// Layer priority for `CssRuleBlock`. Lower numbers cascade first;
568
/// higher numbers override earlier layers at the same specificity.
569
///
570
/// `u8` leaves 256 slots, so a new layer can be inserted between any
571
/// two existing slots without renumbering consumers. The gaps between
572
/// named slots are intentional — fill them with custom intermediate
573
/// layers if/when `@layer` lands.
574
pub mod rule_priority {
575
    /// User-Agent / framework defaults. Widget code that emits its
576
    /// own default CSS uses this. Lowest priority — anything else
577
    /// overrides it.
578
    pub const UA: u8 = 0;
579

            
580
    /// Stylesheets the host system reports (system fonts, theme CSS
581
    /// derived from `SystemStyle`). One step above UA so they win
582
    /// against framework defaults but lose against anything the app
583
    /// author writes.
584
    pub const SYSTEM: u8 = 10;
585

            
586
    /// Default for parser-produced rules: the app author's CSS.
587
    /// Everything coming out of `Css::from_string` lives here.
588
    pub const AUTHOR: u8 = 20;
589

            
590
    /// Inline `style="..."` / `NodeData::set_css(...)` rules — used
591
    /// once the inline-vs-component unification (separate plan) folds
592
    /// inline storage into the same Vec.
593
    pub const INLINE: u8 = 30;
594

            
595
    /// Reserved for direct-rule runtime overrides. Today the
596
    /// prop_cache handles runtime overrides via
597
    /// `user_overridden_properties`; this slot is reserved so a
598
    /// future "push a CssRuleBlock at runtime" path stays above
599
    /// inline. Used only when a callback writes a full rule, not a
600
    /// single property.
601
    pub const RUNTIME: u8 = 50;
602
}
603

            
604
/// One block of rules that applies a bunch of rules to a "path" in the style, i.e.
605
/// `div#myid.myclass -> { ("justify-content", "center") }`
606
///
607
/// The `conditions` field contains @media/@lang/etc. conditions that must ALL be
608
/// satisfied for this rule block to apply (from enclosing @-rule blocks).
609
#[derive(Debug, Default, Clone, PartialEq)]
610
#[repr(C)]
611
pub struct CssRuleBlock {
612
    /// The css path (full selector) of the style ruleset
613
    pub path: CssPath,
614
    /// `"justify-content: center"` =>
615
    /// `CssDeclaration::Static(CssProperty::JustifyContent(LayoutJustifyContent::Center))`
616
    pub declarations: CssDeclarationVec,
617
    /// Conditions from enclosing @-rules (@media, @lang, etc.) that must ALL be
618
    /// satisfied for this rule block to apply. Empty = unconditional.
619
    pub conditions: DynamicSelectorVec,
620
    /// Layer priority. See [`rule_priority`] for slot allocation.
621
    /// `0` = UA / framework, `20` = author CSS (default), higher = wins.
622
    /// Sort key combined with selector specificity in `sort_by_specificity`.
623
    pub priority: u8,
624
}
625

            
626
impl_option!(
627
    CssRuleBlock,
628
    OptionCssRuleBlock,
629
    copy = false,
630
    [Debug, Clone, PartialEq, PartialOrd]
631
);
632

            
633
impl PartialOrd for CssRuleBlock {
634
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
635
        // Compare by path and declarations only, conditions are not ordered
636
        match self.path.partial_cmp(&other.path) {
637
            Some(core::cmp::Ordering::Equal) => self.declarations.partial_cmp(&other.declarations),
638
            ord => ord,
639
        }
640
    }
641
}
642

            
643
impl_vec!(CssDeclaration, CssDeclarationVec, CssDeclarationVecDestructor, CssDeclarationVecDestructorType, CssDeclarationVecSlice, OptionCssDeclaration);
644
impl_vec_mut!(CssDeclaration, CssDeclarationVec);
645
impl_vec_debug!(CssDeclaration, CssDeclarationVec);
646
impl_vec_partialord!(CssDeclaration, CssDeclarationVec);
647
impl_vec_ord!(CssDeclaration, CssDeclarationVec);
648
impl_vec_clone!(
649
    CssDeclaration,
650
    CssDeclarationVec,
651
    CssDeclarationVecDestructor
652
);
653
impl_vec_partialeq!(CssDeclaration, CssDeclarationVec);
654
impl_vec_eq!(CssDeclaration, CssDeclarationVec);
655
impl_vec_hash!(CssDeclaration, CssDeclarationVec);
656

            
657
impl CssRuleBlock {
658
2
    pub fn new(path: CssPath, declarations: Vec<CssDeclaration>) -> Self {
659
2
        Self {
660
2
            path,
661
2
            declarations: declarations.into(),
662
2
            conditions: DynamicSelectorVec::from_const_slice(&[]),
663
2
            priority: rule_priority::AUTHOR,
664
2
        }
665
2
    }
666

            
667
    pub fn with_conditions(
668
        path: CssPath,
669
        declarations: Vec<CssDeclaration>,
670
        conditions: Vec<crate::dynamic_selector::DynamicSelector>,
671
    ) -> Self {
672
        Self {
673
            path,
674
            declarations: declarations.into(),
675
            conditions: conditions.into(),
676
            priority: rule_priority::AUTHOR,
677
        }
678
    }
679
}
680

            
681
/// A group of CSS path selectors, used during selector matching.
682
pub type CssContentGroup<'a> = Vec<&'a CssPathSelector>;
683

            
684
/// Signifies the type of a DOM node without carrying any associated data
685
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
686
#[repr(C)]
687
pub enum NodeTypeTag {
688
    // Document structure
689
    Html,
690
    Head,
691
    Body,
692

            
693
    // Block-level elements
694
    Div,
695
    P,
696
    Article,
697
    Section,
698
    Nav,
699
    Aside,
700
    Header,
701
    Footer,
702
    Main,
703
    Figure,
704
    FigCaption,
705

            
706
    // Headings
707
    H1,
708
    H2,
709
    H3,
710
    H4,
711
    H5,
712
    H6,
713

            
714
    // Inline text
715
    Br,
716
    Hr,
717
    Pre,
718
    BlockQuote,
719
    Address,
720
    Details,
721
    Summary,
722
    Dialog,
723

            
724
    // Lists
725
    Ul,
726
    Ol,
727
    Li,
728
    Dl,
729
    Dt,
730
    Dd,
731
    Menu,
732
    MenuItem,
733
    Dir,
734

            
735
    // Tables
736
    Table,
737
    Caption,
738
    THead,
739
    TBody,
740
    TFoot,
741
    Tr,
742
    Th,
743
    Td,
744
    ColGroup,
745
    Col,
746

            
747
    // Forms
748
    Form,
749
    FieldSet,
750
    Legend,
751
    Label,
752
    Input,
753
    Button,
754
    Select,
755
    OptGroup,
756
    SelectOption,
757
    TextArea,
758
    Output,
759
    Progress,
760
    Meter,
761
    DataList,
762

            
763
    // Inline elements
764
    Span,
765
    A,
766
    Em,
767
    Strong,
768
    B,
769
    I,
770
    U,
771
    S,
772
    Mark,
773
    Del,
774
    Ins,
775
    Code,
776
    Samp,
777
    Kbd,
778
    Var,
779
    Cite,
780
    Dfn,
781
    Abbr,
782
    Acronym,
783
    Q,
784
    Time,
785
    Sub,
786
    Sup,
787
    Small,
788
    Big,
789
    Bdo,
790
    Bdi,
791
    Wbr,
792
    Ruby,
793
    Rt,
794
    Rtc,
795
    Rp,
796
    Data,
797

            
798
    // Embedded content
799
    Canvas,
800
    Object,
801
    Param,
802
    Embed,
803
    Audio,
804
    Video,
805
    Source,
806
    Track,
807
    Map,
808
    Area,
809
    Svg,
810
    /// SVG `<path>` element.
811
    SvgPath,
812
    /// SVG `<circle>` element.
813
    SvgCircle,
814
    /// SVG `<rect>` element.
815
    SvgRect,
816
    /// SVG `<ellipse>` element.
817
    SvgEllipse,
818
    /// SVG `<line>` element.
819
    SvgLine,
820
    /// SVG `<polygon>` element.
821
    SvgPolygon,
822
    /// SVG `<polyline>` element.
823
    SvgPolyline,
824
    /// SVG `<g>` group element.
825
    SvgG,
826

            
827
    // SVG container elements
828
    /// SVG `<defs>` element.
829
    SvgDefs,
830
    /// SVG `<symbol>` element.
831
    SvgSymbol,
832
    /// SVG `<use>` element.
833
    SvgUse,
834
    /// SVG `<switch>` element.
835
    SvgSwitch,
836

            
837
    // SVG text elements
838
    /// SVG `<text>` element.
839
    SvgText,
840
    /// SVG `<tspan>` element.
841
    SvgTspan,
842
    /// SVG `<textPath>` element.
843
    SvgTextPath,
844

            
845
    // SVG paint server elements
846
    /// SVG `<linearGradient>` element.
847
    SvgLinearGradient,
848
    /// SVG `<radialGradient>` element.
849
    SvgRadialGradient,
850
    /// SVG `<stop>` element.
851
    SvgStop,
852
    /// SVG `<pattern>` element.
853
    SvgPattern,
854

            
855
    // SVG clipping/masking elements
856
    /// SVG `<clipPath>` element.
857
    SvgClipPathElement,
858
    /// SVG `<mask>` element.
859
    SvgMask,
860

            
861
    // SVG filter elements
862
    /// SVG `<filter>` element.
863
    SvgFilter,
864
    /// SVG `<feBlend>` element.
865
    SvgFeBlend,
866
    /// SVG `<feColorMatrix>` element.
867
    SvgFeColorMatrix,
868
    /// SVG `<feComponentTransfer>` element.
869
    SvgFeComponentTransfer,
870
    /// SVG `<feComposite>` element.
871
    SvgFeComposite,
872
    /// SVG `<feConvolveMatrix>` element.
873
    SvgFeConvolveMatrix,
874
    /// SVG `<feDiffuseLighting>` element.
875
    SvgFeDiffuseLighting,
876
    /// SVG `<feDisplacementMap>` element.
877
    SvgFeDisplacementMap,
878
    /// SVG `<feDistantLight>` element.
879
    SvgFeDistantLight,
880
    /// SVG `<feDropShadow>` element.
881
    SvgFeDropShadow,
882
    /// SVG `<feFlood>` element.
883
    SvgFeFlood,
884
    /// SVG `<feFuncR>` element.
885
    SvgFeFuncR,
886
    /// SVG `<feFuncG>` element.
887
    SvgFeFuncG,
888
    /// SVG `<feFuncB>` element.
889
    SvgFeFuncB,
890
    /// SVG `<feFuncA>` element.
891
    SvgFeFuncA,
892
    /// SVG `<feGaussianBlur>` element.
893
    SvgFeGaussianBlur,
894
    /// SVG `<feImage>` element.
895
    SvgFeImage,
896
    /// SVG `<feMerge>` element.
897
    SvgFeMerge,
898
    /// SVG `<feMergeNode>` element.
899
    SvgFeMergeNode,
900
    /// SVG `<feMorphology>` element.
901
    SvgFeMorphology,
902
    /// SVG `<feOffset>` element.
903
    SvgFeOffset,
904
    /// SVG `<fePointLight>` element.
905
    SvgFePointLight,
906
    /// SVG `<feSpecularLighting>` element.
907
    SvgFeSpecularLighting,
908
    /// SVG `<feSpotLight>` element.
909
    SvgFeSpotLight,
910
    /// SVG `<feTile>` element.
911
    SvgFeTile,
912
    /// SVG `<feTurbulence>` element.
913
    SvgFeTurbulence,
914

            
915
    // SVG marker/image elements
916
    /// SVG `<marker>` element.
917
    SvgMarker,
918
    /// SVG `<image>` element.
919
    SvgImage,
920
    /// SVG `<foreignObject>` element.
921
    SvgForeignObject,
922

            
923
    // SVG descriptive elements
924
    /// SVG `<title>` element.
925
    SvgTitle,
926
    /// SVG `<desc>` element.
927
    SvgDesc,
928
    /// SVG `<metadata>` element.
929
    SvgMetadata,
930
    /// SVG `<a>` element.
931
    SvgA,
932
    /// SVG `<view>` element.
933
    SvgView,
934
    /// SVG `<style>` element.
935
    SvgStyle,
936
    /// SVG `<script>` element.
937
    SvgScript,
938

            
939
    // SVG animation elements
940
    /// SVG `<animate>` element.
941
    SvgAnimate,
942
    /// SVG `<animateMotion>` element.
943
    SvgAnimateMotion,
944
    /// SVG `<animateTransform>` element.
945
    SvgAnimateTransform,
946
    /// SVG `<set>` element.
947
    SvgSet,
948
    /// SVG `<mpath>` element.
949
    SvgMpath,
950

            
951
    // Metadata
952
    Title,
953
    Meta,
954
    Link,
955
    Script,
956
    Style,
957
    Base,
958

            
959
    // Special
960
    Text,
961
    Img,
962
    VirtualView,
963
    /// Icon element - resolved to actual content by IconProvider
964
    Icon,
965
    /// Invisible probe — `NodeType::GeolocationProbe`. Zero-size in
966
    /// layout, skipped in the display list. CSS tag: `geolocation-probe`.
967
    GeolocationProbe,
968

            
969
    // Pseudo-elements
970
    Before,
971
    After,
972
    Marker,
973
    Placeholder,
974
}
975

            
976
/// Error returned when a CSS tag name string cannot be mapped to a [`NodeTypeTag`].
977
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
978
pub enum NodeTypeTagParseError<'a> {
979
    Invalid(&'a str),
980
}
981

            
982
impl<'a> fmt::Display for NodeTypeTagParseError<'a> {
983
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
984
        match &self {
985
            NodeTypeTagParseError::Invalid(e) => write!(f, "Invalid node type: {}", e),
986
        }
987
    }
988
}
989

            
990
/// Owned version of [`NodeTypeTagParseError`] for storage across lifetime boundaries.
991
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
992
#[repr(C, u8)]
993
pub enum NodeTypeTagParseErrorOwned {
994
    Invalid(AzString),
995
}
996

            
997
impl<'a> NodeTypeTagParseError<'a> {
998
    pub fn to_contained(&self) -> NodeTypeTagParseErrorOwned {
999
        match self {
            NodeTypeTagParseError::Invalid(s) => NodeTypeTagParseErrorOwned::Invalid(s.to_string().into()),
        }
    }
}
impl NodeTypeTagParseErrorOwned {
    pub fn to_shared<'a>(&'a self) -> NodeTypeTagParseError<'a> {
        match self {
            NodeTypeTagParseErrorOwned::Invalid(s) => NodeTypeTagParseError::Invalid(s),
        }
    }
}
/// Parses the node type from a CSS string such as `"div"` => `NodeTypeTag::Div`
impl NodeTypeTag {
6509
    pub fn from_str(css_key: &str) -> Result<Self, NodeTypeTagParseError<'_>> {
6509
        match css_key {
            // Document structure
6509
            "html" => Ok(NodeTypeTag::Html),
6430
            "head" => Ok(NodeTypeTag::Head),
6423
            "body" => Ok(NodeTypeTag::Body),
            // Block-level elements
3835
            "div" => Ok(NodeTypeTag::Div),
2296
            "p" => Ok(NodeTypeTag::P),
1305
            "article" => Ok(NodeTypeTag::Article),
1298
            "section" => Ok(NodeTypeTag::Section),
1291
            "nav" => Ok(NodeTypeTag::Nav),
1284
            "aside" => Ok(NodeTypeTag::Aside),
1277
            "header" => Ok(NodeTypeTag::Header),
1270
            "footer" => Ok(NodeTypeTag::Footer),
1263
            "main" => Ok(NodeTypeTag::Main),
1256
            "figure" => Ok(NodeTypeTag::Figure),
1249
            "figcaption" => Ok(NodeTypeTag::FigCaption),
            // Headings
1242
            "h1" => Ok(NodeTypeTag::H1),
1228
            "h2" => Ok(NodeTypeTag::H2),
1214
            "h3" => Ok(NodeTypeTag::H3),
1200
            "h4" => Ok(NodeTypeTag::H4),
1193
            "h5" => Ok(NodeTypeTag::H5),
1186
            "h6" => Ok(NodeTypeTag::H6),
            // Inline text
1179
            "br" => Ok(NodeTypeTag::Br),
1172
            "hr" => Ok(NodeTypeTag::Hr),
1165
            "pre" => Ok(NodeTypeTag::Pre),
1005
            "blockquote" => Ok(NodeTypeTag::BlockQuote),
998
            "address" => Ok(NodeTypeTag::Address),
991
            "details" => Ok(NodeTypeTag::Details),
984
            "summary" => Ok(NodeTypeTag::Summary),
977
            "dialog" => Ok(NodeTypeTag::Dialog),
            // Lists
970
            "ul" => Ok(NodeTypeTag::Ul),
956
            "ol" => Ok(NodeTypeTag::Ol),
942
            "li" => Ok(NodeTypeTag::Li),
928
            "dl" => Ok(NodeTypeTag::Dl),
921
            "dt" => Ok(NodeTypeTag::Dt),
914
            "dd" => Ok(NodeTypeTag::Dd),
907
            "menu" => Ok(NodeTypeTag::Menu),
900
            "menuitem" => Ok(NodeTypeTag::MenuItem),
893
            "dir" => Ok(NodeTypeTag::Dir),
            // Tables
886
            "table" => Ok(NodeTypeTag::Table),
872
            "caption" => Ok(NodeTypeTag::Caption),
865
            "thead" => Ok(NodeTypeTag::THead),
851
            "tbody" => Ok(NodeTypeTag::TBody),
837
            "tfoot" => Ok(NodeTypeTag::TFoot),
830
            "tr" => Ok(NodeTypeTag::Tr),
816
            "th" => Ok(NodeTypeTag::Th),
802
            "td" => Ok(NodeTypeTag::Td),
788
            "colgroup" => Ok(NodeTypeTag::ColGroup),
781
            "col" => Ok(NodeTypeTag::Col),
            // Forms
774
            "form" => Ok(NodeTypeTag::Form),
760
            "fieldset" => Ok(NodeTypeTag::FieldSet),
753
            "legend" => Ok(NodeTypeTag::Legend),
746
            "label" => Ok(NodeTypeTag::Label),
739
            "input" => Ok(NodeTypeTag::Input),
718
            "button" => Ok(NodeTypeTag::Button),
704
            "select" => Ok(NodeTypeTag::Select),
697
            "optgroup" => Ok(NodeTypeTag::OptGroup),
690
            "option" => Ok(NodeTypeTag::SelectOption),
683
            "textarea" => Ok(NodeTypeTag::TextArea),
676
            "output" => Ok(NodeTypeTag::Output),
669
            "progress" => Ok(NodeTypeTag::Progress),
662
            "meter" => Ok(NodeTypeTag::Meter),
655
            "datalist" => Ok(NodeTypeTag::DataList),
            // Inline elements
648
            "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)),
        }
6509
    }
}
impl fmt::Display for NodeTypeTag {
48715
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48715
        match self {
            // Document structure
58
            NodeTypeTag::Html => write!(f, "html"),
7
            NodeTypeTag::Head => write!(f, "head"),
1996
            NodeTypeTag::Body => write!(f, "body"),
            // Block elements
16072
            NodeTypeTag::Div => write!(f, "div"),
58
            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
58
            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
1639
            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"),
1690
            NodeTypeTag::Tr => write!(f, "tr"),
7
            NodeTypeTag::Th => write!(f, "th"),
4240
            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
2659
            NodeTypeTag::Span => write!(f, "span"),
2506
            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
16888
            NodeTypeTag::Text => write!(f, "text"),
109
            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"),
        }
48715
    }
}
/// 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 {
4
    pub fn new(selectors: Vec<CssPathSelector>) -> Self {
4
        Self {
4
            selectors: selectors.into(),
4
        }
4
    }
    /// Prepend a `Root` scope selector (push_front) confining this rule to the owner
    /// node `start` (whose subtree spans the inclusive flat ids `[start, end]`).
    /// Two cases (#47 leak fix + descendant-selector support):
    ///
    /// - A **bare `*` rule** (the `parse_inline` wrapper for a `with_css`/`set_css`
    ///   bare-declaration string) is scoped **node-only** (`[start, start]`):
    ///   inline-style semantics — it applies to the OWNER only and must not leak to
    ///   descendants or siblings. `[Root([s,s]), Global]` matches `s` only.
    /// - A rule with a **real selector** (`.menu-item`, `div`, a descendant chain —
    ///   from `add_component_css` / a component stylesheet) is scoped to the whole
    ///   **subtree** (`[start, end]`), so its selectors match within the owner's
    ///   subtree (e.g. a menu container's `.menu-item` children). `[Root([s,e]),
    ///   Class(x)]` matches any node in `[s,e]` that also matches `.x`.
26138
    pub fn push_front_scope(&mut self, start: usize, end: usize) {
26138
        let is_bare_global = self.selectors.as_ref().len() == 1
25219
            && matches!(self.selectors.as_ref().first(), Some(CssPathSelector::Global));
26138
        let range = if is_bare_global {
18394
            CssScopeRange { start, end: start }
        } else {
7744
            CssScopeRange { start, end }
        };
26138
        let mut selectors = Vec::with_capacity(self.selectors.as_ref().len() + 1);
26138
        selectors.push(CssPathSelector::Root(range));
26138
        selectors.extend(self.selectors.as_ref().iter().cloned());
26138
        self.selectors = selectors.into();
26138
    }
}
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
    }
}
/// Inclusive range of flat `NodeId`s describing a node's subtree `[start, end]`
/// (`end = start + estimated_total_children`, since the flat arena lays subtrees
/// out contiguously). Carried by [`CssPathSelector::Root`] to scope inline css to
/// a subtree, and is the unit of future parallel per-subtree cascading.
/// `repr(C)` for FFI / api.json codegen.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct CssScopeRange {
    /// First flat NodeId of the subtree (the owning node itself).
    pub start: usize,
    /// Last flat NodeId of the subtree, inclusive (`start` for a leaf).
    pub end: usize,
}
impl CssScopeRange {
    /// True if `node` (a flat NodeId index) is inside this subtree range.
    #[inline]
666634
    pub fn contains(&self, node: usize) -> bool {
666634
        self.start <= node && node <= self.end
666634
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
#[derive(Default)]
pub enum CssPathSelector {
    /// Represents the `*` selector
    #[default]
    Global,
    /// Scope marker carrying a node's **subtree range** `[start, end]` (inclusive
    /// flat `NodeId`s; `end = start + estimated_total_children`). Matches a node
    /// iff `start <= node <= end`. Synthesized at flatten time and `push_front`-ed
    /// onto every inline (`with_css`/`set_css`) rule's path, so the rule compounds
    /// with the `parse_inline` `*` wrapper (`[Root(s,e), Global, …]`) and is scoped
    /// to that node's subtree instead of leaking to the whole tree (#47). Because
    /// the flat arena lays subtrees out contiguously, this range is also the unit
    /// of future parallel per-subtree cascading.
    Root(CssScopeRange),
    /// `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 {
43
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::CssPathSelector::*;
43
        match &self {
            Global => write!(f, "*"),
1
            Root(r) => write!(f, ":root({}..={})", r.start, r.end),
            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, "~"),
        }
43
    }
}
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.
6284
    pub fn empty() -> Self {
6284
        Default::default()
6284
    }
    /// 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.
8194
    pub fn sort_by_specificity(&mut self) {
33698
        self.rules.as_mut().sort_by(|a, b| {
33047
            a.priority.cmp(&b.priority)
33047
                .then_with(|| get_specificity(&a.path).cmp(&get_specificity(&b.path)))
33047
        });
8194
    }
8399
    pub fn rules<'a>(&'a self) -> core::slice::Iter<'a, CssRuleBlock> {
8399
        self.rules.as_ref().iter()
8399
    }
    /// 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.
3499474
    pub fn iter_inline_properties<'a>(
3499474
        &'a self,
3499474
    ) -> impl Iterator<
3499474
        Item = (
3499474
            &'a crate::props::property::CssProperty,
3499474
            &'a DynamicSelectorVec,
3499474
        ),
3499474
    > + 'a {
3499474
        self.rules.as_ref().iter().flat_map(|r| {
36271
            r.declarations.as_ref().iter().filter_map(move |d| match d {
36271
                CssDeclaration::Static(p) => Some((p, &r.conditions)),
                CssDeclaration::Dynamic(_) => None,
36271
            })
36271
        })
3499474
    }
}
#[cfg(test)]
mod root_scope_tests {
    use super::*;
    #[test]
1
    fn scope_range_contains() {
1
        let r = CssScopeRange { start: 3, end: 7 };
1
        assert!(r.contains(3) && r.contains(5) && r.contains(7));
1
        assert!(!r.contains(2) && !r.contains(8));
        // leaf: start == end matches only itself
1
        let leaf = CssScopeRange { start: 4, end: 4 };
1
        assert!(leaf.contains(4));
1
        assert!(!leaf.contains(3) && !leaf.contains(5));
1
    }
    #[test]
1
    fn push_front_scope_compounds_with_wrapper() {
        // a bare-decl `set_css` path is `[Global]` (the parse_inline `*` wrapper) and
        // is scoped NODE-ONLY ([start, start]) so it applies to the owner only.
1
        let mut p = CssPath::new(vec![CssPathSelector::Global]);
1
        p.push_front_scope(5, 9);
1
        assert_eq!(
1
            p.selectors.as_ref(),
1
            &[
1
                CssPathSelector::Root(CssScopeRange { start: 5, end: 5 }),
1
                CssPathSelector::Global
1
            ][..]
        );
        // a path with a real selector is SUBTREE-scoped ([start, end]).
1
        let subtree = CssScopeRange { start: 5, end: 9 };
1
        let mut p2 = CssPath::new(vec![
1
            CssPathSelector::Global,
1
            CssPathSelector::Children,
1
            CssPathSelector::Class("foo".to_string().into()),
        ]);
1
        p2.push_front_scope(5, 9);
1
        assert_eq!(p2.selectors.as_ref()[0], CssPathSelector::Root(subtree));
1
        assert_eq!(p2.selectors.as_ref().len(), 4);
1
    }
    #[test]
1
    fn root_display_roundtrips() {
1
        let s = CssPathSelector::Root(CssScopeRange { start: 2, end: 6 });
1
        assert_eq!(format!("{}", s), ":root(2..=6)");
1
    }
    #[test]
1
    fn parse_inline_keeps_layout_and_style_decls() {
        // set_css("width: 200px; height: 100px; background: red") must keep all
        // three declarations (layout + style) as Static props in the parsed rule.
1
        let css = Css::parse_inline("width: 200px; height: 100px; background: red");
1
        let mut types = Vec::new();
1
        for r in css.rules.as_ref() {
3
            for d in r.declarations.as_ref() {
3
                if let crate::css::CssDeclaration::Static(p) = d {
3
                    types.push(alloc::format!("{:?}", p.get_type()));
3
                }
            }
        }
1
        println!("INLINE PROP TYPES: {:?}", types);
1
        assert!(
3
            types.iter().any(|t| t.contains("width")),
            "width must survive parse_inline as a Static decl; got {:?}",
            types
        );
1
        assert!(
2
            types.iter().any(|t| t.contains("height")),
            "height must survive parse_inline; got {:?}",
            types
        );
1
    }
}
#[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).
66088
pub fn get_specificity(path: &CssPath) -> (usize, usize, usize, usize) {
66088
    let id_count = path
66088
        .selectors
66088
        .iter()
127270
        .filter(|x| matches!(x, CssPathSelector::Id(_)))
66088
        .count();
66088
    let class_count = path
66088
        .selectors
66088
        .iter()
127270
        .filter(|x| {
105835
            matches!(
127270
                x,
                CssPathSelector::Class(_)
                    | CssPathSelector::PseudoSelector(_)
                    | CssPathSelector::Attribute(_)
            )
127270
        })
66088
        .count();
66088
    let div_count = path
66088
        .selectors
66088
        .iter()
127270
        .filter(|x| matches!(x, CssPathSelector::Type(_)))
66088
        .count();
66088
    (id_count, class_count, div_count, path.selectors.len())
66088
}