Skip to main content

azul_core/
prop_cache.rs

1//! CSS property cache for efficient style resolution and animation.
2//!
3//! This module implements a cache layer between the raw CSS stylesheet and the rendered DOM.
4//! It resolves CSS properties for each node, handling:
5//!
6//! - **Cascade resolution**: Computes final values from CSS rules, inline styles, and inheritance
7//! - **Pseudo-class states**: Caches styles for `:hover`, `:active`, `:focus`, etc.
8//! - **Animation support**: Tracks animating properties for smooth interpolation
9//! - **Performance**: Avoids re-parsing and re-resolving unchanged properties
10//!
11//! # Architecture
12//!
13//! The cache is organized per-node and per-property-type. Each property has a dedicated
14//! getter method that:
15//!
16//! 1. Checks if the property is cached
17//! 2. If not, resolves it from CSS rules + inline styles
18//! 3. Caches the result for subsequent frames
19//!
20//! # Thread Safety
21//!
22//! Not thread-safe. Each window has its own cache instance.
23
24extern crate alloc;
25
26use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec};
27
28use crate::dom::NodeType;
29
30/// Tracks the origin of a CSS property value.
31/// Used to correctly implement the CSS cascade and inheritance rules.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub enum CssPropertyOrigin {
34    /// Property was inherited from parent node (only for inheritable properties)
35    Inherited,
36    /// Property is the node's own value (from UA CSS, CSS file, inline style, or user override)
37    Own,
38}
39
40/// A CSS property with its origin tracking.
41#[derive(Debug, Clone, PartialEq)]
42pub struct CssPropertyWithOrigin {
43    pub property: CssProperty,
44    pub origin: CssPropertyOrigin,
45}
46
47/// Represents a single step in a CSS property dependency chain.
48/// Example: "10% of node 5" or "1.2em of node 3"
49#[derive(Debug, Clone, PartialEq)]
50pub enum CssDependencyChainStep {
51    /// Value depends on a percentage of another node's resolved value
52    /// e.g., font-size: 150% means 1.5 * parent's font-size
53    Percent { source_node: NodeId, factor: f32 },
54
55    /// Value depends on an em multiple of another node's font-size
56    /// e.g., padding: 2em means 2.0 * current element's font-size
57    Em { source_node: NodeId, factor: f32 },
58
59    /// Value depends on a rem multiple of root node's font-size
60    /// e.g., margin: 1.5rem means 1.5 * root font-size
61    Rem { factor: f32 },
62
63    /// Absolute value (px, pt, etc.) - no further dependencies
64    Absolute { pixels: f32 },
65}
66
67/// A dependency chain for a CSS property value.
68/// Example: [10% of node 10, then 1.2em of that, then 1.5em of that]
69///
70/// During layout, this chain is resolved by:
71/// 1. Starting with the root dependency (e.g., node 10's resolved font-size)
72/// 2. Applying each transformation in sequence
73/// 3. Producing the final pixel value
74#[derive(Debug, Clone, PartialEq)]
75pub struct CssDependencyChain {
76    /// The property type this chain is for
77    pub property_type: CssPropertyType,
78
79    /// The ordered list of dependencies, from root to leaf
80    /// Empty if the value is absolute (no dependencies)
81    pub steps: Vec<CssDependencyChainStep>,
82
83    /// Cached resolved value (in pixels) from the last resolution
84    /// None if the chain hasn't been resolved yet
85    pub cached_pixels: Option<f32>,
86}
87
88impl CssDependencyChain {
89    /// Create a new dependency chain for an absolute pixel value
90    pub fn absolute(property_type: CssPropertyType, pixels: f32) -> Self {
91        Self {
92            property_type,
93            steps: vec![CssDependencyChainStep::Absolute { pixels }],
94            cached_pixels: Some(pixels),
95        }
96    }
97
98    /// Create a new dependency chain for a percentage-based value
99    pub fn percent(property_type: CssPropertyType, source_node: NodeId, factor: f32) -> Self {
100        Self {
101            property_type,
102            steps: vec![CssDependencyChainStep::Percent {
103                source_node,
104                factor,
105            }],
106            cached_pixels: None,
107        }
108    }
109
110    /// Create a new dependency chain for an em-based value
111    pub fn em(property_type: CssPropertyType, source_node: NodeId, factor: f32) -> Self {
112        Self {
113            property_type,
114            steps: vec![CssDependencyChainStep::Em {
115                source_node,
116                factor,
117            }],
118            cached_pixels: None,
119        }
120    }
121
122    /// Create a new dependency chain for a rem-based value
123    pub fn rem(property_type: CssPropertyType, factor: f32) -> Self {
124        Self {
125            property_type,
126            steps: vec![CssDependencyChainStep::Rem { factor }],
127            cached_pixels: None,
128        }
129    }
130
131    /// Check if this chain depends on a specific node
132    pub fn depends_on(&self, node_id: NodeId) -> bool {
133        self.steps.iter().any(|step| match step {
134            CssDependencyChainStep::Percent { source_node, .. } => *source_node == node_id,
135            CssDependencyChainStep::Em { source_node, .. } => *source_node == node_id,
136            _ => false,
137        })
138    }
139
140    /// Resolve the dependency chain to a pixel value.
141    ///
142    /// # Arguments
143    /// * `resolve_node_value` - Closure to resolve a node's property value to pixels
144    /// * `root_font_size` - Root element's font-size for rem calculations
145    ///
146    /// # Returns
147    /// The resolved pixel value, or None if any dependency couldn't be resolved
148    pub fn resolve<F>(&mut self, mut resolve_node_value: F, root_font_size: f32) -> Option<f32>
149    where
150        F: FnMut(NodeId, CssPropertyType) -> Option<f32>,
151    {
152        let mut current_value: Option<f32> = None;
153
154        for step in &self.steps {
155            match step {
156                CssDependencyChainStep::Absolute { pixels } => {
157                    current_value = Some(*pixels);
158                }
159                CssDependencyChainStep::Percent {
160                    source_node,
161                    factor,
162                } => {
163                    let source_val = resolve_node_value(*source_node, self.property_type)?;
164                    current_value = Some(source_val * factor);
165                }
166                CssDependencyChainStep::Em {
167                    source_node,
168                    factor,
169                } => {
170                    let font_size = resolve_node_value(*source_node, CssPropertyType::FontSize)?;
171                    current_value = Some(font_size * factor);
172                }
173                CssDependencyChainStep::Rem { factor } => {
174                    current_value = Some(root_font_size * factor);
175                }
176            }
177        }
178
179        self.cached_pixels = current_value;
180        current_value
181    }
182}
183
184use azul_css::{
185    css::{Css, CssPath},
186    props::{
187        basic::{StyleFontFamily, StyleFontFamilyVec, StyleFontSize},
188        layout::{LayoutDisplay, LayoutHeight, LayoutWidth},
189        property::{
190            BoxDecorationBreakValue, BreakInsideValue, CaretAnimationDurationValue,
191            CaretColorValue, CaretWidthValue, ClipPathValue, ColumnCountValue, ColumnFillValue,
192            ColumnRuleColorValue, ColumnRuleStyleValue, ColumnRuleWidthValue, ColumnSpanValue,
193            ColumnWidthValue, ContentValue, CounterIncrementValue, CounterResetValue, CssProperty,
194            CssPropertyType, FlowFromValue, FlowIntoValue, LayoutAlignContentValue,
195            LayoutAlignItemsValue, LayoutAlignSelfValue, LayoutBorderBottomWidthValue,
196            LayoutBorderLeftWidthValue, LayoutBorderRightWidthValue, LayoutBorderSpacingValue,
197            LayoutBorderTopWidthValue, LayoutBoxSizingValue, LayoutClearValue,
198            LayoutColumnGapValue, LayoutDisplayValue, LayoutFlexBasisValue,
199            LayoutFlexDirectionValue, LayoutFlexGrowValue, LayoutFlexShrinkValue,
200            LayoutFlexWrapValue, LayoutFloatValue, LayoutGapValue, LayoutGridAutoColumnsValue,
201            LayoutGridAutoFlowValue, LayoutGridAutoRowsValue, LayoutGridColumnValue,
202            LayoutGridRowValue, LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
203            LayoutHeightValue, LayoutInsetBottomValue, LayoutJustifyContentValue,
204            LayoutJustifyItemsValue, LayoutJustifySelfValue, LayoutLeftValue,
205            LayoutMarginBottomValue, LayoutMarginLeftValue, LayoutMarginRightValue,
206            LayoutMarginTopValue, LayoutMaxHeightValue, LayoutMaxWidthValue, LayoutMinHeightValue,
207            LayoutMinWidthValue, LayoutOverflowValue, LayoutPaddingBottomValue,
208            LayoutPaddingLeftValue, LayoutPaddingRightValue, LayoutPaddingTopValue,
209            LayoutPositionValue, LayoutRightValue, LayoutRowGapValue, LayoutScrollbarWidthValue,
210            LayoutTableLayoutValue, LayoutTextJustifyValue, LayoutTopValue, LayoutWidthValue,
211            LayoutWritingModeValue, LayoutZIndexValue, OrphansValue, PageBreakValue,
212            ScrollbarStyleValue, SelectionBackgroundColorValue, SelectionColorValue,
213            SelectionRadiusValue, ShapeImageThresholdValue, ShapeInsideValue, ShapeMarginValue,
214            ShapeOutsideValue, StringSetValue, StyleBackfaceVisibilityValue,
215            StyleBackgroundContentVecValue, StyleBackgroundPositionVecValue,
216            StyleBackgroundRepeatVecValue, StyleBackgroundSizeVecValue,
217            StyleBorderBottomColorValue, StyleBorderBottomLeftRadiusValue,
218            StyleBorderBottomRightRadiusValue, StyleBorderBottomStyleValue,
219            StyleBorderCollapseValue, StyleBorderLeftColorValue, StyleBorderLeftStyleValue,
220            StyleBorderRightColorValue, StyleBorderRightStyleValue, StyleBorderTopColorValue,
221            StyleBorderTopLeftRadiusValue, StyleBorderTopRightRadiusValue,
222            StyleBorderTopStyleValue, StyleBoxShadowValue, StyleCaptionSideValue, StyleCursorValue,
223            StyleDirectionValue, StyleEmptyCellsValue, StyleExclusionMarginValue,
224            StyleFilterVecValue, StyleFontFamilyVecValue, StyleFontSizeValue, StyleFontStyleValue,
225            StyleFontValue, StyleFontWeightValue, StyleHangingPunctuationValue,
226            StyleHyphenationLanguageValue, StyleHyphensValue, StyleInitialLetterValue,
227            StyleLetterSpacingValue, StyleLineClampValue, StyleLineHeightValue,
228            StyleListStylePositionValue, StyleListStyleTypeValue, StyleMixBlendModeValue,
229            StyleOpacityValue, StylePerspectiveOriginValue, StyleScrollbarColorValue,
230            StyleTabSizeValue, StyleTextAlignValue, StyleTextColorValue,
231            StyleTextCombineUprightValue, StyleTextDecorationValue, StyleTextIndentValue,
232            StyleTransformOriginValue, StyleTransformVecValue, StyleUserSelectValue,
233            StyleVerticalAlignValue, StyleVisibilityValue, StyleWhiteSpaceValue,
234            StyleWordSpacingValue, WidowsValue,
235        },
236        style::{StyleCursor, StyleTextColor, StyleTransformOrigin},
237    },
238    AzString,
239};
240
241use crate::{
242    dom::{NodeData, NodeId, TabIndex, TagId},
243    id::{NodeDataContainer, NodeDataContainerRef},
244    style::CascadeInfo,
245    styled_dom::{
246        NodeHierarchyItem, NodeHierarchyItemId, NodeHierarchyItemVec, ParentWithNodeDepth,
247        ParentWithNodeDepthVec, StyledNodeState, TagIdToNodeIdMapping,
248    },
249};
250
251use azul_css::dynamic_selector::{
252    CssPropertyWithConditions, CssPropertyWithConditionsVec, DynamicSelectorContext,
253};
254
255/// Macro to match on any CssProperty variant and access the inner CssPropertyValue<T>.
256/// This allows generic operations on cascade keywords without writing 190+ match arms.
257macro_rules! match_property_value {
258    ($property:expr, $value:ident, $expr:expr) => {
259        match $property {
260            CssProperty::CaretColor($value) => $expr,
261            CssProperty::CaretAnimationDuration($value) => $expr,
262            CssProperty::SelectionBackgroundColor($value) => $expr,
263            CssProperty::SelectionColor($value) => $expr,
264            CssProperty::SelectionRadius($value) => $expr,
265            CssProperty::TextColor($value) => $expr,
266            CssProperty::FontSize($value) => $expr,
267            CssProperty::FontFamily($value) => $expr,
268            CssProperty::FontWeight($value) => $expr,
269            CssProperty::FontStyle($value) => $expr,
270            CssProperty::TextAlign($value) => $expr,
271            CssProperty::TextJustify($value) => $expr,
272            CssProperty::VerticalAlign($value) => $expr,
273            CssProperty::LetterSpacing($value) => $expr,
274            CssProperty::TextIndent($value) => $expr,
275            CssProperty::InitialLetter($value) => $expr,
276            CssProperty::LineClamp($value) => $expr,
277            CssProperty::HangingPunctuation($value) => $expr,
278            CssProperty::TextCombineUpright($value) => $expr,
279            CssProperty::ExclusionMargin($value) => $expr,
280            CssProperty::HyphenationLanguage($value) => $expr,
281            CssProperty::LineHeight($value) => $expr,
282            CssProperty::WordSpacing($value) => $expr,
283            CssProperty::TabSize($value) => $expr,
284            CssProperty::WhiteSpace($value) => $expr,
285            CssProperty::Hyphens($value) => $expr,
286            CssProperty::Direction($value) => $expr,
287            CssProperty::UserSelect($value) => $expr,
288            CssProperty::TextDecoration($value) => $expr,
289            CssProperty::Cursor($value) => $expr,
290            CssProperty::Display($value) => $expr,
291            CssProperty::Float($value) => $expr,
292            CssProperty::BoxSizing($value) => $expr,
293            CssProperty::Width($value) => $expr,
294            CssProperty::Height($value) => $expr,
295            CssProperty::MinWidth($value) => $expr,
296            CssProperty::MinHeight($value) => $expr,
297            CssProperty::MaxWidth($value) => $expr,
298            CssProperty::MaxHeight($value) => $expr,
299            CssProperty::Position($value) => $expr,
300            CssProperty::Top($value) => $expr,
301            CssProperty::Right($value) => $expr,
302            CssProperty::Left($value) => $expr,
303            CssProperty::Bottom($value) => $expr,
304            CssProperty::ZIndex($value) => $expr,
305            CssProperty::FlexWrap($value) => $expr,
306            CssProperty::FlexDirection($value) => $expr,
307            CssProperty::FlexGrow($value) => $expr,
308            CssProperty::FlexShrink($value) => $expr,
309            CssProperty::FlexBasis($value) => $expr,
310            CssProperty::JustifyContent($value) => $expr,
311            CssProperty::AlignItems($value) => $expr,
312            CssProperty::AlignContent($value) => $expr,
313            CssProperty::AlignSelf($value) => $expr,
314            CssProperty::JustifyItems($value) => $expr,
315            CssProperty::JustifySelf($value) => $expr,
316            CssProperty::BackgroundContent($value) => $expr,
317            CssProperty::BackgroundPosition($value) => $expr,
318            CssProperty::BackgroundSize($value) => $expr,
319            CssProperty::BackgroundRepeat($value) => $expr,
320            CssProperty::OverflowX($value) => $expr,
321            CssProperty::OverflowY($value) => $expr,
322            CssProperty::PaddingTop($value) => $expr,
323            CssProperty::PaddingLeft($value) => $expr,
324            CssProperty::PaddingRight($value) => $expr,
325            CssProperty::PaddingBottom($value) => $expr,
326            CssProperty::MarginTop($value) => $expr,
327            CssProperty::MarginLeft($value) => $expr,
328            CssProperty::MarginRight($value) => $expr,
329            CssProperty::MarginBottom($value) => $expr,
330            CssProperty::BorderTopLeftRadius($value) => $expr,
331            CssProperty::BorderTopRightRadius($value) => $expr,
332            CssProperty::BorderBottomLeftRadius($value) => $expr,
333            CssProperty::BorderBottomRightRadius($value) => $expr,
334            CssProperty::BorderTopColor($value) => $expr,
335            CssProperty::BorderRightColor($value) => $expr,
336            CssProperty::BorderLeftColor($value) => $expr,
337            CssProperty::BorderBottomColor($value) => $expr,
338            CssProperty::BorderTopStyle($value) => $expr,
339            CssProperty::BorderRightStyle($value) => $expr,
340            CssProperty::BorderLeftStyle($value) => $expr,
341            CssProperty::BorderBottomStyle($value) => $expr,
342            CssProperty::BorderTopWidth($value) => $expr,
343            CssProperty::BorderRightWidth($value) => $expr,
344            CssProperty::BorderLeftWidth($value) => $expr,
345            CssProperty::BorderBottomWidth($value) => $expr,
346            CssProperty::BoxShadow($value) => $expr,
347            CssProperty::Opacity($value) => $expr,
348            CssProperty::Transform($value) => $expr,
349            CssProperty::TransformOrigin($value) => $expr,
350            CssProperty::PerspectiveOrigin($value) => $expr,
351            CssProperty::BackfaceVisibility($value) => $expr,
352            CssProperty::MixBlendMode($value) => $expr,
353            CssProperty::Filter($value) => $expr,
354            CssProperty::Visibility($value) => $expr,
355            CssProperty::WritingMode($value) => $expr,
356            CssProperty::GridTemplateColumns($value) => $expr,
357            CssProperty::GridTemplateRows($value) => $expr,
358            CssProperty::GridAutoColumns($value) => $expr,
359            CssProperty::GridAutoRows($value) => $expr,
360            CssProperty::GridAutoFlow($value) => $expr,
361            CssProperty::GridColumn($value) => $expr,
362            CssProperty::GridRow($value) => $expr,
363            CssProperty::GridTemplateAreas($value) => $expr,
364            CssProperty::Gap($value) => $expr,
365            CssProperty::ColumnGap($value) => $expr,
366            CssProperty::RowGap($value) => $expr,
367            CssProperty::Clear($value) => $expr,
368            CssProperty::ScrollbarStyle($value) => $expr,
369            CssProperty::ScrollbarWidth($value) => $expr,
370            CssProperty::ScrollbarColor($value) => $expr,
371            CssProperty::ListStyleType($value) => $expr,
372            CssProperty::ListStylePosition($value) => $expr,
373            CssProperty::Font($value) => $expr,
374            CssProperty::ColumnCount($value) => $expr,
375            CssProperty::ColumnWidth($value) => $expr,
376            CssProperty::ColumnSpan($value) => $expr,
377            CssProperty::ColumnFill($value) => $expr,
378            CssProperty::ColumnRuleStyle($value) => $expr,
379            CssProperty::ColumnRuleWidth($value) => $expr,
380            CssProperty::ColumnRuleColor($value) => $expr,
381            CssProperty::FlowInto($value) => $expr,
382            CssProperty::FlowFrom($value) => $expr,
383            CssProperty::ShapeOutside($value) => $expr,
384            CssProperty::ShapeInside($value) => $expr,
385            CssProperty::ShapeImageThreshold($value) => $expr,
386            CssProperty::ShapeMargin($value) => $expr,
387            CssProperty::ClipPath($value) => $expr,
388            CssProperty::Content($value) => $expr,
389            CssProperty::CounterIncrement($value) => $expr,
390            CssProperty::CounterReset($value) => $expr,
391            CssProperty::StringSet($value) => $expr,
392            CssProperty::Orphans($value) => $expr,
393            CssProperty::Widows($value) => $expr,
394            CssProperty::PageBreakBefore($value) => $expr,
395            CssProperty::PageBreakAfter($value) => $expr,
396            CssProperty::PageBreakInside($value) => $expr,
397            CssProperty::BreakInside($value) => $expr,
398            CssProperty::BoxDecorationBreak($value) => $expr,
399            CssProperty::TableLayout($value) => $expr,
400            CssProperty::BorderCollapse($value) => $expr,
401            CssProperty::BorderSpacing($value) => $expr,
402            CssProperty::CaptionSide($value) => $expr,
403            CssProperty::EmptyCells($value) => $expr,
404        }
405    };
406}
407
408/// Returns the CSS-specified initial value for a given property type.
409/// These are the default values defined by the CSS specification, not UA stylesheet values.
410fn get_initial_value(property_type: CssPropertyType) -> Option<CssProperty> {
411    use azul_css::css::CssPropertyValue;
412
413    // For now, we return None for most properties and implement only the most critical ones.
414    // This can be expanded as needed.
415    match property_type {
416        // Most properties: return None (no initial value implemented yet)
417        // This means cascade keywords will fall back to parent values or remain unresolved
418        _ => None,
419    }
420}
421
422// NOTE: To avoid large memory allocations, this is a "cache" that stores all the CSS properties
423// found in the DOM. This cache exists on a per-DOM basis, so it scales independent of how many
424// nodes are in the DOM.
425//
426// If each node would carry its own CSS properties, that would unnecessarily consume memory
427// because most nodes use the default properties or override only one or two properties.
428//
429// The cache can compute the property of any node at any given time, given the current node
430// state (hover, active, focused, normal). This way we don't have to duplicate the CSS properties
431// onto every single node and exchange them when the style changes. Two caches can be appended
432// to each other by simply merging their NodeIds.
433#[derive(Debug, Default, Clone, PartialEq)]
434pub struct CssPropertyCache {
435    // number of nodes in the current DOM
436    pub node_count: usize,
437
438    // properties that were overridden in callbacks (not specific to any node state)
439    pub user_overridden_properties: Vec<BTreeMap<CssPropertyType, CssProperty>>,
440
441    // non-default CSS properties that were cascaded from the parent
442    pub cascaded_normal_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
443    pub cascaded_hover_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
444    pub cascaded_active_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
445    pub cascaded_focus_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
446    pub cascaded_dragging_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
447    pub cascaded_drag_over_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
448
449    // non-default CSS properties that were set via a CSS file
450    pub css_normal_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
451    pub css_hover_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
452    pub css_active_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
453    pub css_focus_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
454    pub css_dragging_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
455    pub css_drag_over_props: Vec<BTreeMap<CssPropertyType, CssProperty>>,
456
457    // NEW: Computed values cache - pre-resolved inherited properties
458    pub computed_values: Vec<BTreeMap<CssPropertyType, CssPropertyWithOrigin>>,
459
460    // NEW: Dependency chains for relative values (em, %, rem, etc.)
461    pub dependency_chains: Vec<BTreeMap<CssPropertyType, CssDependencyChain>>,
462
463    // Pre-resolved property cache: Vec indexed by node ID, inner Vec sorted by CssPropertyType.
464    pub resolved_cache: Vec<Vec<(CssPropertyType, CssProperty)>>,
465
466    // Compact layout cache: three-tier numeric encoding for O(1) layout lookups.
467    // Built once after restyle + apply_ua_css + compute_inherited_values.
468    pub compact_cache: Option<azul_css::compact_cache::CompactLayoutCache>,
469}
470
471impl CssPropertyCache {
472    /// Restyles the CSS property cache with a new CSS file
473    #[must_use]
474    pub fn restyle(
475        &mut self,
476        css: &mut Css,
477        node_data: &NodeDataContainerRef<NodeData>,
478        node_hierarchy: &NodeHierarchyItemVec,
479        non_leaf_nodes: &ParentWithNodeDepthVec,
480        html_tree: &NodeDataContainerRef<CascadeInfo>,
481    ) -> Vec<TagIdToNodeIdMapping> {
482        use azul_css::{
483            css::{CssDeclaration, CssPathPseudoSelector::*},
484            props::layout::LayoutDisplay,
485        };
486
487        let css_is_empty = css.is_empty();
488
489        if !css_is_empty {
490            css.sort_by_specificity();
491
492            macro_rules! filter_rules {($expected_pseudo_selector:expr, $node_id:expr) => {{
493                css
494                .rules() // can not be parallelized due to specificity order matching
495                .filter(|rule_block| crate::style::rule_ends_with(&rule_block.path, $expected_pseudo_selector))
496                .filter(|rule_block| crate::style::matches_html_element(
497                    &rule_block.path,
498                    $node_id,
499                    &node_hierarchy.as_container(),
500                    &node_data,
501                    &html_tree,
502                    $expected_pseudo_selector
503                ))
504                // rule matched, now copy all the styles of this rule
505                .flat_map(|matched_rule| {
506                    matched_rule.declarations
507                    .iter()
508                    .filter_map(move |declaration| {
509                        match declaration {
510                            CssDeclaration::Static(s) => Some(s),
511                            CssDeclaration::Dynamic(_d) => None, // TODO: No variable support yet!
512                        }
513                    })
514                })
515                .map(|prop| prop.clone())
516                .collect::<Vec<CssProperty>>()
517            }};}
518
519            // NOTE: This is wrong, but fast
520            //
521            // Get all nodes that end with `:hover`, `:focus` or `:active`
522            // and copy the respective styles to the `hover_css_constraints`, etc. respectively
523            //
524            // NOTE: This won't work correctly for paths with `.blah:hover > #thing`
525            // but that can be fixed later
526
527            // go through each HTML node (in parallel) and see which CSS rules match
528            let css_normal_rules: NodeDataContainer<(NodeId, Vec<CssProperty>)> = node_data
529                .transform_nodeid_multithreaded_optional(|node_id| {
530                    let r = filter_rules!(None, node_id);
531                    if r.is_empty() {
532                        None
533                    } else {
534                        Some((node_id, r))
535                    }
536                });
537
538            let css_hover_rules: NodeDataContainer<(NodeId, Vec<CssProperty>)> = node_data
539                .transform_nodeid_multithreaded_optional(|node_id| {
540                    let r = filter_rules!(Some(Hover), node_id);
541                    if r.is_empty() {
542                        None
543                    } else {
544                        Some((node_id, r))
545                    }
546                });
547
548            let css_active_rules: NodeDataContainer<(NodeId, Vec<CssProperty>)> = node_data
549                .transform_nodeid_multithreaded_optional(|node_id| {
550                    let r = filter_rules!(Some(Active), node_id);
551                    if r.is_empty() {
552                        None
553                    } else {
554                        Some((node_id, r))
555                    }
556                });
557
558            let css_focus_rules: NodeDataContainer<(NodeId, Vec<CssProperty>)> = node_data
559                .transform_nodeid_multithreaded_optional(|node_id| {
560                    let r = filter_rules!(Some(Focus), node_id);
561                    if r.is_empty() {
562                        None
563                    } else {
564                        Some((node_id, r))
565                    }
566                });
567
568            let css_dragging_rules: NodeDataContainer<(NodeId, Vec<CssProperty>)> = node_data
569                .transform_nodeid_multithreaded_optional(|node_id| {
570                    let r = filter_rules!(Some(Dragging), node_id);
571                    if r.is_empty() {
572                        None
573                    } else {
574                        Some((node_id, r))
575                    }
576                });
577
578            let css_drag_over_rules: NodeDataContainer<(NodeId, Vec<CssProperty>)> = node_data
579                .transform_nodeid_multithreaded_optional(|node_id| {
580                    let r = filter_rules!(Some(DragOver), node_id);
581                    if r.is_empty() {
582                        None
583                    } else {
584                        Some((node_id, r))
585                    }
586                });
587
588            // Assign CSS rules to Vec-based storage (indexed by NodeId)
589            macro_rules! assign_css_rules {
590                ($field:expr, $rules:expr) => {{
591                    for entry in $field.iter_mut() { entry.clear(); }
592                    for (n, map) in $rules.internal.into_iter() {
593                        $field[n.index()] = map.into_iter()
594                            .map(|prop| (prop.get_type(), prop))
595                            .collect();
596                    }
597                }};
598            }
599
600            assign_css_rules!(self.css_normal_props, css_normal_rules);
601            assign_css_rules!(self.css_hover_props, css_hover_rules);
602            assign_css_rules!(self.css_active_props, css_active_rules);
603            assign_css_rules!(self.css_focus_props, css_focus_rules);
604            assign_css_rules!(self.css_dragging_props, css_dragging_rules);
605            assign_css_rules!(self.css_drag_over_props, css_drag_over_rules);
606        }
607
608        // Inheritance: Inherit all values of the parent to the children, but
609        // only if the property is inheritable and isn't yet set
610        for ParentWithNodeDepth { depth: _, node_id } in non_leaf_nodes.iter() {
611            let parent_id = match node_id.into_crate_internal() {
612                Some(s) => s,
613                None => continue,
614            };
615
616            // Inherit CSS properties from map A -> map B
617            // map B will be populated with all inherited CSS properties
618            macro_rules! inherit_props {
619                ($from_inherit_map:expr, $to_inherit_map:expr) => {
620                    let parent_map = &$from_inherit_map[parent_id.index()];
621                    if !parent_map.is_empty() {
622                        let parent_inherit_props: Vec<(CssPropertyType, CssProperty)> = parent_map
623                            .iter()
624                            .filter(|(css_prop_type, _)| css_prop_type.is_inheritable())
625                            .map(|(css_prop_type, css_prop)| (*css_prop_type, css_prop.clone()))
626                            .collect();
627
628                        if !parent_inherit_props.is_empty() {
629                            // only override the rule if the child does not already have an
630                            // inherited rule
631                            for child_id in parent_id.az_children(&node_hierarchy.as_container()) {
632                                let child_map = &mut $to_inherit_map[child_id.index()];
633
634                                for (inherited_rule_type, inherited_rule_value) in parent_inherit_props.iter() {
635                                    let _ = child_map
636                                        .entry(*inherited_rule_type)
637                                        .or_insert_with(|| inherited_rule_value.clone());
638                                }
639                            }
640                        }
641                    }
642                };
643            }
644
645            // Same as inherit_props, but filters along the inline node data instead
646            // Uses the new CssPropertyWithConditions system
647            macro_rules! inherit_inline_css_props {($filter_pseudo_state:expr, $to_inherit_map:expr) => {{
648                let parent_inheritable_css_props = &node_data[parent_id]
649                .css_props
650                .iter()
651                // Filter by pseudo state condition
652                .filter(|css_prop| {
653                    // Check if property matches the desired pseudo state
654                    let conditions = css_prop.apply_if.as_slice();
655                    if conditions.is_empty() {
656                        // No conditions = Normal state
657                        $filter_pseudo_state == PseudoStateType::Normal
658                    } else {
659                        // Check if all conditions are the matching pseudo state
660                        conditions.iter().all(|c| {
661                            matches!(c, DynamicSelector::PseudoState(state) if *state == $filter_pseudo_state)
662                        })
663                    }
664                })
665                // Get the inner property
666                .map(|css_prop| &css_prop.property)
667                // test whether the property is inheritable
668                .filter(|css_prop| css_prop.get_type().is_inheritable())
669                .cloned()
670                .collect::<Vec<CssProperty>>();
671
672                if !parent_inheritable_css_props.is_empty() {
673                    // only override the rule if the child does not already have an inherited rule
674                    for child_id in parent_id.az_children(&node_hierarchy.as_container()) {
675                        let child_map = &mut $to_inherit_map[child_id.index()];
676                        for inherited_rule in parent_inheritable_css_props.iter() {
677                            let _ = child_map
678                            .entry(inherited_rule.get_type())
679                            .or_insert_with(|| inherited_rule.clone());
680                        }
681                    }
682                }
683
684            }};}
685
686            use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
687            // strongest inheritance first
688
689            // Inherit inline CSS properties
690            inherit_inline_css_props!(PseudoStateType::Normal, self.cascaded_normal_props);
691            inherit_inline_css_props!(PseudoStateType::Hover, self.cascaded_hover_props);
692            inherit_inline_css_props!(PseudoStateType::Active, self.cascaded_active_props);
693            inherit_inline_css_props!(PseudoStateType::Focus, self.cascaded_focus_props);
694            inherit_inline_css_props!(PseudoStateType::Dragging, self.cascaded_dragging_props);
695            inherit_inline_css_props!(PseudoStateType::DragOver, self.cascaded_drag_over_props);
696
697            // Inherit the CSS properties from the CSS file
698            if !css_is_empty {
699                inherit_props!(self.css_normal_props, self.cascaded_normal_props);
700                inherit_props!(self.css_hover_props, self.cascaded_hover_props);
701                inherit_props!(self.css_active_props, self.cascaded_active_props);
702                inherit_props!(self.css_focus_props, self.cascaded_focus_props);
703                inherit_props!(self.css_dragging_props, self.cascaded_dragging_props);
704                inherit_props!(self.css_drag_over_props, self.cascaded_drag_over_props);
705            }
706
707            // Inherit properties that were inherited in a previous iteration of the loop
708            inherit_props!(self.cascaded_normal_props, self.cascaded_normal_props);
709            inherit_props!(self.cascaded_hover_props, self.cascaded_hover_props);
710            inherit_props!(self.cascaded_active_props, self.cascaded_active_props);
711            inherit_props!(self.cascaded_focus_props, self.cascaded_focus_props);
712            inherit_props!(self.cascaded_dragging_props, self.cascaded_dragging_props);
713            inherit_props!(self.cascaded_drag_over_props, self.cascaded_drag_over_props);
714        }
715
716        // When restyling, the tag / node ID mappings may change, regenerate them
717        // See if the node should have a hit-testing tag ID
718        let default_node_state = StyledNodeState::default();
719
720        // In order to hit-test `:hover` and `:active` selectors,
721        // we need to insert "tag IDs" for all rectangles
722        // that have a non-normal path ending, for example if we have
723        // `#thing:hover`, then all nodes selected by `#thing`
724        // need to get a TagId, otherwise, they can't be hit-tested.
725
726        // NOTE: restyling a DOM may change the :hover nodes, which is
727        // why the tag IDs have to be re-generated on every .restyle() call!
728        
729        // Keep a reference to the node data container for use in the closure
730        let node_data_container = &node_data.internal;
731        
732        node_data
733            .internal
734            .iter()
735            .enumerate()
736            .filter_map(|(node_id, node_data)| {
737                let node_id = NodeId::new(node_id);
738
739                let should_auto_insert_tabindex = node_data
740                    .get_callbacks()
741                    .iter()
742                    .any(|cb| cb.event.is_focus_callback());
743
744                let tab_index = match node_data.get_tab_index() {
745                    Some(s) => Some(*s),
746                    None => {
747                        if should_auto_insert_tabindex {
748                            Some(TabIndex::Auto)
749                        } else {
750                            None
751                        }
752                    }
753                };
754
755                let mut node_should_have_tag = false;
756
757                // workaround for "goto end" - early break if
758                // one of the conditions is true
759                loop {
760                    // check for display: none
761                    let display = self
762                        .get_display(&node_data, &node_id, &default_node_state)
763                        .and_then(|p| p.get_property_or_default())
764                        .unwrap_or_default();
765
766                    if display == LayoutDisplay::None {
767                        node_should_have_tag = false;
768                        break;
769                    }
770
771                    if node_data.has_context_menu() {
772                        node_should_have_tag = true;
773                        break;
774                    }
775
776                    if tab_index.is_some() {
777                        node_should_have_tag = true;
778                        break;
779                    }
780
781                    // check for context menu
782                    if node_data.get_context_menu().is_some() {
783                        node_should_have_tag = true;
784                        break;
785                    }
786
787                    // check for :hover
788                    let node_has_hover_props = {
789                        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
790                        node_data.css_props.as_ref().iter().any(|p| {
791                            p.apply_if.as_slice().iter().any(|c| {
792                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Hover))
793                            })
794                        })
795                    } || self.css_hover_props.get(node_id.index()).map_or(false, |m| !m.is_empty())
796                        || self.cascaded_hover_props.get(node_id.index()).map_or(false, |m| !m.is_empty());
797
798                    if node_has_hover_props {
799                        node_should_have_tag = true;
800                        break;
801                    }
802
803                    // check for :active
804                    let node_has_active_props = {
805                        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
806                        node_data.css_props.as_ref().iter().any(|p| {
807                            p.apply_if.as_slice().iter().any(|c| {
808                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Active))
809                            })
810                        })
811                    } || self.css_active_props.get(node_id.index()).map_or(false, |m| !m.is_empty())
812                        || self.cascaded_active_props.get(node_id.index()).map_or(false, |m| !m.is_empty());
813
814                    if node_has_active_props {
815                        node_should_have_tag = true;
816                        break;
817                    }
818
819                    // check for :focus
820                    let node_has_focus_props = {
821                        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
822                        node_data.css_props.as_ref().iter().any(|p| {
823                            p.apply_if.as_slice().iter().any(|c| {
824                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Focus))
825                            })
826                        })
827                    } || self.css_focus_props.get(node_id.index()).map_or(false, |m| !m.is_empty())
828                        || self.cascaded_focus_props.get(node_id.index()).map_or(false, |m| !m.is_empty());
829
830                    if node_has_focus_props {
831                        node_should_have_tag = true;
832                        break;
833                    }
834
835                    // check for :dragging
836                    let node_has_dragging_props = {
837                        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
838                        node_data.css_props.as_ref().iter().any(|p| {
839                            p.apply_if.as_slice().iter().any(|c| {
840                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::Dragging))
841                            })
842                        })
843                    } || self.css_dragging_props.get(node_id.index()).map_or(false, |m| !m.is_empty())
844                        || self.cascaded_dragging_props.get(node_id.index()).map_or(false, |m| !m.is_empty());
845
846                    if node_has_dragging_props {
847                        node_should_have_tag = true;
848                        break;
849                    }
850
851                    // check for :drag-over
852                    let node_has_drag_over_props = {
853                        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
854                        node_data.css_props.as_ref().iter().any(|p| {
855                            p.apply_if.as_slice().iter().any(|c| {
856                                matches!(c, DynamicSelector::PseudoState(PseudoStateType::DragOver))
857                            })
858                        })
859                    } || self.css_drag_over_props.get(node_id.index()).map_or(false, |m| !m.is_empty())
860                        || self.cascaded_drag_over_props.get(node_id.index()).map_or(false, |m| !m.is_empty());
861
862                    if node_has_drag_over_props {
863                        node_should_have_tag = true;
864                        break;
865                    }
866
867                    // check whether any Hover(), Active() or Focus() callbacks are present
868                    let node_only_window_callbacks = node_data.get_callbacks().is_empty()
869                        || node_data
870                            .get_callbacks()
871                            .iter()
872                            .all(|cb| cb.event.is_window_callback());
873
874                    if !node_only_window_callbacks {
875                        node_should_have_tag = true;
876                        break;
877                    }
878
879                    // check for non-default cursor: property - needed for hit-testing cursor
880                    let node_has_non_default_cursor = self
881                        .get_cursor(&node_data, &node_id, &default_node_state)
882                        .is_some();
883
884                    if node_has_non_default_cursor {
885                        node_should_have_tag = true;
886                        break;
887                    }
888
889                    // check for overflow: scroll or overflow: auto - needed for scroll hit-testing
890                    // Nodes with these overflow values need hit-test tags so that
891                    // scroll wheel events can be correctly assigned to scrollable containers
892                    let node_has_overflow_scroll = {
893                        use azul_css::props::layout::LayoutOverflow;
894                        let overflow_x = self
895                            .get_overflow_x(&node_data, &node_id, &default_node_state)
896                            .and_then(|p| p.get_property_or_default());
897                        let overflow_y = self
898                            .get_overflow_y(&node_data, &node_id, &default_node_state)
899                            .and_then(|p| p.get_property_or_default());
900
901                        let x_scrollable = matches!(
902                            overflow_x,
903                            Some(LayoutOverflow::Scroll | LayoutOverflow::Auto)
904                        );
905                        let y_scrollable = matches!(
906                            overflow_y,
907                            Some(LayoutOverflow::Scroll | LayoutOverflow::Auto)
908                        );
909                        x_scrollable || y_scrollable
910                    };
911
912                    if node_has_overflow_scroll {
913                        node_should_have_tag = true;
914                        break;
915                    }
916
917                    // Check for selectable text - nodes that contain text children and
918                    // user-select != none need hit-test tags for text selection support.
919                    // The cursor resolution algorithm in CursorTypeHitTest ensures that
920                    // explicit cursor properties (e.g., cursor:pointer on button) take
921                    // precedence over the implicit I-beam from text children.
922                    let node_has_selectable_text = {
923                        use azul_css::props::style::StyleUserSelect;
924                        use crate::dom::NodeType;
925                        
926                        // Check if this node has immediate text children
927                        let has_text_children = {
928                            let hier = node_hierarchy.as_container()[node_id];
929                            let mut has_text = false;
930                            if let Some(first_child) = hier.first_child_id(node_id) {
931                                let mut child_id = Some(first_child);
932                                while let Some(cid) = child_id {
933                                    let child_data = &node_data_container[cid.index()];
934                                    if matches!(child_data.get_node_type(), NodeType::Text(_)) {
935                                        has_text = true;
936                                        break;
937                                    }
938                                    child_id = node_hierarchy.as_container()[cid].next_sibling_id();
939                                }
940                            }
941                            has_text
942                        };
943                        
944                        if has_text_children {
945                            // Check user-select property on this container (default is selectable)
946                            let user_select = self
947                                .get_user_select(&node_data, &node_id, &default_node_state)
948                                .and_then(|p| p.get_property().cloned())
949                                .unwrap_or(StyleUserSelect::Auto);
950                            
951                            !matches!(user_select, StyleUserSelect::None)
952                        } else {
953                            false
954                        }
955                    };
956
957                    if node_has_selectable_text {
958                        node_should_have_tag = true;
959                        break;
960                    }
961
962                    break;
963                }
964
965                if !node_should_have_tag {
966                    None
967                } else {
968                    Some(TagIdToNodeIdMapping {
969                        tag_id: TagId::from_crate_internal(TagId::unique()),
970                        node_id: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
971                        tab_index: tab_index.into(),
972                    })
973                }
974            })
975            .collect()
976    }
977
978    pub fn get_computed_css_style_string(
979        &self,
980        node_data: &NodeData,
981        node_id: &NodeId,
982        node_state: &StyledNodeState,
983    ) -> String {
984        let mut s = String::new();
985        if let Some(p) = self.get_background_content(&node_data, node_id, node_state) {
986            s.push_str(&format!("background: {};", p.get_css_value_fmt()));
987        }
988        if let Some(p) = self.get_background_position(&node_data, node_id, node_state) {
989            s.push_str(&format!("background-position: {};", p.get_css_value_fmt()));
990        }
991        if let Some(p) = self.get_background_size(&node_data, node_id, node_state) {
992            s.push_str(&format!("background-size: {};", p.get_css_value_fmt()));
993        }
994        if let Some(p) = self.get_background_repeat(&node_data, node_id, node_state) {
995            s.push_str(&format!("background-repeat: {};", p.get_css_value_fmt()));
996        }
997        if let Some(p) = self.get_font_size(&node_data, node_id, node_state) {
998            s.push_str(&format!("font-size: {};", p.get_css_value_fmt()));
999        }
1000        if let Some(p) = self.get_font_family(&node_data, node_id, node_state) {
1001            s.push_str(&format!("font-family: {};", p.get_css_value_fmt()));
1002        }
1003        if let Some(p) = self.get_text_color(&node_data, node_id, node_state) {
1004            s.push_str(&format!("color: {};", p.get_css_value_fmt()));
1005        }
1006        if let Some(p) = self.get_text_align(&node_data, node_id, node_state) {
1007            s.push_str(&format!("text-align: {};", p.get_css_value_fmt()));
1008        }
1009        if let Some(p) = self.get_line_height(&node_data, node_id, node_state) {
1010            s.push_str(&format!("line-height: {};", p.get_css_value_fmt()));
1011        }
1012        if let Some(p) = self.get_letter_spacing(&node_data, node_id, node_state) {
1013            s.push_str(&format!("letter-spacing: {};", p.get_css_value_fmt()));
1014        }
1015        if let Some(p) = self.get_word_spacing(&node_data, node_id, node_state) {
1016            s.push_str(&format!("word-spacing: {};", p.get_css_value_fmt()));
1017        }
1018        if let Some(p) = self.get_tab_size(&node_data, node_id, node_state) {
1019            s.push_str(&format!("tab-size: {};", p.get_css_value_fmt()));
1020        }
1021        if let Some(p) = self.get_cursor(&node_data, node_id, node_state) {
1022            s.push_str(&format!("cursor: {};", p.get_css_value_fmt()));
1023        }
1024        if let Some(p) = self.get_box_shadow_left(&node_data, node_id, node_state) {
1025            s.push_str(&format!(
1026                "-azul-box-shadow-left: {};",
1027                p.get_css_value_fmt()
1028            ));
1029        }
1030        if let Some(p) = self.get_box_shadow_right(&node_data, node_id, node_state) {
1031            s.push_str(&format!(
1032                "-azul-box-shadow-right: {};",
1033                p.get_css_value_fmt()
1034            ));
1035        }
1036        if let Some(p) = self.get_box_shadow_top(&node_data, node_id, node_state) {
1037            s.push_str(&format!("-azul-box-shadow-top: {};", p.get_css_value_fmt()));
1038        }
1039        if let Some(p) = self.get_box_shadow_bottom(&node_data, node_id, node_state) {
1040            s.push_str(&format!(
1041                "-azul-box-shadow-bottom: {};",
1042                p.get_css_value_fmt()
1043            ));
1044        }
1045        if let Some(p) = self.get_border_top_color(&node_data, node_id, node_state) {
1046            s.push_str(&format!("border-top-color: {};", p.get_css_value_fmt()));
1047        }
1048        if let Some(p) = self.get_border_left_color(&node_data, node_id, node_state) {
1049            s.push_str(&format!("border-left-color: {};", p.get_css_value_fmt()));
1050        }
1051        if let Some(p) = self.get_border_right_color(&node_data, node_id, node_state) {
1052            s.push_str(&format!("border-right-color: {};", p.get_css_value_fmt()));
1053        }
1054        if let Some(p) = self.get_border_bottom_color(&node_data, node_id, node_state) {
1055            s.push_str(&format!("border-bottom-color: {};", p.get_css_value_fmt()));
1056        }
1057        if let Some(p) = self.get_border_top_style(&node_data, node_id, node_state) {
1058            s.push_str(&format!("border-top-style: {};", p.get_css_value_fmt()));
1059        }
1060        if let Some(p) = self.get_border_left_style(&node_data, node_id, node_state) {
1061            s.push_str(&format!("border-left-style: {};", p.get_css_value_fmt()));
1062        }
1063        if let Some(p) = self.get_border_right_style(&node_data, node_id, node_state) {
1064            s.push_str(&format!("border-right-style: {};", p.get_css_value_fmt()));
1065        }
1066        if let Some(p) = self.get_border_bottom_style(&node_data, node_id, node_state) {
1067            s.push_str(&format!("border-bottom-style: {};", p.get_css_value_fmt()));
1068        }
1069        if let Some(p) = self.get_border_top_left_radius(&node_data, node_id, node_state) {
1070            s.push_str(&format!(
1071                "border-top-left-radius: {};",
1072                p.get_css_value_fmt()
1073            ));
1074        }
1075        if let Some(p) = self.get_border_top_right_radius(&node_data, node_id, node_state) {
1076            s.push_str(&format!(
1077                "border-top-right-radius: {};",
1078                p.get_css_value_fmt()
1079            ));
1080        }
1081        if let Some(p) = self.get_border_bottom_left_radius(&node_data, node_id, node_state) {
1082            s.push_str(&format!(
1083                "border-bottom-left-radius: {};",
1084                p.get_css_value_fmt()
1085            ));
1086        }
1087        if let Some(p) = self.get_border_bottom_right_radius(&node_data, node_id, node_state) {
1088            s.push_str(&format!(
1089                "border-bottom-right-radius: {};",
1090                p.get_css_value_fmt()
1091            ));
1092        }
1093        if let Some(p) = self.get_opacity(&node_data, node_id, node_state) {
1094            s.push_str(&format!("opacity: {};", p.get_css_value_fmt()));
1095        }
1096        if let Some(p) = self.get_transform(&node_data, node_id, node_state) {
1097            s.push_str(&format!("transform: {};", p.get_css_value_fmt()));
1098        }
1099        if let Some(p) = self.get_transform_origin(&node_data, node_id, node_state) {
1100            s.push_str(&format!("transform-origin: {};", p.get_css_value_fmt()));
1101        }
1102        if let Some(p) = self.get_perspective_origin(&node_data, node_id, node_state) {
1103            s.push_str(&format!("perspective-origin: {};", p.get_css_value_fmt()));
1104        }
1105        if let Some(p) = self.get_backface_visibility(&node_data, node_id, node_state) {
1106            s.push_str(&format!("backface-visibility: {};", p.get_css_value_fmt()));
1107        }
1108        if let Some(p) = self.get_hyphens(&node_data, node_id, node_state) {
1109            s.push_str(&format!("hyphens: {};", p.get_css_value_fmt()));
1110        }
1111        if let Some(p) = self.get_direction(&node_data, node_id, node_state) {
1112            s.push_str(&format!("direction: {};", p.get_css_value_fmt()));
1113        }
1114        if let Some(p) = self.get_white_space(&node_data, node_id, node_state) {
1115            s.push_str(&format!("white-space: {};", p.get_css_value_fmt()));
1116        }
1117        if let Some(p) = self.get_display(&node_data, node_id, node_state) {
1118            s.push_str(&format!("display: {};", p.get_css_value_fmt()));
1119        }
1120        if let Some(p) = self.get_float(&node_data, node_id, node_state) {
1121            s.push_str(&format!("float: {};", p.get_css_value_fmt()));
1122        }
1123        if let Some(p) = self.get_box_sizing(&node_data, node_id, node_state) {
1124            s.push_str(&format!("box-sizing: {};", p.get_css_value_fmt()));
1125        }
1126        if let Some(p) = self.get_width(&node_data, node_id, node_state) {
1127            s.push_str(&format!("width: {};", p.get_css_value_fmt()));
1128        }
1129        if let Some(p) = self.get_height(&node_data, node_id, node_state) {
1130            s.push_str(&format!("height: {};", p.get_css_value_fmt()));
1131        }
1132        if let Some(p) = self.get_min_width(&node_data, node_id, node_state) {
1133            s.push_str(&format!("min-width: {};", p.get_css_value_fmt()));
1134        }
1135        if let Some(p) = self.get_min_height(&node_data, node_id, node_state) {
1136            s.push_str(&format!("min-height: {};", p.get_css_value_fmt()));
1137        }
1138        if let Some(p) = self.get_max_width(&node_data, node_id, node_state) {
1139            s.push_str(&format!("max-width: {};", p.get_css_value_fmt()));
1140        }
1141        if let Some(p) = self.get_max_height(&node_data, node_id, node_state) {
1142            s.push_str(&format!("max-height: {};", p.get_css_value_fmt()));
1143        }
1144        if let Some(p) = self.get_position(&node_data, node_id, node_state) {
1145            s.push_str(&format!("position: {};", p.get_css_value_fmt()));
1146        }
1147        if let Some(p) = self.get_top(&node_data, node_id, node_state) {
1148            s.push_str(&format!("top: {};", p.get_css_value_fmt()));
1149        }
1150        if let Some(p) = self.get_bottom(&node_data, node_id, node_state) {
1151            s.push_str(&format!("bottom: {};", p.get_css_value_fmt()));
1152        }
1153        if let Some(p) = self.get_right(&node_data, node_id, node_state) {
1154            s.push_str(&format!("right: {};", p.get_css_value_fmt()));
1155        }
1156        if let Some(p) = self.get_left(&node_data, node_id, node_state) {
1157            s.push_str(&format!("left: {};", p.get_css_value_fmt()));
1158        }
1159        if let Some(p) = self.get_padding_top(&node_data, node_id, node_state) {
1160            s.push_str(&format!("padding-top: {};", p.get_css_value_fmt()));
1161        }
1162        if let Some(p) = self.get_padding_bottom(&node_data, node_id, node_state) {
1163            s.push_str(&format!("padding-bottom: {};", p.get_css_value_fmt()));
1164        }
1165        if let Some(p) = self.get_padding_left(&node_data, node_id, node_state) {
1166            s.push_str(&format!("padding-left: {};", p.get_css_value_fmt()));
1167        }
1168        if let Some(p) = self.get_padding_right(&node_data, node_id, node_state) {
1169            s.push_str(&format!("padding-right: {};", p.get_css_value_fmt()));
1170        }
1171        if let Some(p) = self.get_margin_top(&node_data, node_id, node_state) {
1172            s.push_str(&format!("margin-top: {};", p.get_css_value_fmt()));
1173        }
1174        if let Some(p) = self.get_margin_bottom(&node_data, node_id, node_state) {
1175            s.push_str(&format!("margin-bottom: {};", p.get_css_value_fmt()));
1176        }
1177        if let Some(p) = self.get_margin_left(&node_data, node_id, node_state) {
1178            s.push_str(&format!("margin-left: {};", p.get_css_value_fmt()));
1179        }
1180        if let Some(p) = self.get_margin_right(&node_data, node_id, node_state) {
1181            s.push_str(&format!("margin-right: {};", p.get_css_value_fmt()));
1182        }
1183        if let Some(p) = self.get_border_top_width(&node_data, node_id, node_state) {
1184            s.push_str(&format!("border-top-width: {};", p.get_css_value_fmt()));
1185        }
1186        if let Some(p) = self.get_border_left_width(&node_data, node_id, node_state) {
1187            s.push_str(&format!("border-left-width: {};", p.get_css_value_fmt()));
1188        }
1189        if let Some(p) = self.get_border_right_width(&node_data, node_id, node_state) {
1190            s.push_str(&format!("border-right-width: {};", p.get_css_value_fmt()));
1191        }
1192        if let Some(p) = self.get_border_bottom_width(&node_data, node_id, node_state) {
1193            s.push_str(&format!("border-bottom-width: {};", p.get_css_value_fmt()));
1194        }
1195        if let Some(p) = self.get_overflow_x(&node_data, node_id, node_state) {
1196            s.push_str(&format!("overflow-x: {};", p.get_css_value_fmt()));
1197        }
1198        if let Some(p) = self.get_overflow_y(&node_data, node_id, node_state) {
1199            s.push_str(&format!("overflow-y: {};", p.get_css_value_fmt()));
1200        }
1201        if let Some(p) = self.get_flex_direction(&node_data, node_id, node_state) {
1202            s.push_str(&format!("flex-direction: {};", p.get_css_value_fmt()));
1203        }
1204        if let Some(p) = self.get_flex_wrap(&node_data, node_id, node_state) {
1205            s.push_str(&format!("flex-wrap: {};", p.get_css_value_fmt()));
1206        }
1207        if let Some(p) = self.get_flex_grow(&node_data, node_id, node_state) {
1208            s.push_str(&format!("flex-grow: {};", p.get_css_value_fmt()));
1209        }
1210        if let Some(p) = self.get_flex_shrink(&node_data, node_id, node_state) {
1211            s.push_str(&format!("flex-shrink: {};", p.get_css_value_fmt()));
1212        }
1213        if let Some(p) = self.get_justify_content(&node_data, node_id, node_state) {
1214            s.push_str(&format!("justify-content: {};", p.get_css_value_fmt()));
1215        }
1216        if let Some(p) = self.get_align_items(&node_data, node_id, node_state) {
1217            s.push_str(&format!("align-items: {};", p.get_css_value_fmt()));
1218        }
1219        if let Some(p) = self.get_align_content(&node_data, node_id, node_state) {
1220            s.push_str(&format!("align-content: {};", p.get_css_value_fmt()));
1221        }
1222        s
1223    }
1224}
1225
1226#[repr(C)]
1227#[derive(Debug, PartialEq, Clone)]
1228pub struct CssPropertyCachePtr {
1229    pub ptr: Box<CssPropertyCache>,
1230    pub run_destructor: bool,
1231}
1232
1233impl CssPropertyCachePtr {
1234    pub fn new(cache: CssPropertyCache) -> Self {
1235        Self {
1236            ptr: Box::new(cache),
1237            run_destructor: true,
1238        }
1239    }
1240    pub fn downcast_mut<'a>(&'a mut self) -> &'a mut CssPropertyCache {
1241        &mut *self.ptr
1242    }
1243}
1244
1245impl Drop for CssPropertyCachePtr {
1246    fn drop(&mut self) {
1247        self.run_destructor = false;
1248    }
1249}
1250
1251impl CssPropertyCache {
1252    pub fn empty(node_count: usize) -> Self {
1253        Self {
1254            node_count,
1255            user_overridden_properties: vec![BTreeMap::new(); node_count],
1256
1257            cascaded_normal_props: vec![BTreeMap::new(); node_count],
1258            cascaded_hover_props: vec![BTreeMap::new(); node_count],
1259            cascaded_active_props: vec![BTreeMap::new(); node_count],
1260            cascaded_focus_props: vec![BTreeMap::new(); node_count],
1261            cascaded_dragging_props: vec![BTreeMap::new(); node_count],
1262            cascaded_drag_over_props: vec![BTreeMap::new(); node_count],
1263
1264            css_normal_props: vec![BTreeMap::new(); node_count],
1265            css_hover_props: vec![BTreeMap::new(); node_count],
1266            css_active_props: vec![BTreeMap::new(); node_count],
1267            css_focus_props: vec![BTreeMap::new(); node_count],
1268            css_dragging_props: vec![BTreeMap::new(); node_count],
1269            css_drag_over_props: vec![BTreeMap::new(); node_count],
1270
1271            computed_values: vec![BTreeMap::new(); node_count],
1272            dependency_chains: vec![BTreeMap::new(); node_count],
1273            resolved_cache: Vec::new(),
1274            compact_cache: None,
1275        }
1276    }
1277
1278    pub fn append(&mut self, other: &mut Self) {
1279        macro_rules! append_css_property_vec {
1280            ($field_name:ident) => {{
1281                self.$field_name.extend(other.$field_name.drain(..));
1282            }};
1283        }
1284
1285        append_css_property_vec!(user_overridden_properties);
1286        append_css_property_vec!(cascaded_normal_props);
1287        append_css_property_vec!(cascaded_hover_props);
1288        append_css_property_vec!(cascaded_active_props);
1289        append_css_property_vec!(cascaded_focus_props);
1290        append_css_property_vec!(cascaded_dragging_props);
1291        append_css_property_vec!(cascaded_drag_over_props);
1292        append_css_property_vec!(css_normal_props);
1293        append_css_property_vec!(css_hover_props);
1294        append_css_property_vec!(css_active_props);
1295        append_css_property_vec!(css_focus_props);
1296        append_css_property_vec!(css_dragging_props);
1297        append_css_property_vec!(css_drag_over_props);
1298        append_css_property_vec!(computed_values);
1299
1300        // Special handling for dependency_chains: need to adjust source_node IDs
1301        for mut chains_map in other.dependency_chains.drain(..) {
1302            for (_prop_type, chain) in chains_map.iter_mut() {
1303                for step in chain.steps.iter_mut() {
1304                    match step {
1305                        CssDependencyChainStep::Em { source_node, .. } => {
1306                            *source_node = NodeId::new(source_node.index() + self.node_count);
1307                        }
1308                        CssDependencyChainStep::Percent { source_node, .. } => {
1309                            *source_node = NodeId::new(source_node.index() + self.node_count);
1310                        }
1311                        _ => {}
1312                    }
1313                }
1314            }
1315            self.dependency_chains.push(chains_map);
1316        }
1317
1318        self.node_count += other.node_count;
1319
1320        // Invalidate resolved cache since node IDs shifted
1321        self.resolved_cache.clear();
1322    }
1323
1324    pub fn is_horizontal_overflow_visible(
1325        &self,
1326        node_data: &NodeData,
1327        node_id: &NodeId,
1328        node_state: &StyledNodeState,
1329    ) -> bool {
1330        self.get_overflow_x(node_data, node_id, node_state)
1331            .and_then(|p| p.get_property_or_default())
1332            .unwrap_or_default()
1333            .is_overflow_visible()
1334    }
1335
1336    pub fn is_vertical_overflow_visible(
1337        &self,
1338        node_data: &NodeData,
1339        node_id: &NodeId,
1340        node_state: &StyledNodeState,
1341    ) -> bool {
1342        self.get_overflow_y(node_data, node_id, node_state)
1343            .and_then(|p| p.get_property_or_default())
1344            .unwrap_or_default()
1345            .is_overflow_visible()
1346    }
1347
1348    pub fn is_horizontal_overflow_hidden(
1349        &self,
1350        node_data: &NodeData,
1351        node_id: &NodeId,
1352        node_state: &StyledNodeState,
1353    ) -> bool {
1354        self.get_overflow_x(node_data, node_id, node_state)
1355            .and_then(|p| p.get_property_or_default())
1356            .unwrap_or_default()
1357            .is_overflow_hidden()
1358    }
1359
1360    pub fn is_vertical_overflow_hidden(
1361        &self,
1362        node_data: &NodeData,
1363        node_id: &NodeId,
1364        node_state: &StyledNodeState,
1365    ) -> bool {
1366        self.get_overflow_y(node_data, node_id, node_state)
1367            .and_then(|p| p.get_property_or_default())
1368            .unwrap_or_default()
1369            .is_overflow_hidden()
1370    }
1371
1372    pub fn get_text_color_or_default(
1373        &self,
1374        node_data: &NodeData,
1375        node_id: &NodeId,
1376        node_state: &StyledNodeState,
1377    ) -> StyleTextColor {
1378        use crate::ui_solver::DEFAULT_TEXT_COLOR;
1379        self.get_text_color(node_data, node_id, node_state)
1380            .and_then(|fs| fs.get_property().cloned())
1381            .unwrap_or(DEFAULT_TEXT_COLOR)
1382    }
1383
1384    /// Returns the font ID of the
1385    pub fn get_font_id_or_default(
1386        &self,
1387        node_data: &NodeData,
1388        node_id: &NodeId,
1389        node_state: &StyledNodeState,
1390    ) -> StyleFontFamilyVec {
1391        use crate::ui_solver::DEFAULT_FONT_ID;
1392        let default_font_id = vec![StyleFontFamily::System(AzString::from_const_str(
1393            DEFAULT_FONT_ID,
1394        ))]
1395        .into();
1396        let font_family_opt = self.get_font_family(node_data, node_id, node_state);
1397
1398        font_family_opt
1399            .as_ref()
1400            .and_then(|family| Some(family.get_property()?.clone()))
1401            .unwrap_or(default_font_id)
1402    }
1403
1404    pub fn get_font_size_or_default(
1405        &self,
1406        node_data: &NodeData,
1407        node_id: &NodeId,
1408        node_state: &StyledNodeState,
1409    ) -> StyleFontSize {
1410        use crate::ui_solver::DEFAULT_FONT_SIZE;
1411        self.get_font_size(node_data, node_id, node_state)
1412            .and_then(|fs| fs.get_property().cloned())
1413            .unwrap_or(DEFAULT_FONT_SIZE)
1414    }
1415
1416    pub fn has_border(
1417        &self,
1418        node_data: &NodeData,
1419        node_id: &NodeId,
1420        node_state: &StyledNodeState,
1421    ) -> bool {
1422        self.get_border_left_width(node_data, node_id, node_state)
1423            .is_some()
1424            || self
1425                .get_border_right_width(node_data, node_id, node_state)
1426                .is_some()
1427            || self
1428                .get_border_top_width(node_data, node_id, node_state)
1429                .is_some()
1430            || self
1431                .get_border_bottom_width(node_data, node_id, node_state)
1432                .is_some()
1433    }
1434
1435    pub fn has_box_shadow(
1436        &self,
1437        node_data: &NodeData,
1438        node_id: &NodeId,
1439        node_state: &StyledNodeState,
1440    ) -> bool {
1441        self.get_box_shadow_left(node_data, node_id, node_state)
1442            .is_some()
1443            || self
1444                .get_box_shadow_right(node_data, node_id, node_state)
1445                .is_some()
1446            || self
1447                .get_box_shadow_top(node_data, node_id, node_state)
1448                .is_some()
1449            || self
1450                .get_box_shadow_bottom(node_data, node_id, node_state)
1451                .is_some()
1452    }
1453
1454    pub fn get_property<'a>(
1455        &'a self,
1456        node_data: &'a NodeData,
1457        node_id: &NodeId,
1458        node_state: &StyledNodeState,
1459        css_property_type: &CssPropertyType,
1460    ) -> Option<&CssProperty> {
1461        // Fast path: use pre-resolved cache if available (O(1) + O(log m) vs O(log n) × 18)
1462        if let Some(node_props) = self.resolved_cache.get(node_id.index()) {
1463            return node_props
1464                .binary_search_by_key(css_property_type, |(t, _)| *t)
1465                .ok()
1466                .map(|i| &node_props[i].1);
1467        }
1468
1469        // Slow path: full cascade resolution
1470        self.get_property_slow(node_data, node_id, node_state, css_property_type)
1471    }
1472
1473    /// Full cascade resolution without using the resolved cache.
1474    /// Used internally by build_resolved_cache() and as fallback when cache is not built.
1475    fn get_property_slow<'a>(
1476        &'a self,
1477        node_data: &'a NodeData,
1478        node_id: &NodeId,
1479        node_state: &StyledNodeState,
1480        css_property_type: &CssPropertyType,
1481    ) -> Option<&CssProperty> {
1482
1483        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
1484
1485        // First test if there is some user-defined override for the property
1486        if let Some(p) = self
1487            .user_overridden_properties
1488            .get(node_id.index())
1489            .and_then(|n| n.get(css_property_type))
1490        {
1491            return Some(p);
1492        }
1493
1494        // Helper to check if property matches a specific pseudo state
1495        fn matches_pseudo_state(
1496            prop: &azul_css::dynamic_selector::CssPropertyWithConditions,
1497            state: PseudoStateType,
1498        ) -> bool {
1499            let conditions = prop.apply_if.as_slice();
1500            if conditions.is_empty() {
1501                state == PseudoStateType::Normal
1502            } else {
1503                conditions
1504                    .iter()
1505                    .all(|c| matches!(c, DynamicSelector::PseudoState(s) if *s == state))
1506            }
1507        }
1508
1509        // If that fails, see if there is an inline CSS property that matches
1510        // :focus > :active > :hover > normal (fallback)
1511        if node_state.focused {
1512            // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
1513            if let Some(p) = node_data.css_props.as_ref().iter().find_map(|css_prop| {
1514                if matches_pseudo_state(css_prop, PseudoStateType::Focus)
1515                    && css_prop.property.get_type() == *css_property_type
1516                {
1517                    Some(&css_prop.property)
1518                } else {
1519                    None
1520                }
1521            }) {
1522                return Some(p);
1523            }
1524
1525            // PRIORITY 2: CSS stylesheet properties
1526            if let Some(p) = self
1527                .css_focus_props
1528                .get(node_id.index())
1529                .and_then(|map| map.get(css_property_type))
1530            {
1531                return Some(p);
1532            }
1533
1534            // PRIORITY 3: Cascaded/inherited properties
1535            if let Some(p) = self
1536                .cascaded_focus_props
1537                .get(node_id.index())
1538                .and_then(|map| map.get(css_property_type))
1539            {
1540                return Some(p);
1541            }
1542        }
1543
1544        if node_state.active {
1545            // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
1546            if let Some(p) = node_data.css_props.as_ref().iter().find_map(|css_prop| {
1547                if matches_pseudo_state(css_prop, PseudoStateType::Active)
1548                    && css_prop.property.get_type() == *css_property_type
1549                {
1550                    Some(&css_prop.property)
1551                } else {
1552                    None
1553                }
1554            }) {
1555                return Some(p);
1556            }
1557
1558            // PRIORITY 2: CSS stylesheet properties
1559            if let Some(p) = self
1560                .css_active_props
1561                .get(node_id.index())
1562                .and_then(|map| map.get(css_property_type))
1563            {
1564                return Some(p);
1565            }
1566
1567            // PRIORITY 3: Cascaded/inherited properties
1568            if let Some(p) = self
1569                .cascaded_active_props
1570                .get(node_id.index())
1571                .and_then(|map| map.get(css_property_type))
1572            {
1573                return Some(p);
1574            }
1575        }
1576
1577        // :dragging pseudo-state (higher priority than :hover)
1578        if node_state.dragging {
1579            if let Some(p) = node_data.css_props.as_ref().iter().find_map(|css_prop| {
1580                if matches_pseudo_state(css_prop, PseudoStateType::Dragging)
1581                    && css_prop.property.get_type() == *css_property_type
1582                {
1583                    Some(&css_prop.property)
1584                } else {
1585                    None
1586                }
1587            }) {
1588                return Some(p);
1589            }
1590
1591            if let Some(p) = self
1592                .css_dragging_props
1593                .get(node_id.index())
1594                .and_then(|map| map.get(css_property_type))
1595            {
1596                return Some(p);
1597            }
1598
1599            if let Some(p) = self
1600                .cascaded_dragging_props
1601                .get(node_id.index())
1602                .and_then(|map| map.get(css_property_type))
1603            {
1604                return Some(p);
1605            }
1606        }
1607
1608        // :drag-over pseudo-state (higher priority than :hover)
1609        if node_state.drag_over {
1610            if let Some(p) = node_data.css_props.as_ref().iter().find_map(|css_prop| {
1611                if matches_pseudo_state(css_prop, PseudoStateType::DragOver)
1612                    && css_prop.property.get_type() == *css_property_type
1613                {
1614                    Some(&css_prop.property)
1615                } else {
1616                    None
1617                }
1618            }) {
1619                return Some(p);
1620            }
1621
1622            if let Some(p) = self
1623                .css_drag_over_props
1624                .get(node_id.index())
1625                .and_then(|map| map.get(css_property_type))
1626            {
1627                return Some(p);
1628            }
1629
1630            if let Some(p) = self
1631                .cascaded_drag_over_props
1632                .get(node_id.index())
1633                .and_then(|map| map.get(css_property_type))
1634            {
1635                return Some(p);
1636            }
1637        }
1638
1639        if node_state.hover {
1640            // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
1641            if let Some(p) = node_data.css_props.as_ref().iter().find_map(|css_prop| {
1642                if matches_pseudo_state(css_prop, PseudoStateType::Hover)
1643                    && css_prop.property.get_type() == *css_property_type
1644                {
1645                    Some(&css_prop.property)
1646                } else {
1647                    None
1648                }
1649            }) {
1650                return Some(p);
1651            }
1652
1653            // PRIORITY 2: CSS stylesheet properties
1654            if let Some(p) = self
1655                .css_hover_props
1656                .get(node_id.index())
1657                .and_then(|map| map.get(css_property_type))
1658            {
1659                return Some(p);
1660            }
1661
1662            // PRIORITY 3: Cascaded/inherited properties
1663            if let Some(p) = self
1664                .cascaded_hover_props
1665                .get(node_id.index())
1666                .and_then(|map| map.get(css_property_type))
1667            {
1668                return Some(p);
1669            }
1670        }
1671
1672        // Normal/fallback properties - always apply as base layer
1673        // PRIORITY 1: Inline CSS properties (highest priority per CSS spec)
1674        if let Some(p) = node_data.css_props.as_ref().iter().find_map(|css_prop| {
1675            if matches_pseudo_state(css_prop, PseudoStateType::Normal)
1676                && css_prop.property.get_type() == *css_property_type
1677            {
1678                Some(&css_prop.property)
1679            } else {
1680                None
1681            }
1682        }) {
1683            return Some(p);
1684        }
1685
1686        // PRIORITY 2: CSS stylesheet properties
1687        if let Some(p) = self
1688            .css_normal_props
1689            .get(node_id.index())
1690            .and_then(|map| map.get(css_property_type))
1691        {
1692            return Some(p);
1693        }
1694
1695        // PRIORITY 3: Cascaded/inherited properties
1696        if let Some(p) = self
1697            .cascaded_normal_props
1698            .get(node_id.index())
1699            .and_then(|map| map.get(css_property_type))
1700        {
1701            return Some(p);
1702        }
1703
1704        // NEW: Check computed values cache for inherited properties
1705        // This provides efficient access to pre-resolved inherited values
1706        // without needing to walk up the tree
1707        if css_property_type.is_inheritable() {
1708            if let Some(prop_with_origin) = self
1709                .computed_values
1710                .get(node_id.index())
1711                .and_then(|map| map.get(css_property_type))
1712            {
1713                return Some(&prop_with_origin.property);
1714            }
1715        }
1716
1717        // User-agent stylesheet fallback (lowest precedence)
1718        // Check if the node type has a default value for this property
1719        crate::ua_css::get_ua_property(&node_data.node_type, *css_property_type)
1720    }
1721
1722    /// Get a CSS property using DynamicSelectorContext for evaluation.
1723    ///
1724    /// This is the new API that supports @media queries, @container queries,
1725    /// OS-specific styles, and all pseudo-states via `CssPropertyWithConditions`.
1726    ///
1727    /// The evaluation follows "last wins" semantics - properties are evaluated
1728    /// in reverse order and the first matching property wins.
1729    pub fn get_property_with_context<'a>(
1730        &'a self,
1731        node_data: &'a NodeData,
1732        node_id: &NodeId,
1733        context: &DynamicSelectorContext,
1734        css_property_type: &CssPropertyType,
1735    ) -> Option<&CssProperty> {
1736        // First test if there is some user-defined override for the property
1737        if let Some(p) = self
1738            .user_overridden_properties
1739            .get(node_id.index())
1740            .and_then(|n| n.get(css_property_type))
1741        {
1742            return Some(p);
1743        }
1744
1745        // Check inline CSS properties with DynamicSelectorContext evaluation.
1746        // Iterate in REVERSE order - "last found wins" semantics.
1747        // This replaces the old Focus > Active > Hover > Normal priority chain.
1748        if let Some(prop_with_conditions) =
1749            node_data.css_props.as_ref().iter().rev().find(|prop| {
1750                prop.property.get_type() == *css_property_type && prop.matches(context)
1751            })
1752        {
1753            return Some(&prop_with_conditions.property);
1754        }
1755
1756        // Fall back to CSS file and cascaded properties
1757        let legacy_state = StyledNodeState::from_pseudo_state_flags(&context.pseudo_state);
1758        if let Some(p) = self.get_property(node_data, node_id, &legacy_state, css_property_type) {
1759            return Some(p);
1760        }
1761
1762        None
1763    }
1764
1765    /// Check if any properties with conditions would change between two contexts.
1766    /// This is used for re-layout detection on viewport/container resize.
1767    pub fn check_properties_changed(
1768        node_data: &NodeData,
1769        old_context: &DynamicSelectorContext,
1770        new_context: &DynamicSelectorContext,
1771    ) -> bool {
1772        for prop in node_data.css_props.as_ref().iter() {
1773            let was_active = prop.matches(old_context);
1774            let is_active = prop.matches(new_context);
1775            if was_active != is_active {
1776                return true;
1777            }
1778        }
1779        false
1780    }
1781
1782    /// Check if any layout-affecting properties would change between two contexts.
1783    /// This is a more targeted check for re-layout detection.
1784    pub fn check_layout_properties_changed(
1785        node_data: &NodeData,
1786        old_context: &DynamicSelectorContext,
1787        new_context: &DynamicSelectorContext,
1788    ) -> bool {
1789        for prop in node_data.css_props.as_ref().iter() {
1790            // Skip non-layout-affecting properties
1791            if !prop.is_layout_affecting() {
1792                continue;
1793            }
1794
1795            let was_active = prop.matches(old_context);
1796            let is_active = prop.matches(new_context);
1797            if was_active != is_active {
1798                return true;
1799            }
1800        }
1801        false
1802    }
1803
1804    pub fn get_background_content<'a>(
1805        &'a self,
1806        node_data: &'a NodeData,
1807        node_id: &NodeId,
1808        node_state: &StyledNodeState,
1809    ) -> Option<&'a StyleBackgroundContentVecValue> {
1810        self.get_property(
1811            node_data,
1812            node_id,
1813            node_state,
1814            &CssPropertyType::BackgroundContent,
1815        )
1816        .and_then(|p| p.as_background_content())
1817    }
1818
1819    // Method for getting hyphens property
1820    pub fn get_hyphens<'a>(
1821        &'a self,
1822        node_data: &'a NodeData,
1823        node_id: &NodeId,
1824        node_state: &StyledNodeState,
1825    ) -> Option<&'a StyleHyphensValue> {
1826        self.get_property(node_data, node_id, node_state, &CssPropertyType::Hyphens)
1827            .and_then(|p| p.as_hyphens())
1828    }
1829
1830    // Method for getting direction property
1831    pub fn get_direction<'a>(
1832        &'a self,
1833        node_data: &'a NodeData,
1834        node_id: &NodeId,
1835        node_state: &StyledNodeState,
1836    ) -> Option<&'a StyleDirectionValue> {
1837        self.get_property(node_data, node_id, node_state, &CssPropertyType::Direction)
1838            .and_then(|p| p.as_direction())
1839    }
1840
1841    // Method for getting white-space property
1842    pub fn get_white_space<'a>(
1843        &'a self,
1844        node_data: &'a NodeData,
1845        node_id: &NodeId,
1846        node_state: &StyledNodeState,
1847    ) -> Option<&'a StyleWhiteSpaceValue> {
1848        self.get_property(node_data, node_id, node_state, &CssPropertyType::WhiteSpace)
1849            .and_then(|p| p.as_white_space())
1850    }
1851    pub fn get_background_position<'a>(
1852        &'a self,
1853        node_data: &'a NodeData,
1854        node_id: &NodeId,
1855        node_state: &StyledNodeState,
1856    ) -> Option<&'a StyleBackgroundPositionVecValue> {
1857        self.get_property(
1858            node_data,
1859            node_id,
1860            node_state,
1861            &CssPropertyType::BackgroundPosition,
1862        )
1863        .and_then(|p| p.as_background_position())
1864    }
1865    pub fn get_background_size<'a>(
1866        &'a self,
1867        node_data: &'a NodeData,
1868        node_id: &NodeId,
1869        node_state: &StyledNodeState,
1870    ) -> Option<&'a StyleBackgroundSizeVecValue> {
1871        self.get_property(
1872            node_data,
1873            node_id,
1874            node_state,
1875            &CssPropertyType::BackgroundSize,
1876        )
1877        .and_then(|p| p.as_background_size())
1878    }
1879    pub fn get_background_repeat<'a>(
1880        &'a self,
1881        node_data: &'a NodeData,
1882        node_id: &NodeId,
1883        node_state: &StyledNodeState,
1884    ) -> Option<&'a StyleBackgroundRepeatVecValue> {
1885        self.get_property(
1886            node_data,
1887            node_id,
1888            node_state,
1889            &CssPropertyType::BackgroundRepeat,
1890        )
1891        .and_then(|p| p.as_background_repeat())
1892    }
1893    pub fn get_font_size<'a>(
1894        &'a self,
1895        node_data: &'a NodeData,
1896        node_id: &NodeId,
1897        node_state: &StyledNodeState,
1898    ) -> Option<&'a StyleFontSizeValue> {
1899        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontSize)
1900            .and_then(|p| p.as_font_size())
1901    }
1902    pub fn get_font_family<'a>(
1903        &'a self,
1904        node_data: &'a NodeData,
1905        node_id: &NodeId,
1906        node_state: &StyledNodeState,
1907    ) -> Option<&'a StyleFontFamilyVecValue> {
1908        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontFamily)
1909            .and_then(|p| p.as_font_family())
1910    }
1911    pub fn get_font_weight<'a>(
1912        &'a self,
1913        node_data: &'a NodeData,
1914        node_id: &NodeId,
1915        node_state: &StyledNodeState,
1916    ) -> Option<&'a StyleFontWeightValue> {
1917        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontWeight)
1918            .and_then(|p| p.as_font_weight())
1919    }
1920    pub fn get_font_style<'a>(
1921        &'a self,
1922        node_data: &'a NodeData,
1923        node_id: &NodeId,
1924        node_state: &StyledNodeState,
1925    ) -> Option<&'a StyleFontStyleValue> {
1926        self.get_property(node_data, node_id, node_state, &CssPropertyType::FontStyle)
1927            .and_then(|p| p.as_font_style())
1928    }
1929    pub fn get_text_color<'a>(
1930        &'a self,
1931        node_data: &'a NodeData,
1932        node_id: &NodeId,
1933        node_state: &StyledNodeState,
1934    ) -> Option<&'a StyleTextColorValue> {
1935        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextColor)
1936            .and_then(|p| p.as_text_color())
1937    }
1938    // Method for getting text-indent property
1939    pub fn get_text_indent<'a>(
1940        &'a self,
1941        node_data: &'a NodeData,
1942        node_id: &NodeId,
1943        node_state: &StyledNodeState,
1944    ) -> Option<&'a StyleTextIndentValue> {
1945        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextIndent)
1946            .and_then(|p| p.as_text_indent())
1947    }
1948    // Method for getting initial-letter property
1949    pub fn get_initial_letter<'a>(
1950        &'a self,
1951        node_data: &'a NodeData,
1952        node_id: &NodeId,
1953        node_state: &StyledNodeState,
1954    ) -> Option<&'a StyleInitialLetterValue> {
1955        self.get_property(
1956            node_data,
1957            node_id,
1958            node_state,
1959            &CssPropertyType::InitialLetter,
1960        )
1961        .and_then(|p| p.as_initial_letter())
1962    }
1963    // Method for getting line-clamp property
1964    pub fn get_line_clamp<'a>(
1965        &'a self,
1966        node_data: &'a NodeData,
1967        node_id: &NodeId,
1968        node_state: &StyledNodeState,
1969    ) -> Option<&'a StyleLineClampValue> {
1970        self.get_property(node_data, node_id, node_state, &CssPropertyType::LineClamp)
1971            .and_then(|p| p.as_line_clamp())
1972    }
1973    // Method for getting hanging-punctuation property
1974    pub fn get_hanging_punctuation<'a>(
1975        &'a self,
1976        node_data: &'a NodeData,
1977        node_id: &NodeId,
1978        node_state: &StyledNodeState,
1979    ) -> Option<&'a StyleHangingPunctuationValue> {
1980        self.get_property(
1981            node_data,
1982            node_id,
1983            node_state,
1984            &CssPropertyType::HangingPunctuation,
1985        )
1986        .and_then(|p| p.as_hanging_punctuation())
1987    }
1988    // Method for getting text-combine-upright property
1989    pub fn get_text_combine_upright<'a>(
1990        &'a self,
1991        node_data: &'a NodeData,
1992        node_id: &NodeId,
1993        node_state: &StyledNodeState,
1994    ) -> Option<&'a StyleTextCombineUprightValue> {
1995        self.get_property(
1996            node_data,
1997            node_id,
1998            node_state,
1999            &CssPropertyType::TextCombineUpright,
2000        )
2001        .and_then(|p| p.as_text_combine_upright())
2002    }
2003    // Method for getting -azul-exclusion-margin property
2004    pub fn get_exclusion_margin<'a>(
2005        &'a self,
2006        node_data: &'a NodeData,
2007        node_id: &NodeId,
2008        node_state: &StyledNodeState,
2009    ) -> Option<&'a StyleExclusionMarginValue> {
2010        self.get_property(
2011            node_data,
2012            node_id,
2013            node_state,
2014            &CssPropertyType::ExclusionMargin,
2015        )
2016        .and_then(|p| p.as_exclusion_margin())
2017    }
2018    // Method for getting -azul-hyphenation-language property
2019    pub fn get_hyphenation_language<'a>(
2020        &'a self,
2021        node_data: &'a NodeData,
2022        node_id: &NodeId,
2023        node_state: &StyledNodeState,
2024    ) -> Option<&'a StyleHyphenationLanguageValue> {
2025        self.get_property(
2026            node_data,
2027            node_id,
2028            node_state,
2029            &CssPropertyType::HyphenationLanguage,
2030        )
2031        .and_then(|p| p.as_hyphenation_language())
2032    }
2033    // Method for getting caret-color property
2034    pub fn get_caret_color<'a>(
2035        &'a self,
2036        node_data: &'a NodeData,
2037        node_id: &NodeId,
2038        node_state: &StyledNodeState,
2039    ) -> Option<&'a CaretColorValue> {
2040        self.get_property(node_data, node_id, node_state, &CssPropertyType::CaretColor)
2041            .and_then(|p| p.as_caret_color())
2042    }
2043
2044    // Method for getting -azul-caret-width property
2045    pub fn get_caret_width<'a>(
2046        &'a self,
2047        node_data: &'a NodeData,
2048        node_id: &NodeId,
2049        node_state: &StyledNodeState,
2050    ) -> Option<&'a CaretWidthValue> {
2051        self.get_property(node_data, node_id, node_state, &CssPropertyType::CaretWidth)
2052            .and_then(|p| p.as_caret_width())
2053    }
2054
2055    // Method for getting caret-animation-duration property
2056    pub fn get_caret_animation_duration<'a>(
2057        &'a self,
2058        node_data: &'a NodeData,
2059        node_id: &NodeId,
2060        node_state: &StyledNodeState,
2061    ) -> Option<&'a CaretAnimationDurationValue> {
2062        self.get_property(
2063            node_data,
2064            node_id,
2065            node_state,
2066            &CssPropertyType::CaretAnimationDuration,
2067        )
2068        .and_then(|p| p.as_caret_animation_duration())
2069    }
2070
2071    // Method for getting selection-background-color property
2072    pub fn get_selection_background_color<'a>(
2073        &'a self,
2074        node_data: &'a NodeData,
2075        node_id: &NodeId,
2076        node_state: &StyledNodeState,
2077    ) -> Option<&'a SelectionBackgroundColorValue> {
2078        self.get_property(
2079            node_data,
2080            node_id,
2081            node_state,
2082            &CssPropertyType::SelectionBackgroundColor,
2083        )
2084        .and_then(|p| p.as_selection_background_color())
2085    }
2086
2087    // Method for getting selection-color property
2088    pub fn get_selection_color<'a>(
2089        &'a self,
2090        node_data: &'a NodeData,
2091        node_id: &NodeId,
2092        node_state: &StyledNodeState,
2093    ) -> Option<&'a SelectionColorValue> {
2094        self.get_property(
2095            node_data,
2096            node_id,
2097            node_state,
2098            &CssPropertyType::SelectionColor,
2099        )
2100        .and_then(|p| p.as_selection_color())
2101    }
2102
2103    // Method for getting -azul-selection-radius property
2104    pub fn get_selection_radius<'a>(
2105        &'a self,
2106        node_data: &'a NodeData,
2107        node_id: &NodeId,
2108        node_state: &StyledNodeState,
2109    ) -> Option<&'a SelectionRadiusValue> {
2110        self.get_property(
2111            node_data,
2112            node_id,
2113            node_state,
2114            &CssPropertyType::SelectionRadius,
2115        )
2116        .and_then(|p| p.as_selection_radius())
2117    }
2118
2119    // Method for getting text-justify property
2120    pub fn get_text_justify<'a>(
2121        &'a self,
2122        node_data: &'a NodeData,
2123        node_id: &NodeId,
2124        node_state: &StyledNodeState,
2125    ) -> Option<&'a LayoutTextJustifyValue> {
2126        self.get_property(
2127            node_data,
2128            node_id,
2129            node_state,
2130            &CssPropertyType::TextJustify,
2131        )
2132        .and_then(|p| p.as_text_justify())
2133    }
2134
2135    // Method for getting z-index property
2136    pub fn get_z_index<'a>(
2137        &'a self,
2138        node_data: &'a NodeData,
2139        node_id: &NodeId,
2140        node_state: &StyledNodeState,
2141    ) -> Option<&'a LayoutZIndexValue> {
2142        self.get_property(node_data, node_id, node_state, &CssPropertyType::ZIndex)
2143            .and_then(|p| p.as_z_index())
2144    }
2145
2146    // Method for getting flex-basis property
2147    pub fn get_flex_basis<'a>(
2148        &'a self,
2149        node_data: &'a NodeData,
2150        node_id: &NodeId,
2151        node_state: &StyledNodeState,
2152    ) -> Option<&'a LayoutFlexBasisValue> {
2153        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexBasis)
2154            .and_then(|p| p.as_flex_basis())
2155    }
2156
2157    // Method for getting column-gap property
2158    pub fn get_column_gap<'a>(
2159        &'a self,
2160        node_data: &'a NodeData,
2161        node_id: &NodeId,
2162        node_state: &StyledNodeState,
2163    ) -> Option<&'a LayoutColumnGapValue> {
2164        self.get_property(node_data, node_id, node_state, &CssPropertyType::ColumnGap)
2165            .and_then(|p| p.as_column_gap())
2166    }
2167
2168    // Method for getting row-gap property
2169    pub fn get_row_gap<'a>(
2170        &'a self,
2171        node_data: &'a NodeData,
2172        node_id: &NodeId,
2173        node_state: &StyledNodeState,
2174    ) -> Option<&'a LayoutRowGapValue> {
2175        self.get_property(node_data, node_id, node_state, &CssPropertyType::RowGap)
2176            .and_then(|p| p.as_row_gap())
2177    }
2178
2179    // Method for getting grid-template-columns property
2180    pub fn get_grid_template_columns<'a>(
2181        &'a self,
2182        node_data: &'a NodeData,
2183        node_id: &NodeId,
2184        node_state: &StyledNodeState,
2185    ) -> Option<&'a LayoutGridTemplateColumnsValue> {
2186        self.get_property(
2187            node_data,
2188            node_id,
2189            node_state,
2190            &CssPropertyType::GridTemplateColumns,
2191        )
2192        .and_then(|p| p.as_grid_template_columns())
2193    }
2194
2195    // Method for getting grid-template-rows property
2196    pub fn get_grid_template_rows<'a>(
2197        &'a self,
2198        node_data: &'a NodeData,
2199        node_id: &NodeId,
2200        node_state: &StyledNodeState,
2201    ) -> Option<&'a LayoutGridTemplateRowsValue> {
2202        self.get_property(
2203            node_data,
2204            node_id,
2205            node_state,
2206            &CssPropertyType::GridTemplateRows,
2207        )
2208        .and_then(|p| p.as_grid_template_rows())
2209    }
2210
2211    // Method for getting grid-auto-columns property
2212    pub fn get_grid_auto_columns<'a>(
2213        &'a self,
2214        node_data: &'a NodeData,
2215        node_id: &NodeId,
2216        node_state: &StyledNodeState,
2217    ) -> Option<&'a LayoutGridAutoColumnsValue> {
2218        self.get_property(
2219            node_data,
2220            node_id,
2221            node_state,
2222            &CssPropertyType::GridAutoColumns,
2223        )
2224        .and_then(|p| p.as_grid_auto_columns())
2225    }
2226
2227    // Method for getting grid-auto-rows property
2228    pub fn get_grid_auto_rows<'a>(
2229        &'a self,
2230        node_data: &'a NodeData,
2231        node_id: &NodeId,
2232        node_state: &StyledNodeState,
2233    ) -> Option<&'a LayoutGridAutoRowsValue> {
2234        self.get_property(
2235            node_data,
2236            node_id,
2237            node_state,
2238            &CssPropertyType::GridAutoRows,
2239        )
2240        .and_then(|p| p.as_grid_auto_rows())
2241    }
2242
2243    // Method for getting grid-column property
2244    pub fn get_grid_column<'a>(
2245        &'a self,
2246        node_data: &'a NodeData,
2247        node_id: &NodeId,
2248        node_state: &StyledNodeState,
2249    ) -> Option<&'a LayoutGridColumnValue> {
2250        self.get_property(node_data, node_id, node_state, &CssPropertyType::GridColumn)
2251            .and_then(|p| p.as_grid_column())
2252    }
2253
2254    // Method for getting grid-row property
2255    pub fn get_grid_row<'a>(
2256        &'a self,
2257        node_data: &'a NodeData,
2258        node_id: &NodeId,
2259        node_state: &StyledNodeState,
2260    ) -> Option<&'a LayoutGridRowValue> {
2261        self.get_property(node_data, node_id, node_state, &CssPropertyType::GridRow)
2262            .and_then(|p| p.as_grid_row())
2263    }
2264
2265    // Method for getting grid-auto-flow property
2266    pub fn get_grid_auto_flow<'a>(
2267        &'a self,
2268        node_data: &'a NodeData,
2269        node_id: &NodeId,
2270        node_state: &StyledNodeState,
2271    ) -> Option<&'a LayoutGridAutoFlowValue> {
2272        self.get_property(
2273            node_data,
2274            node_id,
2275            node_state,
2276            &CssPropertyType::GridAutoFlow,
2277        )
2278        .and_then(|p| p.as_grid_auto_flow())
2279    }
2280
2281    // Method for getting justify-self property
2282    pub fn get_justify_self<'a>(
2283        &'a self,
2284        node_data: &'a NodeData,
2285        node_id: &NodeId,
2286        node_state: &StyledNodeState,
2287    ) -> Option<&'a LayoutJustifySelfValue> {
2288        self.get_property(
2289            node_data,
2290            node_id,
2291            node_state,
2292            &CssPropertyType::JustifySelf,
2293        )
2294        .and_then(|p| p.as_justify_self())
2295    }
2296
2297    // Method for getting justify-items property
2298    pub fn get_justify_items<'a>(
2299        &'a self,
2300        node_data: &'a NodeData,
2301        node_id: &NodeId,
2302        node_state: &StyledNodeState,
2303    ) -> Option<&'a LayoutJustifyItemsValue> {
2304        self.get_property(
2305            node_data,
2306            node_id,
2307            node_state,
2308            &CssPropertyType::JustifyItems,
2309        )
2310        .and_then(|p| p.as_justify_items())
2311    }
2312
2313    // Method for getting gap property
2314    pub fn get_gap<'a>(
2315        &'a self,
2316        node_data: &'a NodeData,
2317        node_id: &NodeId,
2318        node_state: &StyledNodeState,
2319    ) -> Option<&'a LayoutGapValue> {
2320        self.get_property(node_data, node_id, node_state, &CssPropertyType::Gap)
2321            .and_then(|p| p.as_gap())
2322    }
2323
2324    // Method for getting grid-gap property
2325    pub fn get_grid_gap<'a>(
2326        &'a self,
2327        node_data: &'a NodeData,
2328        node_id: &NodeId,
2329        node_state: &StyledNodeState,
2330    ) -> Option<&'a LayoutGapValue> {
2331        self.get_property(node_data, node_id, node_state, &CssPropertyType::GridGap)
2332            .and_then(|p| p.as_grid_gap())
2333    }
2334
2335    // Method for getting align-self property
2336    pub fn get_align_self<'a>(
2337        &'a self,
2338        node_data: &'a NodeData,
2339        node_id: &NodeId,
2340        node_state: &StyledNodeState,
2341    ) -> Option<&'a LayoutAlignSelfValue> {
2342        self.get_property(node_data, node_id, node_state, &CssPropertyType::AlignSelf)
2343            .and_then(|p| p.as_align_self())
2344    }
2345
2346    // Method for getting font property
2347    pub fn get_font<'a>(
2348        &'a self,
2349        node_data: &'a NodeData,
2350        node_id: &NodeId,
2351        node_state: &StyledNodeState,
2352    ) -> Option<&'a StyleFontValue> {
2353        self.get_property(node_data, node_id, node_state, &CssPropertyType::Font)
2354            .and_then(|p| p.as_font())
2355    }
2356
2357    // Method for getting writing-mode property
2358    pub fn get_writing_mode<'a>(
2359        &'a self,
2360        node_data: &'a NodeData,
2361        node_id: &NodeId,
2362        node_state: &StyledNodeState,
2363    ) -> Option<&'a LayoutWritingModeValue> {
2364        self.get_property(
2365            node_data,
2366            node_id,
2367            node_state,
2368            &CssPropertyType::WritingMode,
2369        )
2370        .and_then(|p| p.as_writing_mode())
2371    }
2372
2373    // Method for getting clear property
2374    pub fn get_clear<'a>(
2375        &'a self,
2376        node_data: &'a NodeData,
2377        node_id: &NodeId,
2378        node_state: &StyledNodeState,
2379    ) -> Option<&'a LayoutClearValue> {
2380        self.get_property(node_data, node_id, node_state, &CssPropertyType::Clear)
2381            .and_then(|p| p.as_clear())
2382    }
2383
2384    // Method for getting shape-outside property
2385    pub fn get_shape_outside<'a>(
2386        &'a self,
2387        node_data: &'a NodeData,
2388        node_id: &NodeId,
2389        node_state: &StyledNodeState,
2390    ) -> Option<&'a ShapeOutsideValue> {
2391        self.get_property(
2392            node_data,
2393            node_id,
2394            node_state,
2395            &CssPropertyType::ShapeOutside,
2396        )
2397        .and_then(|p| p.as_shape_outside())
2398    }
2399
2400    // Method for getting shape-inside property
2401    pub fn get_shape_inside<'a>(
2402        &'a self,
2403        node_data: &'a NodeData,
2404        node_id: &NodeId,
2405        node_state: &StyledNodeState,
2406    ) -> Option<&'a ShapeInsideValue> {
2407        self.get_property(
2408            node_data,
2409            node_id,
2410            node_state,
2411            &CssPropertyType::ShapeInside,
2412        )
2413        .and_then(|p| p.as_shape_inside())
2414    }
2415
2416    // Method for getting clip-path property
2417    pub fn get_clip_path<'a>(
2418        &'a self,
2419        node_data: &'a NodeData,
2420        node_id: &NodeId,
2421        node_state: &StyledNodeState,
2422    ) -> Option<&'a ClipPathValue> {
2423        self.get_property(node_data, node_id, node_state, &CssPropertyType::ClipPath)
2424            .and_then(|p| p.as_clip_path())
2425    }
2426
2427    // Method for getting scrollbar-style property
2428    pub fn get_scrollbar_style<'a>(
2429        &'a self,
2430        node_data: &'a NodeData,
2431        node_id: &NodeId,
2432        node_state: &StyledNodeState,
2433    ) -> Option<&'a ScrollbarStyleValue> {
2434        self.get_property(node_data, node_id, node_state, &CssPropertyType::Scrollbar)
2435            .and_then(|p| p.as_scrollbar())
2436    }
2437
2438    // Method for getting scrollbar-width property
2439    pub fn get_scrollbar_width<'a>(
2440        &'a self,
2441        node_data: &'a NodeData,
2442        node_id: &NodeId,
2443        node_state: &StyledNodeState,
2444    ) -> Option<&'a LayoutScrollbarWidthValue> {
2445        self.get_property(
2446            node_data,
2447            node_id,
2448            node_state,
2449            &CssPropertyType::ScrollbarWidth,
2450        )
2451        .and_then(|p| p.as_scrollbar_width())
2452    }
2453
2454    // Method for getting scrollbar-color property
2455    pub fn get_scrollbar_color<'a>(
2456        &'a self,
2457        node_data: &'a NodeData,
2458        node_id: &NodeId,
2459        node_state: &StyledNodeState,
2460    ) -> Option<&'a StyleScrollbarColorValue> {
2461        self.get_property(
2462            node_data,
2463            node_id,
2464            node_state,
2465            &CssPropertyType::ScrollbarColor,
2466        )
2467        .and_then(|p| p.as_scrollbar_color())
2468    }
2469
2470    // Method for getting visibility property
2471    pub fn get_visibility<'a>(
2472        &'a self,
2473        node_data: &'a NodeData,
2474        node_id: &NodeId,
2475        node_state: &StyledNodeState,
2476    ) -> Option<&'a StyleVisibilityValue> {
2477        self.get_property(node_data, node_id, node_state, &CssPropertyType::Visibility)
2478            .and_then(|p| p.as_visibility())
2479    }
2480
2481    // Method for getting break-before property
2482    pub fn get_break_before<'a>(
2483        &'a self,
2484        node_data: &'a NodeData,
2485        node_id: &NodeId,
2486        node_state: &StyledNodeState,
2487    ) -> Option<&'a PageBreakValue> {
2488        self.get_property(
2489            node_data,
2490            node_id,
2491            node_state,
2492            &CssPropertyType::BreakBefore,
2493        )
2494        .and_then(|p| p.as_break_before())
2495    }
2496
2497    // Method for getting break-after property
2498    pub fn get_break_after<'a>(
2499        &'a self,
2500        node_data: &'a NodeData,
2501        node_id: &NodeId,
2502        node_state: &StyledNodeState,
2503    ) -> Option<&'a PageBreakValue> {
2504        self.get_property(node_data, node_id, node_state, &CssPropertyType::BreakAfter)
2505            .and_then(|p| p.as_break_after())
2506    }
2507
2508    // Method for getting break-inside property
2509    pub fn get_break_inside<'a>(
2510        &'a self,
2511        node_data: &'a NodeData,
2512        node_id: &NodeId,
2513        node_state: &StyledNodeState,
2514    ) -> Option<&'a BreakInsideValue> {
2515        self.get_property(
2516            node_data,
2517            node_id,
2518            node_state,
2519            &CssPropertyType::BreakInside,
2520        )
2521        .and_then(|p| p.as_break_inside())
2522    }
2523
2524    // Method for getting orphans property
2525    pub fn get_orphans<'a>(
2526        &'a self,
2527        node_data: &'a NodeData,
2528        node_id: &NodeId,
2529        node_state: &StyledNodeState,
2530    ) -> Option<&'a OrphansValue> {
2531        self.get_property(node_data, node_id, node_state, &CssPropertyType::Orphans)
2532            .and_then(|p| p.as_orphans())
2533    }
2534
2535    // Method for getting widows property
2536    pub fn get_widows<'a>(
2537        &'a self,
2538        node_data: &'a NodeData,
2539        node_id: &NodeId,
2540        node_state: &StyledNodeState,
2541    ) -> Option<&'a WidowsValue> {
2542        self.get_property(node_data, node_id, node_state, &CssPropertyType::Widows)
2543            .and_then(|p| p.as_widows())
2544    }
2545
2546    // Method for getting box-decoration-break property
2547    pub fn get_box_decoration_break<'a>(
2548        &'a self,
2549        node_data: &'a NodeData,
2550        node_id: &NodeId,
2551        node_state: &StyledNodeState,
2552    ) -> Option<&'a BoxDecorationBreakValue> {
2553        self.get_property(
2554            node_data,
2555            node_id,
2556            node_state,
2557            &CssPropertyType::BoxDecorationBreak,
2558        )
2559        .and_then(|p| p.as_box_decoration_break())
2560    }
2561
2562    // Method for getting column-count property
2563    pub fn get_column_count<'a>(
2564        &'a self,
2565        node_data: &'a NodeData,
2566        node_id: &NodeId,
2567        node_state: &StyledNodeState,
2568    ) -> Option<&'a ColumnCountValue> {
2569        self.get_property(
2570            node_data,
2571            node_id,
2572            node_state,
2573            &CssPropertyType::ColumnCount,
2574        )
2575        .and_then(|p| p.as_column_count())
2576    }
2577
2578    // Method for getting column-width property
2579    pub fn get_column_width<'a>(
2580        &'a self,
2581        node_data: &'a NodeData,
2582        node_id: &NodeId,
2583        node_state: &StyledNodeState,
2584    ) -> Option<&'a ColumnWidthValue> {
2585        self.get_property(
2586            node_data,
2587            node_id,
2588            node_state,
2589            &CssPropertyType::ColumnWidth,
2590        )
2591        .and_then(|p| p.as_column_width())
2592    }
2593
2594    // Method for getting column-span property
2595    pub fn get_column_span<'a>(
2596        &'a self,
2597        node_data: &'a NodeData,
2598        node_id: &NodeId,
2599        node_state: &StyledNodeState,
2600    ) -> Option<&'a ColumnSpanValue> {
2601        self.get_property(node_data, node_id, node_state, &CssPropertyType::ColumnSpan)
2602            .and_then(|p| p.as_column_span())
2603    }
2604
2605    // Method for getting column-fill property
2606    pub fn get_column_fill<'a>(
2607        &'a self,
2608        node_data: &'a NodeData,
2609        node_id: &NodeId,
2610        node_state: &StyledNodeState,
2611    ) -> Option<&'a ColumnFillValue> {
2612        self.get_property(node_data, node_id, node_state, &CssPropertyType::ColumnFill)
2613            .and_then(|p| p.as_column_fill())
2614    }
2615
2616    // Method for getting column-rule-width property
2617    pub fn get_column_rule_width<'a>(
2618        &'a self,
2619        node_data: &'a NodeData,
2620        node_id: &NodeId,
2621        node_state: &StyledNodeState,
2622    ) -> Option<&'a ColumnRuleWidthValue> {
2623        self.get_property(
2624            node_data,
2625            node_id,
2626            node_state,
2627            &CssPropertyType::ColumnRuleWidth,
2628        )
2629        .and_then(|p| p.as_column_rule_width())
2630    }
2631
2632    // Method for getting column-rule-style property
2633    pub fn get_column_rule_style<'a>(
2634        &'a self,
2635        node_data: &'a NodeData,
2636        node_id: &NodeId,
2637        node_state: &StyledNodeState,
2638    ) -> Option<&'a ColumnRuleStyleValue> {
2639        self.get_property(
2640            node_data,
2641            node_id,
2642            node_state,
2643            &CssPropertyType::ColumnRuleStyle,
2644        )
2645        .and_then(|p| p.as_column_rule_style())
2646    }
2647
2648    // Method for getting column-rule-color property
2649    pub fn get_column_rule_color<'a>(
2650        &'a self,
2651        node_data: &'a NodeData,
2652        node_id: &NodeId,
2653        node_state: &StyledNodeState,
2654    ) -> Option<&'a ColumnRuleColorValue> {
2655        self.get_property(
2656            node_data,
2657            node_id,
2658            node_state,
2659            &CssPropertyType::ColumnRuleColor,
2660        )
2661        .and_then(|p| p.as_column_rule_color())
2662    }
2663
2664    // Method for getting flow-into property
2665    pub fn get_flow_into<'a>(
2666        &'a self,
2667        node_data: &'a NodeData,
2668        node_id: &NodeId,
2669        node_state: &StyledNodeState,
2670    ) -> Option<&'a FlowIntoValue> {
2671        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlowInto)
2672            .and_then(|p| p.as_flow_into())
2673    }
2674
2675    // Method for getting flow-from property
2676    pub fn get_flow_from<'a>(
2677        &'a self,
2678        node_data: &'a NodeData,
2679        node_id: &NodeId,
2680        node_state: &StyledNodeState,
2681    ) -> Option<&'a FlowFromValue> {
2682        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlowFrom)
2683            .and_then(|p| p.as_flow_from())
2684    }
2685
2686    // Method for getting shape-margin property
2687    pub fn get_shape_margin<'a>(
2688        &'a self,
2689        node_data: &'a NodeData,
2690        node_id: &NodeId,
2691        node_state: &StyledNodeState,
2692    ) -> Option<&'a ShapeMarginValue> {
2693        self.get_property(
2694            node_data,
2695            node_id,
2696            node_state,
2697            &CssPropertyType::ShapeMargin,
2698        )
2699        .and_then(|p| p.as_shape_margin())
2700    }
2701
2702    // Method for getting shape-image-threshold property
2703    pub fn get_shape_image_threshold<'a>(
2704        &'a self,
2705        node_data: &'a NodeData,
2706        node_id: &NodeId,
2707        node_state: &StyledNodeState,
2708    ) -> Option<&'a ShapeImageThresholdValue> {
2709        self.get_property(
2710            node_data,
2711            node_id,
2712            node_state,
2713            &CssPropertyType::ShapeImageThreshold,
2714        )
2715        .and_then(|p| p.as_shape_image_threshold())
2716    }
2717
2718    // Method for getting content property
2719    pub fn get_content<'a>(
2720        &'a self,
2721        node_data: &'a NodeData,
2722        node_id: &NodeId,
2723        node_state: &StyledNodeState,
2724    ) -> Option<&'a ContentValue> {
2725        self.get_property(node_data, node_id, node_state, &CssPropertyType::Content)
2726            .and_then(|p| p.as_content())
2727    }
2728
2729    // Method for getting counter-reset property
2730    pub fn get_counter_reset<'a>(
2731        &'a self,
2732        node_data: &'a NodeData,
2733        node_id: &NodeId,
2734        node_state: &StyledNodeState,
2735    ) -> Option<&'a CounterResetValue> {
2736        self.get_property(
2737            node_data,
2738            node_id,
2739            node_state,
2740            &CssPropertyType::CounterReset,
2741        )
2742        .and_then(|p| p.as_counter_reset())
2743    }
2744
2745    // Method for getting counter-increment property
2746    pub fn get_counter_increment<'a>(
2747        &'a self,
2748        node_data: &'a NodeData,
2749        node_id: &NodeId,
2750        node_state: &StyledNodeState,
2751    ) -> Option<&'a CounterIncrementValue> {
2752        self.get_property(
2753            node_data,
2754            node_id,
2755            node_state,
2756            &CssPropertyType::CounterIncrement,
2757        )
2758        .and_then(|p| p.as_counter_increment())
2759    }
2760
2761    // Method for getting string-set property
2762    pub fn get_string_set<'a>(
2763        &'a self,
2764        node_data: &'a NodeData,
2765        node_id: &NodeId,
2766        node_state: &StyledNodeState,
2767    ) -> Option<&'a StringSetValue> {
2768        self.get_property(node_data, node_id, node_state, &CssPropertyType::StringSet)
2769            .and_then(|p| p.as_string_set())
2770    }
2771    pub fn get_text_align<'a>(
2772        &'a self,
2773        node_data: &'a NodeData,
2774        node_id: &NodeId,
2775        node_state: &StyledNodeState,
2776    ) -> Option<&'a StyleTextAlignValue> {
2777        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextAlign)
2778            .and_then(|p| p.as_text_align())
2779    }
2780    pub fn get_user_select<'a>(
2781        &'a self,
2782        node_data: &'a NodeData,
2783        node_id: &NodeId,
2784        node_state: &StyledNodeState,
2785    ) -> Option<&'a StyleUserSelectValue> {
2786        self.get_property(node_data, node_id, node_state, &CssPropertyType::UserSelect)
2787            .and_then(|p| p.as_user_select())
2788    }
2789    pub fn get_text_decoration<'a>(
2790        &'a self,
2791        node_data: &'a NodeData,
2792        node_id: &NodeId,
2793        node_state: &StyledNodeState,
2794    ) -> Option<&'a StyleTextDecorationValue> {
2795        self.get_property(
2796            node_data,
2797            node_id,
2798            node_state,
2799            &CssPropertyType::TextDecoration,
2800        )
2801        .and_then(|p| p.as_text_decoration())
2802    }
2803    pub fn get_vertical_align<'a>(
2804        &'a self,
2805        node_data: &'a NodeData,
2806        node_id: &NodeId,
2807        node_state: &StyledNodeState,
2808    ) -> Option<&'a StyleVerticalAlignValue> {
2809        self.get_property(
2810            node_data,
2811            node_id,
2812            node_state,
2813            &CssPropertyType::VerticalAlign,
2814        )
2815        .and_then(|p| p.as_vertical_align())
2816    }
2817    pub fn get_line_height<'a>(
2818        &'a self,
2819        node_data: &'a NodeData,
2820        node_id: &NodeId,
2821        node_state: &StyledNodeState,
2822    ) -> Option<&'a StyleLineHeightValue> {
2823        self.get_property(node_data, node_id, node_state, &CssPropertyType::LineHeight)
2824            .and_then(|p| p.as_line_height())
2825    }
2826    pub fn get_letter_spacing<'a>(
2827        &'a self,
2828        node_data: &'a NodeData,
2829        node_id: &NodeId,
2830        node_state: &StyledNodeState,
2831    ) -> Option<&'a StyleLetterSpacingValue> {
2832        self.get_property(
2833            node_data,
2834            node_id,
2835            node_state,
2836            &CssPropertyType::LetterSpacing,
2837        )
2838        .and_then(|p| p.as_letter_spacing())
2839    }
2840    pub fn get_word_spacing<'a>(
2841        &'a self,
2842        node_data: &'a NodeData,
2843        node_id: &NodeId,
2844        node_state: &StyledNodeState,
2845    ) -> Option<&'a StyleWordSpacingValue> {
2846        self.get_property(
2847            node_data,
2848            node_id,
2849            node_state,
2850            &CssPropertyType::WordSpacing,
2851        )
2852        .and_then(|p| p.as_word_spacing())
2853    }
2854    pub fn get_tab_size<'a>(
2855        &'a self,
2856        node_data: &'a NodeData,
2857        node_id: &NodeId,
2858        node_state: &StyledNodeState,
2859    ) -> Option<&'a StyleTabSizeValue> {
2860        self.get_property(node_data, node_id, node_state, &CssPropertyType::TabSize)
2861            .and_then(|p| p.as_tab_size())
2862    }
2863    pub fn get_cursor<'a>(
2864        &'a self,
2865        node_data: &'a NodeData,
2866        node_id: &NodeId,
2867        node_state: &StyledNodeState,
2868    ) -> Option<&'a StyleCursorValue> {
2869        self.get_property(node_data, node_id, node_state, &CssPropertyType::Cursor)
2870            .and_then(|p| p.as_cursor())
2871    }
2872    pub fn get_box_shadow_left<'a>(
2873        &'a self,
2874        node_data: &'a NodeData,
2875        node_id: &NodeId,
2876        node_state: &StyledNodeState,
2877    ) -> Option<&'a StyleBoxShadowValue> {
2878        self.get_property(
2879            node_data,
2880            node_id,
2881            node_state,
2882            &CssPropertyType::BoxShadowLeft,
2883        )
2884        .and_then(|p| p.as_box_shadow_left())
2885    }
2886    pub fn get_box_shadow_right<'a>(
2887        &'a self,
2888        node_data: &'a NodeData,
2889        node_id: &NodeId,
2890        node_state: &StyledNodeState,
2891    ) -> Option<&'a StyleBoxShadowValue> {
2892        self.get_property(
2893            node_data,
2894            node_id,
2895            node_state,
2896            &CssPropertyType::BoxShadowRight,
2897        )
2898        .and_then(|p| p.as_box_shadow_right())
2899    }
2900    pub fn get_box_shadow_top<'a>(
2901        &'a self,
2902        node_data: &'a NodeData,
2903        node_id: &NodeId,
2904        node_state: &StyledNodeState,
2905    ) -> Option<&'a StyleBoxShadowValue> {
2906        self.get_property(
2907            node_data,
2908            node_id,
2909            node_state,
2910            &CssPropertyType::BoxShadowTop,
2911        )
2912        .and_then(|p| p.as_box_shadow_top())
2913    }
2914    pub fn get_box_shadow_bottom<'a>(
2915        &'a self,
2916        node_data: &'a NodeData,
2917        node_id: &NodeId,
2918        node_state: &StyledNodeState,
2919    ) -> Option<&'a StyleBoxShadowValue> {
2920        self.get_property(
2921            node_data,
2922            node_id,
2923            node_state,
2924            &CssPropertyType::BoxShadowBottom,
2925        )
2926        .and_then(|p| p.as_box_shadow_bottom())
2927    }
2928    pub fn get_border_top_color<'a>(
2929        &'a self,
2930        node_data: &'a NodeData,
2931        node_id: &NodeId,
2932        node_state: &StyledNodeState,
2933    ) -> Option<&'a StyleBorderTopColorValue> {
2934        self.get_property(
2935            node_data,
2936            node_id,
2937            node_state,
2938            &CssPropertyType::BorderTopColor,
2939        )
2940        .and_then(|p| p.as_border_top_color())
2941    }
2942    pub fn get_border_left_color<'a>(
2943        &'a self,
2944        node_data: &'a NodeData,
2945        node_id: &NodeId,
2946        node_state: &StyledNodeState,
2947    ) -> Option<&'a StyleBorderLeftColorValue> {
2948        self.get_property(
2949            node_data,
2950            node_id,
2951            node_state,
2952            &CssPropertyType::BorderLeftColor,
2953        )
2954        .and_then(|p| p.as_border_left_color())
2955    }
2956    pub fn get_border_right_color<'a>(
2957        &'a self,
2958        node_data: &'a NodeData,
2959        node_id: &NodeId,
2960        node_state: &StyledNodeState,
2961    ) -> Option<&'a StyleBorderRightColorValue> {
2962        self.get_property(
2963            node_data,
2964            node_id,
2965            node_state,
2966            &CssPropertyType::BorderRightColor,
2967        )
2968        .and_then(|p| p.as_border_right_color())
2969    }
2970    pub fn get_border_bottom_color<'a>(
2971        &'a self,
2972        node_data: &'a NodeData,
2973        node_id: &NodeId,
2974        node_state: &StyledNodeState,
2975    ) -> Option<&'a StyleBorderBottomColorValue> {
2976        self.get_property(
2977            node_data,
2978            node_id,
2979            node_state,
2980            &CssPropertyType::BorderBottomColor,
2981        )
2982        .and_then(|p| p.as_border_bottom_color())
2983    }
2984    pub fn get_border_top_style<'a>(
2985        &'a self,
2986        node_data: &'a NodeData,
2987        node_id: &NodeId,
2988        node_state: &StyledNodeState,
2989    ) -> Option<&'a StyleBorderTopStyleValue> {
2990        self.get_property(
2991            node_data,
2992            node_id,
2993            node_state,
2994            &CssPropertyType::BorderTopStyle,
2995        )
2996        .and_then(|p| p.as_border_top_style())
2997    }
2998    pub fn get_border_left_style<'a>(
2999        &'a self,
3000        node_data: &'a NodeData,
3001        node_id: &NodeId,
3002        node_state: &StyledNodeState,
3003    ) -> Option<&'a StyleBorderLeftStyleValue> {
3004        self.get_property(
3005            node_data,
3006            node_id,
3007            node_state,
3008            &CssPropertyType::BorderLeftStyle,
3009        )
3010        .and_then(|p| p.as_border_left_style())
3011    }
3012    pub fn get_border_right_style<'a>(
3013        &'a self,
3014        node_data: &'a NodeData,
3015        node_id: &NodeId,
3016        node_state: &StyledNodeState,
3017    ) -> Option<&'a StyleBorderRightStyleValue> {
3018        self.get_property(
3019            node_data,
3020            node_id,
3021            node_state,
3022            &CssPropertyType::BorderRightStyle,
3023        )
3024        .and_then(|p| p.as_border_right_style())
3025    }
3026    pub fn get_border_bottom_style<'a>(
3027        &'a self,
3028        node_data: &'a NodeData,
3029        node_id: &NodeId,
3030        node_state: &StyledNodeState,
3031    ) -> Option<&'a StyleBorderBottomStyleValue> {
3032        self.get_property(
3033            node_data,
3034            node_id,
3035            node_state,
3036            &CssPropertyType::BorderBottomStyle,
3037        )
3038        .and_then(|p| p.as_border_bottom_style())
3039    }
3040    pub fn get_border_top_left_radius<'a>(
3041        &'a self,
3042        node_data: &'a NodeData,
3043        node_id: &NodeId,
3044        node_state: &StyledNodeState,
3045    ) -> Option<&'a StyleBorderTopLeftRadiusValue> {
3046        self.get_property(
3047            node_data,
3048            node_id,
3049            node_state,
3050            &CssPropertyType::BorderTopLeftRadius,
3051        )
3052        .and_then(|p| p.as_border_top_left_radius())
3053    }
3054    pub fn get_border_top_right_radius<'a>(
3055        &'a self,
3056        node_data: &'a NodeData,
3057        node_id: &NodeId,
3058        node_state: &StyledNodeState,
3059    ) -> Option<&'a StyleBorderTopRightRadiusValue> {
3060        self.get_property(
3061            node_data,
3062            node_id,
3063            node_state,
3064            &CssPropertyType::BorderTopRightRadius,
3065        )
3066        .and_then(|p| p.as_border_top_right_radius())
3067    }
3068    pub fn get_border_bottom_left_radius<'a>(
3069        &'a self,
3070        node_data: &'a NodeData,
3071        node_id: &NodeId,
3072        node_state: &StyledNodeState,
3073    ) -> Option<&'a StyleBorderBottomLeftRadiusValue> {
3074        self.get_property(
3075            node_data,
3076            node_id,
3077            node_state,
3078            &CssPropertyType::BorderBottomLeftRadius,
3079        )
3080        .and_then(|p| p.as_border_bottom_left_radius())
3081    }
3082    pub fn get_border_bottom_right_radius<'a>(
3083        &'a self,
3084        node_data: &'a NodeData,
3085        node_id: &NodeId,
3086        node_state: &StyledNodeState,
3087    ) -> Option<&'a StyleBorderBottomRightRadiusValue> {
3088        self.get_property(
3089            node_data,
3090            node_id,
3091            node_state,
3092            &CssPropertyType::BorderBottomRightRadius,
3093        )
3094        .and_then(|p| p.as_border_bottom_right_radius())
3095    }
3096    pub fn get_opacity<'a>(
3097        &'a self,
3098        node_data: &'a NodeData,
3099        node_id: &NodeId,
3100        node_state: &StyledNodeState,
3101    ) -> Option<&'a StyleOpacityValue> {
3102        self.get_property(node_data, node_id, node_state, &CssPropertyType::Opacity)
3103            .and_then(|p| p.as_opacity())
3104    }
3105    pub fn get_transform<'a>(
3106        &'a self,
3107        node_data: &'a NodeData,
3108        node_id: &NodeId,
3109        node_state: &StyledNodeState,
3110    ) -> Option<&'a StyleTransformVecValue> {
3111        self.get_property(node_data, node_id, node_state, &CssPropertyType::Transform)
3112            .and_then(|p| p.as_transform())
3113    }
3114    pub fn get_transform_origin<'a>(
3115        &'a self,
3116        node_data: &'a NodeData,
3117        node_id: &NodeId,
3118        node_state: &StyledNodeState,
3119    ) -> Option<&'a StyleTransformOriginValue> {
3120        self.get_property(
3121            node_data,
3122            node_id,
3123            node_state,
3124            &CssPropertyType::TransformOrigin,
3125        )
3126        .and_then(|p| p.as_transform_origin())
3127    }
3128    pub fn get_perspective_origin<'a>(
3129        &'a self,
3130        node_data: &'a NodeData,
3131        node_id: &NodeId,
3132        node_state: &StyledNodeState,
3133    ) -> Option<&'a StylePerspectiveOriginValue> {
3134        self.get_property(
3135            node_data,
3136            node_id,
3137            node_state,
3138            &CssPropertyType::PerspectiveOrigin,
3139        )
3140        .and_then(|p| p.as_perspective_origin())
3141    }
3142    pub fn get_backface_visibility<'a>(
3143        &'a self,
3144        node_data: &'a NodeData,
3145        node_id: &NodeId,
3146        node_state: &StyledNodeState,
3147    ) -> Option<&'a StyleBackfaceVisibilityValue> {
3148        self.get_property(
3149            node_data,
3150            node_id,
3151            node_state,
3152            &CssPropertyType::BackfaceVisibility,
3153        )
3154        .and_then(|p| p.as_backface_visibility())
3155    }
3156    pub fn get_display<'a>(
3157        &'a self,
3158        node_data: &'a NodeData,
3159        node_id: &NodeId,
3160        node_state: &StyledNodeState,
3161    ) -> Option<&'a LayoutDisplayValue> {
3162        self.get_property(node_data, node_id, node_state, &CssPropertyType::Display)
3163            .and_then(|p| p.as_display())
3164    }
3165    pub fn get_float<'a>(
3166        &'a self,
3167        node_data: &'a NodeData,
3168        node_id: &NodeId,
3169        node_state: &StyledNodeState,
3170    ) -> Option<&'a LayoutFloatValue> {
3171        self.get_property(node_data, node_id, node_state, &CssPropertyType::Float)
3172            .and_then(|p| p.as_float())
3173    }
3174    pub fn get_box_sizing<'a>(
3175        &'a self,
3176        node_data: &'a NodeData,
3177        node_id: &NodeId,
3178        node_state: &StyledNodeState,
3179    ) -> Option<&'a LayoutBoxSizingValue> {
3180        self.get_property(node_data, node_id, node_state, &CssPropertyType::BoxSizing)
3181            .and_then(|p| p.as_box_sizing())
3182    }
3183    pub fn get_width<'a>(
3184        &'a self,
3185        node_data: &'a NodeData,
3186        node_id: &NodeId,
3187        node_state: &StyledNodeState,
3188    ) -> Option<&'a LayoutWidthValue> {
3189        self.get_property(node_data, node_id, node_state, &CssPropertyType::Width)
3190            .and_then(|p| p.as_width())
3191    }
3192    pub fn get_height<'a>(
3193        &'a self,
3194        node_data: &'a NodeData,
3195        node_id: &NodeId,
3196        node_state: &StyledNodeState,
3197    ) -> Option<&'a LayoutHeightValue> {
3198        self.get_property(node_data, node_id, node_state, &CssPropertyType::Height)
3199            .and_then(|p| p.as_height())
3200    }
3201    pub fn get_min_width<'a>(
3202        &'a self,
3203        node_data: &'a NodeData,
3204        node_id: &NodeId,
3205        node_state: &StyledNodeState,
3206    ) -> Option<&'a LayoutMinWidthValue> {
3207        self.get_property(node_data, node_id, node_state, &CssPropertyType::MinWidth)
3208            .and_then(|p| p.as_min_width())
3209    }
3210    pub fn get_min_height<'a>(
3211        &'a self,
3212        node_data: &'a NodeData,
3213        node_id: &NodeId,
3214        node_state: &StyledNodeState,
3215    ) -> Option<&'a LayoutMinHeightValue> {
3216        self.get_property(node_data, node_id, node_state, &CssPropertyType::MinHeight)
3217            .and_then(|p| p.as_min_height())
3218    }
3219    pub fn get_max_width<'a>(
3220        &'a self,
3221        node_data: &'a NodeData,
3222        node_id: &NodeId,
3223        node_state: &StyledNodeState,
3224    ) -> Option<&'a LayoutMaxWidthValue> {
3225        self.get_property(node_data, node_id, node_state, &CssPropertyType::MaxWidth)
3226            .and_then(|p| p.as_max_width())
3227    }
3228    pub fn get_max_height<'a>(
3229        &'a self,
3230        node_data: &'a NodeData,
3231        node_id: &NodeId,
3232        node_state: &StyledNodeState,
3233    ) -> Option<&'a LayoutMaxHeightValue> {
3234        self.get_property(node_data, node_id, node_state, &CssPropertyType::MaxHeight)
3235            .and_then(|p| p.as_max_height())
3236    }
3237    pub fn get_position<'a>(
3238        &'a self,
3239        node_data: &'a NodeData,
3240        node_id: &NodeId,
3241        node_state: &StyledNodeState,
3242    ) -> Option<&'a LayoutPositionValue> {
3243        self.get_property(node_data, node_id, node_state, &CssPropertyType::Position)
3244            .and_then(|p| p.as_position())
3245    }
3246    pub fn get_top<'a>(
3247        &'a self,
3248        node_data: &'a NodeData,
3249        node_id: &NodeId,
3250        node_state: &StyledNodeState,
3251    ) -> Option<&'a LayoutTopValue> {
3252        self.get_property(node_data, node_id, node_state, &CssPropertyType::Top)
3253            .and_then(|p| p.as_top())
3254    }
3255    pub fn get_bottom<'a>(
3256        &'a self,
3257        node_data: &'a NodeData,
3258        node_id: &NodeId,
3259        node_state: &StyledNodeState,
3260    ) -> Option<&'a LayoutInsetBottomValue> {
3261        self.get_property(node_data, node_id, node_state, &CssPropertyType::Bottom)
3262            .and_then(|p| p.as_bottom())
3263    }
3264    pub fn get_right<'a>(
3265        &'a self,
3266        node_data: &'a NodeData,
3267        node_id: &NodeId,
3268        node_state: &StyledNodeState,
3269    ) -> Option<&'a LayoutRightValue> {
3270        self.get_property(node_data, node_id, node_state, &CssPropertyType::Right)
3271            .and_then(|p| p.as_right())
3272    }
3273    pub fn get_left<'a>(
3274        &'a self,
3275        node_data: &'a NodeData,
3276        node_id: &NodeId,
3277        node_state: &StyledNodeState,
3278    ) -> Option<&'a LayoutLeftValue> {
3279        self.get_property(node_data, node_id, node_state, &CssPropertyType::Left)
3280            .and_then(|p| p.as_left())
3281    }
3282    pub fn get_padding_top<'a>(
3283        &'a self,
3284        node_data: &'a NodeData,
3285        node_id: &NodeId,
3286        node_state: &StyledNodeState,
3287    ) -> Option<&'a LayoutPaddingTopValue> {
3288        self.get_property(node_data, node_id, node_state, &CssPropertyType::PaddingTop)
3289            .and_then(|p| p.as_padding_top())
3290    }
3291    pub fn get_padding_bottom<'a>(
3292        &'a self,
3293        node_data: &'a NodeData,
3294        node_id: &NodeId,
3295        node_state: &StyledNodeState,
3296    ) -> Option<&'a LayoutPaddingBottomValue> {
3297        self.get_property(
3298            node_data,
3299            node_id,
3300            node_state,
3301            &CssPropertyType::PaddingBottom,
3302        )
3303        .and_then(|p| p.as_padding_bottom())
3304    }
3305    pub fn get_padding_left<'a>(
3306        &'a self,
3307        node_data: &'a NodeData,
3308        node_id: &NodeId,
3309        node_state: &StyledNodeState,
3310    ) -> Option<&'a LayoutPaddingLeftValue> {
3311        self.get_property(
3312            node_data,
3313            node_id,
3314            node_state,
3315            &CssPropertyType::PaddingLeft,
3316        )
3317        .and_then(|p| p.as_padding_left())
3318    }
3319    pub fn get_padding_right<'a>(
3320        &'a self,
3321        node_data: &'a NodeData,
3322        node_id: &NodeId,
3323        node_state: &StyledNodeState,
3324    ) -> Option<&'a LayoutPaddingRightValue> {
3325        self.get_property(
3326            node_data,
3327            node_id,
3328            node_state,
3329            &CssPropertyType::PaddingRight,
3330        )
3331        .and_then(|p| p.as_padding_right())
3332    }
3333    pub fn get_margin_top<'a>(
3334        &'a self,
3335        node_data: &'a NodeData,
3336        node_id: &NodeId,
3337        node_state: &StyledNodeState,
3338    ) -> Option<&'a LayoutMarginTopValue> {
3339        self.get_property(node_data, node_id, node_state, &CssPropertyType::MarginTop)
3340            .and_then(|p| p.as_margin_top())
3341    }
3342    pub fn get_margin_bottom<'a>(
3343        &'a self,
3344        node_data: &'a NodeData,
3345        node_id: &NodeId,
3346        node_state: &StyledNodeState,
3347    ) -> Option<&'a LayoutMarginBottomValue> {
3348        self.get_property(
3349            node_data,
3350            node_id,
3351            node_state,
3352            &CssPropertyType::MarginBottom,
3353        )
3354        .and_then(|p| p.as_margin_bottom())
3355    }
3356    pub fn get_margin_left<'a>(
3357        &'a self,
3358        node_data: &'a NodeData,
3359        node_id: &NodeId,
3360        node_state: &StyledNodeState,
3361    ) -> Option<&'a LayoutMarginLeftValue> {
3362        self.get_property(node_data, node_id, node_state, &CssPropertyType::MarginLeft)
3363            .and_then(|p| p.as_margin_left())
3364    }
3365    pub fn get_margin_right<'a>(
3366        &'a self,
3367        node_data: &'a NodeData,
3368        node_id: &NodeId,
3369        node_state: &StyledNodeState,
3370    ) -> Option<&'a LayoutMarginRightValue> {
3371        self.get_property(
3372            node_data,
3373            node_id,
3374            node_state,
3375            &CssPropertyType::MarginRight,
3376        )
3377        .and_then(|p| p.as_margin_right())
3378    }
3379    pub fn get_border_top_width<'a>(
3380        &'a self,
3381        node_data: &'a NodeData,
3382        node_id: &NodeId,
3383        node_state: &StyledNodeState,
3384    ) -> Option<&'a LayoutBorderTopWidthValue> {
3385        self.get_property(
3386            node_data,
3387            node_id,
3388            node_state,
3389            &CssPropertyType::BorderTopWidth,
3390        )
3391        .and_then(|p| p.as_border_top_width())
3392    }
3393    pub fn get_border_left_width<'a>(
3394        &'a self,
3395        node_data: &'a NodeData,
3396        node_id: &NodeId,
3397        node_state: &StyledNodeState,
3398    ) -> Option<&'a LayoutBorderLeftWidthValue> {
3399        self.get_property(
3400            node_data,
3401            node_id,
3402            node_state,
3403            &CssPropertyType::BorderLeftWidth,
3404        )
3405        .and_then(|p| p.as_border_left_width())
3406    }
3407    pub fn get_border_right_width<'a>(
3408        &'a self,
3409        node_data: &'a NodeData,
3410        node_id: &NodeId,
3411        node_state: &StyledNodeState,
3412    ) -> Option<&'a LayoutBorderRightWidthValue> {
3413        self.get_property(
3414            node_data,
3415            node_id,
3416            node_state,
3417            &CssPropertyType::BorderRightWidth,
3418        )
3419        .and_then(|p| p.as_border_right_width())
3420    }
3421    pub fn get_border_bottom_width<'a>(
3422        &'a self,
3423        node_data: &'a NodeData,
3424        node_id: &NodeId,
3425        node_state: &StyledNodeState,
3426    ) -> Option<&'a LayoutBorderBottomWidthValue> {
3427        self.get_property(
3428            node_data,
3429            node_id,
3430            node_state,
3431            &CssPropertyType::BorderBottomWidth,
3432        )
3433        .and_then(|p| p.as_border_bottom_width())
3434    }
3435    pub fn get_overflow_x<'a>(
3436        &'a self,
3437        node_data: &'a NodeData,
3438        node_id: &NodeId,
3439        node_state: &StyledNodeState,
3440    ) -> Option<&'a LayoutOverflowValue> {
3441        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowX)
3442            .and_then(|p| p.as_overflow_x())
3443    }
3444    pub fn get_overflow_y<'a>(
3445        &'a self,
3446        node_data: &'a NodeData,
3447        node_id: &NodeId,
3448        node_state: &StyledNodeState,
3449    ) -> Option<&'a LayoutOverflowValue> {
3450        self.get_property(node_data, node_id, node_state, &CssPropertyType::OverflowY)
3451            .and_then(|p| p.as_overflow_y())
3452    }
3453    pub fn get_flex_direction<'a>(
3454        &'a self,
3455        node_data: &'a NodeData,
3456        node_id: &NodeId,
3457        node_state: &StyledNodeState,
3458    ) -> Option<&'a LayoutFlexDirectionValue> {
3459        self.get_property(
3460            node_data,
3461            node_id,
3462            node_state,
3463            &CssPropertyType::FlexDirection,
3464        )
3465        .and_then(|p| p.as_flex_direction())
3466    }
3467    pub fn get_flex_wrap<'a>(
3468        &'a self,
3469        node_data: &'a NodeData,
3470        node_id: &NodeId,
3471        node_state: &StyledNodeState,
3472    ) -> Option<&'a LayoutFlexWrapValue> {
3473        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexWrap)
3474            .and_then(|p| p.as_flex_wrap())
3475    }
3476    pub fn get_flex_grow<'a>(
3477        &'a self,
3478        node_data: &'a NodeData,
3479        node_id: &NodeId,
3480        node_state: &StyledNodeState,
3481    ) -> Option<&'a LayoutFlexGrowValue> {
3482        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexGrow)
3483            .and_then(|p| p.as_flex_grow())
3484    }
3485    pub fn get_flex_shrink<'a>(
3486        &'a self,
3487        node_data: &'a NodeData,
3488        node_id: &NodeId,
3489        node_state: &StyledNodeState,
3490    ) -> Option<&'a LayoutFlexShrinkValue> {
3491        self.get_property(node_data, node_id, node_state, &CssPropertyType::FlexShrink)
3492            .and_then(|p| p.as_flex_shrink())
3493    }
3494    pub fn get_justify_content<'a>(
3495        &'a self,
3496        node_data: &'a NodeData,
3497        node_id: &NodeId,
3498        node_state: &StyledNodeState,
3499    ) -> Option<&'a LayoutJustifyContentValue> {
3500        self.get_property(
3501            node_data,
3502            node_id,
3503            node_state,
3504            &CssPropertyType::JustifyContent,
3505        )
3506        .and_then(|p| p.as_justify_content())
3507    }
3508    pub fn get_align_items<'a>(
3509        &'a self,
3510        node_data: &'a NodeData,
3511        node_id: &NodeId,
3512        node_state: &StyledNodeState,
3513    ) -> Option<&'a LayoutAlignItemsValue> {
3514        self.get_property(node_data, node_id, node_state, &CssPropertyType::AlignItems)
3515            .and_then(|p| p.as_align_items())
3516    }
3517    pub fn get_align_content<'a>(
3518        &'a self,
3519        node_data: &'a NodeData,
3520        node_id: &NodeId,
3521        node_state: &StyledNodeState,
3522    ) -> Option<&'a LayoutAlignContentValue> {
3523        self.get_property(
3524            node_data,
3525            node_id,
3526            node_state,
3527            &CssPropertyType::AlignContent,
3528        )
3529        .and_then(|p| p.as_align_content())
3530    }
3531    pub fn get_mix_blend_mode<'a>(
3532        &'a self,
3533        node_data: &'a NodeData,
3534        node_id: &NodeId,
3535        node_state: &StyledNodeState,
3536    ) -> Option<&'a StyleMixBlendModeValue> {
3537        self.get_property(
3538            node_data,
3539            node_id,
3540            node_state,
3541            &CssPropertyType::MixBlendMode,
3542        )
3543        .and_then(|p| p.as_mix_blend_mode())
3544    }
3545    pub fn get_filter<'a>(
3546        &'a self,
3547        node_data: &'a NodeData,
3548        node_id: &NodeId,
3549        node_state: &StyledNodeState,
3550    ) -> Option<&'a StyleFilterVecValue> {
3551        self.get_property(node_data, node_id, node_state, &CssPropertyType::Filter)
3552            .and_then(|p| p.as_filter())
3553    }
3554    pub fn get_backdrop_filter<'a>(
3555        &'a self,
3556        node_data: &'a NodeData,
3557        node_id: &NodeId,
3558        node_state: &StyledNodeState,
3559    ) -> Option<&'a StyleFilterVecValue> {
3560        self.get_property(node_data, node_id, node_state, &CssPropertyType::BackdropFilter)
3561            .and_then(|p| p.as_backdrop_filter())
3562    }
3563    pub fn get_text_shadow<'a>(
3564        &'a self,
3565        node_data: &'a NodeData,
3566        node_id: &NodeId,
3567        node_state: &StyledNodeState,
3568    ) -> Option<&'a StyleBoxShadowValue> {
3569        self.get_property(node_data, node_id, node_state, &CssPropertyType::TextShadow)
3570            .and_then(|p| p.as_text_shadow())
3571    }
3572    pub fn get_list_style_type<'a>(
3573        &'a self,
3574        node_data: &'a NodeData,
3575        node_id: &NodeId,
3576        node_state: &StyledNodeState,
3577    ) -> Option<&'a StyleListStyleTypeValue> {
3578        self.get_property(
3579            node_data,
3580            node_id,
3581            node_state,
3582            &CssPropertyType::ListStyleType,
3583        )
3584        .and_then(|p| p.as_list_style_type())
3585    }
3586    pub fn get_list_style_position<'a>(
3587        &'a self,
3588        node_data: &'a NodeData,
3589        node_id: &NodeId,
3590        node_state: &StyledNodeState,
3591    ) -> Option<&'a StyleListStylePositionValue> {
3592        self.get_property(
3593            node_data,
3594            node_id,
3595            node_state,
3596            &CssPropertyType::ListStylePosition,
3597        )
3598        .and_then(|p| p.as_list_style_position())
3599    }
3600    pub fn get_table_layout<'a>(
3601        &'a self,
3602        node_data: &'a NodeData,
3603        node_id: &NodeId,
3604        node_state: &StyledNodeState,
3605    ) -> Option<&'a LayoutTableLayoutValue> {
3606        self.get_property(
3607            node_data,
3608            node_id,
3609            node_state,
3610            &CssPropertyType::TableLayout,
3611        )
3612        .and_then(|p| p.as_table_layout())
3613    }
3614    pub fn get_border_collapse<'a>(
3615        &'a self,
3616        node_data: &'a NodeData,
3617        node_id: &NodeId,
3618        node_state: &StyledNodeState,
3619    ) -> Option<&'a StyleBorderCollapseValue> {
3620        self.get_property(
3621            node_data,
3622            node_id,
3623            node_state,
3624            &CssPropertyType::BorderCollapse,
3625        )
3626        .and_then(|p| p.as_border_collapse())
3627    }
3628    pub fn get_border_spacing<'a>(
3629        &'a self,
3630        node_data: &'a NodeData,
3631        node_id: &NodeId,
3632        node_state: &StyledNodeState,
3633    ) -> Option<&'a LayoutBorderSpacingValue> {
3634        self.get_property(
3635            node_data,
3636            node_id,
3637            node_state,
3638            &CssPropertyType::BorderSpacing,
3639        )
3640        .and_then(|p| p.as_border_spacing())
3641    }
3642    pub fn get_caption_side<'a>(
3643        &'a self,
3644        node_data: &'a NodeData,
3645        node_id: &NodeId,
3646        node_state: &StyledNodeState,
3647    ) -> Option<&'a StyleCaptionSideValue> {
3648        self.get_property(
3649            node_data,
3650            node_id,
3651            node_state,
3652            &CssPropertyType::CaptionSide,
3653        )
3654        .and_then(|p| p.as_caption_side())
3655    }
3656    pub fn get_empty_cells<'a>(
3657        &'a self,
3658        node_data: &'a NodeData,
3659        node_id: &NodeId,
3660        node_state: &StyledNodeState,
3661    ) -> Option<&'a StyleEmptyCellsValue> {
3662        self.get_property(node_data, node_id, node_state, &CssPropertyType::EmptyCells)
3663            .and_then(|p| p.as_empty_cells())
3664    }
3665
3666    // Width calculation methods
3667    pub fn calc_width(
3668        &self,
3669        node_data: &NodeData,
3670        node_id: &NodeId,
3671        styled_node_state: &StyledNodeState,
3672        reference_width: f32,
3673    ) -> f32 {
3674        self.get_width(node_data, node_id, styled_node_state)
3675            .and_then(|w| match w.get_property()? {
3676                LayoutWidth::Px(px) => Some(px.to_pixels_internal(
3677                    reference_width,
3678                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3679                )),
3680                _ => Some(0.0), // min-content/max-content not resolved here
3681            })
3682            .unwrap_or(0.0)
3683    }
3684
3685    pub fn calc_min_width(
3686        &self,
3687        node_data: &NodeData,
3688        node_id: &NodeId,
3689        styled_node_state: &StyledNodeState,
3690        reference_width: f32,
3691    ) -> f32 {
3692        self.get_min_width(node_data, node_id, styled_node_state)
3693            .and_then(|w| {
3694                Some(w.get_property()?.inner.to_pixels_internal(
3695                    reference_width,
3696                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3697                ))
3698            })
3699            .unwrap_or(0.0)
3700    }
3701
3702    pub fn calc_max_width(
3703        &self,
3704        node_data: &NodeData,
3705        node_id: &NodeId,
3706        styled_node_state: &StyledNodeState,
3707        reference_width: f32,
3708    ) -> Option<f32> {
3709        self.get_max_width(node_data, node_id, styled_node_state)
3710            .and_then(|w| {
3711                Some(w.get_property()?.inner.to_pixels_internal(
3712                    reference_width,
3713                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3714                ))
3715            })
3716    }
3717
3718    // Height calculation methods
3719    pub fn calc_height(
3720        &self,
3721        node_data: &NodeData,
3722        node_id: &NodeId,
3723        styled_node_state: &StyledNodeState,
3724        reference_height: f32,
3725    ) -> f32 {
3726        self.get_height(node_data, node_id, styled_node_state)
3727            .and_then(|h| match h.get_property()? {
3728                LayoutHeight::Px(px) => Some(px.to_pixels_internal(
3729                    reference_height,
3730                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3731                )),
3732                _ => Some(0.0), // min-content/max-content not resolved here
3733            })
3734            .unwrap_or(0.0)
3735    }
3736
3737    pub fn calc_min_height(
3738        &self,
3739        node_data: &NodeData,
3740        node_id: &NodeId,
3741        styled_node_state: &StyledNodeState,
3742        reference_height: f32,
3743    ) -> f32 {
3744        self.get_min_height(node_data, node_id, styled_node_state)
3745            .and_then(|h| {
3746                Some(h.get_property()?.inner.to_pixels_internal(
3747                    reference_height,
3748                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3749                ))
3750            })
3751            .unwrap_or(0.0)
3752    }
3753
3754    pub fn calc_max_height(
3755        &self,
3756        node_data: &NodeData,
3757        node_id: &NodeId,
3758        styled_node_state: &StyledNodeState,
3759        reference_height: f32,
3760    ) -> Option<f32> {
3761        self.get_max_height(node_data, node_id, styled_node_state)
3762            .and_then(|h| {
3763                Some(h.get_property()?.inner.to_pixels_internal(
3764                    reference_height,
3765                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3766                ))
3767            })
3768    }
3769
3770    // Position calculation methods
3771    pub fn calc_left(
3772        &self,
3773        node_data: &NodeData,
3774        node_id: &NodeId,
3775        styled_node_state: &StyledNodeState,
3776        reference_width: f32,
3777    ) -> Option<f32> {
3778        self.get_left(node_data, node_id, styled_node_state)
3779            .and_then(|l| {
3780                Some(l.get_property()?.inner.to_pixels_internal(
3781                    reference_width,
3782                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3783                ))
3784            })
3785    }
3786
3787    pub fn calc_right(
3788        &self,
3789        node_data: &NodeData,
3790        node_id: &NodeId,
3791        styled_node_state: &StyledNodeState,
3792        reference_width: f32,
3793    ) -> Option<f32> {
3794        self.get_right(node_data, node_id, styled_node_state)
3795            .and_then(|r| {
3796                Some(r.get_property()?.inner.to_pixels_internal(
3797                    reference_width,
3798                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3799                ))
3800            })
3801    }
3802
3803    pub fn calc_top(
3804        &self,
3805        node_data: &NodeData,
3806        node_id: &NodeId,
3807        styled_node_state: &StyledNodeState,
3808        reference_height: f32,
3809    ) -> Option<f32> {
3810        self.get_top(node_data, node_id, styled_node_state)
3811            .and_then(|t| {
3812                Some(t.get_property()?.inner.to_pixels_internal(
3813                    reference_height,
3814                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3815                ))
3816            })
3817    }
3818
3819    pub fn calc_bottom(
3820        &self,
3821        node_data: &NodeData,
3822        node_id: &NodeId,
3823        styled_node_state: &StyledNodeState,
3824        reference_height: f32,
3825    ) -> Option<f32> {
3826        self.get_bottom(node_data, node_id, styled_node_state)
3827            .and_then(|b| {
3828                Some(b.get_property()?.inner.to_pixels_internal(
3829                    reference_height,
3830                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3831                ))
3832            })
3833    }
3834
3835    // Border calculation methods
3836    pub fn calc_border_left_width(
3837        &self,
3838        node_data: &NodeData,
3839        node_id: &NodeId,
3840        styled_node_state: &StyledNodeState,
3841        reference_width: f32,
3842    ) -> f32 {
3843        self.get_border_left_width(node_data, node_id, styled_node_state)
3844            .and_then(|b| {
3845                Some(b.get_property()?.inner.to_pixels_internal(
3846                    reference_width,
3847                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3848                ))
3849            })
3850            .unwrap_or(0.0)
3851    }
3852
3853    pub fn calc_border_right_width(
3854        &self,
3855        node_data: &NodeData,
3856        node_id: &NodeId,
3857        styled_node_state: &StyledNodeState,
3858        reference_width: f32,
3859    ) -> f32 {
3860        self.get_border_right_width(node_data, node_id, styled_node_state)
3861            .and_then(|b| {
3862                Some(b.get_property()?.inner.to_pixels_internal(
3863                    reference_width,
3864                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3865                ))
3866            })
3867            .unwrap_or(0.0)
3868    }
3869
3870    pub fn calc_border_top_width(
3871        &self,
3872        node_data: &NodeData,
3873        node_id: &NodeId,
3874        styled_node_state: &StyledNodeState,
3875        reference_height: f32,
3876    ) -> f32 {
3877        self.get_border_top_width(node_data, node_id, styled_node_state)
3878            .and_then(|b| {
3879                Some(b.get_property()?.inner.to_pixels_internal(
3880                    reference_height,
3881                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3882                ))
3883            })
3884            .unwrap_or(0.0)
3885    }
3886
3887    pub fn calc_border_bottom_width(
3888        &self,
3889        node_data: &NodeData,
3890        node_id: &NodeId,
3891        styled_node_state: &StyledNodeState,
3892        reference_height: f32,
3893    ) -> f32 {
3894        self.get_border_bottom_width(node_data, node_id, styled_node_state)
3895            .and_then(|b| {
3896                Some(b.get_property()?.inner.to_pixels_internal(
3897                    reference_height,
3898                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3899                ))
3900            })
3901            .unwrap_or(0.0)
3902    }
3903
3904    // Padding calculation methods
3905    pub fn calc_padding_left(
3906        &self,
3907        node_data: &NodeData,
3908        node_id: &NodeId,
3909        styled_node_state: &StyledNodeState,
3910        reference_width: f32,
3911    ) -> f32 {
3912        self.get_padding_left(node_data, node_id, styled_node_state)
3913            .and_then(|p| {
3914                Some(p.get_property()?.inner.to_pixels_internal(
3915                    reference_width,
3916                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3917                ))
3918            })
3919            .unwrap_or(0.0)
3920    }
3921
3922    pub fn calc_padding_right(
3923        &self,
3924        node_data: &NodeData,
3925        node_id: &NodeId,
3926        styled_node_state: &StyledNodeState,
3927        reference_width: f32,
3928    ) -> f32 {
3929        self.get_padding_right(node_data, node_id, styled_node_state)
3930            .and_then(|p| {
3931                Some(p.get_property()?.inner.to_pixels_internal(
3932                    reference_width,
3933                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3934                ))
3935            })
3936            .unwrap_or(0.0)
3937    }
3938
3939    pub fn calc_padding_top(
3940        &self,
3941        node_data: &NodeData,
3942        node_id: &NodeId,
3943        styled_node_state: &StyledNodeState,
3944        reference_height: f32,
3945    ) -> f32 {
3946        self.get_padding_top(node_data, node_id, styled_node_state)
3947            .and_then(|p| {
3948                Some(p.get_property()?.inner.to_pixels_internal(
3949                    reference_height,
3950                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3951                ))
3952            })
3953            .unwrap_or(0.0)
3954    }
3955
3956    pub fn calc_padding_bottom(
3957        &self,
3958        node_data: &NodeData,
3959        node_id: &NodeId,
3960        styled_node_state: &StyledNodeState,
3961        reference_height: f32,
3962    ) -> f32 {
3963        self.get_padding_bottom(node_data, node_id, styled_node_state)
3964            .and_then(|p| {
3965                Some(p.get_property()?.inner.to_pixels_internal(
3966                    reference_height,
3967                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3968                ))
3969            })
3970            .unwrap_or(0.0)
3971    }
3972
3973    // Margin calculation methods
3974    pub fn calc_margin_left(
3975        &self,
3976        node_data: &NodeData,
3977        node_id: &NodeId,
3978        styled_node_state: &StyledNodeState,
3979        reference_width: f32,
3980    ) -> f32 {
3981        self.get_margin_left(node_data, node_id, styled_node_state)
3982            .and_then(|m| {
3983                Some(m.get_property()?.inner.to_pixels_internal(
3984                    reference_width,
3985                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
3986                ))
3987            })
3988            .unwrap_or(0.0)
3989    }
3990
3991    pub fn calc_margin_right(
3992        &self,
3993        node_data: &NodeData,
3994        node_id: &NodeId,
3995        styled_node_state: &StyledNodeState,
3996        reference_width: f32,
3997    ) -> f32 {
3998        self.get_margin_right(node_data, node_id, styled_node_state)
3999            .and_then(|m| {
4000                Some(m.get_property()?.inner.to_pixels_internal(
4001                    reference_width,
4002                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4003                ))
4004            })
4005            .unwrap_or(0.0)
4006    }
4007
4008    pub fn calc_margin_top(
4009        &self,
4010        node_data: &NodeData,
4011        node_id: &NodeId,
4012        styled_node_state: &StyledNodeState,
4013        reference_height: f32,
4014    ) -> f32 {
4015        self.get_margin_top(node_data, node_id, styled_node_state)
4016            .and_then(|m| {
4017                Some(m.get_property()?.inner.to_pixels_internal(
4018                    reference_height,
4019                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4020                ))
4021            })
4022            .unwrap_or(0.0)
4023    }
4024
4025    pub fn calc_margin_bottom(
4026        &self,
4027        node_data: &NodeData,
4028        node_id: &NodeId,
4029        styled_node_state: &StyledNodeState,
4030        reference_height: f32,
4031    ) -> f32 {
4032        self.get_margin_bottom(node_data, node_id, styled_node_state)
4033            .and_then(|m| {
4034                Some(m.get_property()?.inner.to_pixels_internal(
4035                    reference_height,
4036                    azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
4037                ))
4038            })
4039            .unwrap_or(0.0)
4040    }
4041
4042    /// Helper function to resolve a CSS property value that may depend on another property.
4043    ///
4044    /// This attempts to compute a final pixel value from a property that uses relative units
4045    /// (em, %, etc.) by referencing another property value.
4046    ///
4047    /// # Arguments
4048    /// * `target_property` - The property to resolve (e.g., child's font-size: 2em)
4049    /// * `reference_property` - The property it depends on (e.g., parent's font-size: 16px)
4050    ///
4051    /// # Returns
4052    /// * `Some(CssProperty)` - A new property with absolute pixel values
4053    /// * `None` - If the property can't be resolved (missing data, incompatible types, etc.)
4054    ///
4055    /// # Examples
4056    /// - `resolve_property_dependency(font-size: 2em, font-size: 16px)` → `font-size: 32px`
4057    /// - `resolve_property_dependency(font-size: 150%, font-size: 20px)` → `font-size: 30px`
4058    /// - `resolve_property_dependency(padding: 2em, font-size: 16px)` → `padding: 32px`
4059
4060    /// Resolves CSS cascade keywords (inherit, initial, revert, unset) for a property.
4061    ///
4062    /// According to CSS Cascade spec (https://css-tricks.com/inherit-initial-unset-revert/):
4063    /// - `inherit`: Use the parent's computed value (or initial value if no parent)
4064    /// - `initial`: Use the CSS-defined initial value (default for that property type)
4065    /// - `revert`: Roll back to the user-agent stylesheet value (if any)
4066    /// - `unset`: Behaves as `inherit` for inherited properties, `initial` for non-inherited
4067    ///   properties
4068    ///
4069    /// # Arguments
4070    /// * `property` - The property to resolve
4071    /// * `property_type` - The type of the property
4072    /// * `node_type` - The node type (for UA CSS lookup)
4073    /// * `parent_value` - The parent's computed value (for inheritance)
4074    /// * `ua_value` - The user-agent stylesheet value (for revert)
4075    ///
4076    /// # Returns
4077    /// * `Some(CssProperty)` - The resolved property
4078    /// * `None` - If the keyword doesn't apply or can't be resolved
4079    fn resolve_cascade_keyword(
4080        property: &CssProperty,
4081        property_type: CssPropertyType,
4082        _node_type: &crate::dom::NodeType,
4083        parent_value: Option<&CssProperty>,
4084        ua_value: Option<&'static CssProperty>,
4085    ) -> Option<CssProperty> {
4086        // For now, implement basic inheritance
4087        // Check if this is an inheritable property and return parent value
4088        if property_type.is_inheritable() {
4089            return parent_value.cloned().or_else(|| ua_value.cloned());
4090        } else {
4091            return ua_value.cloned();
4092        }
4093    }
4094
4095    fn resolve_property_dependency(
4096        target_property: &CssProperty,
4097        reference_property: &CssProperty,
4098    ) -> Option<CssProperty> {
4099        use azul_css::{
4100            css::CssPropertyValue,
4101            props::{
4102                basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
4103                layout::*,
4104                style::{SelectionRadius, StyleLetterSpacing, StyleWordSpacing},
4105            },
4106        };
4107
4108        // Extract PixelValue from various property types (returns owned value)
4109        let get_pixel_value = |prop: &CssProperty| -> Option<PixelValue> {
4110            match prop {
4111                CssProperty::FontSize(val) => val.get_property().map(|v| v.inner),
4112                CssProperty::LetterSpacing(val) => val.get_property().map(|v| v.inner),
4113                CssProperty::WordSpacing(val) => val.get_property().map(|v| v.inner),
4114                CssProperty::PaddingLeft(val) => val.get_property().map(|v| v.inner),
4115                CssProperty::PaddingRight(val) => val.get_property().map(|v| v.inner),
4116                CssProperty::PaddingTop(val) => val.get_property().map(|v| v.inner),
4117                CssProperty::PaddingBottom(val) => val.get_property().map(|v| v.inner),
4118                CssProperty::MarginLeft(val) => val.get_property().map(|v| v.inner),
4119                CssProperty::MarginRight(val) => val.get_property().map(|v| v.inner),
4120                CssProperty::MarginTop(val) => val.get_property().map(|v| v.inner),
4121                CssProperty::MarginBottom(val) => val.get_property().map(|v| v.inner),
4122                CssProperty::MinWidth(val) => val.get_property().map(|v| v.inner),
4123                CssProperty::MinHeight(val) => val.get_property().map(|v| v.inner),
4124                CssProperty::MaxWidth(val) => val.get_property().map(|v| v.inner),
4125                CssProperty::MaxHeight(val) => val.get_property().map(|v| v.inner),
4126                CssProperty::SelectionRadius(val) => val.get_property().map(|v| v.inner),
4127                _ => None,
4128            }
4129        };
4130
4131        let target_pixel_value = get_pixel_value(target_property)?;
4132        let reference_pixel_value = get_pixel_value(reference_property)?;
4133
4134        // Convert reference to absolute pixels first
4135        let reference_px = match reference_pixel_value.metric {
4136            SizeMetric::Px => reference_pixel_value.number.get(),
4137            SizeMetric::Pt => reference_pixel_value.number.get() * 1.333333,
4138            SizeMetric::In => reference_pixel_value.number.get() * 96.0,
4139            SizeMetric::Cm => reference_pixel_value.number.get() * 37.7952755906,
4140            SizeMetric::Mm => reference_pixel_value.number.get() * 3.7795275591,
4141            SizeMetric::Em => return None, // Reference can't be relative
4142            SizeMetric::Rem => return None, // Reference can't be relative
4143            SizeMetric::Percent => return None, // Reference can't be relative
4144            // Reference can't be viewport-relative
4145            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => return None,
4146        };
4147
4148        // Resolve target based on reference
4149        let resolved_px = match target_pixel_value.metric {
4150            SizeMetric::Px => target_pixel_value.number.get(),
4151            SizeMetric::Pt => target_pixel_value.number.get() * 1.333333,
4152            SizeMetric::In => target_pixel_value.number.get() * 96.0,
4153            SizeMetric::Cm => target_pixel_value.number.get() * 37.7952755906,
4154            SizeMetric::Mm => target_pixel_value.number.get() * 3.7795275591,
4155            SizeMetric::Em => target_pixel_value.number.get() * reference_px,
4156            // Use reference as root font-size
4157            SizeMetric::Rem => target_pixel_value.number.get() * reference_px,
4158            SizeMetric::Percent => target_pixel_value.number.get() / 100.0 * reference_px,
4159            // Need viewport context
4160            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => return None,
4161        };
4162
4163        // Create a new property with the resolved value
4164        let resolved_pixel_value = PixelValue::px(resolved_px);
4165
4166        match target_property {
4167            CssProperty::FontSize(_) => Some(CssProperty::FontSize(CssPropertyValue::Exact(
4168                StyleFontSize {
4169                    inner: resolved_pixel_value,
4170                },
4171            ))),
4172            CssProperty::LetterSpacing(_) => Some(CssProperty::LetterSpacing(
4173                CssPropertyValue::Exact(StyleLetterSpacing {
4174                    inner: resolved_pixel_value,
4175                }),
4176            )),
4177            CssProperty::WordSpacing(_) => Some(CssProperty::WordSpacing(CssPropertyValue::Exact(
4178                StyleWordSpacing {
4179                    inner: resolved_pixel_value,
4180                },
4181            ))),
4182            CssProperty::PaddingLeft(_) => Some(CssProperty::PaddingLeft(CssPropertyValue::Exact(
4183                LayoutPaddingLeft {
4184                    inner: resolved_pixel_value,
4185                },
4186            ))),
4187            CssProperty::PaddingRight(_) => Some(CssProperty::PaddingRight(
4188                CssPropertyValue::Exact(LayoutPaddingRight {
4189                    inner: resolved_pixel_value,
4190                }),
4191            )),
4192            CssProperty::PaddingTop(_) => Some(CssProperty::PaddingTop(CssPropertyValue::Exact(
4193                LayoutPaddingTop {
4194                    inner: resolved_pixel_value,
4195                },
4196            ))),
4197            CssProperty::PaddingBottom(_) => Some(CssProperty::PaddingBottom(
4198                CssPropertyValue::Exact(LayoutPaddingBottom {
4199                    inner: resolved_pixel_value,
4200                }),
4201            )),
4202            CssProperty::MarginLeft(_) => Some(CssProperty::MarginLeft(CssPropertyValue::Exact(
4203                LayoutMarginLeft {
4204                    inner: resolved_pixel_value,
4205                },
4206            ))),
4207            CssProperty::MarginRight(_) => Some(CssProperty::MarginRight(CssPropertyValue::Exact(
4208                LayoutMarginRight {
4209                    inner: resolved_pixel_value,
4210                },
4211            ))),
4212            CssProperty::MarginTop(_) => Some(CssProperty::MarginTop(CssPropertyValue::Exact(
4213                LayoutMarginTop {
4214                    inner: resolved_pixel_value,
4215                },
4216            ))),
4217            CssProperty::MarginBottom(_) => Some(CssProperty::MarginBottom(
4218                CssPropertyValue::Exact(LayoutMarginBottom {
4219                    inner: resolved_pixel_value,
4220                }),
4221            )),
4222            CssProperty::MinWidth(_) => Some(CssProperty::MinWidth(CssPropertyValue::Exact(
4223                LayoutMinWidth {
4224                    inner: resolved_pixel_value,
4225                },
4226            ))),
4227            CssProperty::MinHeight(_) => Some(CssProperty::MinHeight(CssPropertyValue::Exact(
4228                LayoutMinHeight {
4229                    inner: resolved_pixel_value,
4230                },
4231            ))),
4232            CssProperty::MaxWidth(_) => Some(CssProperty::MaxWidth(CssPropertyValue::Exact(
4233                LayoutMaxWidth {
4234                    inner: resolved_pixel_value,
4235                },
4236            ))),
4237            CssProperty::MaxHeight(_) => Some(CssProperty::MaxHeight(CssPropertyValue::Exact(
4238                LayoutMaxHeight {
4239                    inner: resolved_pixel_value,
4240                },
4241            ))),
4242            CssProperty::SelectionRadius(_) => Some(CssProperty::SelectionRadius(
4243                CssPropertyValue::Exact(SelectionRadius {
4244                    inner: resolved_pixel_value,
4245                }),
4246            )),
4247            _ => None,
4248        }
4249    }
4250
4251    /// Build a dependency chain for a CSS property value.
4252    ///
4253    /// This analyzes the property value and creates a chain of dependencies that can be
4254    /// resolved later during layout. For example:
4255    ///
4256    /// - `font-size: 16px` → Absolute chain with 16.0 pixels
4257    /// - `font-size: 1.5em` → Em chain depending on parent's font-size
4258    /// - `font-size: 150%` → Percent chain depending on parent's font-size
4259    /// - `padding: 2em` → Em chain depending on current node's font-size
4260    ///
4261    /// # Arguments
4262    ///
4263    /// * `node_id` - The node this property belongs to
4264    /// * `parent_id` - The parent node (for inheritance)
4265    /// * `property` - The CSS property to analyze
4266    ///
4267    /// Returns a dependency chain, or None if the property doesn't support chaining
4268    fn build_dependency_chain(
4269        &self,
4270        node_id: NodeId,
4271        parent_id: Option<NodeId>,
4272        property: &CssProperty,
4273    ) -> Option<CssDependencyChain> {
4274        use azul_css::props::basic::{length::SizeMetric, pixel::PixelValue};
4275
4276        let prop_type = property.get_type();
4277
4278        // For now, only handle font-size dependency chains
4279        // Other properties will be handled during layout resolution
4280        if prop_type != CssPropertyType::FontSize {
4281            return None;
4282        }
4283
4284        // Extract PixelValue from FontSize property
4285        let pixel_value = match property {
4286            CssProperty::FontSize(val) => val.get_property().map(|v| &v.inner)?,
4287            _ => return None,
4288        };
4289
4290        let number = pixel_value.number.get();
4291
4292        // For font-size: em/% refers to parent's font-size
4293        let source_node = parent_id?;
4294
4295        match pixel_value.metric {
4296            SizeMetric::Px => Some(CssDependencyChain::absolute(prop_type, number)),
4297            SizeMetric::Pt => {
4298                // 1pt = 1.333333px
4299                Some(CssDependencyChain::absolute(prop_type, number * 1.333333))
4300            }
4301            SizeMetric::Em => Some(CssDependencyChain::em(prop_type, source_node, number)),
4302            SizeMetric::Rem => {
4303                // Rem refers to root font-size
4304                Some(CssDependencyChain::rem(prop_type, number))
4305            }
4306            SizeMetric::Percent => Some(CssDependencyChain::percent(
4307                prop_type,
4308                source_node,
4309                number / 100.0,
4310            )),
4311            SizeMetric::In => {
4312                // 1in = 96px
4313                Some(CssDependencyChain::absolute(prop_type, number * 96.0))
4314            }
4315            SizeMetric::Cm => {
4316                // 1cm = 37.7952755906px
4317                Some(CssDependencyChain::absolute(
4318                    prop_type,
4319                    number * 37.7952755906,
4320                ))
4321            }
4322            SizeMetric::Mm => {
4323                // 1mm = 3.7795275591px
4324                Some(CssDependencyChain::absolute(
4325                    prop_type,
4326                    number * 3.7795275591,
4327                ))
4328            }
4329            // Viewport units: Cannot be resolved via dependency chain, need viewport context
4330            // These should be resolved at layout time using ResolutionContext
4331            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
4332                // For now, treat as unresolvable (need viewport size at layout time)
4333                None
4334            }
4335        }
4336    }
4337
4338    /// Applies user-agent (UA) CSS properties to the cascade before inheritance.
4339    ///
4340    /// UA CSS has the lowest priority in the cascade, so it should only be applied
4341    /// if the node doesn't already have the property from inline styles or author CSS.
4342    ///
4343    /// This is critical for text nodes: UA CSS properties (like font-weight: bold for H1)
4344    /// must be in the cascade maps so they can be inherited by child text nodes.
4345    pub fn apply_ua_css(&mut self, node_data: &[NodeData]) {
4346        use azul_css::props::property::CssPropertyType;
4347
4348        // Apply UA CSS to all nodes
4349        for (node_index, node) in node_data.iter().enumerate() {
4350            let node_id = NodeId::new(node_index);
4351            let node_type = &node.node_type;
4352
4353            // Get all possible CSS property types and check if UA CSS defines them
4354            // We need to check all properties that this node type might have
4355            let property_types = [
4356                CssPropertyType::Display,
4357                CssPropertyType::Width,
4358                CssPropertyType::Height,
4359                CssPropertyType::FontSize,
4360                CssPropertyType::FontWeight,
4361                CssPropertyType::FontFamily,
4362                CssPropertyType::MarginTop,
4363                CssPropertyType::MarginBottom,
4364                CssPropertyType::MarginLeft,
4365                CssPropertyType::MarginRight,
4366                CssPropertyType::PaddingTop,
4367                CssPropertyType::PaddingBottom,
4368                CssPropertyType::PaddingLeft,
4369                CssPropertyType::PaddingRight,
4370                // Add more as needed
4371            ];
4372
4373            for prop_type in &property_types {
4374                // Check if UA CSS defines this property for this node type
4375                if let Some(ua_prop) = crate::ua_css::get_ua_property(node_type, *prop_type) {
4376                    // Only insert if the property is NOT already set by inline CSS or author CSS
4377                    // UA CSS has LOWEST priority
4378                    let has_inline = node.css_props.iter().any(|p| {
4379                        // Check if this is a normal (unconditional) property
4380                        let is_normal = p.apply_if.as_slice().is_empty();
4381                        is_normal && p.property.get_type() == *prop_type
4382                    });
4383
4384                    let has_css = self
4385                        .css_normal_props
4386                        .get(node_id.index())
4387                        .map(|map| map.contains_key(prop_type))
4388                        .unwrap_or(false);
4389
4390                    let has_cascaded = self
4391                        .cascaded_normal_props
4392                        .get(node_id.index())
4393                        .map(|map| map.contains_key(prop_type))
4394                        .unwrap_or(false);
4395
4396                    // Insert UA CSS only if not already present (lowest priority)
4397                    if !has_inline && !has_css && !has_cascaded {
4398                        self.cascaded_normal_props[node_id.index()]
4399                            .entry(*prop_type)
4400                            .or_insert_with(|| ua_prop.clone());
4401                    }
4402                }
4403            }
4404        }
4405    }
4406
4407    /// Compute inherited values and dependency chains for all nodes in the DOM tree.
4408    ///
4409    /// Implements CSS inheritance: walk tree depth-first, apply cascade priority
4410    /// (inherited → cascaded → css → inline → user), create dependency chains for
4411    /// relative values. Call `apply_ua_css()` before this function.
4412    pub fn compute_inherited_values(
4413        &mut self,
4414        node_hierarchy: &[NodeHierarchyItem],
4415        node_data: &[NodeData],
4416    ) -> Vec<NodeId> {
4417        node_hierarchy
4418            .iter()
4419            .enumerate()
4420            .filter_map(|(node_index, hierarchy_item)| {
4421                let node_id = NodeId::new(node_index);
4422                let parent_id = hierarchy_item.parent_id();
4423                let parent_computed =
4424                    parent_id.and_then(|pid| self.computed_values.get(pid.index()).cloned());
4425
4426                let mut ctx = InheritanceContext {
4427                    node_id,
4428                    parent_id,
4429                    computed_values: BTreeMap::new(),
4430                    dependency_chains: BTreeMap::new(),
4431                };
4432
4433                // Step 1: Inherit from parent
4434                if let Some(ref parent_values) = parent_computed {
4435                    self.inherit_from_parent(&mut ctx, parent_values);
4436                }
4437
4438                // Steps 2-5: Apply cascade in priority order
4439                self.apply_cascade_properties(
4440                    &mut ctx,
4441                    node_id,
4442                    &parent_computed,
4443                    node_data,
4444                    node_index,
4445                );
4446
4447                // Check for changes and store
4448                let changed = self.store_if_changed(&ctx);
4449                changed.then_some(node_id)
4450            })
4451            .collect()
4452    }
4453
4454    /// Inherit inheritable properties from parent node
4455    fn inherit_from_parent(
4456        &self,
4457        ctx: &mut InheritanceContext,
4458        parent_values: &BTreeMap<CssPropertyType, CssPropertyWithOrigin>,
4459    ) {
4460        for (prop_type, prop_with_origin) in
4461            parent_values.iter().filter(|(pt, _)| pt.is_inheritable())
4462        {
4463            ctx.computed_values.insert(
4464                *prop_type,
4465                CssPropertyWithOrigin {
4466                    property: prop_with_origin.property.clone(),
4467                    origin: CssPropertyOrigin::Inherited,
4468                },
4469            );
4470
4471            // Don't inherit font-size chains (would cause double resolution)
4472            if *prop_type == CssPropertyType::FontSize {
4473                continue;
4474            }
4475
4476            if let Some(chain) = ctx
4477                .parent_id
4478                .and_then(|pid| self.dependency_chains.get(pid.index()))
4479                .and_then(|chains| chains.get(prop_type))
4480            {
4481                ctx.dependency_chains.insert(*prop_type, chain.clone());
4482            }
4483        }
4484    }
4485
4486    /// Apply all cascade properties in priority order
4487    fn apply_cascade_properties(
4488        &self,
4489        ctx: &mut InheritanceContext,
4490        node_id: NodeId,
4491        parent_computed: &Option<BTreeMap<CssPropertyType, CssPropertyWithOrigin>>,
4492        node_data: &[NodeData],
4493        node_index: usize,
4494    ) {
4495        // Step 2: Cascaded properties (UA CSS)
4496        if let Some(cascaded_props) = self.cascaded_normal_props.get(node_id.index()).cloned() {
4497            for (prop_type, prop) in cascaded_props.iter() {
4498                if self.should_apply_cascaded(&ctx.computed_values, *prop_type, prop) {
4499                    self.process_property(ctx, prop, parent_computed);
4500                }
4501            }
4502        }
4503
4504        // Step 3: CSS properties (stylesheets)
4505        if let Some(css_props) = self.css_normal_props.get(node_id.index()) {
4506            for (_, prop) in css_props.iter() {
4507                self.process_property(ctx, prop, parent_computed);
4508            }
4509        }
4510
4511        // Step 4: Inline CSS properties
4512        for inline_prop in node_data[node_index].css_props.iter() {
4513            // Only apply unconditional (normal) properties
4514            if inline_prop.apply_if.as_slice().is_empty() {
4515                self.process_property(ctx, &inline_prop.property, parent_computed);
4516            }
4517        }
4518
4519        // Step 5: User-overridden properties
4520        if let Some(user_props) = self.user_overridden_properties.get(node_id.index()) {
4521            for (_, prop) in user_props.iter() {
4522                self.process_property(ctx, prop, parent_computed);
4523            }
4524        }
4525    }
4526
4527    /// Check if a cascaded property should be applied
4528    fn should_apply_cascaded(
4529        &self,
4530        computed: &BTreeMap<CssPropertyType, CssPropertyWithOrigin>,
4531        prop_type: CssPropertyType,
4532        prop: &CssProperty,
4533    ) -> bool {
4534        // Skip relative font-size if we already have inherited resolved value
4535        if prop_type == CssPropertyType::FontSize {
4536            if let Some(existing) = computed.get(&prop_type) {
4537                if existing.origin == CssPropertyOrigin::Inherited
4538                    && Self::has_relative_font_size_unit(prop)
4539                {
4540                    return false;
4541                }
4542            }
4543        }
4544
4545        match computed.get(&prop_type) {
4546            None => true,
4547            Some(existing) => existing.origin == CssPropertyOrigin::Inherited,
4548        }
4549    }
4550
4551    /// Process a single property: resolve dependencies and store
4552    fn process_property(
4553        &self,
4554        ctx: &mut InheritanceContext,
4555        prop: &CssProperty,
4556        parent_computed: &Option<BTreeMap<CssPropertyType, CssPropertyWithOrigin>>,
4557    ) {
4558        let prop_type = prop.get_type();
4559
4560        let resolved = if prop_type == CssPropertyType::FontSize {
4561            self.resolve_font_size_property(prop, parent_computed)
4562        } else {
4563            self.resolve_other_property(prop, &ctx.computed_values)
4564        };
4565
4566        ctx.computed_values.insert(
4567            prop_type,
4568            CssPropertyWithOrigin {
4569                property: resolved.clone(),
4570                origin: CssPropertyOrigin::Own,
4571            },
4572        );
4573
4574        if let Some(chain) = self.build_dependency_chain(ctx.node_id, ctx.parent_id, &resolved) {
4575            ctx.dependency_chains.insert(prop_type, chain);
4576        }
4577    }
4578
4579    /// Resolve font-size property (uses parent's font-size as reference)
4580    fn resolve_font_size_property(
4581        &self,
4582        prop: &CssProperty,
4583        parent_computed: &Option<BTreeMap<CssPropertyType, CssPropertyWithOrigin>>,
4584    ) -> CssProperty {
4585        const DEFAULT_FONT_SIZE_PX: f32 = 16.0;
4586
4587        let parent_font_size = parent_computed
4588            .as_ref()
4589            .and_then(|p| p.get(&CssPropertyType::FontSize));
4590
4591        match parent_font_size {
4592            Some(pfs) => Self::resolve_property_dependency(prop, &pfs.property)
4593                .unwrap_or_else(|| Self::resolve_font_size_to_pixels(prop, DEFAULT_FONT_SIZE_PX)),
4594            None => Self::resolve_font_size_to_pixels(prop, DEFAULT_FONT_SIZE_PX),
4595        }
4596    }
4597
4598    /// Resolve other properties (uses current node's font-size as reference)
4599    fn resolve_other_property(
4600        &self,
4601        prop: &CssProperty,
4602        computed: &BTreeMap<CssPropertyType, CssPropertyWithOrigin>,
4603    ) -> CssProperty {
4604        computed
4605            .get(&CssPropertyType::FontSize)
4606            .and_then(|fs| Self::resolve_property_dependency(prop, &fs.property))
4607            .unwrap_or_else(|| prop.clone())
4608    }
4609
4610    /// Convert font-size to absolute pixels
4611    fn resolve_font_size_to_pixels(prop: &CssProperty, reference_px: f32) -> CssProperty {
4612        use azul_css::{
4613            css::CssPropertyValue,
4614            props::basic::{font::StyleFontSize, length::SizeMetric, pixel::PixelValue},
4615        };
4616
4617        const DEFAULT_FONT_SIZE_PX: f32 = 16.0;
4618
4619        let CssProperty::FontSize(css_val) = prop else {
4620            return prop.clone();
4621        };
4622
4623        let Some(font_size) = css_val.get_property() else {
4624            return prop.clone();
4625        };
4626
4627        let resolved_px = match font_size.inner.metric {
4628            SizeMetric::Px => font_size.inner.number.get(),
4629            SizeMetric::Pt => font_size.inner.number.get() * 1.333333,
4630            SizeMetric::In => font_size.inner.number.get() * 96.0,
4631            SizeMetric::Cm => font_size.inner.number.get() * 37.7952755906,
4632            SizeMetric::Mm => font_size.inner.number.get() * 3.7795275591,
4633            SizeMetric::Em => font_size.inner.number.get() * reference_px,
4634            SizeMetric::Rem => font_size.inner.number.get() * DEFAULT_FONT_SIZE_PX,
4635            SizeMetric::Percent => font_size.inner.number.get() / 100.0 * reference_px,
4636            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
4637                return prop.clone();
4638            }
4639        };
4640
4641        CssProperty::FontSize(CssPropertyValue::Exact(StyleFontSize {
4642            inner: PixelValue::px(resolved_px),
4643        }))
4644    }
4645
4646    /// Check if font-size has relative unit (em, rem, %)
4647    fn has_relative_font_size_unit(prop: &CssProperty) -> bool {
4648        use azul_css::props::basic::length::SizeMetric;
4649
4650        let CssProperty::FontSize(css_val) = prop else {
4651            return false;
4652        };
4653
4654        css_val
4655            .get_property()
4656            .map(|fs| {
4657                matches!(
4658                    fs.inner.metric,
4659                    SizeMetric::Em | SizeMetric::Rem | SizeMetric::Percent
4660                )
4661            })
4662            .unwrap_or(false)
4663    }
4664
4665    /// Store computed values if changed, returns true if values were updated
4666    fn store_if_changed(&mut self, ctx: &InheritanceContext) -> bool {
4667        let values_changed = self
4668            .computed_values
4669            .get(ctx.node_id.index())
4670            .map(|old| old != &ctx.computed_values)
4671            .unwrap_or(true);
4672
4673        let chains_changed = self
4674            .dependency_chains
4675            .get(ctx.node_id.index())
4676            .map(|old| old != &ctx.dependency_chains)
4677            .unwrap_or(!ctx.dependency_chains.is_empty());
4678
4679        let changed = values_changed || chains_changed;
4680
4681        self.computed_values[ctx.node_id.index()] = ctx.computed_values.clone();
4682        if !ctx.dependency_chains.is_empty() {
4683            self.dependency_chains[ctx.node_id.index()] = ctx.dependency_chains.clone();
4684        }
4685
4686        changed
4687    }
4688}
4689
4690/// Context for computing inherited values for a single node
4691struct InheritanceContext {
4692    node_id: NodeId,
4693    parent_id: Option<NodeId>,
4694    computed_values: BTreeMap<CssPropertyType, CssPropertyWithOrigin>,
4695    dependency_chains: BTreeMap<CssPropertyType, CssDependencyChain>,
4696}
4697
4698impl CssPropertyCache {
4699    /// Resolve a dependency chain to an absolute pixel value.
4700    ///
4701    /// This walks through the chain and resolves each dependency:
4702    /// - Absolute values: return immediately
4703    /// - Em values: multiply by source node's font-size
4704    /// - Percent values: multiply by source node's property value
4705    /// - Rem values: multiply by root node's font-size
4706    ///
4707    /// # Arguments
4708    /// * `node_id` - The node to resolve the property for
4709    /// * `property_type` - The property type to resolve
4710    /// * `root_font_size` - Root element's font-size for rem calculations (default 16px)
4711    ///
4712    /// # Returns
4713    /// The resolved pixel value, or None if the chain couldn't be resolved
4714    pub fn resolve_dependency_chain(
4715        &self,
4716        node_id: NodeId,
4717        property_type: CssPropertyType,
4718        root_font_size: f32,
4719    ) -> Option<f32> {
4720        let chain = self.get_chain(node_id, property_type)?;
4721
4722        if let Some(cached) = chain.cached_pixels {
4723            return Some(cached);
4724        }
4725
4726        chain.steps.clone().iter().try_fold(None, |_, step| {
4727            Some(self.resolve_step(step, property_type, root_font_size))
4728        })?
4729    }
4730
4731    /// Get a dependency chain for a node/property pair
4732    fn get_chain(
4733        &self,
4734        node_id: NodeId,
4735        property_type: CssPropertyType,
4736    ) -> Option<&CssDependencyChain> {
4737        self.dependency_chains
4738            .get(node_id.index())
4739            .and_then(|chains| chains.get(&property_type))
4740    }
4741
4742    /// Resolve a single step in a dependency chain
4743    fn resolve_step(
4744        &self,
4745        step: &CssDependencyChainStep,
4746        property_type: CssPropertyType,
4747        root_font_size: f32,
4748    ) -> Option<f32> {
4749        match step {
4750            CssDependencyChainStep::Absolute { pixels } => Some(*pixels),
4751            CssDependencyChainStep::Percent {
4752                source_node,
4753                factor,
4754            } => {
4755                let source_val = self.get_cached_pixels(*source_node, property_type)?;
4756                Some(source_val * factor)
4757            }
4758            CssDependencyChainStep::Em {
4759                source_node,
4760                factor,
4761            } => {
4762                let font_size = self.get_cached_pixels(*source_node, CssPropertyType::FontSize)?;
4763                Some(font_size * factor)
4764            }
4765            CssDependencyChainStep::Rem { factor } => Some(root_font_size * factor),
4766        }
4767    }
4768
4769    /// Get cached pixel value for a node's property
4770    fn get_cached_pixels(&self, node_id: NodeId, property_type: CssPropertyType) -> Option<f32> {
4771        self.get_chain(node_id, property_type)
4772            .and_then(|chain| chain.cached_pixels)
4773    }
4774
4775    /// Update a property value and invalidate all dependent chains.
4776    ///
4777    /// When a property changes (e.g., font-size changes from 16px to 20px):
4778    /// 1. Update the property value in computed_values
4779    /// 2. Update/rebuild the dependency chain
4780    /// 3. Find all nodes whose chains depend on this node
4781    /// 4. Invalidate their cached values
4782    /// 5. Return list of affected nodes that need re-layout
4783    ///
4784    /// # Arguments
4785    /// * `node_id` - The node whose property changed
4786    /// * `property` - The new property value
4787    /// * `node_hierarchy` - DOM tree (needed to find children)
4788    /// * `node_data` - Node data array
4789    ///
4790    /// # Returns
4791    /// Vector of NodeIds that were affected and need re-layout
4792    pub fn update_property_and_invalidate_dependents(
4793        &mut self,
4794        node_id: NodeId,
4795        property: CssProperty,
4796        node_hierarchy: &[NodeHierarchyItem],
4797        _node_data: &[NodeData],
4798    ) -> Vec<NodeId> {
4799        let prop_type = property.get_type();
4800
4801        self.update_computed_property(node_id, property.clone());
4802        self.rebuild_dependency_chain(node_id, prop_type, &property, node_hierarchy);
4803
4804        let mut affected = self.invalidate_dependents(node_id);
4805        affected.push(node_id);
4806        affected
4807    }
4808
4809    /// Update a property in computed_values with Own origin
4810    fn update_computed_property(&mut self, node_id: NodeId, property: CssProperty) {
4811        self.computed_values[node_id.index()]
4812            .insert(
4813                property.get_type(),
4814                CssPropertyWithOrigin {
4815                    property,
4816                    origin: CssPropertyOrigin::Own,
4817                },
4818            );
4819    }
4820
4821    /// Rebuild the dependency chain for a property
4822    fn rebuild_dependency_chain(
4823        &mut self,
4824        node_id: NodeId,
4825        prop_type: CssPropertyType,
4826        property: &CssProperty,
4827        node_hierarchy: &[NodeHierarchyItem],
4828    ) {
4829        let parent_id = node_hierarchy
4830            .get(node_id.index())
4831            .and_then(|h| h.parent_id());
4832
4833        if let Some(chain) = self.build_dependency_chain(node_id, parent_id, property) {
4834            self.dependency_chains[node_id.index()]
4835                .insert(prop_type, chain);
4836        }
4837    }
4838
4839    /// Invalidate cached values for all chains that depend on a node
4840    fn invalidate_dependents(&mut self, node_id: NodeId) -> Vec<NodeId> {
4841        self.dependency_chains
4842            .iter_mut()
4843            .enumerate()
4844            .filter_map(|(dep_node_idx, chains)| {
4845                let affected = chains.values_mut().any(|chain| {
4846                    if chain.depends_on(node_id) {
4847                        chain.cached_pixels = None;
4848                        true
4849                    } else {
4850                        false
4851                    }
4852                });
4853                affected.then_some(NodeId::new(dep_node_idx))
4854            })
4855            .collect()
4856    }
4857
4858    /// Property types that may have User-Agent CSS defaults.
4859    /// Used by build_resolved_cache to ensure UA CSS properties are included.
4860    const UA_PROPERTY_TYPES: &'static [CssPropertyType] = &[
4861        CssPropertyType::Display,
4862        CssPropertyType::Width,
4863        CssPropertyType::Height,
4864        CssPropertyType::FontSize,
4865        CssPropertyType::FontWeight,
4866        CssPropertyType::FontFamily,
4867        CssPropertyType::MarginTop,
4868        CssPropertyType::MarginBottom,
4869        CssPropertyType::MarginLeft,
4870        CssPropertyType::MarginRight,
4871        CssPropertyType::PaddingTop,
4872        CssPropertyType::PaddingBottom,
4873        CssPropertyType::PaddingLeft,
4874        CssPropertyType::PaddingRight,
4875        CssPropertyType::BreakInside,
4876        CssPropertyType::BreakBefore,
4877        CssPropertyType::BreakAfter,
4878        CssPropertyType::BorderCollapse,
4879        CssPropertyType::BorderSpacing,
4880        CssPropertyType::TextAlign,
4881        CssPropertyType::VerticalAlign,
4882        CssPropertyType::ListStyleType,
4883        CssPropertyType::ListStylePosition,
4884    ];
4885
4886    /// Build a pre-resolved cache of all CSS properties for every node.
4887    ///
4888    /// After calling restyle(), apply_ua_css(), and compute_inherited_values(),
4889    /// call this to pre-resolve the CSS cascade for all nodes. This builds a flat
4890    /// Vec<Vec<(CssPropertyType, CssProperty)>> where:
4891    /// - Outer Vec is indexed by node ID (O(1) access)
4892    /// - Inner Vec is sorted by CssPropertyType (O(log m) binary search, m ≈ 5-10)
4893    ///
4894    /// This replaces 18+ BTreeMap lookups per get_property() call with
4895    /// a single Vec index + binary search, typically a 5-10x speedup.
4896    ///
4897    /// The returned cache should be assigned to self.resolved_cache.
4898    pub fn build_resolved_cache(
4899        &self,
4900        node_data: &[NodeData],
4901        styled_nodes: &[crate::styled_dom::StyledNode],
4902    ) -> Vec<Vec<(CssPropertyType, CssProperty)>> {
4903        use alloc::collections::BTreeSet;
4904        use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType};
4905
4906        let node_count = node_data.len();
4907        let mut resolved = Vec::with_capacity(node_count);
4908
4909        for node_index in 0..node_count {
4910            let node_id = NodeId::new(node_index);
4911            let nd = &node_data[node_index];
4912            let node_state = &styled_nodes[node_index].styled_node_state;
4913
4914            // Collect all property types that might be set for this node.
4915            // BTreeSet<CssPropertyType> is cheap (u8-sized keys, small set per node).
4916            let mut prop_types = BTreeSet::new();
4917
4918            if let Some(map) = self.user_overridden_properties.get(node_id.index()) {
4919                prop_types.extend(map.keys().copied());
4920            }
4921            for css_prop in nd.css_props.iter() {
4922                let conditions = css_prop.apply_if.as_slice();
4923                let matches = if conditions.is_empty() {
4924                    true
4925                } else {
4926                    conditions.iter().all(|c| match c {
4927                        DynamicSelector::PseudoState(PseudoStateType::Hover) => node_state.hover,
4928                        DynamicSelector::PseudoState(PseudoStateType::Active) => node_state.active,
4929                        DynamicSelector::PseudoState(PseudoStateType::Focus) => node_state.focused,
4930                        DynamicSelector::PseudoState(PseudoStateType::Dragging) => node_state.dragging,
4931                        DynamicSelector::PseudoState(PseudoStateType::DragOver) => node_state.drag_over,
4932                        _ => false,
4933                    })
4934                };
4935                if matches {
4936                    prop_types.insert(css_prop.property.get_type());
4937                }
4938            }
4939            if let Some(map) = self.css_normal_props.get(node_id.index()) {
4940                prop_types.extend(map.keys().copied());
4941            }
4942            if let Some(map) = self.cascaded_normal_props.get(node_id.index()) {
4943                prop_types.extend(map.keys().copied());
4944            }
4945            if let Some(map) = self.computed_values.get(node_id.index()) {
4946                for pt in map.keys() {
4947                    if pt.is_inheritable() {
4948                        prop_types.insert(*pt);
4949                    }
4950                }
4951            }
4952            // Pseudo-state maps (only when state is active)
4953            if node_state.hover {
4954                if let Some(map) = self.css_hover_props.get(node_id.index()) {
4955                    prop_types.extend(map.keys().copied());
4956                }
4957                if let Some(map) = self.cascaded_hover_props.get(node_id.index()) {
4958                    prop_types.extend(map.keys().copied());
4959                }
4960            }
4961            if node_state.active {
4962                if let Some(map) = self.css_active_props.get(node_id.index()) {
4963                    prop_types.extend(map.keys().copied());
4964                }
4965                if let Some(map) = self.cascaded_active_props.get(node_id.index()) {
4966                    prop_types.extend(map.keys().copied());
4967                }
4968            }
4969            if node_state.focused {
4970                if let Some(map) = self.css_focus_props.get(node_id.index()) {
4971                    prop_types.extend(map.keys().copied());
4972                }
4973                if let Some(map) = self.cascaded_focus_props.get(node_id.index()) {
4974                    prop_types.extend(map.keys().copied());
4975                }
4976            }
4977            if node_state.dragging {
4978                if let Some(map) = self.css_dragging_props.get(node_id.index()) {
4979                    prop_types.extend(map.keys().copied());
4980                }
4981                if let Some(map) = self.cascaded_dragging_props.get(node_id.index()) {
4982                    prop_types.extend(map.keys().copied());
4983                }
4984            }
4985            if node_state.drag_over {
4986                if let Some(map) = self.css_drag_over_props.get(node_id.index()) {
4987                    prop_types.extend(map.keys().copied());
4988                }
4989                if let Some(map) = self.cascaded_drag_over_props.get(node_id.index()) {
4990                    prop_types.extend(map.keys().copied());
4991                }
4992            }
4993            // UA CSS
4994            for pt in Self::UA_PROPERTY_TYPES {
4995                if crate::ua_css::get_ua_property(&nd.node_type, *pt).is_some() {
4996                    prop_types.insert(*pt);
4997                }
4998            }
4999
5000            // Resolve each property through the cascade. get_property_slow
5001            // returns a reference; we only clone the winning value per type.
5002            let mut props: Vec<(CssPropertyType, CssProperty)> =
5003                Vec::with_capacity(prop_types.len());
5004            for prop_type in &prop_types {
5005                if let Some(prop) = self.get_property_slow(nd, &node_id, node_state, prop_type) {
5006                    props.push((*prop_type, prop.clone()));
5007                }
5008            }
5009            // Props are already sorted because BTreeSet iterates in Ord order.
5010            resolved.push(props);
5011        }
5012
5013        resolved
5014    }
5015
5016    /// Invalidate the resolved cache for a single node.
5017    /// Call this when a node's state changes (e.g., hover on/off) or
5018    /// when a property is overridden dynamically.
5019    /// After invalidation, rebuild the node's entry by calling
5020    /// rebuild_resolved_node().
5021    pub fn invalidate_resolved_node(
5022        &mut self,
5023        node_id: NodeId,
5024        node_data: &NodeData,
5025        styled_node: &crate::styled_dom::StyledNode,
5026    ) {
5027        let idx = node_id.index();
5028        if idx >= self.resolved_cache.len() {
5029            return;
5030        }
5031
5032        let node_state = &styled_node.styled_node_state;
5033
5034        // Build the resolved properties using shared reference (&self via get_property_slow)
5035        let mut prop_types = alloc::collections::BTreeSet::new();
5036
5037        if let Some(map) = self.user_overridden_properties.get(node_id.index()) {
5038            prop_types.extend(map.keys().copied());
5039        }
5040        for css_prop in node_data.css_props.iter() {
5041            prop_types.insert(css_prop.property.get_type());
5042        }
5043        if let Some(map) = self.css_normal_props.get(node_id.index()) {
5044            prop_types.extend(map.keys().copied());
5045        }
5046        if let Some(map) = self.cascaded_normal_props.get(node_id.index()) {
5047            prop_types.extend(map.keys().copied());
5048        }
5049        if let Some(map) = self.computed_values.get(node_id.index()) {
5050            prop_types.extend(map.keys().copied());
5051        }
5052        for pt in Self::UA_PROPERTY_TYPES {
5053            if crate::ua_css::get_ua_property(&node_data.node_type, *pt).is_some() {
5054                prop_types.insert(*pt);
5055            }
5056        }
5057        if node_state.hover {
5058            if let Some(map) = self.css_hover_props.get(node_id.index()) {
5059                prop_types.extend(map.keys().copied());
5060            }
5061            if let Some(map) = self.cascaded_hover_props.get(node_id.index()) {
5062                prop_types.extend(map.keys().copied());
5063            }
5064        }
5065        if node_state.active {
5066            if let Some(map) = self.css_active_props.get(node_id.index()) {
5067                prop_types.extend(map.keys().copied());
5068            }
5069            if let Some(map) = self.cascaded_active_props.get(node_id.index()) {
5070                prop_types.extend(map.keys().copied());
5071            }
5072        }
5073        if node_state.focused {
5074            if let Some(map) = self.css_focus_props.get(node_id.index()) {
5075                prop_types.extend(map.keys().copied());
5076            }
5077            if let Some(map) = self.cascaded_focus_props.get(node_id.index()) {
5078                prop_types.extend(map.keys().copied());
5079            }
5080        }
5081
5082        let mut props = Vec::with_capacity(prop_types.len());
5083        for prop_type in &prop_types {
5084            if let Some(prop) = self.get_property_slow(node_data, &node_id, node_state, prop_type) {
5085                props.push((*prop_type, prop.clone()));
5086            }
5087        }
5088
5089        // Now assign to the cache entry (mutable borrow happens after shared borrows end)
5090        self.resolved_cache[idx] = props;
5091    }
5092
5093    /// Clear the entire resolved cache. Call after major DOM changes.
5094    pub fn invalidate_resolved_cache(&mut self) {
5095        self.resolved_cache.clear();
5096    }
5097}
5098
5099#[cfg(test)]
5100mod tests {
5101    use super::*;
5102    use crate::dom::NodeType;
5103
5104    #[test]
5105    fn test_ua_css_p_tag_properties() {
5106        // Create an empty CssPropertyCache
5107        let cache = CssPropertyCache::empty(1);
5108
5109        // Create a minimal <p> tag NodeData using public API
5110        let mut node_data = NodeData::create_node(NodeType::P);
5111
5112        let node_id = NodeId::new(0);
5113        let node_state = StyledNodeState::default();
5114
5115        // Test that <p> has display: block from UA CSS
5116        let display = cache.get_display(&node_data, &node_id, &node_state);
5117        assert!(
5118            display.is_some(),
5119            "Expected <p> to have display property from UA CSS"
5120        );
5121        if let Some(d) = display {
5122            if let Some(display_value) = d.get_property() {
5123                assert_eq!(
5124                    *display_value,
5125                    LayoutDisplay::Block,
5126                    "Expected <p> to have display: block, got {:?}",
5127                    display_value
5128                );
5129            }
5130        }
5131
5132        // NOTE: <p> does NOT have width: 100% in standard UA CSS
5133        // Block elements have width: auto by default, which means "fill available width"
5134        // but it's not the same as width: 100%. The difference is critical for flexbox.
5135        let width = cache.get_width(&node_data, &node_id, &node_state);
5136        // Width should be None because <p> should use auto width (no explicit width property)
5137        assert!(
5138            width.is_none(),
5139            "Expected <p> to NOT have explicit width (should be auto), but got {:?}",
5140            width
5141        );
5142
5143        // Test that <p> does NOT have a default height from UA CSS
5144        // (height should be auto, which means None)
5145        let height = cache.get_height(&node_data, &node_id, &node_state);
5146        println!("Height for <p> tag: {:?}", height);
5147
5148        // Height should be None because <p> should use auto height
5149        assert!(
5150            height.is_none(),
5151            "Expected <p> to NOT have explicit height (should be auto), but got {:?}",
5152            height
5153        );
5154    }
5155
5156    #[test]
5157    fn test_ua_css_body_tag_properties() {
5158        let cache = CssPropertyCache::empty(1);
5159
5160        let node_data = NodeData::create_node(NodeType::Body);
5161
5162        let node_id = NodeId::new(0);
5163        let node_state = StyledNodeState::default();
5164
5165        // NOTE: <body> does NOT have width: 100% in standard UA CSS
5166        // It inherits from the Initial Containing Block (ICB) and has width: auto
5167        let width = cache.get_width(&node_data, &node_id, &node_state);
5168        // Width should be None because <body> should use auto width (no explicit width property)
5169        assert!(
5170            width.is_none(),
5171            "Expected <body> to NOT have explicit width (should be auto), but got {:?}",
5172            width
5173        );
5174
5175        // Note: height: 100% was removed from UA CSS (ua_css.rs:506 commented out)
5176        // This is correct - <body> should have height: auto by default per CSS spec
5177        let height = cache.get_height(&node_data, &node_id, &node_state);
5178        assert!(
5179            height.is_none(),
5180            "<body> should not have explicit height from UA CSS (should be auto)"
5181        );
5182
5183        // Test margins (body has 8px margins from UA CSS)
5184        let margin_top = cache.get_margin_top(&node_data, &node_id, &node_state);
5185        assert!(
5186            margin_top.is_some(),
5187            "Expected <body> to have margin-top from UA CSS"
5188        );
5189    }
5190}